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

Click to view full size: IFS fractal image. Click to view full size.

wshowfs.cpp - Iterated Function Set fractals

This program continuously generates random iterated function system (IFS) sets and displays in color the fractal images they define. It is an MDI application that allows having multiple windows drawing at the same time. It allows editing transform sets on the fly, and immediately starts drawing the revised set. It also has a genetic method for crossbreeding two parent sets to produce one or more child sets that show related characteristics but with mutations.

It was written using Borland C++ 4.0 with ObjectWindows Library (OWL) 2.0 for a Win32s target. Win32s was a 16 bit Windows subsystem that emulated 32-bit Windows. The program will probably still compile and run under Win16 (Windows 3.1) with few or no changes.

The program demonstrates effective use of IdleAction(). It only calculates a fixed number of points with each call, so it doesn't hog CPU time and runs well when other applications are also running.

This program uses SDibWindow, SDib, and other library classes you'll find on this site, so the project winds up being large. Links to the needed source code are provided in the listings below where they are referenced.

Download:

Click here to download wshowfs.zip (about 101 KB).

The zip contains these files that are unique to this project:

WShowfs.cpp Source code for the Borland C++ 4.0 ObjectWindows (OWL) 2.0 Windows 3.1 version. Same as the listing on this page.
Transfrm.cpp Source code (platform independent) for the IFS transform sets.
wshowfsprojectnodes.gif The same screenshot of the project nodes shown below on this page.
WShowfs.HPJ
WShowfs.RTF
WShowfs.HLP
Files for building the program's Windows 3.1 WinHelp file, plus the precompiled Help file. The RTF file is based on my helpfile.dot MSWord template.
WShowfs.RC
WShowfs.RH
The Windows resource definitions.
WShowfs.INI The Windows 3.1 .ini file in which the program records the location of its data files.
WShowfs.DEF The Windows module definition file.
COPYING.TXT GNU GPL License Version 2, viewed best in Word or WordPad.
GRAYSCAL.MAP A gray scale color map file.
LIBRARY.CPP A module that pulls into the project the library classes it needs.
TRANEDIT.CPP Legacy code with resource definitions for a dialog box that turned out to be less practical than a text editor dialog box.
*.BMP 9 small bitmap icons for the application's toolbar.
*.PFS, *.IFS Sample data files to demonstrate the file formats and for experimentation. 8 are .PFS (polar notation) and 2 are .IFS (rectangular notation).

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

Screenshot of the project nodes in the Borland C++ 4.0 IDE.

 

Click to view full size: IFS fractal image. Click to view full size.

wshowfs.cpp

/*	wshowfs.cpp			 12-14-01
	Copyright (C)1988-1999, 2001 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.

This program continuously generates random variables for Iterated Function Set (IFS) fractal
images and displays the associated images. It allows creating and editing transform sets
on the fly.

It was written using Borland C++ 4.0 with ObjectWindows Library (OWL) 2.0 for a Win32s target.
Win32s was a subsystem that emulated true 32-bit Windows. It might still compile and run
under Win16 with no or few changes. It is an MDI application, but not Doc/View model.

It demonstrates a genetic method of crossbreeding two parent transform sets to produce
one or more child sets that show some related characteristics but with mutations.

------
To Do:

go over program flow, and flow within each function, VERY carefully.  This is a
complicated program with a lot going on, and the program still can get confused.
More reorganizing and simplifying may eventually take care of some problems.
copy to Help any useful comments found.

Opening the Load List dialog box after the first time (second+) results in a
"Dialog box error". It is also very easy to select too many files in that box,
causing a different error (too long a return string for the system to handle).
Drag and Drop from File Manager allows an unlimited number of files, and is
the preferable method.

Most of the SDibWindow-related palette manipulation commands cause point plotting
confusion, and result in no drawing being done (blank screen). Don't know why;
it might just be because out of 256 colors, it only uses the first 2 or 3(?);
don't use those commands.

if I need accel "D", then View|Draw could be "G" for "Go", same accel as Wlife2d and wanimate.

RTF: check Colors Menu Commands section against sdibwin's, and delete section
from wshowfs.rtf (they're dup).  review and revise for new SDibwin and other features.
ensure drag/drop is described.

develop a composite measure of overall success of an IFS conversion to PFS.
(# of transforms that did not exactly duplicate IFS values?  0 = perfect)
then report failure after reading any IFS file that fails.

when you create an IFS file from scratch, pgm thinks its PFS until saved & reopened.

add some way (text gadgets or info window) to view (only) the screen limits
maxx,maxy,minx,miny, xrange, yrange.  (so you can see the real extents, values produced)
AND add an isotropic mode where xrange = yrange = max(xrange,yrange) (and xscale = yscale?)
so you have the option of drawing the true proportions, not squished to viewport.
(but don't ever allow zooming: pointless, it's the same at all scales.)

------
CRASH NOTES:
--TD32 can trap SIGFPE's, but at pgm startup it quickly reports an "Access Violation"
  at 0xffffffff (?).  Make that "handled by pgm", and you can run normally.
  Sometime test what this Access Violation is.  Suspect it is something normal to Win32s,
  perhaps even a test for whether it's true NT or 32s.
--Fixed infinite loop bug in SDib::RandomizeColors().  That was the long-sought hangup cause.

------
NOTES:
--Tested pretty thoroughly after SDibWindow addition:
  seems very responsive and stable, better than ever.
--Periodically copy .EXE and .HLP to C:\UTIL because this is the FileManager
  viewer for .IFS and .PFS files.
--Win32 Pentium 328000 pts/30 sec. vs 318000 Win16: 3% faster
--Doc/View is UNdesirable.  It adds lots of overhead and slowdown with no additional
  functionality, and achieving the SAME functionality requires pointless workarounds.
  The current design is efficient.
--wshowfs.8 is the last working version before sdibwindow changes.

*/
// You MUST comment these out for any final version, since you don't want
// debug output generated if not running under debugger.
// #define __DEBUG	2
// #define __TRACE
// #define __WARN

#include <owl\owlpch.h>
#include <owl\chooseco.h>
#include <owl\opensave.h>
#include <owl\radiobut.h>
#include <owl\buttonga.h>
#include <owl\editsear.h>
#include <owl\statusba.h>
#include <owl\controlb.h>
#include <owl\inputdia.h>
#include <classlib\arrays.h>
#include <math.h>
#include <mmsystem.h>
#include <float.h>       				// _fpreset()
#include <dir.h>                    	// MAXPATH
#pragma hdrstop

#include "c:\bcs\my.h"
#include "c:\bcs\library\sdibwin.h"

#include "wshowfs.rh"
#include "transfrm.h"

const char AppName[] =	"Fractal Display";
char INIFilename[] = 	"wshowfs.ini";
char HelpFilename[] = 	"wshowfs.hlp";

extern TColor MyCustomColors[16];    // in mylib.cpp, for TChooseColor

//////////////////////////////////////////////////////////////////////////////
// transform editor dialog box transfer struct
// this format (struct as opposed to a simple char array only) allows additional members
// for enhancements to the TDialog that uses it.
struct TranStruct
{
	TranStruct() { filechars[0] = 0; }		// constructor
	char filechars[MAXFILECHARS]; 			// matches size of filechars in STransformSet
};
//////////////////////////////////////////////////////////////////////////////
// transform set editor dialog box
class STranSetDialog : public TDialog
{
public:
	STranSetDialog(TWindow* parent, TranStruct* tts);	// constructor
	TEdit* Editor;

protected:
	void EvSize(UINT sizeType, TSize& size);
	void CmHelp();

DECLARE_RESPONSE_TABLE(STranSetDialog);
};
//----------------------------------------------------------------------------
DEFINE_RESPONSE_TABLE1(STranSetDialog,TDialog)
	EV_WM_SIZE,
	EV_COMMAND(IDHELP,CmHelp),
