|
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 Payments Humor Music |
Iterated Function Set (IFS) fractal display program for Borland C++ ObjectWindows (OWL)WSHOWFS.CPP 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 algorithm 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. These are two images from the gallery of IFS fractal images generated by this program. Click to view full size:
Download:Click here to download wshowfs.zip (about 101 KB). The zip contains these files that are unique to this project. In addition, the program requires SDibWindow, SDib, and several other library classes 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.
This screenshot of the project nodes in the Borland C++ 4.0 IDE might help you set up the project nodes.
|
/* 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 3 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.
|
|
|