|
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 |
|
|
Microsoft Visual C++ 2005/2008 program zooms into the logistic map bifurcation diagramWhen the equation X=RX(1-X) is iterated many times and graphed, a characteristic design emerges that demonstrates equation instability and deterministic chaos. The graph also has some of the same visual appeal that the Mandelbrot set has, although not as varied. VCBif allows zooming into the logistic map bifurcation diagram in the same way a Mandelbrot program zooms into the Mandelbrot set. It has color cycling (color animation using palette rotation), plus the ability to quickly and easily load, swap, reverse, and complement palettes, generate random new gradient palettes, and it has an auto-run mode that continuously generates new pictures of random regions at random magnifications. There is a bifurcation diagram image gallery that has some additional information about how the images are generated. This Microsoft Visual C++ .NET version was converted from the Borland OWL version, whose page has links to earlier versions in other languages. The OWL download has two files (one is an .RTF document source for WinHelp) that might be useful but aren't duplicated in the download here. Although this VC++ version is functional and already exceeding my expectations, conversion work is still ongoing, so some features are commented out. There is no exception handling. I sometimes do get unhandled exceptions while converting new features, so cannot guarantee that they won't occur. Download:Click here to download vcbif.zip (49 KB). The zip contains the following files that are unique to this project:
Also required are the following files available at these links:
After you create the project in the Visual C++ IDE, but before you run the program, create the following two subdirectories which the project uses for data files:
|
The following listing omits about 1000 lines of repetitive WinForms setup code.
/* VCBif.cpp 06-26-2008 MSVC++
Copyright (C)1990-2001, 2006, 2008 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.
Calculates and displays regions of the logistic map bifurcation diagram.
Works the same as Winbrot, continuously displaying randomly selected regions
at randomly selected zoom magnifications.
------
TO DO:
--stringcontains might be case sensitive.
--bring over <g>raph mode (see temp.cpp)
--Can DirectSound be used to implement the sound that the old DOS version has?
------
NOTES:
--Increasing the timer interval from 1ms to 20ms stopped the CPU from being at 100%, stopped the fan from whirring,
but had no noticeable effect on calculation time or color cycling speed. This could be a clue to what's wrong with applications
like antivirus etc. that sometimes peg the CPU.
--I think this now has in it everything that is useful from SDibWindow. Stripped of VCBif code, it could become the new base class.
--SDib is functional, and has in it everything that is useful from the old SDib. If derived from Bitmap, it could become a new base class, too.
--Saving .INF file is now permitted; be sure to keep them separate from Winbrot .INF files.
*/
#pragma once
#include <stdio.h>
#include <time.h>
#include <math.h>
#include <iomanip>
#include <complex>
#include <vector>
#include <string>
#include <istream>
#include <vcclr.h>
#include "OptionsDialog.h"
#include "PICOptionsDialog.h"
#include "ListEditorDialog.h"
#include "..\..\mylib\mylib.cpp"
#include "..\..\mylib\doubrect.cpp"
#include "..\..\mylib\filearay.cpp"
#include "..\..\mylib\sdib.cpp"
typedef DoubleRect MandelRect; // appropriate name for this application
const char AppName[] = "VCBif";
char INIFilename[] = "vcbif.ini";
char HelpFilename[] = "vcbif.hlp";
namespace VCBif {
using namespace std;
using namespace System;
using namespace System::ComponentModel;
using namespace System::Collections;
using namespace System::Windows::Forms;
using namespace System::Data;
using namespace System::Drawing;
using namespace System::Drawing::Imaging;
using namespace System::Diagnostics;
/// <summary>
/// Summary for Form1
///
/// WARNING: If you change the name of this class, you will need to change the
/// 'Resource File Name' property for the managed resource compiler tool
/// associated with all .resx files this class depends on. Otherwise,
/// the designers will not be able to interact properly with localized
/// resources associated with this form.
/// </summary>
public ref class Form1 : public System::Windows::Forms::Form
{
protected:
OptionsDialog^ optionsdialog; //primary dialog box for runtime options
PICOptionsDialog^ PICoptionsdialog; //options when loading a .PIC file for display (not yet used)
ListEditorDialog^ listeditordialog; //dialog box for editing a list of files (not yet used)
Stopwatch^ sw; //for timing things
// used in loops:
int zpower; // the power to which z is raised (normally 2)
int row; // in this app, the next dib COLUMN to be calculated
double dx; // x (absolute) interval each horiz. pixel
double dy; // y (absolute) interval each vert. pixel
uint* PicLinebuf; // escape times for 1 line HEIGHT of current dib
bool allowzoom; // whether current display is a calculated Mandelbrot area
double mindiff; // minimum difference to consider 2 doubles equal
// if too small, fails to report obvious cycles
// if too large, may report nonexistent cycles
//THINGS THAT MUST BE POINTERS FOR CLR
double c; // (formerly complex) only the real is used, for the interval to plot.
vector<MandelRect> *prev; // list of calculated regions, for backing out
MandelRect *curr; // currently-displayed region
vector<complex<double>> *Cycles; // list of cycles encountered as: [x,cyclelength]
FileArray *FileList; // command line arguments and/or user-selected files
FileArray *MapFiles; // list of all available .MAP files (see ctor)
SDib *Colors; // for now, it only manages the palette
Bitmap^ dib; // holds currently displayed design
int FirstFreeColor; // first palette index that can be freely randomized or changed
int autorotatecolors; // whether and which direction to rotate palette in IdleAction
// values are -1,0,+1.
bool Dragging; // used while selecting a new calculate region,
Point DragStart, DragEnd; // corners of region being selected with box
Rectangle DragRect, DibDragRect; // dragged area in window, and in dib, coordinates
DoubleRect *DragPct; // dragged area, as percentages of screen dimensions
public:
//----------------------------------------------------------------------------
// CONSTRUCTOR
Form1(void)
{
InitializeComponent();
// unsure if this placement is ideal. In Borland, it had to go in OwlMain to work.
srand((unsigned)time(NULL));
// pointers
optionsdialog = gcnew OptionsDialog;
PICoptionsdialog = gcnew PICOptionsDialog;
listeditordialog = gcnew ListEditorDialog;
sw = gcnew Stopwatch;
prev = new vector<MandelRect>;
curr = new MandelRect;
Cycles = new vector<complex<double>>;
FileList = new FileArray;
MapFiles = new FileArray;
Colors = new SDib;
Colors->SetColor(0,Color::Black); // background to match window (the rest are grayscale)
FirstFreeColor = 1;
// dib must be kept valid throughout the life of the application
dib = gcnew Bitmap(pictureBox1->Width,pictureBox1->Height,System::Drawing::Imaging::PixelFormat::Format8bppIndexed);
//dib = gcnew Bitmap(pictureBox1->Width,pictureBox1->Height); //TEMP, 32-bit color for later experimentation
pictureBox1->Image = dib;
PicLinebuf = new uint[dib->Height];
// other variables
Dragging = false; // this is only true during a drag
DragStart = DragEnd = Point(0,0);
DibDragRect = DragRect = Rectangle(DragStart,System::Drawing::Size(0,0));
DragPct = new DoubleRect(0,0,0,0);
SetAutoRotate(0); // set autorotate OFF
allowzoom = FALSE; // no zooming: nothing displayed
dx = dy = 0.;
row = INT_MAX; // set to the flag for "done", to prevent auto-calculation
mindiff = .000001;
//--------------------
string s;
// get command line arguments (list of files to display)
array<String^>^ args = System::Environment::GetCommandLineArgs();
for(int i = 0 ; i < args->Length ; i++)
{
//This will be overly complicated because filenames might or might not have paths.
}
//Load all available MAP files
array<String^>^ fa = System::IO::Directory::GetFiles(MAPopenFileDialog->InitialDirectory, "*.map");
System::Collections::IEnumerator^ e = fa->GetEnumerator();
while(e->MoveNext() != false)
{
SystemStringToBasicString(e->Current->ToString(), s);
MapFiles->AddFile(s);
}
delete e;
delete fa;
sw->Start(); // start timing so a new design appears at startup
IdleActionTimer->Start(); // you can't ever turn this off
}
protected:
/// <summary>
/// Clean up any resources being used.
/// </summary>
//----------------------------------------------------------------------------
// DESTRUCTOR
~Form1()
{
if (components)
{
delete components;
}
CmFileStop();
if(sw) delete sw;
if(prev) delete prev;
if(curr) delete curr;
if(Cycles) delete Cycles;
if(FileList) delete FileList;
if(DragPct) delete DragPct;
if(MapFiles) delete MapFiles;
if(Colors) delete Colors;
delete optionsdialog;
delete PICoptionsdialog;
delete listeditordialog;
delete[] PicLinebuf;
if(dib) delete dib;
}
//-----------------------------
private:
/// <summary>
/// Required designer variable.
/// </summary>
//----------------------------------------------------------------------------
#pragma region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
void InitializeComponent(void)
{
//many lines of setup code omitted here
//
// IdleActionTimer
//
this->IdleActionTimer->Interval = 50;
this->IdleActionTimer->Tick += gcnew System::EventHandler(this, &Form1::IdleActionTimer_Tick);
//
}
#pragma endregion
//----------------------------------------------------------------------------
// returns a random non-boring area within the map-set.
MandelRect randomarea()
{
//I suspect the Winbrot boring region tests are unnecessary for Bif
static MandelRect boring[1] = // areas to be excluded
{
MandelRect(0., 0., 3., 0.) // 0 to 2 is just a curve
};
bool goodarea = FALSE; // whether the chosen area is acceptable
MandelRect m; // function's return value
while(!goodarea)
{
goodarea = TRUE;
// calculate a random left point within the "bif" area, and a random width.
double tleft = (double)random(40000) / 10000.; // 0 to 4
double tright = tleft + (((double)random(1000000)) / 1e6); // 0 to 1.0
tright = min(tright, 4.0); //constrain right edge
// create the proposed area (constructor will normalize it)
m = MandelRect(tleft,1,tright,-1);
// the following tests reject the area if it...
// exceeds interesting "bif" area boundaries (or is 0-width)
if((m.left < 3.5) || (m.left == m.right))
{
goodarea = FALSE;
continue;
}
// or is in one of the "boring" regions
for(int i = 0 ; i < 1 ; i++)
if(boring[i].Contains(m))
{
goodarea = FALSE;
break;
}
if(!goodarea)
continue;
}
SetParentCaption(" - Random Area");
return(m);
} //randomarea
//----------------------------------------------------------------------------
private: System::Void CmFileStop()
{
// stop in-progress calc and clean up
row = INT_MAX; // set to the flag for "done", to prevent auto-calculation
//Colors->Locked = FALSE; // ok to re-enable color rotation, etc.
}
//----------------------------------------------------------------------------
// set curr and initialize all variables to begin calculating the new region.
void setcurr(MandelRect& newcurr)
{
CmFileStop(); // stop calc
*curr = newcurr; // this also normalizes
if(curr->right > 4.0)
curr->right = 4.0;
prev->push_back(*curr); // add to "previous" list immediately
c = curr->left; // calculation will start at left
row = 0; // init, and signals that calculation should start
sw->Reset(); // stop timing previous display interval
Cycles->clear(); // empty cycle list from prior region
// if size override, use those, else recreate dib for screen size at this moment
int newwidth = ((optionsdialog->WidthOverride->Value > 0) ? (int)optionsdialog->WidthOverride->Value : 0);
int newheight = ((optionsdialog->HeightOverride->Value > 0) ? (int)optionsdialog->HeightOverride->Value : 0);
makenewdib(0,newwidth,newheight,FALSE);
//dib->Erase(Colors->GetColor(0)); // ensure it's blank
delete[] PicLinebuf; // resize to match dib HEIGHT
PicLinebuf = new uint[dib->Height];
dx = curr->Width() / (double)(dib->Width - 1); // x interval each horiz. pixel
dy = curr->Height() / (double)(dib->Height - 1); // y interval each vert. pixel
// if we just calculated dib to exactly fit the window, fittowindow isn't necessary anymore and zooming can be allowed.
allowzoom = FALSE;
if((optionsdialog->WidthOverride->Value == 0) && (optionsdialog->HeightOverride->Value == 0))
{
fitToWindowToolStripMenuItem->Checked = false;
allowzoom = TRUE;
}
// palette must be locked during calculation, else each line is set to a different color
// scheme. It doesn't bother the .PIC file, but the dib's colors are, and stay, wrong.
//(The above isn't true anymore.)
//Colors->Locked = TRUE;
IdleActionTimer->Start();
} //setcurr
//----------------------------------------------------------------------------
// reinitialize dib, 1) from .BMP file or 2) with specified extents, or 3) sized to window,
// depending on which parameters are supplied. Optionally copy the old dib's pixels
// so that whatever portion of an OLD design hasn't yet been overwritten remains visible.
// Otherwise, it's just a big blank area.
// returns false if file load failed, true otherwise. Any serious error closes application.
bool makenewdib(const char* filename, int width, int height, bool copypixels)
{
bool result = false;
Bitmap^ newdib;
if(filename) // create the dib from a file
{
//#error should probably use try/catch.
String^ s = gcnew String(filename);
Bitmap^ newdib2 = gcnew Bitmap(s);
delete s;
if(newdib2->PixelFormat == PixelFormat::Format8bppIndexed)
result = true;
else
{
delete newdib2;
result = false;
//I cannot permit this until I figure out how to convert.
//A non-8bppIndexed bmp will crash the program in MANY places.
//convert it to 256 color, indexed
//Probable solution is to create a fresh 256-color bitmap and draw the loaded bitmap to it;
//(will it only used the 256 most common colors, or what?)
//this fails with an OutOfMemory exception
//newdib = newdib2->Clone(Rectangle(0,0,newdib->Width,newdib->Height),PixelFormat::Format8bppIndexed);
//delete newdib2;
//catch // file read failed
//{
// string s = string("Bitmap file ") + filename + " is not usable by this program.";
// MessageBox(s.c_str(),"File Load Error",MB_OK);
// result = false;
//}
}
}
if(!result) // if no filename, or file failed
{
result = true;
if(!width)
width = pictureBox1->Width;
if(!height)
height = pictureBox1->Height;
newdib = gcnew Bitmap(width,height,System::Drawing::Imaging::PixelFormat::Format8bppIndexed);
//newdib = gcnew Bitmap(width,height); //TEMP, 32-bit color
//catch
//{
// string s = "DIB creation error. Application will close.";
// MessageBox(s.c_str(),GetApplication()->GetName(),MB_OK);
// result = false;
// this->Close();
//}
}
pictureBox1->Image = newdib; //the Image can NEVER be a deleted object, not even for a moment!
delete dib; // delete old dib
dib = newdib; // install new dib
pictureBox1->Image = dib;
LoadDibPalette();
return(result);
} //makenewdib
//----------------------------------------------------------------------------
// calculate and draw 1 COLUMN of points in a new picture.
// NOTE THAT row IS MISUSED HERE as the column BECAUSE IT'S A HOLDOVER FROM WINBROT.
// returns TRUE if another one should be generated, FALSE if not (quit)
// (return value was from DOS version, not currently used here)
// should do a careful check of the maxx, maxy, dx, dy calculations and usage (1 off?)
bool calcmbrot()
{
// if(IsIconic()) // minimized, no point in doing anything
// return(FALSE);
if(row >= dib->Width) // no design in process (either none, or was finished)
return(TRUE);
// if it falls through, calc the current line (just one)
double r = c; // copy to local to avoid needless complex math. r is the value between 0 and 4.
// fittowindow must remain OFF during calculation so any partial-screen Invalidate() region
// calculated on the basis of the dib is accurate for the screen. (is this still true?)
// if user did anything to turn allowzoom OFF, we don't toggle it back on here. maybe could?
// paper note said:
// "rem out and test. also try invalidating using GetClientRect().Width() below."
fitToWindowToolStripMenuItem->Checked = false;
//1 article says never do this because picturebox might refresh its own display at any time,
//undoing your changes. Here it doesn't matter.
Graphics^ sc = pictureBox1->CreateGraphics();
System::Drawing::Font^ font = gcnew System::Drawing::Font("Arial", 12);
SolidBrush^ brush = gcnew SolidBrush(Color::White);
StringFormat^ stringformat = gcnew StringFormat();
char buf[200]; // status text goes to SCREEN, not bitmap, to avoid interfering with hitcounts
if(!optionsdialog->invisible->Checked)
{
//sc->SetBkColor(Color::Black);
//--------------------
// (W)HOLE MODE
//--------------------
ostrstream os(buf,sizeof(buf));
os << setprecision(15);
os << "r=" << setw(17) << setiosflags(ios::left) << r << resetiosflags(ios::left);
os << " of " << curr->left << " to " << curr->right;
os << ", step=" << dx << ends;
String^ s = gcnew String(buf);
sc->DrawString(s, font, brush, 0, 0, stringformat);
delete s;
}
double a = .5; // a is always between 0 and 1
for(int i = 0 ; i < 50 ; i++) // allow equation to settle down
a = (r * a) - (r * a * a);
double refpoint; // used to determine cycle length
bool firstpass = TRUE; // whether first pass through loop
int pointcount = 0; // absolute # of points to plot per stripe, an option.
int dupcount = 0; // how many pixels are already set
ulong cyclecount = 0; // 3-cycle, etc.
bool gotacycle = FALSE;
int maxy = dib->Height - 1; // highest numbered dib scanline
//Note that the program now DOES use PicLinebuf, which can count higher than 255.
//Do NOT remove its use and revert to direct use of the pixel's byte value.
for(int i = 0 ; i < dib->Height ; i++)
PicLinebuf[i] = 0;
while(1) // this loop plots a vertical stripe. exits when too many dup hits.
{
a = (r * a) - (r * a * a); // the basic equation: a = r(a - aa)
int y = (int)(maxy - (a * (double)maxy)); // translate to plot point, and invert for display
if(firstpass)
{
refpoint = a; // save first a to test for repeating cycle
firstpass = FALSE;
}
else // if not first and no cycle found yet, do the cycle test
if(!gotacycle)
{
cyclecount++;
if(fabs(a - refpoint) <= mindiff)
{
gotacycle = TRUE;
// add to list only if list is empty OR the new cyclecount is different
// from the previous one, to omit long lists of dups.
int i = Cycles->size();
if(!i || ((ulong)(imag((*Cycles)[i-1])) != cyclecount))
Cycles->push_back(complex<double>(r,cyclecount));
if(!optionsdialog->invisible->Checked)
{
// #error reuse os from above?
ostrstream(buf,sizeof(buf)) << setw(10) << cyclecount << "-cycle" << ends;
String^ s = gcnew String(buf);
sc->DrawString(s, font, brush, 0, 20, stringformat);// 2nd line down
delete s;
}
// even if a cycle was found, DO keep plotting so point hitcounts are correct
// break; // old method quit plotting
}
}
uint oldcount = PicLinebuf[y]++; // increment hit count of this "pixel"
pointcount++;
// DECIDE WHETHER TO EXIT LOOP
bool quit = false;
switch((int)optionsdialog->exitmode->Value)
{
case 0: // end when any 1 point gets hit maxhits times
if(PicLinebuf[y] >= optionsdialog->maxhits->Value)
quit = true;
break;
case 1: // end after absolute # of points plotted (the most "honest" option)
if(pointcount >= optionsdialog->maxhits->Value)
quit = true;
break;
case 2: // end when there are maxhits # of points consecutively re-hit
if(oldcount == 0)
dupcount = 0;
else
if(++dupcount >= optionsdialog->maxhits->Value)
quit = true;
break;
}
if(quit)
break;
} // end while(1) (plot all the points in a stripe)
// restrict hitcount to 255 (if necessary), and transfer it to dib as a color value
//Graphics^ g = Graphics::FromImage(dib); //for experimenting later with 32-bit color
//Pen^ p = gcnew Pen(Color::White);
//This method is the equivalent of the old SDib::SetPixelByte.
int pixelwidth = 1;
BitmapData^ bits = dib->LockBits(Rectangle(0,0,dib->Width,dib->Height), System::Drawing::Imaging::ImageLockMode::ReadWrite, dib->PixelFormat);
int column = row * pixelwidth;
for(int i = 0 ; i < dib->Height ; i++)
{
uchar color = (uchar)(min((uint)255,PicLinebuf[i]));
//#error no more SetPixelByte - must use Colors[i] from some sort of color palette.
//p->Color = Color::FromArgb(255,Colors->Entries[color]);
//dib->SetPixel(row,i,Colors[color]);
System::Runtime::InteropServices::Marshal::WriteByte(bits->Scan0, (bits->Stride * i) + column, color);
}
dib->UnlockBits(bits);
//delete p;
//delete g;
if(!optionsdialog->invisible->Checked) //invalidate the areas we changed
{
pictureBox1->Invalidate(Rectangle(0,0,pictureBox1->Width,40)); //the text area at top
pictureBox1->Invalidate(Rectangle(row,0,1,pictureBox1->Height)); // the COLUMN we just drew
}
c += dx; // increment x axis (move right)
row++; // it's actually the rows we're counting, though.
if(row >= dib->Width) // if we just finished the design,
{
CmFileStop(); // reset variables, etc.
pictureBox1->Invalidate(); // entire screen at design end, to ensure it's right
sw->Start(); // start new timing interval.
}
delete font;
delete brush;
delete stringformat;
return(TRUE);
} //calcmbrot
//----------------------------------------------------------------------------
// read and display an .INF file
bool LoadINF(const string& infilename)
{
MandelRect m;
ifstream infile(infilename.c_str());
if(!(infile >> m)) // if the read failed, file's no good (or none)
return(FALSE);
SetParentCaption(" - " + infilename);
setcurr(m); // succeeded, so copy it
return(TRUE);
} //LoadINF
//----------------------------------------------------------------------------
// open file, method depends on fileext.
// could allow .MAP files on command line if you allow .MAP here:
// add the MAP to MapFiles list AND load it into dib immediately. set result to FALSE because no design was actually loaded.
bool OpenFile(const string& filename)
{
bool result = FALSE;
CmFileStop(); // STOP any calculation in progress
sw->Reset(); // stop auto-timer: a file's display time shouldn't be limited
string s(filename);
if(!s.length())
return(result);
if(stringcontains(s,".pic") || stringcontains(s,".bmp"))
{
this->WindowState = Windows::Forms::FormWindowState::Maximized;
allowzoom = FALSE; // only a PIC or BMP, no bounding data
}
//if(stringcontains(s,".pic"))
// result = LoadPIC3d(s); // this is why row must be MAXINT (NO calc allowed for PIC)
//else
if(stringcontains(s,".inf"))
result = LoadINF(s); // this will reset row to 0 to start new calc
else
if(stringcontains(s,".bmp"))
result = LoadBMP(s);
else
{
MessageBox::Show("Can only load .INF, .PIC (not yet allowed), .BMP files.",this->Text);
return(FALSE);
}
if(!result) // load error
{
toupper(s);
prepend(s,string("Error loading file: "));
String^ t = gcnew String(s.c_str());
MessageBox::Show(t,this->Text);
delete t;
}
return(result);
} //OpenFile
//----------------------------------------------------------------------------
private: System::Void openToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
if(DATAopenFileDialog->ShowDialog() != System::Windows::Forms::DialogResult::OK)
return;
string s;
SystemStringToBasicString(DATAopenFileDialog->FileName, s);
OpenFile(s);
}
//----------------------------------------------------------------------------
private: System::Void optionsToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
//The dialog's variables are always live. If you don't stop IdleAction, it's reading them constantly as you change them.
//That also implies that they DO get changed even if it's not the OK button that the user clicks.
IdleActionTimer->Stop();
System::Decimal dw = optionsdialog->WidthOverride->Value;
System::Decimal dh = optionsdialog->HeightOverride->Value;
if(optionsdialog->ShowDialog() == System::Windows::Forms::DialogResult::OK)
{
//there's nothing to do here.
}
//don't allow changing the screen dimensions in the middle of a calculation
if((dw != optionsdialog->WidthOverride->Value) || (dh != optionsdialog->HeightOverride->Value))
CmFileCalcAgain(); // start fresh with a correctly resized new dib
pictureBox1->Invalidate(); // even this may not be necessary
IdleActionTimer->Start();
}
//----------------------------------------------------------------------------
// zoom into specified quadrants
// these don't care about fittowindow: a quadrant is the same either way
void QuadrantZoom(int zoomtype)
{
if(allowzoom)
{
SetParentCaption(" - New Quadrant Zoom");
switch(zoomtype)
{
case 0: setcurr(curr->IsCenterQuadOf()); break; // zooms out
case 1: setcurr(curr->TopLeftQuad()); break; //left
case 2: setcurr(curr->TopRightQuad()); break; //right
case 5: setcurr(curr->CenterQuad()); break; //center
}
}
else // for now, for BMP, you must use mouse drag to define area
{
// method (but not worth doing):
// copy dib->Rect() to a DoubleRect; calc its quadrant, according to cmd;
// make a Rectangle from the DoubleRect, and WriteFilePIC() using it.
String^ s = gcnew String("Use mouse to select area of PIC.");
String^ t = gcnew String("Cannot Quadrant-Select a PIC/BMP");
MessageBox::Show(s,t);
delete t;
delete s;
}
}
//----------------------------------------------------------------------------
private: System::Void zoomOutToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
QuadrantZoom(0);
}
//------------------------------------
private: System::Void zoomLeftToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
QuadrantZoom(1);
}
//------------------------------------
private: System::Void zoomRightToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
QuadrantZoom(2);
}
//------------------------------------
private: System::Void zoomCenterToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
QuadrantZoom(5);
}
//----------------------------------------------------------------------------
// this is also called at startup: i.e. a newly created window gets resized on creation. ?
private: System::Void Form1_Resize(System::Object^ sender, System::EventArgs^ e)
{
if(fitToWindowToolStripMenuItem->Checked)
pictureBox1->Invalidate(); // force new stretched redraw in new window size
// allowzoom = FALSE; // you can no longer zoom if window doesn't match dib
}
//----------------------------------------------------------------------------
// originally, and usually, selects and displays a random region.
// but if there are any files in FileList, loads the next one.
// (a feature currently undocumented, but useful at startup),
void CmFileNew()
{
bool gotone = FALSE;
while(FileList->size()) // If a file fails, go on to the next one
{
// copy a name from the list, delete it from list, try to use it to initialize
gotone = OpenFile(FileList->GetNext());
if(gotone)
break;
}
if(!gotone) // last resort
setcurr(randomarea()); // the original CmFileNew had only this 1 line
}
//----------------------------------------------------------------------------
private: System::Void newRandomAreatNToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
CmFileNew();
}
//----------------------------------------------------------------------------
// recalculate curr and display. must re-init, even though it's the same curr
private: System::Void CmFileCalcAgain()
{
setcurr(*curr); // auto-adds it to prev
if(prev->size() >= 2) // if it isn't first entry, (if it wasn't
prev->erase(prev->end() - 1); // user's first command), remove it
}
//----------------------------------------------------------------------------
private: System::Void calcAgainSameAreaToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
CmFileCalcAgain(); // if no previous, just stay with current
}
//----------------------------------------------------------------------------
// display the previous area (the one before the currently displayed one).
// As you back up over entries, they are deleted from the list.
// The last prev entry will always be the one currently being calculated or displayed.
private: System::Void previousAreaToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
if(prev->size() >= 2)
{
setcurr((*prev)[prev->size() - 2]); // set to the one BEFORE the current
prev->erase(prev->end() - 1); // now remove its duplicate entry
prev->erase(prev->end() - 1); // AND remove the one you backed up over
// (i.e. the currently displayed one)
SetParentCaption(" - Previous (#" + tostring(prev->size()) + ")");
return;
}
CmFileCalcAgain(); // if no previous, just stay with current
}
//----------------------------------------------------------------------------
// flip current region so it's upside down and recalc. (Mset symmetrical above/below y axis)
// (horizontal flip not so easy: would have to flip the BMP)
private: System::Void flipVerticalToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
setcurr(curr->FlippedVertical());
}
//----------------------------------------------------------------------------
// start fresh with whole bif region.
// it has (unused) height to prevent divbyzero errors or errors with zoombox drags
private: System::Void restartwholeBifAreaToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
setcurr(MandelRect(0., 2, 3.9999999999, -2));
}
//----------------------------------------------------------------------------
private: System::Void quitCalculatingToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
CmFileStop();
}
//----------------------------------------------------------------------------
private: System::Void saveAsToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
if(DATAsaveFileDialog->ShowDialog() != System::Windows::Forms::DialogResult::OK)
return;
// Write current design to a file
string s;
SystemStringToBasicString(DATAsaveFileDialog->FileName, s);
tolower(s);
bool result = FALSE;
if(stringcontains(s,".bmp"))
{
result = true; //Save returns void
String^ t = gcnew String(s.c_str());
dib->Save(t);
delete t;
}
//else
// if(stringcontains(s,".pic"))
// result = Colors->WriteFilePIC(s);
else
if(stringcontains(s,".inf"))
{
ofstream outfile(s.c_str());
outfile << *curr << endl;
result = !outfile.fail();
}
else
if(stringcontains(s,".dat"))
{
// Write the list of cycles encountered to a file with standard name CYCLES.DAT.
if(row < dib->Width) // don't write while a design is in process because cycles isn't completed.
{
MessageBox::Show("Canceled. Please wait for the design to finish drawing.",this->Text);
return;
}
ofstream outfile(s.c_str());
outfile << setprecision(15);
for(uint i = 0 ; i < Cycles->size() ; i++)
outfile << (*Cycles)[i] << endl;
outfile << endl;
result = !outfile.fail();
}
else
MessageBox::Show("Can only save .INF, .PIC, .BMP files.",this->Text);
if(!result) // load error
{
toupper(s);
prepend(s,string("Error loading file: "));
String^ t = gcnew String(s.c_str());
MessageBox::Show(t,this->Text);
delete t;
}
#if 0
//This is all from SDibWindow, apparently completely overridden by VCBif
//Keep because SDibWindow might eventually get recreated in VC++.
// Write current design to a .bmp file
// static so that the name of the last-saved file is the default
static TOpenSaveDialog::TData bmpfiledata( // flags ok
OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY,
"Bitmap Files (*.BMP)|*.BMP|PIC Files (*.PIC)|*.PIC|All Files (*.*)|*.*|",0,0,"BMP");
if(TFileSaveDialog(this,bmpfiledata).Execute() != IDOK) // select file
return;
string s = bmpfiledata.FileName;
bool result = false;
if(s.contains(".bmp"))
result = Colors->WriteFile(s.c_str());
else
if(s.contains(".pic"))
result = Colors->WriteFilePIC(s);
else
MessageBox("Can only save .PIC,.BMP files.",GetApplication()->GetName(),MB_OK);
if(!result) // load error
{
s.to_upper();
s.prepend("Error saving file: ");
MessageBox(s.c_str(),GetApplication()->GetName(),MB_OK);
}
#endif
}
//----------------------------------------------------------------------------
private: System::Void aboutToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
String^ s = gcnew String("Copyright (C)1990-2001, 2008 Steven Whitney");
String^ t = gcnew String(this->Text);
MessageBox::Show(s,t);
delete t;
delete s;
}
//----------------------------------------------------------------------------
private: System::Void rearrangeHeightsToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
#if(0)
HCURSOR oldcursor = ::SetCursor(::LoadCursor(NULL,IDC_WAIT));
if(Colors->RandomizeHeights())
Invalidate(false);
::SetCursor(oldcursor); // restore cursor
#endif
}
//----------------------------------------------------------------------------
private: System::Void averagePixelsToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
#if(0)
// any user-specified settings, and any multiple passes, should be handled here.
// it is too trivial an effect to provide for it in IdleAction, with its complications.
static uint passcount = 1;
static int creep = false;
char buf[20];
ostrstream(buf,sizeof(buf)) << passcount << " " << creep << ends;
if(TInputDialog(this,"Number of Averaging Passes and Mode",
"Modes 0=average, 1=creep\nEntry Format: passcount mode",
buf,sizeof(buf)).Execute() != IDOK)
return;
istrstream(buf) >> passcount >> creep;
if(!passcount)
return;
HCURSOR oldcursor = ::SetCursor(::LoadCursor(NULL,IDC_WAIT));
for(int i = 0 ; i < passcount ; i++)
if(!Colors->AveragePixels(creep))
break;
Invalidate(false);
::SetCursor(oldcursor); // restore cursor
#endif
}
//----------------------------------------------------------------------------
private: System::Void invertHeightsToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
#if(0)
HCURSOR oldcursor = ::SetCursor(::LoadCursor(NULL,IDC_WAIT));
if(Colors->InvertHeights())
Invalidate(false);
::SetCursor(oldcursor); // restore cursor
#endif
}
//----------------------------------------------------------------------------
private: System::Void randomizePixelsToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
#if(0)
HCURSOR oldcursor = ::SetCursor(::LoadCursor(NULL,IDC_WAIT));
if(Colors->RandomizeField())
Invalidate(false);
::SetCursor(oldcursor); // restore cursor
#endif
}
//----------------------------------------------------------------------------
private: System::Void flipHorizontalToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
dib->RotateFlip(RotateFlipType::RotateNoneFlipX);
pictureBox1->Invalidate();
}
//----------------------------------------------------------------------------
private: System::Void flipVerticalToolStripMenuItem1_Click(System::Object^ sender, System::EventArgs^ e)
{
dib->RotateFlip(RotateFlipType::RotateNoneFlipY);
pictureBox1->Invalidate();
}
//----------------------------------------------------------------------------
private: System::Void Form1_DragDrop(System::Object^ sender, System::Windows::Forms::DragEventArgs^ e)
{
#if(0)
// this EvDrop is called if you drop the files in THIS window's client area.
// adds dropped files to this window's list.
// useful because CmFileOpen can only open a few files at a time:
// MAXPATH used by the dialog box is only 80, and the path alone uses up much of it.
// with this, you can drop an entire directory!
int count = e->Data-> dropinfo.DragQueryFileCount();
for(int i = 0 ; i < count ; i++)
{
int length = dropinfo.DragQueryFileNameLen(i) + 1;
char* filename = new char[length];
dropinfo.DragQueryFile(i,filename,length);
FileList->AddFile(string(filename));
delete[] filename;
}
dropinfo.DragFinish();
#endif
}
//----------------------------------------------------------------------------
private: System::Void IdleActionTimer_Tick(System::Object^ sender, System::EventArgs^ e)
{
// if time interval elapsed, (only possible when not calculating), autostart a new design.
if(sw->Elapsed >= System::TimeSpan::FromSeconds((double)optionsdialog->displaysecs->Value))
{
// This is here so you only do it if pgm is in "auto-run" mode (not for every CmFileNew)
if(!random(4))
{
if(MapFiles->size()) // choose a .MAP file at random, for variety
Colors->LoadColors(MapFiles->Random());
if(!random(2)) // doubles the available palettes
Colors->ReverseColors();
if(!random(2)) // even more variety
Colors->ComplementColors();
Colors->RotateColors(random(256)); // whichever palette you have, rotate it a bit
}
else //occasionally use randomly generated palette
Colors->RandomWalkColors(0); //(the stock palettes get boring)
if(optionsdialog->displaysecs->Value == 0) // "startup" flag: it is 0 ONLY at startup
{
optionsdialog->displaysecs->Value = INT_MAX; // set to actual default = very long time
}
if(autorotatecolors) // only if user has turned autorotate ON, set a direction
SetAutoRotate(random(2) ? 1 : -1);
CmFileNew(); // autostart a new design.
}
calcmbrot(); // if calculating, calc 1 line.
// this handles palette rotation, if any.
if(Colors->RotateColors(autorotatecolors)) // rotate by -1, 0, or +1
{
LoadDibPalette();
pictureBox1->Invalidate(); // if it WAS rotated, Invalidate entire screen
}
}
//----------------------------------------------------------------------------
// returns false if user pressed cancel, otherwise true (i.e. "issue is resolved").
// you MUST either get a NO, CANCEL, or DO a save.
bool QuerySavePalette()
{
// 9/12/2006 Bug fix: changed from while() to if() to avoid infinite loop possibility in IDNO case.
if(Colors->palettechanged) // only true if user manually altered specific colors
switch(MessageBox::Show("Save changed palette as .MAP?",".MAP File Save",
System::Windows::Forms::MessageBoxButtons::YesNo,
System::Windows::Forms::MessageBoxIcon::Question))
{
//#error CmColorsSaveAs is not defined yet.
case System::Windows::Forms::DialogResult::Yes: /* CmColorsSaveAs(); */ break;
case System::Windows::Forms::DialogResult::No: break;
case System::Windows::Forms::DialogResult::Cancel: return(false); // user didn't resolve the question.
}
return true;
} //QuerySavePalette
//----------------------------------------------------------------------------
// It appears that you can no longer prevent the form from closing.
// You must do whatever you have to do so the form CAN close.
private: System::Void Form1_FormClosing(System::Object^ sender, System::Windows::Forms::FormClosingEventArgs^ e)
{
QuerySavePalette();
}
//----------------------------------------------------------------------------
private: System::Void fitToWindowToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
pictureBox1->SizeMode = (fitToWindowToolStripMenuItem->Checked ?
System::Windows::Forms::PictureBoxSizeMode::StretchImage :
System::Windows::Forms::PictureBoxSizeMode::Normal);
//still not sure if fittowindow means you can't zoom
//allowzoom = FALSE;
pictureBox1->Invalidate(); // force redisplay in new fittowindow mode
}
//----------------------------------------------------------------------------
private: System::Void saveAsMAPToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
if(MAPsaveFileDialog->ShowDialog() != System::Windows::Forms::DialogResult::OK)
return;
string s;
SystemStringToBasicString(MAPsaveFileDialog->FileName, s);
tolower(s);
Colors->WriteColors(s);
}
//----------------------------------------------------------------------------
// Loads color palette from a Fractint .MAP file, Format: r g b possiblecomment\n
// If file is a valid .map file, but too short, colors are reused in order.
// If file isn't a valid .map file, colors aren't changed.
// returns true if loaded, false if it failed.
bool LoadMAP(const string& filename)
{
if(!QuerySavePalette())
return false;
pictureBox1->Invalidate(); // even for error, so you can see whatever did result
if(!Colors->LoadColors(filename) && !Colors->Locked)
{
string s = ".MAP file (" + filename + ") invalid or corrupt";
String^ t = gcnew String(s.c_str());
MessageBox::Show(t,"Cannot Load Colors");
delete t;
return(false);
}
LoadDibPalette();
return(true);
} //LoadMAP
//----------------------------------------------------------------------------
private: System::Void loadMAPToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
if(MAPopenFileDialog->ShowDialog() != System::Windows::Forms::DialogResult::OK)
return;
string s;
SystemStringToBasicString(MAPopenFileDialog->FileName, s);
LoadMAP(s);
}
//----------------------------------------------------------------------------
// opens the .PIC file, and uses its data to draw onto dib.
// returns true if file successfully loaded, false if not
// Could check whether associated .inf file is available; if so, read it into curr.
// KEEP THIS VERSION as a reference, even though it's obsolete. It's small.
bool LoadPIC(const string& filename)
{
#if(0)
int maxx = 0, maxy = 0; // default values in case of file error
ifstream infile(filename.c_str(),ios::binary);
infile >> maxx; infile.ignore(1); infile >> maxy; infile.ignore(1); // width,height\n
if(infile.fail() || !maxx || !maxy) // if the reads failed, file is no good (or none)
return(false);
SetParentCaption(string(" - " + filename);
HCURSOR oldcursor = ::SetCursor(::LoadCursor(NULL,IDC_WAIT));
// no point copying pixels into area the next loop will completely overwrite,
// UNLESS this fn gets broken into IdleAction pieces (no need. this is fast enough)
makenewdib(0,maxx,maxy,false); // create new dib to match image size; the new dib is unlocked
TDibDC dibdc(*dib); // for drawing to dib
TClientDC sc(*this); // allow watching it being drawn (because it takes a while)
// int x;
Color color;
uchar* buf = new uchar[maxx];
for(int y = 0 ; infile && (y < maxy) ; y++)
if(infile.read(buf,maxx))
{
Colors->SetScanLineBytes(y,buf); // new: set BMP bytes directly
// for(x = 0 ; x < maxx ; x++)
// {
// color = Colors->GetColor(buf[x]);
// dibdc.SetPixel(x,y,color); // old: set each pixel
// sc->SetPixel(x,y,color); // draw on screen (only minor slowdown)
// }
}
delete[] buf;
fitToWindowToolStripMenuItem->Checked = false; // first display is in its "real" size
Invalidate(false); // force displaying the entire new image
::SetCursor(oldcursor); // restore cursor
return(!infile.fail());
#endif
return false;
} //LoadPIC
//----------------------------------------------------------------------------
// loads a .PIC file onto dib (and screen), with optional 3d drawing options.
// Some 3d modes seem pretty useless, but leave them in so I don't forget what they
// look like and then want them back, having forgotten that they're useless!
// returns true if file successfully loaded, false if not
bool LoadPIC3d(const string& filename)
{
#if(0)
static uint iniskip = 0; // number of leading lines to skip over completely
static uint skip = 0; // number of lines to skip between displayed lines
static uint dispmode = 0; // how to plot. default = overhead.
static double vertsizeadj = 1; // a vertical scaling control for displayed image
//---------------------------- // get 3d drawing options from user
PICOpStruct w; // transfer struct for the dialog
w.overhead = (dispmode == 0);
w.drawpts = (dispmode == 1);
w.ovpts = (dispmode == 2);
w.drawlines = (dispmode == 3);
w.ovlines = (dispmode == 4);
w.areaheight = (dispmode == 5);
w.areanear = (dispmode == 6);
ostrstream(w.iniskiptext,sizeof(w.iniskiptext)) << iniskip << ends;
ostrstream(w.skiptext,sizeof(w.skiptext)) << skip << ends;
ostrstream(w.vertsizetext,sizeof(w.vertsizetext)) << vertsizeadj << ends;
if(PICoptionsdialog(this,&w).Execute() != IDOK) // dialog: get 3d options from user
return(true); // prevents error message
if(w.overhead) dispmode = 0;
else if(w.drawpts) dispmode = 1;
else if(w.ovpts) dispmode = 2;
else if(w.drawlines) dispmode = 3;
else if(w.ovlines) dispmode = 4;
else if(w.areaheight) dispmode = 5;
else if(w.areanear) dispmode = 6;
else dispmode = 0;
istrstream(w.iniskiptext) >> iniskip;
istrstream(w.skiptext) >> skip;
double d;
istrstream(w.vertsizetext) >> d;
if((d > 0) && (d < 256)) // 0 illegal, > 255 pointless (way off screen vertically)
vertsizeadj = d;
//----------------------------
int maxx = 0, maxy = 0; // default values are in case of file read error
ifstream infile(filename.c_str(),ios::binary);
infile >> maxx; infile.ignore(1); infile >> maxy; infile.ignore(1); // width,height\n
int fline = 0; // scanline # from file being displayed
for(int i = 0 ; i < iniskip ; i++, fline++) // skip over initial ignored lines.
infile.ignore(maxx); // ignore(iniskip * maxx) could overflow int
if(infile.fail() || !maxx || !maxy) // if the read failed, file's no good, or none,
return(false); // or used up.
SetParentCaption(string(" - " + filename);
HCURSOR oldcursor = ::SetCursor(::LoadCursor(NULL,IDC_WAIT));
makenewdib(0,maxx,maxy,false); // create new dib to match PIC file image size
TDibDC dibdc(*dib); // for drawing to dib
EraseWindow(); // prepare to draw to the screen simultaneously
TClientDC sc(*this); // to draw on (REQUIRED when erasing between plots)
sc->SetBkColor(Color::Black); // background and text colors for line number status display
sc->SetTextColor(Color::White);
//----------
// Proper drawing to the dib so the end display is good is the primary task for all cases,
// so the dib is sized to match the PIC file, and heights are scaled to the dib height.
// the accuracy of the window drawing is secondary.
// all options draw to both dib and window. dib version (where points HAVE
// accumulated even if overlay wasn't used) gets painted to screen after exit.
// NOTE: YOU NOW *MUST* SIZE TO DIB, NOT WINDOW
int dibheight = dib->Height - 1; // Height() - 1 is correct
// scalefactor scales input values so the lowest possible (0) is at dib bottom,
// highest possible (255) is at dib top. vertsizeadj then determines what percent
// of this maximum possible vertical range is actually used.
double scalefactor = (double)dibheight / 255; // .pic file can only have values 0 to 255
scalefactor *= vertsizeadj; // adjust final vertical height
//----------
// precalculate just once all the possible y values you may have to plot
int* yvals = new int[256]; // holds all possible actual y axis values to plot
for(i = 0 ; i < 256 ; i++)
yvals[i] = (int)range((double)0,(double)dibheight,dibheight - (i * scalefactor));
//----------
// points array for Polyline
Point* pts = new Point[maxx + 2];
pts[maxx] = Point(maxx - 1, dibheight); // preset bottom right
pts[maxx + 1] = Point(0 , dibheight); // preset bottom left
//----------
uchar* buf = new uchar[maxx]; // enough chars for 1 x-axis line of pixels (=1 scanline)
char textbuf[20]; // text of the line number, for status line display
Color color = dibdc.GetNearestColor(Color::White); // initialized for cases 1,3
int colorindex = 0; // index into dib colors, initialization reqd for cases 2,4,6
int x, y; // point being plotted
bool firstentry = true; // some cases use it
switch(dispmode)
{
case 0: // overhead: straight copy .PIC to screen, color = height
for(y = 0 ; infile.read(buf,maxx) ; y++)
{
// keep for some cases: the line #s help you plan iniskip and skip values
// ostrstream(textbuf,sizeof(textbuf)) << ++fline << ends; sc->TextOut(0,0,textbuf);
// for(x = 0 ; x < maxx ; x++)
// {
// color = Colors->GetColor(buf[x]);
// dibdc.SetPixel(x,y,color); // uses dib's palette
// sc->SetPixel(x,y,color); // draw on screen (only minor slowdown)
// }
Colors->SetScanLineBytes(y,buf); // new: set BMP bytes directly - FAST!
for(i = 0 ; i < skip ; i++, fline++) // if skip is 0, nothing happens
infile.ignore(maxx); // eof ok, will just exit on next pass
}
break;
case 1: // points, with erase, 1 color
colorindex = Colors->FindColor(color); // get index (any ok) of nearest color
if(colorindex < 0) colorindex = 1; // prevent negative: must have SOMETHING
while(infile.read(buf,maxx))
{
// EraseWindow(); // old: screen flicker
ostrstream(textbuf,sizeof(textbuf)) << ++fline << ends; sc->TextOut(0,0,textbuf);
// points stayed on screen for maximum possible time, through read, etc.
if(!firstentry)
for(x = 0 ; x < maxx ; x++) // erase prior points
sc->SetPixel(pts[x],Color::Black); // assume black for now
for(x = 0 ; x < maxx ; x++) // calc and set the points
{
pts[x] = Point(x,yvals[buf[x]]); // save, for erasure
// dibdc.SetPixel(pts[x],color); // dib uses its own preset (above) color
Colors->SetPixelByte(pts[x],(uchar)colorindex); // new: write byte directly
sc->SetPixel(pts[x],Color::White); // screen uses white
}
for(i = 0 ; i < skip ; i++, fline++)
infile.ignore(maxx);
firstentry = false;
}
break;
case 2: // overlay points, color = nearness
while(infile.read(buf,maxx))
{
// EraseWindow(); // here in case cases 2,3 get merged
ostrstream(textbuf,sizeof(textbuf)) << ++fline << ends; sc->TextOut(0,0,textbuf);
for(x = 0 ; x < maxx ; x++)
{
y = yvals[buf[x]];
Colors->SetPixelByte(x,y,(uchar)colorindex); // new: write byte directly
color = Colors->GetColor(colorindex);
// dibdc.SetPixel(x,y,color);
sc->SetPixel(x,y,color);
}
for(i = 0 ; i < skip ; i++, fline++)
infile.ignore(maxx);
if(++colorindex >= Colors->NumColors())
colorindex = 0;
}
break;
case 3: // LINES, with erase, 1 color
while(infile.read(buf,maxx))
{
// EraseWindow();
ostrstream(textbuf,sizeof(textbuf)) << ++fline << ends; sc->TextOut(0,0,textbuf);
if(!firstentry)
{
sc->SelectObject(TPen(Color::Black));
sc->MoveTo(pts[0]); // erase prior line on screen only
sc->Polyline(pts,maxx); // very fast
sc->RestorePen();
}
for(x = 0 ; x < maxx ; x++)
pts[x] = Point(x,yvals[buf[x]]);
dibdc.SelectObject(TPen(color));
sc->SelectObject(TPen(Color::White));
dibdc.MoveTo(pts[0]); // set starting cp
sc->MoveTo(pts[0]);
dibdc.Polyline(pts,maxx); // might not set last pt
sc->Polyline(pts,maxx); // very fast
dibdc.RestorePen();
sc->RestorePen();
for(i = 0 ; i < skip ; i++, fline++)
infile.ignore(maxx);
firstentry = false;
}
break;
case 4: // OVERLAY LINES, color = nearness
while(infile.read(buf,maxx))
{
// EraseWindow();
ostrstream(textbuf,sizeof(textbuf)) << ++fline << ends; sc->TextOut(0,0,textbuf);
for(x = 0 ; x < maxx ; x++)
pts[x] = Point(x,yvals[buf[x]]);
color = Colors->GetColor(colorindex);
dibdc.SelectObject(TPen(color));
sc->SelectObject(TPen(color));
dibdc.MoveTo(pts[0]);
sc->MoveTo(pts[0]);
dibdc.Polyline(pts,maxx);
sc->Polyline(pts,maxx);
dibdc.RestorePen();
sc->RestorePen();
for(i = 0 ; i < skip ; i++, fline++)
infile.ignore(maxx);
if(++colorindex >= Colors->NumColors())
colorindex = 0;
}
break;
case 5: // FILL AREAS, color = height
while(infile.read(buf,maxx))
{
ostrstream(textbuf,sizeof(textbuf)) << ++fline << ends; sc->TextOut(0,0,textbuf);
for(x = 0 ; x < maxx ; x++)
{
y = yvals[buf[x]];
color = Colors->GetColor(buf[x]);
dibdc.SelectObject(TPen(color));
sc->SelectObject(TPen(color));
dibdc.MoveTo(x,y);
sc->MoveTo(x,y);
dibdc.LineTo(x,dibheight); // probably doesn't set bottom pixel
sc->LineTo(x,dibheight);
dibdc.RestorePen();
sc->RestorePen();
}
for(i = 0 ; i < skip ; i++, fline++)
infile.ignore(maxx);
}
break;
case 6: // FILL AREAS, color = nearness
// this case is now very fast
while(infile.read(buf,maxx))
{
ostrstream(textbuf,sizeof(textbuf)) << ++fline << ends; sc->TextOut(0,0,textbuf);
for(x = 0 ; x < maxx ; x++)
pts[x] = Point(x,yvals[buf[x]]);
color = Colors->GetColor(colorindex);
dibdc.SelectObject(TPen(color));
sc->SelectObject(TPen(color));
dibdc.SelectObject(TBrush(color));
sc->SelectObject(TBrush(color));
dibdc.MoveTo(pts[0]);
sc->MoveTo(pts[0]);
dibdc.Polygon(pts,maxx + 2);
sc->Polygon(pts,maxx + 2);
dibdc.RestorePen();
sc->RestorePen();
dibdc.RestoreBrush();
sc->RestoreBrush();
for(i = 0 ; i < skip ; i++, fline++)
infile.ignore(maxx);
if(++colorindex >= Colors->NumColors())
colorindex = 0;
}
break;
} // END SWITCH
delete[] pts;
delete[] yvals;
delete[] buf;
fitToWindowToolStripMenuItem->Checked = false; // first display is in its "real" size
Invalidate(false); // force displaying the entire new image
::SetCursor(oldcursor); // restore cursor
return(true);
// return(!infile.fail()); // old: the whiles() above read UNTIL it fails
#endif
return false;
} //LoadPIC3d
//----------------------------------------------------------------------------
// opens the .BMP file.
// returns true if file successfully loaded, false if not
// This now allows palette animation, etc. with any BMP file! But some BMP files
// created by other apps seem incompatible with OWL TDib format (GDI Errors).
bool LoadBMP(const string& filename)
{
pictureBox1->Invalidate(); // force displaying the entire new image (whatever it is)
fitToWindowToolStripMenuItem->Checked = false; // first display is in its "real" size
if(!makenewdib(filename.c_str(),0,0,FALSE))
return(false);
SetParentCaption(" - " + filename);
return(true);
} //LoadBMP
//----------------------------------------------------------------------------
private: System::Void indexToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
//WinHelp(HelpFilename,HELP_INDEX,0);
}
//----------------------------------------------------------------------------
private: System::Void exitToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
this->Close();
}
//----------------------------------------------------------------------------
// fill the window with black when you don't want to wait for Invalidate
void EraseWindow(Color color)
{
//1 article says never do this because picturebox might refresh its own display at any time, undoing your changes.
pictureBox1->CreateGraphics()->Clear(color);
} //EraseWindow
//----------------------------------------------------------------------------
// sets caption for this window.
void SetParentCaption(const string& caption)
{
string t = AppName + caption;
String^ s = gcnew String(t.c_str());
this->Text = s;
delete s;
} //SetParentCaption
//----------------------------------------------------------------------------
// Edit the FileList in a dialog box with a multi-line edit control.
private: System::Void editListToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
#if(0)
FileArray* list = FileList;
//#error this will be ListEditorDialog->TextBox1->Text
ListEditorStruct* tts = new ListEditorStruct; // newed because it's big
// first write (in TEdit-incompatible format) using filechars as a temp buf
ostrstream(tts->filechars,sizeof(tts->filechars)) << (*list) << ends;
// now change text to TEdit format and write it back in again
LFtoCRLF(string(tts->filechars)).copy(tts->filechars,sizeof(tts->filechars));
if(SListEditDialog(this,tts).Execute() == IDOK)
{
list->Flush(); // load the edited list
istrstream(tts->filechars) >> (*list);
}
delete tts;
#endif
}
//----------------------------------------------------------------------------
private: System::Void editMAPFileListToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
#if(0)
// Edit one of the FileLists in a dialog box with a multi-line edit control.
FileArray* list = MapFiles;
ListEditorStruct* tts = new ListEditorStruct; // newed because it's big
// first write (in TEdit-incompatible format) using filechars as a temp buf
ostrstream(tts->filechars,sizeof(tts->filechars)) << (*list) << ends;
// now change text to TEdit format and write it back in again
LFtoCRLF(string(tts->filechars)).copy(tts->filechars,sizeof(tts->filechars));
if(SListEditDialog(this,tts).Execute() == IDOK)
{
list->Flush(); // load the edited list
istrstream(tts->filechars) >> (*list);
}
delete tts;
#endif
}
//----------------------------------------------------------------------------
// handles the various palette manipulation commands, especially the ones
// where user might hold down key to auto-repeat. You can disallow these when
// pgm is busy (calculating or drawing) by overriding this virtual fn
// in your derived class, and calling this one only if(when) you want to allow it,
// OR turning off other activities when these are called. (see wshowfs.cpp for example)
//----------------------------------------------------------------------------
private: System::Void randomizeToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
Colors->RandomWalkColors(0);
LoadDibPalette();
pictureBox1->Invalidate();
}
//----------------------------------------------------------------------------
private: System::Void reverseToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
Colors->ReverseColors();
LoadDibPalette();
pictureBox1->Invalidate();
}
//----------------------------------------------------------------------------
private: System::Void complementToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
Colors->ComplementColors();
LoadDibPalette();
pictureBox1->Invalidate();
}
//----------------------------------------------------------------------------
private: System::Void lightenToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
Colors->LightenColors();
LoadDibPalette();
pictureBox1->Invalidate();
}
//----------------------------------------------------------------------------
private: System::Void darkenToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
Colors->DarkenColors();
LoadDibPalette();
pictureBox1->Invalidate();
}
//----------------------------------------------------------------------------
private: System::Void rotateForwardToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
SetAutoRotate(0); // user wants manual rotation, so turn autorotate off
Colors->RotateColors(1);
LoadDibPalette();
pictureBox1->Invalidate();
}
//----------------------------------------------------------------------------
private: System::Void rotateBackwardToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
SetAutoRotate(0);
Colors->RotateColors(-1);
LoadDibPalette();
pictureBox1->Invalidate();
}
//----------------------------------------------------------------------------
private: System::Void nextMAPFileToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
if(LoadMAP(MapFiles->Next()))
{
LoadDibPalette();
pictureBox1->Invalidate();
}
}
//----------------------------------------------------------------------------
private: System::Void previousMAPFileToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
if(LoadMAP(MapFiles->Prev()))
{
LoadDibPalette();
pictureBox1->Invalidate();
}
}
//----------------------------------------------------------------------------
private: System::Void pictureBox1_Paint(System::Object^ sender, System::Windows::Forms::PaintEventArgs^ e)
{
//LoadDibPalette(); //When put here, it does NOT force autorefresh. Don't know why.
#if 0
// Called whenever window needs redrawing. OWLPG:37 modified from bmpview.cpp
// copies information from the permanent dib to the current screen.
// Note the parms are provided for your USE: dc is pre-created for you, erase indicates
// whether YOUR OWN Invalidate() calls specified true, and invalidarea has been
// accumulated by Windows as the total of regions specified by your Invalidate() calls.
// See VCPP:49 for drawing speed tips.
// minimized, no point doing anything, or selecting an area, leave screen alone
if(Dragging || IsIconic())
// #error test sometime whether an iconic window makes all its children iconic.
// here, a Parent TMDIChild COULD be even if the main window isn't.
// || GetApplication()->GetMainWindow()->IsIconic() || (Parent && Parent->IsIconic()))
return;
// "redraw entire window because palette or fittowindow mode may have changed"
// still true? Maybe not. Each action SHOULD be Invalidating only the area it affects.
// So even a simple test if(invalidarea == GetClientRect()) might help.
// This is the original version, reliable. Keep.
// if(fitToWindowToolStripMenuItem->Checked) dc.StretchDIBits(GetClientRect(),dib->Rect(),*dib,SRCCOPY);
// else dc.SetDIBitsToDevice(dib->Rect(),Point(0,0),*dib);
// stretch method won't change; just stretch entire design to entire window:
// if you try to do partial region, you must calculate the dib startpoint
// to be the same % of its size as the invalidarea is of the screen's size.
// e.g. if the invalidarea starts at (50% width, 50% height) of the window, you must
// get data from the dib at (50% width, 50% height) of IT. But this seems likely to
// introduce rounding-to-int error artifacts, and any error will be obvious in the display.
// SEE WINAPI PAINTING AND DRAWING OVERVIEW, and GetUpdateRect()
// Experiments to retry in SetDIBitsToDevice. Low priority because they may be difficult,
// drawing could be inaccurate, and current drawing isn't that slow.
// 1. replace dib->Rect() with GetClientRect because it's really supposed to be the destination.
// first try, it didn't crash when dib didn't have enough data to fill the area,
// but there were errors.
// 2. change parms to (invalidarea,invalidarea.TopLeft(),*dib);
// 3. Use IntersectClipRect to determine what portion of the invalid area intersects
// the data that the dib can provide to draw in it?
// StretchDIBits seems to be implemented as SetDIBits if the 2 areas are identical.
// if so, you could call Stretch, and fittowindow would be unnecessary, EXCEPT that
// in some pgms I specifically DON'T want to stretch, even if I could. This is a
// reminder NOT to make that change.
int i; // temporary, to see if errors CAN occur (yes, they can)
if(fitToWindowToolStripMenuItem->Checked) // fittowindow means "always force stretch"
// dest source
i = dc.StretchDIBits(GetClientRect(),dib->Rect(),*dib,SRCCOPY); // fits to screen
else // this one can be used, even if the 2 rects aren't the same
// dest dib origin
i = dc.SetDIBitsToDevice(dib->Rect(),Point(0,0), *dib);
if(!i)
{
ostrstream os;
os << "StretchDIBits or SetDIBitsToDevice failure in Paint" << endl;
os << "Client rect: " << GetClientRect() << endl;
os << "Dib rect : " << dib->Rect() << endl;
os << "invalidarea: " << invalidarea << endl;
os << ends;
string s = os.str();
delete[] os.str();
logerror(s,"error.log");
}
#endif
}
//----------------------------------------------------------------------------
// copy all colors from the palette to the dib
void LoadDibPalette()
{
//this appears to be the proper (and possibly only legal) way to do it:
//GET the palette, change the entries, put the palette back.
ColorPalette^ p = dib->Palette;
for(int i = 0 ; i < 256 ; i++)
p->Entries[i] = Colors->GetColor(i);
dib->Palette = p;
}
//----------------------------------------------------------------------------
// set color cycling mode and keep the menu item check states in sync.
// valid directions are 1 (forward), -1 (backward), 0 (off)
void SetAutoRotate(int direction)
{
switch(direction)
{
case 0:
autorotatecolors = 0;
autoCycleForwardToolStripMenuItem->Checked = false;
autoCycleBackwardToolStripMenuItem->Checked = false;
break;
case 1:
autorotatecolors = 1;
autoCycleForwardToolStripMenuItem->Checked = true;
autoCycleBackwardToolStripMenuItem->Checked = false;
break;
case -1:
autorotatecolors = -1;
autoCycleForwardToolStripMenuItem->Checked = false;
autoCycleBackwardToolStripMenuItem->Checked = true;
break;
}
}
//----------------------------------------------------------------------------
// There are 4 ways to turn it off:
// Select either direction twice, Select 1-step cycling in either direction.
// You can go from either direction directly to the other.
private: System::Void autoCycleForwardToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
//If already rotating in THIS direction, turn it off.
SetAutoRotate((autorotatecolors == 1) ? 0 : 1);
}
//----------------------------------------------------------------------------
private: System::Void autoCycleBackwardToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e)
{
SetAutoRotate((autorotatecolors == -1) ? 0 : -1);
}
//----------------------------------------------------------------------------
private: System::Void pictureBox1_MouseDown(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e)
{
//LEFT MOUSE BUTTON DOWN BEGINS A ZOOM AREA SELECTION
if(e->Button == System::Windows::Forms::MouseButtons::Left)
{
// begin defining a new area to calculate
if(Dragging)
return;
pictureBox1->Capture = true; // all mouse events to go current window.
DragStart = DragEnd = Point(e->X,e->Y); // so (start == end) can test for a useless drag
Dragging = true;
}
#if(0)
#error this is sloppy and half-done. I lost interest.
//RIGHT MOUSE BUTTON DOWN ALLOWS EDITING THE COLOR UNDER THE CURSOR
else
if(e->Button == System::Windows::Forms::MouseButtons::Right)
{
// Edit the color under the cursor at the time of the mouse click
// You add to a particular location by clicking that location before Adding color.
if(Colors->Locked)
{
MessageBox::Show("Colors are locked while drawing.","Cannot Edit Color");
return;
}
#error this is wrong: there is no i
//get the current color at the pixel
int pixelwidth = 1;
BitmapData^ bits = dib->LockBits(Rectangle(0,0,dib->Width,dib->Height), System::Drawing::Imaging::ImageLockMode::ReadOnly, dib->PixelFormat);
int column = row * pixelwidth;
uchar ch = System::Runtime::InteropServices::Marshal::ReadByte((bits->Scan0, (bits->Stride * i) + column);
dib->UnlockBits(bits);
Color color = Colors->GetColor(ch); // get color from the colors array
Point dibpoint = point; // but what we really need is the color at the DIB's pixel,
Rectangle r = pictureBox1->Size();
if(r != dib->Size()) // and if stretch is on, it's not the same point.
{
// calculate the equivalent point in the dib, if possible
dibpoint = Point(MulDiv(dibpoint.x, dib->Width - 1, r.Width() - 1),
MulDiv(dibpoint.y, dib->Height - 1, r.Height() - 1));
// if the color at the dib's pixel is the same as the screen's color at its pixel,
// then our calculation probably succeeded, and we have a good chance of
// identifying the exact palette entry to change. else require that window:dib be 1:1.
if(TDibDC(*dib).GetPixel(dibpoint) != color)
{
MessageBox("Could not locate color to edit.\nTurn off FitToWindow and retry.","Cannot Edit Color",MB_OK);
return;
}
}
colorDialog1->Color = color;
if(colorDialog1->ShowDialog() == System::Windows::Forms::DialogResult::OK)
{
// Colors->MapColor(color,choose.Color,false); // old method: could set wrong pal entry
Colors->SetColor(Colors->GetPixelByte(dibpoint),choose.Color); // new: set exact entry
Colors->palettechanged = true;
LoadDibPalette();
pictureBox1->Invalidate();
}
}
#endif
}
//----------------------------------------------------------------------------
private: System::Void pictureBox1_MouseMove(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e)
{
// drag cursor to define a new calculation area
if(!Dragging) // only allow processing if left button is currently pressed.
return;
#if(0)
//research indicates drawing a focus rectangle simply is now all but impossible in GDI+.
Point old;
Dragging->GetCurrentPosition(old);
Dragging->DrawFocusRect(Rectangle(DragStart,old).Normalize()); // erase the old rectangle
// you could constrain selection box's shape here to match the window.
Dragging->DrawFocusRect(Rectangle(DragStart,point).Normalize()); // draw the new rectangle
Dragging->MoveTo(point); // update current position
// Invalidate(false); // save as reminder for other pgms that this is unnecessary
#endif
}
//----------------------------------------------------------------------------
private: System::Void pictureBox1_MouseUp(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e)
{
//in case of problems with the scaling: http://www.bobpowell.net/coordinates.htm
// RELEASED left mouse button after defining a selection area.
if(e->Button == System::Windows::Forms::MouseButtons::Left)
{
if(!Dragging) // if something messed up, do nothing.
return;
Dragging = false;
pictureBox1->Capture = false;
DragEnd = Point(e->X,e->Y); // caller can now use DragStart and DragEnd as needed.
// calculate other useful stats about the drag
// Normalize in case user dragged up and left
int minX = min(DragStart.X,DragEnd.X);
int maxX = max(DragStart.X,DragEnd.X);
int minY = min(DragStart.Y,DragEnd.Y);
int maxY = max(DragStart.Y,DragEnd.Y);
int DragWidth = maxX - minX;
int DragHeight = maxY - minY;
DragStart = Point(minX,minY);
DragEnd = Point(maxX,maxY);
DibDragRect = DragRect = Rectangle(minX,minY,DragWidth,DragHeight);
Rectangle clientrect(0,0,pictureBox1->Width,pictureBox1->Height); // get screen dimensions
// calculate what percent the point coordinates are of the screen dimensions
// ex: if width is 800, rightmost point of 799 translates to 1.0.
// store in a DoubleRect for convenience.
// THE CONSTRUCTOR NORMALIZES IT, WHICH MAKES IT UPSIDE DOWN COMPARED TO DRAGRECT,
// WHICH HAS HIGHER Y VALUE AT BOTTOM. DON'T CHANGE THE CALCULATION HERE,
// BECAUSE DOUBLERECT FREQUENTLY AUTO-NORMALIZES ITSELF, WHICH WOULD UNDO THE CHANGE, ANYWAY.
// INSTEAD, BE SURE TO NORMALIZE() ANY TRECT THAT YOU CREATE USING DRAGPCT.
*DragPct = DoubleRect((double)(DragRect.Left) / (clientrect.Width - 1),
(double)(DragRect.Top) / (clientrect.Height - 1),
(double)(DragRect.Right) / (clientrect.Width - 1),
(double)(DragRect.Bottom) / (clientrect.Height - 1) );
// if image ACTUALLY IS being stretched, calculate the drag region in dib coordinates, too.
// otherwise DibDragRect, which is in client coordinates, IS already the area we want!
if(fitToWindowToolStripMenuItem->Checked)
if(dib->Size != pictureBox1->Size)
{
// translate screen percents to coordinates in dib.
// Normalize() is REQUIRED because DragPct is inherently upside down.
//#error is that still true? This no longer normalizes because Rectangle can't.
DibDragRect = Rectangle((int)(DragPct->left * dib->Width),
(int)(DragPct->top * dib->Height),
(int)(DragPct->right * dib->Width),
(int)(DragPct->bottom * dib->Height));
}
//------------------------------------------
if(DragStart == DragEnd) // prevent zero-area selection
return;
if(allowzoom) // IF CURRENT DISPLAY IS A CALCULATED MANDELBROT REGION
{
SetParentCaption(string(" - New Zoom View"));
if(fitToWindowToolStripMenuItem->Checked)
{
// translate screen percents to percents of curr
setcurr(MandelRect(curr->left + DragPct->left * curr->Width(),
curr->top - DragPct->top * curr->Height(),
curr->left + DragPct->right * curr->Width(),
curr->top - DragPct->bottom * curr->Height() ));
}
else
{
// translate the selected screen rectangle to a mandelbrot-scaled rectangle
// everything is relative to left and top, which are always valid and known.
// if window is larger than dib and user dragged outside the design,
// the new region will simply include an area that wasn't in the previous design.
setcurr(MandelRect(curr->left + (dx * DragRect.Left),
curr->top - (dy * DragRect.Top),
curr->left + (dx * DragRect.Right),
curr->top - (dy * DragRect.Bottom)) );
}
}
else // WRITE SELECTED PORTION OF DISPLAYED BMP TO DISK FILE AS A .PIC
{
if(fitToWindowToolStripMenuItem->Checked)
{
// translate screen percents to percents of dib. destroys orig value.
// Normalize() fixes DragPct being upside down compared to a Rectangle.
// #error Also had to remove Normalize() here.
DragRect = Rectangle((int)(DragPct->left * dib->Width),
(int)(DragPct->top * dib->Height),
(int)(DragPct->right * dib->Width),
(int)(DragPct->bottom * dib->Height) );
}
else
{
// DragRect, which is in client coordinates, IS already the area we want to save!
}
// the write will include only the portion inside the dib.
string s;
String^ tempfile = gcnew String(DATAsaveFileDialog->InitialDirectory + "\\area.pic");
SystemStringToBasicString(tempfile,s);
delete tempfile;
//Colors->WriteFilePIC(s,DragRect);
//OpenFile(tempfile); // immediate load with 3d options
// old: just inform
// string s = string("The selected region of the screen image\n") +
// "has been written to the file " + tempfile;
// MessageBox(s.c_str(),"Graphic Area Saved",MB_OK);
// Invalidate(FALSE); // only if not zooming, clean up display
}
}
}
};
}
|
|
|
|