END_RESPONSE_TABLE;
//----------------------------------------------------------------------------
// constructor
STranSetDialog::STranSetDialog(TWindow* parent, TranStruct* tts)
	: TDialog(parent,TResId(TRANSETEDITOR))
{
Editor = new TEdit(this,IDC_TRANSETEDIT,sizeof(tts->filechars));
SetTransferBuffer(tts);
}
//----------------------------------------------------------------------------
// resizes the TEdit to fit
void STranSetDialog::EvSize(UINT sizeType, TSize& size)
{
TDialog::EvSize(sizeType, size);

// #error use:  actual area to size into, then adjust.
// but IS the dialog now displayed in its new size?
// also, preserve a minimum size for the Dialog?
// test for type = minimize, maximize?
// TRect rect = GetClientRect();
// see framewin.cpp for an "official" way to resize a child win.  It does use MoveWindow().
//
// TRACE("STranSetDialog::EvSize about to reposition its TEdit");
int newwidth = (int)max((long)size.cx - 11,20L);  	// win16/32 compatibility
int newheight = (int)max((long)size.cy - 46,20L);
Editor->MoveWindow(5,40,newwidth,newheight,TRUE);
}                    			// EvSize
//----------------------------------------------------------------------------
void STranSetDialog::CmHelp()
{ WinHelp(HelpFilename,HELP_PARTIALKEY,(DWORD)(LPSTR)"Transform Editor Dialog Box"); }
//----------------------------------------------------------------------------
//					// end class STranSetDialog
//////////////////////////////////////////////////////////////////////////////
// transfer information for the Options dialog box
// would like to add:
// Default background color for all windows (but how to present in dialog box?)
// an embedded TChooseColor?
struct OpStruct
{
	OpStruct();
	OpStruct(const OpStruct& other);    				// copy
	OpStruct& operator = (const OpStruct& other);   	// assignment

	// order here MUST match order in SOptionsDialog constructor!
	BOOL showequats;			// whether to automatically show on-screen data area
	BOOL beepon;				// whether or not to use sound (minimally)
	BOOL autoaborttests;		// whether draw() should apply tests for automatic abort
	BOOL upsdwn;            	// however, screen is rightside up when upsdwn is TRUE
	BOOL autorandomizecolors;	// whether to randomize colors before each design
	BOOL xysamerotation;
	BOOL xysamescale;
	BOOL colorbyhits;  			// radio boxes for dispmode
	BOOL colorbytransform;
	BOOL monochrome;
	BOOL optionsatstartup;
	BOOL mutateonmate;
	char numtransformsedit[10];	// these must be char[], not strings
	char duplimitedit[10];
};
//----------------------------------------------------------------------------
// constructor
OpStruct::OpStruct()
{
showequats = TRUE;			// these are the startup defaults for SFractalWindow
beepon = TRUE;
autoaborttests = TRUE;
upsdwn = FALSE;     	 	// whether to invert y axis (yes if FALSE)
autorandomizecolors = TRUE;
xysamerotation = FALSE;
xysamescale = FALSE;

colorbyhits = FALSE;
colorbytransform = TRUE;  	// only ONE in this set can be true!
monochrome = FALSE;

optionsatstartup = TRUE;
mutateonmate = FALSE;

numtransformsedit[0] = 0;
duplimitedit[0] = 0;
}
//----------------------------------------------------------------------------
// copy constructor
OpStruct::OpStruct(const OpStruct& other) { *this = other; }
//----------------------------------------------------------------------------
// assignment
// this may be useful for initializing an option set from a master set,
// so user doesn't have to repeatedly set the same options.
OpStruct& OpStruct::operator = (const OpStruct& other)
{
if(&other != this)
{
	showequats 			= other.showequats;
	beepon 				= other.beepon;
	autoaborttests		= other.autoaborttests;
	upsdwn 				= other.upsdwn;
	autorandomizecolors = other.autorandomizecolors;
	xysamerotation 		= other.xysamerotation;
	xysamescale 		= other.xysamescale;

	colorbyhits 		= other.colorbyhits;
	colorbytransform 	= other.colorbytransform;
	monochrome 			= other.monochrome;

	optionsatstartup 	= other.optionsatstartup;
	mutateonmate 		= other.mutateonmate;

	strcpy(numtransformsedit,other.numtransformsedit);
	strcpy(duplimitedit,other.duplimitedit);
}
return(*this);
}						// operator =
//----------------------------------------------------------------------------
// 						end struct OpStruct
//////////////////////////////////////////////////////////////////////////////
// options dialog box
class SOptionsDialog : public TDialog
{
public:
	SOptionsDialog(TWindow* parent, OpStruct* ots);

protected:
	void CmHelp();

DECLARE_RESPONSE_TABLE(SOptionsDialog);
};
//----------------------------------------------------------------------------
DEFINE_RESPONSE_TABLE1(SOptionsDialog,TDialog)
	EV_COMMAND(IDHELP,CmHelp),
END_RESPONSE_TABLE;
//----------------------------------------------------------------------------
// constructor
SOptionsDialog::SOptionsDialog(TWindow* parent, OpStruct* ots)
	: TDialog(parent,TResId(OPTIONSDIALOG))
{
	new TCheckBox(this,IDC_SHOWEQUATS,0);   		// 0 = TGroupBox*
	new TCheckBox(this,IDC_BEEPON,0);
	new TCheckBox(this,IDC_AUTOABORTTESTS,0);
	new TCheckBox(this,IDC_UPSDWN,0);
	new TCheckBox(this,IDC_AUTORANDCOLORS,0);
	new TCheckBox(this,IDC_XYSAMEROT,0);
	new TCheckBox(this,IDC_XYSAMESCALE,0);

	// unsure whether these need a TGroupBox to belong to, but the way they're defined
	// in .RC may do it automatically.
	new TRadioButton(this,IDC_COLORBYHITS,0);
	new TRadioButton(this,IDC_COLORBYTRANSFORM,0);
	new TRadioButton(this,IDC_COLORMONOCHROME,0);

	new TCheckBox(this,IDC_VIEWOPTIONSATSTARTUP,0);
	new TCheckBox(this,IDC_MUTATEONMATE,0);
	new TEdit(this,IDC_NUMTRANSFORMS,10);
	new TEdit(this,IDC_DUPLIMIT,10);
	SetTransferBuffer(ots);
}
//----------------------------------------------------------------------------
// This is called when user presses the Help button.  When a button is clicked, a BN_CLICKED
// message is sent to the parent, but the dialog interprets it as an IDHELP command because
// that's the ID given to that button in the .rc (rh) file.  It does a keyword search for
// the given partial keyword.  The advantage of this method is you don't have to use .HPJ[MAP]
// or a header file to make sure identifiers in your program and help file agree.
// You just have to make sure the keyword IS used in your help file,
// which is a lot easier to remember than a bunch of mapped identifiers.
// I don't like the double-cast, but it does work fine.
void SOptionsDialog::CmHelp()
{ WinHelp(HelpFilename,HELP_PARTIALKEY,(DWORD)(LPSTR)"Options Dialog"); }
//----------------------------------------------------------------------------
// 						end class SOptionsDialog
//////////////////////////////////////////////////////////////////////////////
// An SFractalWindow displays an IFS(PFS)-type fractal image in its window.
class SFractalWindow : public SDibWindow
{
public:
	SFractalWindow(TWindow *parent = 0);
	~SFractalWindow();

	// overridden TWindow
	BOOL IdleAction(long idlecount);
	BOOL CanClose();

	// public event handlers
	void CmFileNext();
	void CmZeroParents();

	// functions
	void reincarnate();					// conditionally become a child of src1,src2

	// variables
	OpStruct ots;      					// Options Dialog transfer structure
	static SFractalWindow* src1;     	// used for mating
	static SFractalWindow* src2;
	static BOOL ViewOptionsAtStartup;
	static BOOL MutateOnMate;
	static BOOL dragging;
	static int BasicDrawCount;   		// plot count each IdleAction, if just 1 window
	static int DrawCount;				// basic count, ADJUSTABLE by # of drawing windows
	static BOOL Drawing;				// if FALSE, all windows auto-drawing is suspended

protected:
	// other variables
	enum dispmodes { colorbyhits, colorbytransform, monochrome } dispmode;
	STransformSet TranSet;		// container of transforms (this is a large object, > 2k)
	BOOL drawing;				// whether this window is automatically drawing
	long n;                 	// counts iterations
	long dups;         			// counts consecutive pixels that were already on when hit
	long pix;           		// number of pixels turned on
	long duplimit;   			// when reached, image is finished
	double xfactor, yfactor;	// for fitting image to screen

	static int FracwinCount;	// # of SFractalWindows in existence

	// normal functions
	void NewSetDisplayPrep();			// determine exact fit to client area
	void EraseDib();					// erase to its background color, draw data area
	void draw(int drawcount = 0); 		   			// plot points
	void showdataarea(BOOL unconditional = FALSE);	// draw data area in window
	void setdispmode(dispmodes);					// changes dispmode
	void UpdateViewMenu();                       	// check menu items to match settings
	BOOL QuerySaveTranset();

	// TWindow overridden virtuals
	void SetupWindow();
	void CleanupWindow();

	// event handlers
	void EvSetFocus(HWND hWndLostFocus);
	void EvLButtonDown(UINT, TPoint&);
	void EvLButtonUp(UINT, TPoint&);
	void EvRButtonDown(UINT, TPoint&);
	void EvRButtonUp(UINT, TPoint&);
	void EvMouseMove(UINT modKeys, TPoint& point);
	void CmFileSave();
	void CmFileLoad();
	void CmFileEdit();
	virtual void CmColorsManip(WPARAM cmd);	// handles various palette manipulations
	void CmViewOptions();
	void CmSelectAsParent();
	void CmViewDraw();
	void CmViewUpsideDown();
	void CmViewAutoAbortTests();
	void CmViewShowEquations();
	void CmViewAutoRandomizeColors();
	void CmViewNumberOfPoints();
	void CmViewRandomizeColors();  			// has a method peculiar to this app

DECLARE_RESPONSE_TABLE(SFractalWindow);
};
//----------------------------------------------------------------------------
DEFINE_RESPONSE_TABLE1(SFractalWindow, SDibWindow)
	EV_WM_LBUTTONDOWN,
	EV_WM_LBUTTONUP,
	EV_WM_RBUTTONDOWN,
	EV_WM_RBUTTONUP,
	EV_WM_MOUSEMOVE,
	EV_WM_SETFOCUS,
	EV_COMMAND(CM_FILENEXT,CmFileNext),
	EV_COMMAND(CM_FILELOAD,CmFileLoad),
	EV_COMMAND(CM_FILEEDIT,CmFileEdit),
	EV_COMMAND(CM_FILESAVE,CmFileSave),
	EV_COMMAND(CM_SELECTASPARENT,CmSelectAsParent),
	EV_COMMAND(CM_ZEROPARENTS,CmZeroParents),
	EV_COMMAND(CM_VIEWDRAW,CmViewDraw),
	EV_COMMAND(CM_VIEWOPTIONS,CmViewOptions),
	EV_COMMAND(CM_VIEWUPSIDEDOWN,CmViewUpsideDown),
	EV_COMMAND(CM_VIEWNUMBEROFPOINTS,CmViewNumberOfPoints),
	EV_COMMAND(CM_VIEWAUTOABORTTESTS,CmViewAutoAbortTests),
	EV_COMMAND(CM_VIEWSHOWEQUATIONS,CmViewShowEquations),
	EV_COMMAND(CM_VIEWRANDOMIZECOLORS,CmViewRandomizeColors),
	EV_COMMAND(CM_VIEWAUTORANDOMIZECOLORS,CmViewAutoRandomizeColors),
END_RESPONSE_TABLE;
//----------------------------------------------------------------------------
// initialize static members
SFractalWindow* SFractalWindow::src1 = 0;
SFractalWindow* SFractalWindow::src2 = 0;
BOOL SFractalWindow::ViewOptionsAtStartup = TRUE;
BOOL SFractalWindow::MutateOnMate = FALSE;
int SFractalWindow::FracwinCount = 0;
int SFractalWindow::BasicDrawCount = 1000;
int SFractalWindow::DrawCount = SFractalWindow::BasicDrawCount;
BOOL SFractalWindow::dragging = FALSE;
BOOL SFractalWindow::Drawing = TRUE;

//----------------------------------------------------------------------------
// constructor
SFractalWindow::SFractalWindow(TWindow *parent) :  SDibWindow(parent)
{
// initialize other variables
duplimit = 1000L;    		// when reached, image is finished & new one started
drawing = FALSE;
fittowindow = FALSE;   		// permanently FALSE in this app; dib is always SIZED to window

// adjust DrawCount with # of windows drawing, to keep system delay constant.
DrawCount = BasicDrawCount / ++FracwinCount;

// don't use setdispmode() OR any method that might call it!
dispmode =
	ots.colorbytransform ? colorbytransform : (ots.colorbyhits ? colorbyhits : monochrome);

// SDibWindow ctor filled it, but in this app SFractalClient manages the list at startup.
FileList.Flush();

}								//constructor
//----------------------------------------------------------------------------
// destructor
SFractalWindow::~SFractalWindow()
{
src1 = src2 = 0;	// ensure that they don't reference this deleted window

DrawCount = BasicDrawCount / max(--FracwinCount,1); 		// 1 in case none left
}
//----------------------------------------------------------------------------
void SFractalWindow::SetupWindow() { SDibWindow::SetupWindow(); }
void SFractalWindow::CleanupWindow() { SDibWindow::CleanupWindow(); }
//----------------------------------------------------------------------------
// fill the dib with colors[0], and draw data area, if needed.
// useful for erasing when rescaling isn't also required.
void SFractalWindow::EraseDib()
{
dib->Erase(dib->GetColor(0));	// note GetColor(0), not SDib's TColor::Black
n = dups = pix = 0L; 			// blank screen: zero the pixel-counting variables
showdataarea();					// data for the data area must be current when called
Invalidate(FALSE);				// force drawing the new dib
}  	        					//EraseDib
//----------------------------------------------------------------------------
// set scaling factors so design is centered in the display screen, and reinitialize dib.
// you must call this each time you reload TranSet, either from file or random.
// When you scale by this method, it automatically squishes the
// design to fit the screen, out of proportion compared to "raw".
// When this function is done, you have a dib that is matched to the screen,
// erased to the current background color, and possibly with a heading.
void SFractalWindow::NewSetDisplayPrep()
{
if(!IsWindow())						// new, supposed to prevent GetClientRect crash,
	return;							// (just in case; should be impossible anyway)

makenewdib(0,0,0,FALSE);
if(ots.autorandomizecolors)
	CmViewRandomizeColors();

// set scaling factors so the actually-drawn design fits inside the dib
// xrange, yrange have usable default values even when TranSet is empty.
xfactor = dib->Width()  / TranSet.xrange;
yfactor = dib->Height() / TranSet.yrange;

// this caption is used in some places as the document's title.
// so be sure it's always current and correct.
string caption;
if(TranSet.GetFileName().length())
	caption = to_lower(TranSet.GetFileName());
else
	caption = string("Untitled ") + tostring(TranSet.id());
SetParentCaption(caption.c_str());

EraseDib();						// and maybe draw data area
}								// NewSetDisplayPrep
//----------------------------------------------------------------------------
// automatically called when the window receives input focus.
// intended to ensure that it automatically updates the View menu to match its settings.
// seems to work.
void SFractalWindow::EvSetFocus(HWND hWndLostFocus)
{
SDibWindow::EvSetFocus(hWndLostFocus);		// reminder that it does exist, not documented
UpdateViewMenu();
}
//----------------------------------------------------------------------------
// does nothing, so you can click to change focus without inadvertently doing anything else.
// REMEMBER YOU CAN USE CTL,ALT,(SHIFT?) COMBINATIONS, TOO, GIVING OPTIONAL ACCESS TO
// SDIBWIN'S FNS.
void SFractalWindow::EvLButtonDown(UINT modKeys, TPoint& point)
{
TWindow::EvLButtonDown(modKeys,point);	// intentionally bypass SDibWindow's nonvirtual
if(dragging)
	::SetCursor(::LoadCursor(NULL,IDC_CROSS));
}						// EvLButtonDown
//----------------------------------------------------------------------------
void SFractalWindow::EvLButtonUp(UINT modKeys, TPoint& point)
{
TWindow::EvLButtonUp(modKeys,point);	// intentionally bypass SDibWindow's nonvirtual
if(dragging)
	::SetCursor(::LoadCursor(NULL,IDC_CROSS));
}						//EvLButtonUp
//----------------------------------------------------------------------------
// unsure why this doesn't change the focus to current window, while LButtonDown does.
// but it doesn't, EVEN if you modify it to just call EvLButtonDown.
// RButton used for mating because if Windows accidentally sends the message to the wrong
// window, RButton is much less likely to cause problems there: won't change focus,
// inadvertently drag ProgMan icons around, etc.
// For even more functionality, RButton could open a floating SpeedMenu:
// (Save, Edit Color, Select As Parent, etc.)
void SFractalWindow::EvRButtonDown(UINT modKeys, TPoint& point)
{
// intentionally bypass SDibWindow's, counting on its NOT being virtual
TWindow::EvRButtonDown(modKeys,point);
CmSelectAsParent();
}						//EvRButtonDown
//----------------------------------------------------------------------------
// see help|SetCursor.  Windows restores cursor to arrow after each mouse move,
// so this resets it back to the one we want.
void SFractalWindow::EvMouseMove(UINT modKeys, TPoint& point)
{
// it will do nothing: DragDC is always 0; BUT THAT MIGHT CHANGE.
SDibWindow::EvMouseMove(modKeys, point);
if(dragging)
	::SetCursor(::LoadCursor(NULL,IDC_CROSS));
}
//----------------------------------------------------------------------------
void SFractalWindow::EvRButtonUp(UINT modKeys, TPoint& point)
{
TWindow::EvRButtonUp(modKeys,point);	// SDibWin doesn't have one

// prevent selecting the first parent on an upstroke: if you're using the non-drag method,
// when you click down to select the 2nd window, the mating occurs before you have time to
// release the key, src1-2 are zeroed, and the upstroke would start a new round.
if(!dragging)
	return;
CmSelectAsParent();
}						// EvRButtonUp
//----------------------------------------------------------------------------
// because this is virtual, THIS version is called for ALL the covered colors commands,
// even though the command is in SDibWindow's response table.
void SFractalWindow::CmColorsManip(WPARAM cmd)
{
SDibWindow::CmColorsManip(cmd);
Drawing = FALSE;   				// to prevent Windows message pileup and pgm crash
}                        		//CmColorsManip
//----------------------------------------------------------------------------
// check or uncheck the View Menu items, as needed, to match current state.
// these View Menu items are all handy to have available through accelerators.
// reminder: items CAN have accelerators without being in the menu at all.
void SFractalWindow::UpdateViewMenu()
{
TMenu menu(*(GetApplication()->GetMainWindow()));
menu.CheckMenuItem(CM_VIEWUPSIDEDOWN,
					MF_BYCOMMAND | (ots.upsdwn ? MF_CHECKED : MF_UNCHECKED));
menu.CheckMenuItem(CM_VIEWAUTOABORTTESTS,
					MF_BYCOMMAND | (ots.autoaborttests ? MF_CHECKED : MF_UNCHECKED));
menu.CheckMenuItem(CM_VIEWSHOWEQUATIONS,
					MF_BYCOMMAND | (ots.showequats ? MF_CHECKED : MF_UNCHECKED));
menu.CheckMenuItem(CM_VIEWAUTORANDOMIZECOLORS,
					MF_BYCOMMAND | (ots.autorandomizecolors ? MF_CHECKED : MF_UNCHECKED));
menu.CheckMenuItem(CM_VIEWDRAW,
					MF_BYCOMMAND | (Drawing ? MF_CHECKED : MF_UNCHECKED));
menu.CheckMenuItem(CM_VIEWAUTOROTATECOLORS,
					MF_BYCOMMAND | (autorotatecolors ? MF_CHECKED : MF_UNCHECKED));
}              					// UpdateViewMenu
//----------------------------------------------------------------------------
void SFractalWindow::CmViewAutoRandomizeColors()
{ ots.autorandomizecolors = !ots.autorandomizecolors; }
//----------------------------------------------------------------------------
// randomly set colors in palette
// background is always reset to black, but user can change it for
// one particular display by Rclicking on the background color and editing it.
// this is the remaining color command not routed thru CmColorsManip, but it's always
// been allowed while drawing, and should be, and has always been ok.
void SFractalWindow::CmViewRandomizeColors()
{
dib->SetColor(0,TColor::Black);		// background black
dib->RandomizeColors(1);			// preserves color at 0
Invalidate(FALSE);
}
//----------------------------------------------------------------------------
// if ots.showequats is TRUE or you specify unconditional, draw data area on screen.
// you can call this function and let it handle the decision to draw or not.
void SFractalWindow::showdataarea(BOOL unconditional)
{
if(TranSet.IsEmpty())							// can't draw
	return;
if(!ots.showequats && !unconditional)			// don't draw
	return;

// almost the same as Parent->Title, provided that WAS set first.  could use instead.
string title;
if(TranSet.GetFileName().length())
	title = to_upper(TranSet.GetFileName());
else
	title = string("Untitled - ") + tostring(TranSet.id());

char buf[80];
ostrstream os(buf,sizeof(buf));					// build text for heading

TDibDC dibdc(*dib);			    				// dc for drawing onto the dib
dibdc.SetMapMode(MM_TEXT);						// pixel=pixel and +y is DOWN. (0,0)= top left
dibdc.SelectStockObject(SYSTEM_FONT);
// dibdc.SelectStockObject(ANSI_FIXED_FONT);	// numbers line up; try it sometime
dibdc.SetBkColor(dib->GetColor(0));
dibdc.SetTextColor(dib->GetColor(1));			// colors[1] is our standard text color

dibdc.TextOut(0,0,title.c_str());					// draw heading
int y = dibdc.GetTextExtent(title.c_str(),title.length()).cy;	// vert. pos.
for(int i = 0 ; i < TranSet.GetItemsInContainer() ; i++)		// show transforms
{
	os.seekp(0);									// reset output stream to start
	TranSet[i].RoundedOutput = TRUE;
	os << TranSet[i] << ends;						// format the transform data
	if(dispmode == colorbytransform)        		// match color used for
		dibdc.SetTextColor(dib->GetColor(i+2));		// this transform's plotted points
	dibdc.TextOut(0,y,buf);							// write transform data
	y += dibdc.GetTextExtent(buf,strlen(buf)).cy;	// new vertical pos.
}
os.seekp(0);
os << n << " points" << ends;						// point count report
dibdc.SetTextColor(dib->GetColor(1));
dibdc.TextOut(0,y,buf);
}                           // showdataarea
//----------------------------------------------------------------------------
// draw this fractal on the screen.  with drawcount 2000, Windows slowdown is negligible.
void SFractalWindow::draw(int drawcount)
{
// only if this is the active window, set View menu item checkmarks to match
// this is the first attempt to get this to a central loc called often enough but not too often
// the View menu is only supposed to match the active child.
if(HWindow && (HWindow == ::GetFocus()))
	UpdateViewMenu();

// various tests: sometimes you can't draw, sometimes you shouldn't.
// if main window (app) is iconic, are all owned windows? even if not, the MDIChild host
// could be even if the app is not, so must test it, too.
if(!drawing || !Drawing || TranSet.IsEmpty() ||
	GetApplication()->GetMainWindow()->IsIconic() || Parent->IsIconic())
		return;

// if window size changed, resize dib to match it.  replaces the removed call in EvSize.
if(GetClientRect() != dib->Rect())
	NewSetDisplayPrep();

if(!drawcount)		// if not specified, use the static member value (same for all objects)
	drawcount = DrawCount;

BOOL aborted = FALSE;
TPoint tempoint;						// (x,y) converted to ints
uchar color;
for(int i = 0 ; i < drawcount ; i++)	// plot requested number of points
{
	int k = TranSet.CalcNext();			// calculate new point, retain which transform used

	// you MUST test the raw values against the precomputed min,max values: if your drawing
	// is running to infinity, the xyfactor scaling below will eventually fail to
	// scale the points legal int values, and the points will land at random screen locs
	// (which I've seen happen).  You'll eventually get floating point overflow, too.
	// There's no way to predict WHEN that will happen, so you must abort as soon as
	// the design runs off the precomputed (raw) page (which was already very forgiving).
	// You must do this test even if autoaborttests is FALSE.
	// see also note at calcextent().

	if(TranSet.XYIsOutOfBounds())
	{
		if(ots.beepon)
			// MessageBeep(MB_ICONQUESTION);
			sndPlaySound("glasbk.wav",SND_SYNC);
		aborted = TRUE;
		break;
	}
	tempoint.x = (TranSet.x - TranSet.minx) * xfactor;
	tempoint.y = (TranSet.y - TranSet.miny) * yfactor;
	if(!ots.upsdwn)										// invert if upside down is FALSE
		tempoint.y = (dib->Height() - 1) - tempoint.y;
	color = dib->GetPixelByte(tempoint);				// get color at this point
	if(color == 0)					// if pixel has not been set before (new one),
	{                      			// reset consecutive dup. count
		dups = 0L;          		// to zero (it's a totally new point)
		pix++;             			// and increment pixel-on count
	}
	else                    		// otherwise, it was a duplicate,
		dups++;
	switch(dispmode)		// color for setting the point depends on the mode
	{
		// monochrome must be here because color was an INPUT value above
		// (so don't delete it on the idea that monocolor doesn't change)
		case(monochrome):
			color = 1;
			break;
		case(colorbyhits):
			if(color < 255)              	// prevent wrap to 0
				color++;
			break;
		case(colorbytransform):
			color = (uchar)min(k+2,255);	// avoid background & text colors
			break;
	}
	// set point, whether or not it was a dup. because it might be in a new color
	dib->SetPixelByte(tempoint,color);
}												// end for(i = 0 to drawcount)
n += drawcount;                					// increment iteration counter
if(!aborted && ots.autoaborttests)
	if(dups >= duplimit)
		aborted = TRUE;
if(aborted)
	CmFileNext();								// old method, probably ok
// 	PostMessage(WM_COMMAND, CM_FILENEXT);	// new method, also works

Invalidate(FALSE);		// force screen display of points we plotted to the dib
}               		// draw
//----------------------------------------------------------------------------
// returns FALSE if user pressed cancel, otherwise TRUE (i.e. "issue is resolved").
BOOL SFractalWindow::QuerySaveTranset()
{
while(TranSet.IsDirty() & USEREDITED)
	switch(MessageBox("Transform set has been changed. Save?", Parent->Title,
						MB_YESNOCANCEL | MB_ICONQUESTION))
	{
		case IDNO:            	// will be discarded
			TranSet.dirtyflag &= ~USEREDITED;
			break;
		case IDCANCEL:       	// user didn't resolve the question.
			return FALSE;
		case IDYES:         	// in case user doesn't actually save, must loop again.
			CmFileSave();       // you MUST either get a NO,CANCEL, or DO a save.
			break;
	}
return TRUE;
}								//QuerySaveTranset
//----------------------------------------------------------------------------
// possibly be reborn as a child of src1,src2 if this isn't src1 or src2.
void SFractalWindow::reincarnate()
{
if(!src1 || !src2)										// must have 2 parents
	return;
if(TranSet.GetFileName().length())						// preserve file-based windows
	return;
if(!(src1->TranSet.polar) || !(src2->TranSet.polar))	// PFS.  prevent mating IFS with PFS
	return;

// from here, definitely want to preserve: it's either a parent or a new child
// after the first user-initiated mating, all windows are non-autoabort until reset.
// no (changed my mind)- let them abort & move on.
// ots.autoaborttests = FALSE;

if((src1 == this) || (src2 == this))		// this is a parent.  nothing else to do.
	return;
											// this is a child.  create its new set.
if(!QuerySaveTranset())						// same query as if loading a new file.
	return;
TranSet.CreateFrom(src1->TranSet,src2->TranSet,TRUE,MutateOnMate);
NewSetDisplayPrep();

// override caption.
// this is handy, but any subsequent redraw reverts it back,
// AND multiple merges produce quite long titles.
// string caption = string("M: ")+ (src1->Parent->Title) + " + " + (src2->Parent->Title);
// string caption =
// 	string("(M:")+ tostring(src1->TranSet.id()) + "+" + tostring(src2->TranSet.id()) + ")";
// if(Parent)
// 	Parent->SetCaption(caption.c_str());
}							//reincarnate
//----------------------------------------------------------------------------
void SFractalWindow::CmZeroParents()
{
src1 = src2 = 0;
dragging = FALSE;
::SetCursor(::LoadCursor(NULL,IDC_ARROW));	// serves as reset if it got stuck on cross
}                       	//CmZeroParents
//----------------------------------------------------------------------------
// copy this to either src1 or (if src1 is used) src2 as a parent.
// can be invoked by menu selection, toolbar button click, or mouse:
// 1) Rclick in first window, drag to second window, and release.
// 2) Rclick in first window and release, Rclick in second window and release.
// These 2 methods are supposed to be equivalent, and are supposed to work EVEN if user
// begins or ends the clicks/drags in nonclient areas or even outside the application.
// This does not use SetCapture() because 2nd window must be able to get its click.
// RBUp and RBDown are treated as equivalent separate events, not as the start and end of
// a process.
// the reason there's audio & visual verification is that it seems sometimes there
// are multiple mates when you only intend one.  unsure if sticky mouse button (yes)
// or problem here.  possibly both.
// reducing .INI file drawcount improved reliability, but if problems persist,
// disable both mouse methods or figure out how to incorporate SetCapture().
void SFractalWindow::CmSelectAsParent()
{
// this test also prevents going to a cross cursor when there's only 1 window open,
// and getting stuck with it because there's no 2nd parent to select.
if(FracwinCount < 3)
{
	// #error stop drawing anyway, so you can do so with < 3 wins open
	// otherwise you must close all open windows, as only method
	// (don't know what that old note was supposed to mean)
	MessageBox("There must be at least 3 windows open:\n2 as parents, plus\n"
			   "1 or more random (not displaying files) for children.",
			   "Can't Select Parent",MB_OK | MB_ICONINFORMATION);
	CmZeroParents();	// in case a parent was selected BEFORE window count reduced to < 3
	return;
}
// s/b rare. IdleAction should have mated and zeroed them immediately.
// but it is happening.  suspect mouse button giving multiple clicks
// that are all getting through at once, before IdleAction gets its chance.
if(src1 && src2)
	return;

// this is already a parent; don't duplicate.  no msgbox because if src1==this,
// it's probably just the upclick from the downclick that SET it as this;
// so don't interrupt.  src2 check probably unnecessary because if src2 has
// any nonzero value, so does src1, and it would have returned already.
if((src1 == this) || (src2 == this))
	return;
if(!src1)
{
	src1 = this;
	::SetCursor(::LoadCursor(NULL,IDC_CROSS));
	// dragging is considered true even if we're not literally dragging.
	// whether we are or not, cursor changes to cross to indicate that one
	// parent has been selected and the 2nd one is needed.
	dragging = TRUE;
}
else 					// src2 is null
{
	src2 = this;
	::SetCursor(::LoadCursor(NULL,IDC_ARROW));
	dragging = FALSE;
}
MessageBeep(MB_OK); 		 	// ding
}                  				//CmSelectAsParent
//----------------------------------------------------------------------------
// reset the display mode and do whatever is required to implement the new mode
void SFractalWindow::setdispmode(dispmodes newmode)
{
if(newmode == dispmode)		// avoid Invalidating if no change
	return;
dispmode = newmode;
EraseDib();
}                      			//setdispmode
//----------------------------------------------------------------------------
void SFractalWindow::CmViewUpsideDown()
{
ots.upsdwn = !ots.upsdwn;
EraseDib();						// must erase screen to start new inverted design
}                      			//CmViewUpsideDown
//----------------------------------------------------------------------------
void SFractalWindow::CmViewAutoAbortTests() { ots.autoaborttests = !ots.autoaborttests; }
void SFractalWindow::CmViewNumberOfPoints() { showdataarea(TRUE); }
void SFractalWindow::CmViewShowEquations() { ots.showequats = !ots.showequats; showdataarea(); }
void SFractalWindow::CmViewDraw() { Drawing = !Drawing; }
//----------------------------------------------------------------------------
// Allow user to edit options in the Options dialog box.
// this function does NOT reinitialize a new transform set, so you will
// return to the same set that was being drawn, with the old settings.
// some choices won't take effect until the NEXT design.
void SFractalWindow::CmViewOptions()
{
// initialize transfer buffer data
ots.colorbyhits = (dispmode == colorbyhits);
ots.colorbytransform = (dispmode == colorbytransform);
ots.monochrome = (dispmode == monochrome);

ots.optionsatstartup = SFractalWindow::ViewOptionsAtStartup;
ots.mutateonmate = SFractalWindow::MutateOnMate;

ostrstream(ots.numtransformsedit,sizeof(ots.numtransformsedit))
	<< TranSet.GetRandomTransformCount() << ends;
ostrstream(ots.duplimitedit,sizeof(ots.duplimitedit)) << duplimit << ends;

SOptionsDialog od(this, &ots);
od.SetCaption(Parent->Title);	// caption to match the (focus) child window it pertains to
if(od.Execute() == IDOK)
{
	// copy transfer buffer data back
	if(ots.colorbyhits)
		setdispmode(colorbyhits);
	else
		if(ots.colorbytransform)
			setdispmode(colorbytransform);
		else                          // no test here, monochrome is default, just in case
		{
			setdispmode(monochrome);
			TChooseColorDialog::TData choose;			// now ask which monocolor to use
			choose.Flags = CC_FULLOPEN | CC_RGBINIT;	// flags checked: ok
			choose.Color = dib->GetColor(1);
			choose.CustColors = MyCustomColors;			// a global colors array in mylib.cpp
			TChooseColorDialog ccd(this, choose);
			ccd.SetCaption("Select Monochrome Color to Use");
			if(ccd.Execute() == IDOK)
			{
				dib->SetColor(1,choose.Color);  	 	// obviously, if we specified the color,
				ots.autorandomizecolors = FALSE;		// and no autorandomize
			}
		}

	SFractalWindow::ViewOptionsAtStartup = ots.optionsatstartup;
	SFractalWindow::MutateOnMate = ots.mutateonmate;

	int i;
	istrstream(ots.numtransformsedit) >> i;	// no change if unparseable
	TranSet.SetRandomTransformCount(i);

	long n;
	istrstream(ots.duplimitedit) >> n;
	if(n > 0L)
		duplimit = n;
}
EraseDib();						// erase screen to implement screen-related changes
}								//CmViewOptions
//----------------------------------------------------------------------------
// Load next file in FileList.  formerly CmFileNew, but Next
// emphasizes that it may be the next in the queue, OR a new one.
void SFractalWindow::CmFileNext()
{
if(!QuerySaveTranset())
	return;
if(!FileList.GetItemsInContainer()) 	// list empty, need random for sure
	TranSet.NewRandomSet(ots.xysamerotation,ots.xysamescale);
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
	if(TranSet.ReadFile(FileList.GetNext().c_str()))	// this Flushes first
		break;											// file successfully read
	else     	// failed try leaves set empty.  Not an error, but requires manual FileNext.
		MessageBox(TranSet.GetLastErrorText().c_str(),"File Error",MB_OK | MB_ICONEXCLAMATION);
}
NewSetDisplayPrep();									// no problem if set is empty
// should show at beginning, so a new random set has specified settings, but
// CmViewOptions calls EraseDib, so be careful about moving.  it currently works!
if(ViewOptionsAtStartup && !drawing)
	CmViewOptions();		// set options
drawing = TRUE;				// enable auto-drawing
}        					// CmFileNext
//----------------------------------------------------------------------------
// #error every Save is treated as a SaveAs, so you are warned that the file already exists
// even if you are obviously just Saving it as itself.
// somewhat confusing, but may be desirable.  could set OFN_OVERWRITEPROMPT based on
// whether (what?)
void SFractalWindow::CmFileSave()
{
chdir(DATADir.c_str());

// reminder: I think that all SFractalWindows SHARE this TData.
static TOpenSaveDialog::TData filedata(						// flags ok
	OFN_PATHMUSTEXIST | OFN_CREATEPROMPT |OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY,
	"Polar Files (*.PFS)|*.PFS|IFS Files (*.IFS)|*.IFS|",0,0,"PFS");

if(TranSet.GetFileName().length())	// put file name in filename box as suggested default
	ostrstream(filedata.FileName,MAXPATH) << TranSet.GetFileName() << ends;
else
{
	// if current set doesn't already have a file name,
	// find next available ####.PFS file in sequence in DATADir.
	string nextfile;
	char buf[20];
	int piccount = -1;			// good method: fills in holes, if any, in series
	ofstream outfile;
	do						   	// find first unused number
	{
		// pad with leading zeroes? orders correctly lexically, but makes filenames long.
		ostrstream(buf,sizeof(buf)) << ++piccount << ends;
		nextfile = string(buf) + ".PFS";
		outfile.open(nextfile.c_str(),ios::noreplace);
	}
	while(!outfile);			// fails if file exists
	outfile.close();
	unlink(nextfile.c_str());	// delete the empty file
	ostrstream(filedata.FileName,MAXPATH) << nextfile << ends;
}
if(TFileSaveDialog(this,filedata).Execute() != IDOK)	// select file
	return;
TranSet.WriteFile(filedata.FileName);	// transet gets FULL path name to copy
NewSetDisplayPrep();					// force redraw with (probably new) name
char buf[MAXPATH];     			// see also Get/SetCurrentDirectory (Win32 only)
getcwd(buf,MAXPATH);
DATADir = buf;
}								//CmFileSave
//----------------------------------------------------------------------------
// user-selected files are added to the FileList queue.
void SFractalWindow::CmFileLoad()
{
chdir(DATADir.c_str());
static TOpenSaveDialog::TData filedata(OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT, // flags ok
	"Polar Files (*.PFS)|*.PFS|IFS Files (*.IFS)|*.IFS|",0,0,"PFS");

// if there's a holdover name, use it in case TranSet's filename got lost through autoabort.
// else use the current set's filename, if any; else leave blank.
if((filedata.FileName[0] == 0) && TranSet.GetFileName().length())
	ostrstream(filedata.FileName,MAXPATH) << TranSet.GetFileName() << ends;

TFileOpenDialog fd(this,filedata);
fd.SetCaption("Load File(s) into FileList");
if(fd.Execute() != IDOK)
{
	if(filedata.Error)
	{
		string s = string("Error code: ") + tostring(filedata.Error);
		if(filedata.Error == 0x3003)
			s += "\nToo many files selected at one time.";
		MessageBox(s.c_str(),"Dialog box error",MB_OK | MB_ICONEXCLAMATION);
	}
	return;
}
// DON'T flush FileList here because multiselect bufsize MAXPATH limits to only a few files,
// so you may need multiple calls to build your entire list.
FileList.AddDialogList(filedata.FileName);
// CmFileNext();	// auto-initialize & display the first (valid) one (also UNdesirable)
char buf[MAXPATH];     		// see also Get/SetCurrentDirectory (Win32 only)
getcwd(buf,MAXPATH);
DATADir = buf;
}               				// CmFileLoad
//----------------------------------------------------------------------------
// Edit the current transform set in a dialog box with a single multi-line edit control.
// the set doesn't have to be from a file, and is not auto-written to any file.
// After editing, the new design is automatically displayed.
void SFractalWindow::CmFileEdit()
{
TranStruct* tts = new TranStruct;           // (local copy of edited text)
strcpy(tts->filechars,TranSet.filechars);	// copy so transfer mechanism can auto-load it
STranSetDialog* TransEditor = new STranSetDialog(this,tts);
TransEditor->SetCaption(Parent->Title);		// caption to match window (filename or untitled)
if(TransEditor->Execute() == IDOK)			// auto-loads AND auto-writes tts buffer,
{
	strcpy(TranSet.filechars,tts->filechars);	// copy text back to TranSet only if IDOK
	TranSet.dirtyflag |= USEREDITED;
	if(!TranSet.loadfromtext())
		MessageBox(TranSet.GetLastErrorText().c_str(),"Transform Set Load Error",
					MB_OK | MB_ICONEXCLAMATION);
}
ots.autoaborttests = FALSE;		// even if you aborted edit, you probably want to keep set
setdispmode(colorbytransform);	// helps to see results of editing

BOOL oldrc = ots.autorandomizecolors;	// you probably don't want to autorandomize colors
ots.autorandomizecolors = FALSE;		// trick it into NOT randomizing
NewSetDisplayPrep();	 				// new design can produce new raw min,max values
ots.autorandomizecolors = oldrc;		// restore old setting

delete TransEditor;
delete tts;
}								//CmFileEdit
//----------------------------------------------------------------------------
BOOL SFractalWindow::CanClose()
{
if(!SDibWindow::CanClose() || !QuerySaveTranset())
	return(FALSE);
return TRUE;
}                 	     		//CanClose
//----------------------------------------------------------------------------
// IdleAction was chosen instead of EvTimer because we don't want our activity
// to be performed at timed intervals, which slows us down.
// Instead, we want to draw as much as possible.
BOOL SFractalWindow::IdleAction(long idlecount)
{
// if dragging, don't do anything: prevents auto-abort while selecting parents
// and ensures timely mouse message handling.
if(!dragging)
{
	// auto-rotate may be safe even while Drawing:
	// no msgs generated, and done only once per call, anyway.
	// may be an interesting effect.
	SDibWindow::IdleAction(idlecount);
	draw();
}
return(TRUE);
}
//----------------------------------------------------------------------------
// 						end class SFractalWindow
//////////////////////////////////////////////////////////////////////////////
// A TMDIClient with an IdleAction that iterates through its children.
// When TMDIFrame iterates through its children, it hits the TMDIClient,
// but the chain is broken because TMDIClient only uses TWindow's IdleAction,
// which does nothing.  Once TMDIClient iterates through its children, the
// chain is restored, because most of its children are TFrameWindow-derived,
// and thus do iterate through their children.
//
// This could become a (differently-named) base class also used in other programs,
// especially for its handling of object mating: should be applicable to anything.
class SFractalClient : public TMDIClient
{
public:
	SFractalClient(TModule* module = 0);
	~SFractalClient();

	// virtuals: TMDIClient
	TMDIChild* InitChild();

	// virtuals: TWindow
	virtual BOOL IdleAction(long idlecount);

	// functions
	void Startup();				// interpret command line, open initial child windows, etc.
	void OpenFile(const string& filename);

	FileArray FileList;					// list of files to create windows for

protected:
	// TWindow overridden virtuals
	virtual void SetupWindow();
	virtual void CleanupWindow();

	// event handlers
	void EvDropFiles(TDropInfo dropInfo);
	void CmFileExit() { GetApplication()->GetMainWindow()->CloseWindow(); }
	void CmFileOpen();
	void CmFileNew();
	void CmHelpIndex();
	void CmHelpAbout();
// 	void CmFileClose();	// a reminder NOT to try to close children this way.
						// CloseWindow() crashes the app.  and methods from
						// mdiclient.cpp won't compile!

DECLARE_RESPONSE_TABLE(SFractalClient);
};
//----------------------------------------------------------------------------
DEFINE_RESPONSE_TABLE1(SFractalClient,TMDIClient)
	EV_WM_DROPFILES,
	EV_COMMAND(CM_FILEEXIT,CmFileExit),
	EV_COMMAND(CM_FILEOPEN,CmFileOpen),
	EV_COMMAND(CM_FILENEW,CmFileNew),
	EV_COMMAND(CM_HELPINDEX,CmHelpIndex),
	EV_COMMAND(CM_HELPABOUT,CmHelpAbout),
END_RESPONSE_TABLE;
//----------------------------------------------------------------------------
// constructor
SFractalClient::SFractalClient(TModule* module) : TMDIClient(module)
{
TRACE("SFractalClient::SFractalClient");
}
//----------------------------------------------------------------------------
// destructor
SFractalClient::~SFractalClient()
{
// #error see context sensitive help examples.
// more complicated than this.  See Help.  this window didn't request the help.
// probably must change so it DOES, being provided with the command and data to use.
// WinHelp(HelpFilename,HELP_QUIT,0L);
}
//----------------------------------------------------------------------------
void SFractalClient::SetupWindow()
{
TRACE("SFractalClient::SetupWindow");

TMDIClient::SetupWindow();
DragAcceptFiles(TRUE);
}						//SetupWindow
//----------------------------------------------------------------------------
void SFractalClient::CleanupWindow()
{
// pointless?  one example does it, in another it's commented out.
// DragAcceptFiles(FALSE);
TMDIClient::CleanupWindow();    		// must do base last
}                      	//CleanupWindow
//----------------------------------------------------------------------------
// this EvDrop is called if you drop the files in the Client area, and opens
// a new window for each dropped file.
// adds dropped files to list, but initialize is done in IdleAction, when
// test for Iconic is more convenient.
void SFractalClient::EvDropFiles(TDropInfo dropinfo)
{
int count = dropinfo.DragQueryFileCount();
for(int i = 0 ; i < count ; i++)
{
	// seems wasteful to new each one, but this will still work if path exceeds MAXPATH.
	int length = dropinfo.DragQueryFileNameLen(i) + 1;
	char* filename = new char[length];
	dropinfo.DragQueryFile(i,filename,length);
	FileList.AddFile(string(filename));
	delete[] filename;
}
dropinfo.DragFinish();
}            			//EvDropFiles
//----------------------------------------------------------------------------
// children have SFractalWindow clients because they're the most common and
// the background color is black, which looks best on startup.
// (if you don't set the client here, it's white briefly, then flashes to black)
// if you want a different client, replace (and delete) the SFractalWindow after creation
TMDIChild* SFractalClient::InitChild()
{
return new TMDIChild(*this, "Untitled", new SFractalWindow);
}
//----------------------------------------------------------------------------
// open a new window, and start drawing a new random fractal in it.
void SFractalClient::CmFileNew()
{
TMDIChild* child = dynamic_cast<TMDIChild*>(CreateChild());
if(child)
{
	SFractalWindow* s = dynamic_cast<SFractalWindow*>(child->GetClientWindow());
	if(s)
		s->CmFileNext();
}												// if(child)
TileChildren();    								// size them to fill the window
}							//CmFileNew
//----------------------------------------------------------------------------
// open file in a new window
void SFractalClient::OpenFile(const string& filename)
{
TMDIChild* child = dynamic_cast<TMDIChild*>(CreateChild());
if(child)
{
	SFractalWindow* s = dynamic_cast<SFractalWindow*>(child->GetClientWindow());
	if(s)
	{
		s->FileList.AddFile(filename);
		s->CmFileNext();
	}
}
TileChildren();    								// size them to fill the window
}						//OpenFile
//----------------------------------------------------------------------------
// select file(s) to put into new windows.
void SFractalClient::CmFileOpen()
{
// note this doesn't and can't have DATADir method
TOpenSaveDialog::TData filedata(OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT, // flags ok
	"Polar Files (*.PFS)|*.PFS|IFS Files (*.IFS)|*.IFS|",0,0,"PFS");
if((TFileOpenDialog(this,filedata)).Execute() == IDOK)
	FileList.AddDialogList(filedata.FileName);
else
	if(filedata.Error)
	{
		string s = string("Too many files?\nError code: ") + tostring(filedata.Error);
		MessageBox(s.c_str(),"Dialog box error",MB_OK | MB_ICONEXCLAMATION);
	}
}							//CmFileOpen
//----------------------------------------------------------------------------
void SFractalClient::CmHelpIndex() { WinHelp(HelpFilename,HELP_INDEX,0); }
//----------------------------------------------------------------------------
void SFractalClient::CmHelpAbout()
{
MessageBox("Copyright (C)1988-1999,2001 Steven Whitney",GetApplication()->GetName(),
			MB_OK | MB_ICONINFORMATION);
}
//----------------------------------------------------------------------------
// call IdleAction for all children.  plus, does any top-level IdleAction behavior.
BOOL SFractalClient::IdleAction(long idlecount)
{
// if any files have been added to the list, open windows for them now.
// but if minimized, wait until it's restored so there actually is a drawing surface.
// alternatively, could automatically restore or maximize. at least try it to test.
if(!(GetApplication()->GetMainWindow()->IsIconic()))
	while(FileList.GetItemsInContainer())
		OpenFile(FileList.GetNext());

BOOL mating = FALSE;
// if 2 different parents have been specified, turn the non-parent windows into their children.
// doing it here ensures that regardless of when the mouse messages were received and
// acted on, we do the mating in exactly one loop, as soon as both parents are valid,
// and then reset so it isn't done again.
if((SFractalWindow::src1 && SFractalWindow::src2) &&
	(SFractalWindow::src1 != SFractalWindow::src2))
		mating = TRUE;
if(mating)
{
// 	MessageBeep(MB_ICONASTERISK);
	for(TWindow* child = GetFirstChild() ; child ; child = child->Next())
	{
		// got a child, but is it a TMDIChild?
		TMDIChild* mdichild = dynamic_cast<TMDIChild*>(child);
		if(mdichild)
		{
			SFractalWindow* s = dynamic_cast<SFractalWindow*>(mdichild->GetClientWindow());
			if(s)
				s->reincarnate();		// maybe get reborn with a new set
		}
		if(child == GetLastChild())
			break;
	}
}
idlecount = 0L;		// force each TFrame to iterate thru its children every pass
for(TWindow* child = GetFirstChild() ; child ; child = child->Next())
{
	// all windows do at least have an IdleAction.
	// TWindow's IdleAction does NOT iterate through its children.
	// But if win is actually TWindow-derived with a better overriding IdleAction,
	// that one will be called, even through the TWindow*, because TWindow's is virtual.
	// Thus, a TMDIChild (TFrameWindow-derived) WILL iterate through its children,
	// and my customized IdleAction, in the client window, will get called.
	child->IdleAction(idlecount);
	if(child == GetLastChild())
		break;
}

// done.  reset.
// would prefer using CmZeroParents(), but we don't have an object to call it for,
// so unsure what its SetCursor would do. (what the scope would be)
if(mating)
	SFractalWindow::src1 = SFractalWindow::src2 = 0;
return TRUE;
}							//IdleAction
//----------------------------------------------------------------------------
// do initial setup when application first starts,
// interpret command line, open initial child windows, etc.
void SFractalClient::Startup()
{
TRACE("SFractalClient::Startup");

#if defined(__WIN32__)		// get command line arguments (list of files to display)
	// discard program name, then the rest are file names
	string pgmname;
	istrstream(GetCommandLine()) >> pgmname >> FileList;
#else
	for(int i = 1 ; i < _argc ; i++)
		FileList.AddFile(string(_argv[i]));
#endif	//(__WIN32__)

int numchildren = 1;	// at fn end, it's the number of child windows created

// If there are files on the command line, open 1 window and put all the files in its list.
// Thus, *.PFS on command line won't open 100 windows!
if(FileList.GetItemsInContainer())
{
	// you should be able to override CreateChild to return a TMDIChild
	// AND GetClientWindow to return a SFractalWindow.  unsure if desirable.
	TMDIChild* child = dynamic_cast<TMDIChild*>(CreateChild());
	if(child)
	{
		child->Show(SW_SHOWMAXIMIZED);
		SFractalWindow* s = dynamic_cast<SFractalWindow*>(child->GetClientWindow());
		if(s)
			while(FileList.GetItemsInContainer())
				s->FileList.AddFile(FileList.GetNext());
	}
}
else 	// no files, create desired number of startup random windows
{
	// return to last-saved data directory only if there were no files on command line:
	// if there were files, assume user wants THEIR dir.
	char buf[MAXPATH];            		// filename from .INI file
	if(getcwd(buf,sizeof(buf)))      	// temp use to get current logged dir
	{                                   // (getcwd shouldn't ever fail)
		string cwd = buf;				// copy it to use as the default parameter
		GetPrivateProfileString("info","datadir",cwd.c_str(),buf,sizeof(buf),INIFilename);
		string s(buf);
		if(cwd != s)			// don't bother if not found or same as current cwd
			FilePathParser(s + "\\").ChDir();
	}
	// 4 startup windows, each a different XYSame combination.
	numchildren = 4;
	for(int i = 0 ; i < numchildren ; i++)
	{
		TMDIChild* child = dynamic_cast<TMDIChild*>(CreateChild());
		if(child)
		{
			SFractalWindow* s = dynamic_cast<SFractalWindow*>(child->GetClientWindow());
			if(s)
			{
				switch(i)
				{
					case 0: s->ots.xysamerotation = FALSE; s->ots.xysamescale = FALSE; break;
					case 1: s->ots.xysamerotation = TRUE; s->ots.xysamescale = FALSE; break;
					case 2: s->ots.xysamerotation = FALSE; s->ots.xysamescale = TRUE; break;
					case 3: s->ots.xysamerotation = TRUE; s->ots.xysamescale = TRUE; break;
				}
			}		// if(s)
		}			// if(child)
	}				// for(numchildren)
}					// else
if(numchildren > 1)
	TileChildren();    								// size them to fill the window

// start each child drawing.
// general method for running through child list.  Advantage is that you can perform
// any action inside the loop without having to create an additional specialized function
// just so ForEach can call it.
for(TWindow* child = GetFirstChild() ; child ; child = child->Next())
{
	// got a child, but is it a TMDIChild?
	TMDIChild* mdichild = dynamic_cast<TMDIChild*>(child);
	if(mdichild)
	{
		SFractalWindow* s = dynamic_cast<SFractalWindow*>(mdichild->GetClientWindow());
		if(s)
			s->CmFileNext();
	}
	// this could go inside for() if I could figure out what value it has AFTER the last child.
	// not 0.  (the while(child) alone above results in endless loop)
	if(child == GetLastChild())
		break;
}
}							//Startup
//----------------------------------------------------------------------------
// 						end class SFractalClient
//////////////////////////////////////////////////////////////////////////////
// The application object
class TMyApp : public TApplication
{
public:
	TMyApp(const char far *title);

protected:
	virtual BOOL IdleAction(long idlecount);
	virtual void InitInstance();
	virtual int TermInstance(int status);
	virtual void InitMainWindow();

// 	void EvDropFiles(TDropInfo dropInfo);

DECLARE_RESPONSE_TABLE(TMyApp);
};
//----------------------------------------------------------------------------
DEFINE_RESPONSE_TABLE1(TMyApp,TApplication)
// 	EV_WM_DROPFILES,
END_RESPONSE_TABLE;
//----------------------------------------------------------------------------
// constructor.  title only used in case of error
TMyApp::TMyApp(const char far *title) : TApplication(title) {}
//----------------------------------------------------------------------------
// this method seems ok.  other apps do run fine.
BOOL TMyApp::IdleAction(long /* idlecount */)
{
TApplication::IdleAction(0);
return TRUE;
}
//----------------------------------------------------------------------------
void TMyApp::InitInstance()
{
// after calling this, ALL window operations are legal, since window is created AND visible.
TApplication::InitInstance();					// calls InitMainWindow

// basic # of points to plot each IdleAction, if only 1 active window.
// in file so it can be customized to computer's speed for acceptable draw delay.
int i = GetPrivateProfileInt("info","drawcount",SFractalWindow::BasicDrawCount,INIFilename);
if(i)
	SFractalWindow::BasicDrawCount = i;

TDecoratedMDIFrame* frame = dynamic_cast<TDecoratedMDIFrame*>(GetMainWindow());
if(frame)
{
	// for now, a way to generate a global composite menu while keeping resources separate
	// for later.  you must merge when win is displayed.
	// but commands never available, because TransEditor is modal.
// 	frame->MergeMenu(TMenuDescr(TResId(IDM_EDITSEARCH),0,2,0,0,0,0));

	SFractalClient* client = dynamic_cast<SFractalClient*>(frame->GetClientWindow());
	if(client)
		client->Startup();

	// the frame CANNOT get dropped files because it owns the entire main window;
	// it would intercept a drop before any other window could process it.
	// so you could not drop into an SFractalWindow.  Because of this, however,
	// you can't drop files into the app when it's iconized on the desktop.
	// frame->DragAcceptFiles(TRUE);

}	// if frame
}                       	//InitInstance
//----------------------------------------------------------------------------
// if there are multiple instances running, .INI file gets info from the last closed.
int TMyApp::TermInstance(int status)
{
TRACE("TMyApp::TermInstance");

// write to WSHOWFS.INI:
// default dir is our current dir
char buf[MAXPATH];
if(getcwd(buf,sizeof(buf)))
	WritePrivateProfileString("info","datadir",buf,INIFilename);

WritePrivateProfileString("info","drawcount",
				tostring(SFractalWindow::BasicDrawCount).c_str(),INIFilename);

return TApplication::TermInstance(status);					// call last?
}                      		//TermInstance
//----------------------------------------------------------------------------
void TMyApp::InitMainWindow()
{
// TRACE("TMyApp::InitMainWindow");

TDecoratedMDIFrame* frame =
	new TDecoratedMDIFrame(Name, TResId(MENU_1), *(new SFractalClient), TRUE);

frame->SetMenuDescr(TMenuDescr(TResId(MENU_1),1,0,2,1,1,1));
frame->Attr.AccelTable = TResId(MENU_1);

// try each TBorderStyle None, Plain, Raised (it's raised),
// Recessed (like IDE), Embossed (looks same as Recessed)
TStatusBar* sb = new TStatusBar(frame, TGadget::Recessed);
frame->Insert(*sb, TDecoratedFrame::Bottom);

// Construct a control bar
TControlBar *cb = new TControlBar(frame);
cb->Insert(*new TButtonGadget(TResId(CM_FILENEW),CM_FILENEW,TButtonGadget::Command));
cb->Insert(*new TButtonGadget(TResId(CM_FILEOPEN),CM_FILEOPEN,TButtonGadget::Command));
cb->Insert(*new TButtonGadget(TResId(CM_FILESAVE),CM_FILESAVE,TButtonGadget::Command));
cb->Insert(*new TSeparatorGadget(24));
cb->Insert(*new TButtonGadget(TResId(CM_FILELOAD),CM_FILELOAD,TButtonGadget::Command));
cb->Insert(*new TButtonGadget(TResId(CM_FILENEXT),CM_FILENEXT,TButtonGadget::Command));
cb->Insert(*new TSeparatorGadget(24));
cb->Insert(*new TButtonGadget(TResId(CM_SELECTASPARENT),CM_SELECTASPARENT,TButtonGadget::Command));
cb->Insert(*new TButtonGadget(TResId(CM_ZEROPARENTS),CM_ZEROPARENTS,TButtonGadget::Command));
cb->Insert(*new TSeparatorGadget(24));
cb->Insert(*new TButtonGadget(TResId(CM_TILECHILDREN),CM_TILECHILDREN,TButtonGadget::Command));
cb->Insert(*new TSeparatorGadget(24));
cb->Insert(*new TButtonGadget(TResId(CM_HELPINDEX),CM_HELPINDEX,TButtonGadget::Command));
cb->SetHintMode(TGadgetWindow::EnterHints);
frame->Insert(*cb, TDecoratedFrame::Top);

nCmdShow = SW_SHOWMAXIMIZED;				// SW_SHOW;
SetMainWindow(frame);

EnableCtl3d(TRUE);			// these are better.  makes all controls automatically 3d.
}							//InitMainWindow
#if 0
//----------------------------------------------------------------------------
// supposed to allow dropping on title bar or other nonclient areas, but it doesn't.
// the App CAN process dropped files, but only a WINDOW can accept them.
// e.g. you can enable drop for the main window, then let the App's EvDrop handle it.
// (but not desirable or possible here)
void TMyApp::EvDropFiles(TDropInfo dropinfo)
{
TDecoratedMDIFrame* frame = dynamic_cast<TDecoratedMDIFrame*>(GetMainWindow());
if(frame)
{
	SFractalClient* client = dynamic_cast<SFractalClient*>(frame->GetClientWindow());
	if(client)
		client->EvDropFiles(dropinfo);
}
}            			//EvDropFiles
//----------------------------------------------------------------------------
#endif 		// 0
// 						end class TMyApp
//////////////////////////////////////////////////////////////////////////////
// OwlMain
int OwlMain(int /* argc */, char* /* argv */ [])
{
randomize();
string::set_case_sensitive(0);

// Run() calls: InitApplication(), InitInstance() { (which calls: InitMainWindow() },
// then displays main window.
TMyApp* myapp = new TMyApp(AppName);
int retval = myapp->Run();
delete myapp;

return(retval);
}

Additional code for this project is on the next page.

 

 

Valid HTML 4.01 Transitional Valid CSS
View content labeling at ICRA.
Copyright ©2007 Steven Whitney. Last modified 09/25/2007.