home *** CD-ROM | disk | FTP | other *** search
/ Otherware / Otherware_1_SB_Development.iso / mac / developm / scnote / fracapp.007 / UFracApp.inc1.p < prev    next >
Text File  |  1988-08-17  |  98KB  |  2,151 lines

  1. {------------------------------------------------------------------------------
  2. #
  3. #    Apple Macintosh Developer Technical Support
  4. #
  5. #    MacApp Color QuickDraw Fractal Sample Application
  6. #
  7. #    FracApp
  8. #
  9. #    UFracApp.inc1.p    -    Pascal Source
  10. #
  11. #    Copyright ⌐ 1988 Apple Computer, Inc.
  12. #    All rights reserved.
  13. #
  14. #    Versions:    1.0                    8/88
  15. #
  16. #    Components:    MFracApp.p            August 1, 1988
  17. #                UFracApp.p            August 1, 1988
  18. #                UFracApp.inc1.p        August 1, 1988
  19. #                FracApp.r            August 1, 1988
  20. #                FracApp.make        August 1, 1988
  21. #
  22. #    This is a program to calculate the Mandelbrot set, allowing you to zoom in on areas
  23. #    that are selected with the mouse.  There are some special color tricks played
  24. #    in order to make the program more jazzy.  A special color table is used to give
  25. #    smooth transitions from one color to the next.  Color table animation is also
  26. #    supported, for the wowem effect of flowing Mandelbrot images. 
  27. #    The program is written in MacApp 1.1, which explains why it has a real user
  28. #    interface.  Mandelbrot images take about 30 minutes to calculate.  It is
  29. #    Juggler aware so you can put the program in the background where it will
  30. #    continue to calculate, while you do something more important, like look at 
  31. #    the source code.  It also handles multiple documents, and reading/writing of
  32. #    PICT files using the bottlenecks to minimize the memory hit.
  33. #    
  34. #    This program is intended to be a real world example of handling color in a
  35. #    nontrivial fashion.  As such it has some rather special color requirements,
  36. #    and those don╒t match the current system architecture very well.  The program
  37. #    is designed to be compatible with the future, it will not break in future
  38. #    systems.  However, it does not use the Palette Manager, which means that
  39. #    there will be situations where the colors will not look right in either FracApp
  40. #    or another program running under MultiFinder.  The approach that FracApp
  41. #    uses is thus not the preferred Apple approach and does NOT have the Apple
  42. #    seal of approval from engineering.  The only way to get the stamp of approval
  43. #    is to use the Palette Manager.  To do a program of this form, you cannot
  44. #    use the Palette Manager without some extra hacks that are compatibility
  45. #    risks in themselves.  So... use at your own risk.  If you are forced to revise your 
  46. #    program because you followed this as an example, you cannot gripe to Apple, since
  47. #    it is not fully approved.  You just have to change your program, which I hope is no
  48. #    big deal.  You can give this code to other people, as long as they recognize
  49. #    that it is not fully approved too. 
  50. #    
  51. #    Unless you have very special color requirements, you should use the Palette 
  52. #    Manager.  It works for most things, and is much easier to use than the
  53. #    approach taken here.  There are a few things it won╒t do of course, leading
  54. #    to this code.  If you can do it, use the Palette Manager and save yourself
  55. #    some grief.
  56. #            Written in MacApp Object Pascal code.
  57. #            Compatibility rating = 2.  (nothing will break, but it may not
  58. #                always look correct.)
  59. #
  60. ------------------------------------------------------------------------------}
  61. {Copyright 1988 by Bob.  All rights reserved, since Bob has all rights.
  62.     February 1, 1988. 
  63.     Written by Bo3b Johnson of Developer Technical Support. }
  64.     
  65. { Version 1.0 }
  66.  
  67. { The following is a list of features or bug fixes that could be added to the program:
  68.   *** Check the segmentation.
  69.   *** Bug with selection rectangle on edge of document, moving by 1 pixel.
  70.   *** Make it run crashless using temp documents to store partial fractals.
  71.   *** Updates could be cleaner so no partial fractals are displayed.
  72.   *** Override window.updateevent so we can avoid the EraseRect on updates.
  73.   *** Draw selection rect in offscreen, copy up to screen for flicker free selection.
  74.   *** Crash on 3 monitor system during window drag.
  75.   *** Could set the bytes directly in offscreen PixMap, skip using MoveTo:Line. 
  76.   *** Copy up from small picture up to big screen gets garbage, src rect too big?
  77.   *** Allow a way to Zoom in using coordinates.
  78.   *** Allow the user to set the colors used in display.
  79.   *** Bigger penSize for fast, lo-res fractals.  Allow user to set size of pen.
  80.   *** Add MacApp debugging stuff like Inspect and TList object checking.
  81.   *** Some things for the reader to do to modify the program. 
  82.   }
  83.  
  84. { Where does it fit:
  85.     This is a series of sample programs for those doing development
  86.     using Color QuickDraw.  Since the whole color problem depends
  87.     upon the exact effect desired, there are a number of answers
  88.     to how to use colors, from the simple to the radically complex.
  89.     These programs try to cover the gamut, so you should use 
  90.     which ever seems appropriate.  In most cases, use the simplest
  91.     one that will give the desired results.  The compatibility
  92.     rating is from 0..9 where low is better.  The more known risks 
  93.     there are the higher the rating.
  94.     
  95.     
  96.     The programs (in order of compatibility):
  97.     
  98.         SillyBalls:
  99.             This is the simplest use of Color QuickDraw, and does
  100.             not use the Palette Manager.  It draws randomly colored
  101.             balls in a color window.  This is intended to give you
  102.             the absolute minimum required to get color on the screen.
  103.             Written in straight Pascal code.
  104.             Compatibility rating = 0, no known risks.
  105.         
  106.         FracAppPalette:
  107.             This is a version of FracApp that uses only the Palette
  108.             Manager.  It does not support color table animation
  109.             since that part of the Palette Manager is not sufficient.
  110.             The program demonstrates a full color palette that is
  111.             used to display the Mandelbrot set.  It uses an offscreen
  112.             gDevice w/ Port to handle the data, using CopyBits to
  113.             draw to the window.  The Palette is automatically 
  114.             associated with each window.  The PICT files are read
  115.             and written using the bottlenecks, to save on memory
  116.             useage.
  117.             Written in MacApp Object Pascal code.
  118.             Compatibility rating = 0, no known risks.
  119.         
  120.         TubeTest:
  121.             This is a small demo program that demonstrates using the
  122.             Palette Manager for color table animation.  It uses a 
  123.             color palette with animating entries, and draws using the
  124.             Palette Manager.  There are two circles of animating colors
  125.             which gives a flowing tube effect.  This is a valid case
  126.             for using the animating colors aspect of the Palette Manager,
  127.             since the image is being drawn directly.
  128.             Written in straight Pascal code.
  129.             Compatibility rating = 0, no known risks.
  130.         
  131.         FracApp:    (***)
  132.             This is the ╘commercial quality╒ version of FracApp.  This
  133.             version supports color table animation, using an offscreen
  134.             gDevice w/ Port, and handles multiple documents.  The
  135.             CopyBits updates to the screen are as fast as possible.  The
  136.             program does not use the Palette Manager, except to
  137.             provide for the system palette, or color modes with less than
  138.             255 colors.  For color table animation using an offscreen
  139.             gDevice w/ Port, it uses the Color Manager and handles the
  140.             colors itself.  Strict compatibility was relaxed to allow for
  141.             a higher performance program.  This is the most ╘real╒ of the
  142.             sample programs.
  143.             Written in MacApp Object Pascal code.
  144.             Compatibility rating = 2.  (nothing will break, but it may not
  145.                 always look correct.)
  146.         
  147.         FracApp300:
  148.             This doesn't support colors, but demonstrates how to create and
  149.             use a 300 dpi bitmap w/ Port.  The bitmap is printed at full
  150.             resolution on LaserWriters, and clipped on other printers (but
  151.             they still print).  It demonstrates how to use a high resolution
  152.             image as a PICT file, and how to print them out.
  153.             Written in MacApp Object Pascal code.
  154.             Compatibility rating = 1.  (The use of PrGeneral is slightly 
  155.                 out of the ordinary, although supported.)
  156. }
  157.     
  158. { Reasons for this version of reality (the strategy):
  159.     The main idea behind this program is to allow you to create and fool around 
  160.     with the Mandelbrot set, using this number cruncher wizzo computer we got
  161.     here.  While we are making these documents, we also throw in a couple of
  162.     special effects to make it more fun, like the special color mapping and the
  163.     color table rotation.  This program is intended to be a real-life program,
  164.     done as well as possible given current constraints.  The program is supposed
  165.     to be something that somebody (who was silly perhaps) might try to sell.
  166.     The idea is to pretend that we are developers as well, testing the development
  167.     world as well as making a fun program.  Well, life╒s a bitch as a developer
  168.     trying to write a program of this form.  No intentional shortcuts were taken
  169.     in the program, but some things were left out due to time constraints.  Like
  170.     all real programs, some people will like it and some people will hate it.  I
  171.     hope you like it, but if you don╒t, send me some mail telling me why.
  172.     
  173.     The overall structure of the program is to have the MacApp Document object
  174.     handle all the data.  This includes an offscreen gDevice and port that are
  175.     the actual fractal data.  The View object uses the Document╒s data to draw
  176.     into the window visible to the user.  The Document does all the work of
  177.     calculating new fractals during Idle times.  It also handles saving the
  178.     data to disk and reading it back.  The data files are PICT so as to be as
  179.     compatible as we can.  The color table animation is handled by the Application
  180.     object during its Idle processing.  The program handles zooming in to
  181.     see closer views of the environment, using selection rectangles as you
  182.     would expect.  This whole block of comments at the beginning are intended
  183.     to describe some more macroscopic problems and structure.  In the code 
  184.     itself you will find the tactical comments dealing with how a specific
  185.     operation is done.  
  186.     
  187.     The fractal calculation is done by the CalcCity routine of the Document.  The
  188.     Application object gets the DoIdle call, and he calls each document to 
  189.     have a pixel calculated in each document.  The calculation is done a pixel
  190.     at a time so that it can be done in the background with no visible effect
  191.     on the foreground app.  As each horizontal line of data is finished it is 
  192.     updated to the screen.  The algorithm is very simple.
  193.     
  194.     The color handling to be as nice as possible under MultiFinder is found in
  195.     the AboutToLoseControl and RegainControl methods of the Application.  In
  196.     addition, the color table animation is done by the RotateColors routine,
  197.     called from the Application's DoIdle method after it gives some CPU time
  198.     to each open document.  The handling of the gDevices on the system, essentially
  199.     all the work the Palette Manager would have done for us can be found in 
  200.     the GarDevice methods.  Those handle changing the colors and color table
  201.     animation.
  202.     
  203.     Allocating and using an offscreen gDevice and port are found in the Document
  204.     object, as the BuildOffWorld method.  The port offscreen is used as a drawing
  205.     environment once a pixel is calculated, as seen in DoIdle for the Document.
  206.  
  207.  
  208.     You should use the Palette Manager if at all possible.  For other references
  209.     see the other sample programs in this collection.  
  210.     This program does not fully use the 
  211.     Palette Manager.  I apologize.  The Palette Manager has two major
  212.     flaws for a program of this form.  It does not provide a convenient way to 
  213.     remap the colors after the color table is scrambled so that updates to the
  214.     screen can be as fast as possible; and it does not support a single animating 
  215.     palette used for multiple windows.  The first can be partially solved using the
  216.     CopyBits to itself trick, but this is inconvenient while a fractal is being
  217.     calculated.  The second can only be solved by incredible hacks, which 
  218.     would not be done in a commercial program.  The problem stems from having
  219.     195 animating colors for each window, but I only wish to use 195 colors
  220.     for the whole program, not each window.  In addition, this is 195 animating
  221.     colors.  If they were standard colors, then the same entries in the table 
  222.     would be used for multiple windows. The answer to the problems
  223.     was to use the Color Manager, and try to be as friendly as possible.  When
  224.     the Palette Manager is fixed, the program can be revised to reflect these
  225.     changes and live a much happier life.
  226.     
  227.     The Color Manager is used, primarily the SetEntries call in order to set
  228.     the color enviroment the way we need it.  Color Search Procs are also 
  229.     used to map the fractals into devices that have fewer colors.  Using
  230.     SetEntries can be a little rough since the Palette Manager is also using
  231.     it to set the color environment and does not respect our use of the
  232.     color table.  FracApp could be described as Palette Manager aware, but
  233.     not friendly.  It uses the PM whenever it can, but for the fanciest effects
  234.     it must bypass the PM.  Using the Palette Manager is far and away the 
  235.     most sensible thing to do, but we were stuck here with a low-performance
  236.     program that would not be commercially viable.  If you don╒t need some
  237.     of the high-end tricks played here, your life will be much easier if you
  238.     use the Palette Manager instead.  In particular, FracApp knows and keeps
  239.     track of all the Monitors connected to the machine, which is normally
  240.     done by the Palette Manager.  If you can do it, save yourself some time
  241.     and use the PM.
  242.     
  243.     This color changing will only be done if we have enough colors in the system
  244.     to do the full blown fractal (ie. > kNumColors).  If not, we can╒t get a reasonable
  245.     fractal anyway, so we will just do zebra fractals.  This way you can still 
  246.     use the program in lesser color modes, it just isn╒t as impressive.  This is far
  247.     preferable to a ╘you must use 256 color mode╒ dialog.  Color table animation 
  248.     will only be enabled when running with enough colors.  If we have enough colors 
  249.     on a given monitor then we just set the ctSeeds to match to get a fast update.
  250.     
  251.     When we set up the offscreen gDevice, we set it up using a 3 bit iTable to save
  252.     on memory that isn't used.  We don╒t use the iTable in the offscreen gDevice
  253.     since it is never the destination of a CopyBits or color selection operation.
  254.     There is thus no reason to waste memory on unused data.
  255.     
  256.     When a document is saved the color table from the offscreen gDevice is used.
  257.     This is the clut resource that is used uniformly throughout the program.  If
  258.     you hate my choice of colors, you can change the clut to something else and
  259.     all should work the same (except old documents will map into something else).
  260.     When the color animation is done, we don╒t rotate the offscreen guys, we just
  261.     move a copy of the system color table around.
  262.     
  263.     There is a known bug with programs that animate the color table in the
  264.     background.  This doesn╒t seem like a reasonable goal given the environment,
  265.     but some programs wish to do this.  If they do, the color table will get
  266.     munged up, giving you some wild effects.
  267.     
  268.     The Palette Manager is used to the extent that we have the System Palette
  269.     (14 colors) attached to each window automatically.  If the window is on
  270.     a monitor with not enough colors to do our thing, the Palette will be used
  271.     instead, giving a fixed color environment so the fractals look the same 
  272.     each time they are displayed.  If we have enough colors on the device we
  273.     walk all over the colors there, so the PM is not being used there.
  274.     
  275.     The Apple Menu was a special hack to make sure that it would draw in the
  276.     right colors, and not be animated when we were doing the color table
  277.     animation.  It is done by making sure that the system palette is always
  278.     available, even when we hammer on the color table.  We hammer an
  279.     extra 14 colors when we do it, and make sure the menubar is redrawn
  280.     after we hammer.  To avoid having the Apple animate, we Reserve the
  281.     entries we use.  When the menubar is drawn it will only match non-
  282.     reserved entries.
  283.     
  284.     The program currently uses ScreenBits.Bounds as the determining size of the
  285.     view and thus the fractal that is calculated.  This is OK, but a better approach
  286.     would be to allow the user to specify the limits of the Fractal they want.  This
  287.     involves adding another dialog and save defaults type of feature but is a better
  288.     way to solve the problem since it is not obvious what the best size would be.
  289.     ScreenBits.bounds is easy to use, but whenever there is no obvious best answer
  290.     it is always better to let the user decide instead, that way they can╒t bitch.
  291.     
  292.     We have to be pretty rowdy about changing the color table.  In order to be sure that
  293.     our colors are set up the right way, we need to check the depth and make sure
  294.     the colors are right at each update event.  We may get an update during the program
  295.     that comes from changing depth.  During any update, if we don╒t have enough
  296.     colors to do the full fractal, we will use a color search proc to map the
  297.     fractal into the color space available.  This will make zebra fractals, a
  298.     reasonable compromise.  
  299.     
  300.     Another goal of course was to get this thing done.  A goal that tends to slide
  301.     away as more things are added to the program, so in true Macintosh style,
  302.     the 1.0 version of the program is somewhat limited, and may not be fully
  303.     debugged.  Some things specifically left out: printing the documents to 
  304.     a LaserWriter with grey scales instead, using temporary documents to 
  305.     make the program crashless (so you can start up where you left off, saving
  306.     the computation it took to get there), an option to make the ╘pen╒ size bigger
  307.     so you can do a low-res fractal to begin with.  These things are all admirable 
  308.     features to add, but you have to finish version 1.0 sometime, so this was it.  
  309.     These other things will be added if possible.
  310.     
  311.     Carefully watch the 881 flag with this MPW business.  There are a number of
  312.     ridiculous problems associated with its use.  In particular the $LOAD files
  313.     are dangerous to use with 881 and the combination of the two will often 
  314.     end up compiling successfully into a program that is garbage and will
  315.     crash upon running it.  This, remind yourself, is a feature of the most 
  316.     powerful development system around.  In order to build currently, the main
  317.     program MFracApp.p should be compiled with new $LOAD files and the 881
  318.     flag turned off.  All of the MacApp sources should be compiled with a new
  319.     $LOAD file and 881 turned off.  The last step should be to compile the UFracApp.p
  320.     with the 881 flag turned on, and with new $LOAD files as well.  Beware or
  321.     be prepared to spend a lot of time on something silly.  To solve this 
  322.     problem the Make file used with FracApp has a specific compile rule for 
  323.     the UFracApp file.  UFracApp also uses its own $LOAD file, that is different
  324.     from the MacApp and MFracApp ones.  The 881 flag is not specifically
  325.     required, but it makes code that uses the 881 directly instead of going
  326.     through SANE for a speed up of about 10 times.  Since we are speed freaks
  327.     here as well, the 881 option had to be used.  Given the problems, I would
  328.     probably skip the 881 option and do the time critical pieces in assembler.
  329.     
  330.     If you want to know how long a fractal took to calculate there is a time
  331.     stamp saved in the file header.  It is no longer drawn in the window since
  332.     it is not accurate when the program has run in the background or if there
  333.     are multiple documents open.  This could be added again if desired.
  334.     This will be made more accurate in the future, probably
  335.     using a calculation that gives us a once through the loop calculation
  336.     in units of TimeDBRA, so we can be more machine independent.
  337.     
  338.     Notably I am aware of the fact that this program does not really calculate
  339.     Fractals.  Actually it calculates and displays the Mandelbrot set which is
  340.     not self-similar so it cannot really be called a fractal.  It is distressing to
  341.     add to the confusion as to what fractals are, but it is too late.  For more
  342.     information on Fractals and the Mandelbrot set (no umlaut on the o), you
  343.     could see Mandelbrot╒s book ╘The Fractal Geometry of Nature╒, but it is 
  344.     pretty mathematical and not all that helpful.  A better source is the
  345.     Peitgen-Richter book ╘The Beauty of Fractals╒, which has a do it yourself
  346.     section in the back.
  347.     
  348.     The program is structured primarily around the document.  The document is
  349.     the object to create and maintain the offscreen gDevice & port.  The document
  350.     converts the offscreen data into a PICT file when saved or restored.  The
  351.     document also does the calculation of the fractal, keeping the offscreen data
  352.     up to data as it goes along.  The view only handles taking the document data
  353.     and displaying it.
  354.     
  355.     For the zoom operation, there was no really great way to handle the new
  356.     document case based on another document.  This is a little strange to be 
  357.     doing, and the structure of MacApp was such that we couldn╒t get to the 
  358.     data desired at the right time.  The logical place to put it in at 
  359.     DoMakeDocument was too early to have the gDevice allocated and ready
  360.     to start more stuff.  The problem was resolved by using global variables
  361.     to transmit the information to the other piece of the program that
  362.     might need it.  Essentially DoInitialState decides if this is a brand new
  363.     base level document or a zoom in based on the state of the global variables.
  364.     
  365.     When we feel obligated to go whap on a monitor and change the color table,
  366.     we use a special TList built during the IFracApplication.  The TGarList is
  367.     a list of display devices in the system.  Whenever we need to do something
  368.     to a display device we do an Each operation and do it to each device in the
  369.     system.  This simplifies the handling of multiple monitors dramatically.
  370.     Thank you MacApp, this is cool.  The GarDevices have a number of methods
  371.     that handle the special things we do to the gDevices in the system.  When
  372.     the Palette Manager is more robust, this GarDevice object can probably
  373.     be removed, since virtually all of the work done by the GarDevice are things
  374.     that the Palette Manager will handle for us.
  375.     
  376.     The MacApp memory management approach is used as well.  The big pieces
  377.     used by the Documents come out of permanent memory, helping to avoid
  378.     a crash from no memory.  When we allocate something that will be thrown
  379.     away immediately, like a spare color table or something, it of course
  380.     comes out of normal memory.  We did not do a full blown memory analysis
  381.     of the program since it is such a memory hog anyway.  The mem! resource
  382.     is set up in a form that is roughly close (a bit high) without trying to be
  383.     extra accurate.  When a document takes 400K of RAM to open it hardly 
  384.     seems relevant to make sure the mem! is accurate to 2K.  Because of this
  385.     somewhat cavalier approach you may not be able to open a document in
  386.     a few cases where you really should be able to.  The mem! in use of 40K
  387.     is close with a +2K/-10K error on how big it should really be.
  388.     
  389.     The QuickDraw BottleNecks are used to both read and write the actual
  390.     fractal data from/to the disk.  This is done since the data in the document
  391.     may be very large (100K) and if we just spool the data from the file
  392.     we don't actually have to use that extra hunk of memory.  We have to read
  393.     the data anyway, so we go ahead and just read it in as we play it back.
  394.     As it is played back it goes into the offscreen gDevice's pixMap, so we
  395.     have the data to display.  No memory hit for the document is a big win.
  396.     When writing the data, the same thing is true so we don't have to have
  397.     a huge handle to hold the picture data itself.  We also avoid the problem
  398.     of not having enough memory to create the picture in the first place,
  399.     making a document unsaveable.  That is particualarly annoying, and
  400.     is easy to avoid using the spooling approach. 
  401.     
  402.     While in the foreground we set the color table to be our special set of 
  403.     colors, making the display as good as we can get it for us.  This has the
  404.     side effect of making programs in the background change.  When we are 
  405.     in the background we make no claims on the color table.  We don╒t bother 
  406.     to Protect entries, but we Reserve the entries so that Color2Index
  407.     will find colors other than the animating ones we are using.  This is
  408.     specifically to make the Apple Menu look correct, but will also help
  409.     the programs in the background to avoid having them animate when
  410.     we are in the front.  When they are redrawn, they will use the non-
  411.     animating entries out of the color table.  Again, in the background
  412.     we make no claims, so any color in the table is available for use.
  413.     
  414.     With thanks to Skippy Blair for the discussions of color QuickDraw and the 
  415.     Palette Manager.  Thanks to Darin Adler for further discussion of the Palette
  416.     Manager and for good suggestions on making it more MacApp friendly.
  417.     Thanks too to Dave McGary for the discussion on color mapping in other than 256
  418.     color mode, and for coming up with the solution to use a MOD or DIV to wrap
  419.     the excess colors around, making it come out in the stripes when not enough
  420.     colors are available.
  421.     }
  422.     
  423.  
  424.     { Global variables.  }
  425. VAR
  426.     gRotateOn:            BOOLEAN;                { whether we are color animating or not. }
  427.     gStaggerCount:     INTEGER;                { for staggering windows. }
  428.     gGarList:                TList;                    { List of gDevices in system. }
  429.     gBackGround:        Boolean;                { flag if we are in background or not. }
  430.     gOurColors:            CTabHandle;            { color table we use as a source for documents. }
  431.     gRealMin,
  432.     gRealMax,
  433.     gImagMin,
  434.     gImagMax:            Extended;                { used for zooming in operation. }
  435.         
  436.         { The next globals are used for the QuickDraw bottlenecks when reading or
  437.             writing a picture to disk.  These are needed, since the bottlenecks cannot
  438.             be owned procedures. }
  439.     gPictSize:            LongInt;                { number of bytes used for saving a PICT. }
  440.     gPictError:            OSErr;                    { do some error handling in bottleneck. }
  441.     gPictRefNum:        Integer;                { Need the refnum of the open file too. }
  442.     gPictHandle:            PicHandle;            { for reading/writing a picture. }
  443.  
  444.  
  445. { Set some compiler options that we desire for the main body of code only.  You
  446.     might wish to leave on the range checking, but I was not satisfied with having
  447.     to push and pop in the code, and was not pleased with the slowdown in performance. 
  448.     These can be dangerous to turn off, especially the $H.  For those who use MPW
  449.     more than I, you probably want to make these settable from the command line,
  450.     or use the MacApp debug or no-debug compile time variables.  }
  451. {$PUSH}        { Save the compiler state before we change it. }
  452. {$D+}            { Debugging labels on for the code here. }
  453. {$R-}            { No range checking to make things faster. }
  454. {$OV-}            { No overflow checking either. }
  455. {$H-}            { No handle checking to avoid compiler complaints on WITHs.  Be careful. }
  456. {$N+}    
  457.  
  458. {-------------------------------  Application  -------------------------------}
  459.  
  460. PROCEDURE TFracAppApplication.IFracAppApplication(itsMainFileType: OSType);
  461.  
  462. VAR        I:                        Integer;
  463.             nextDevice:        GDHandle;
  464.             nextGar:            TGarDevice;
  465.  
  466. BEGIN
  467.     gStaggerCount := 0;
  468.     IApplication(itsMainFileType);
  469.     fIdlePriority := 1;                        { say we need Idle time calls to TApp.DoIdle }
  470.     gRotateOn := FALSE;                    { no color rotation as we start. }
  471.     gBackGround := FALSE;                { not in background to start. }
  472.     gRealMin := 0;                            { must be set to empty to start with. }
  473.     gRealMax := 0;                            { so we do normal document open. }
  474.     
  475.         { Now allocate a color table that we will use whenever we create a new document
  476.             or need to compare colors.  This is so we have the same color table for each
  477.             document, as well having the ctSeed the same for them all. }
  478.     gOurColors := GetCTable(kClut);                    { install our new desired one from clut }
  479.     FailNil (gOurColors);
  480.     
  481.         { In order to handle the multiple gDevices that abound in Mac II, we need to make
  482.             a TList of the gDevices so we can more easily keep track of them.  The TGarDevice
  483.             objects will be used to keep track of the devices, one object for each gDevice.
  484.             We need to set up this TList here though.  The GarDevice list won't change while
  485.             the program is running.  We only add devices that are screen devices, to avoid
  486.             any spurious devices that we don't care about.  When initialized each GarDevice
  487.             will set up the color environment as best it can. }
  488.     gGarList:= NewList;
  489.     
  490.     nextDevice := GetDeviceList;                        { First gDevice in system. }
  491.     While  nextDevice <> NIL  DO BEGIN
  492.         IF TestDeviceAttribute(nextDevice, screenDevice)  AND 
  493.                 TestDeviceAttribute(nextDevice, screenActive)  THEN BEGIN
  494.             New (nextGar);                                        { Make a new object for this device. }
  495.             FailNil (nextGar);                                    { If not possible, we are toast. }
  496.             nextGar.IGarDevice (nextDevice);            { Init the GarDevice object. }
  497.         END;                                                            { Add a legitimate screen Device. }
  498.         nextDevice := GetNextDevice(nextDevice); { move to next gDevice in chain. }
  499.     END;    { While }
  500. END;        { TFracAppApplication.IFracAppApplication }
  501.  
  502.  
  503.     { OK, this is where a new document gets created.  This does the init for the
  504.         document object itself.  After it is done, the view and window can
  505.         be created, relying upon the data in the document. }
  506. FUNCTION  TFracAppApplication.DoMakeDocument(itsCmdNumber: CmdNumber):
  507.         TDocument; OVERRIDE;
  508.         
  509. VAR        aFracAppDocument:    TFracAppDocument;
  510.  
  511. BEGIN
  512.     { Allocate and initialize the document}
  513.     New(aFracAppDocument);
  514.     FailNil(aFracAppDocument);
  515.  
  516.     { Now initialize the document fields, and set up the global state of the fractal
  517.         to a default set of the starting fractal. }
  518.     aFracAppDocument.IFracAppDocument;
  519.  
  520.     { We successfully created a document so we can return the document object for
  521.         use by the application. }
  522.     DoMakeDocument := aFracAppDocument;
  523.     
  524. END;    { TFracAppApplication.DoMakeDocument }
  525.  
  526.  
  527.     { Performs Idle time processing for the application.  This will do the
  528.         fractal calculation during the idle times.  It will allow each open
  529.         document a chance to calculate.  The CalcCity is a method owned by
  530.         each document that will get a call from the ForAllDocumentsDo. 
  531.         The documents don't use the DoIdle routine since we want each
  532.         open document to get time, not just the one in the target chain. }
  533. PROCEDURE  TFracAppApplication.DoIdle (phase: IdlePhase); OVERRIDE;
  534.  
  535.         { Give each document some CPU time. }
  536.     PROCEDURE DoFractalCalc(aDocument: TFracAppDocument);
  537.     
  538.     BEGIN
  539.         aDocument.CalcCity;                        { give the document its time to calc. }
  540.     END;
  541.  
  542.         { Give each GarDevice the message to rotate colors. }
  543.     PROCEDURE DoRotateColors(curGar: TGarDevice);
  544.     
  545.     BEGIN
  546.         curGar.RotateColors;                        { tell the device to rotate colors if possible. }
  547.     END;
  548.  
  549. BEGIN
  550.     IF phase = IdleContinue  THEN  BEGIN
  551.     
  552.             { Send the message to each open document to calculate the next pixel. }
  553.         ForAllDocumentsDo (DoFractalCalc);
  554.     
  555.             { Now we have calculated the next pixel in each document.  If the Rotate Colors menu
  556.                 option has been checked we want to rotate the color table using our routine.  We
  557.                 need to rotate all color tables in devices we can see, so we'll use the GarList to
  558.                 do them all. }
  559.         IF  (gRotateOn) AND (gFrontWindow <> NIL)  AND (NOT gBackGround)  THEN  
  560.                 gGarList.Each (DoRotateColors);
  561.     END;
  562. END;        { TFracAppApplication.DoIdle }
  563.  
  564.  
  565.     { Set up the menus choices in Fractal Menu.  Set the Rotate Colors choice to
  566.         be enabled if we have a device with enough colors, and set the check mark
  567.         on if it has been chosen.  We will go through the chain of devices to see if
  568.         any are capable of rotation, and if so will enable the menu.  If we have a 
  569.         saved color table for any device, that device can do rotation. }
  570. PROCEDURE TFracAppApplication.DoSetupMenus;  OVERRIDE;
  571.  
  572. VAR        rotateFlag:    Boolean;
  573.     
  574.     
  575.     PROCEDURE  CheckRotate (curGar: TGarDevice);
  576.     
  577.     BEGIN
  578.         IF curGar.fColorTable <> NIL  THEN rotateFlag := TRUE;
  579.     END;
  580.     
  581. BEGIN
  582.     INHERITED DoSetupMenus;                                    { Do mainline stuff first. }
  583.     
  584.     rotateFlag := FALSE;                                                { Assume we can't do it. }
  585.     gGarList.Each (CheckRotate);                                { Change flag if needed. }
  586.     
  587.     EnableCheck (kRotateColors, rotateFlag, gRotateOn);    { enabled, and checked or not. }
  588. END;        { TFracAppApplication.DoSetupMenus }
  589.  
  590.  
  591.     { Handle the menu choice out of the Fractal Menu for Rotate Colors.  This will either
  592.         set or unset the check mark, thus enabling or disabling the color rotation 
  593.         performed in the Idle loop. }
  594. FUNCTION TFracAppApplication.DoMenuCommand(
  595.                             aCmdNumber: CmdNumber): TCommand;  OVERRIDE;
  596.  
  597. BEGIN
  598.     DoMenuCommand := gNoChanges;            { assume no command object returned. }
  599.     
  600.     CASE  aCmdNumber  OF
  601.         kRotateColors:
  602.             { Chose the rotate colors menu option.  We want to set the check mark, or unset it
  603.                 if already there, and reflect the menu state in our document variable which is
  604.                 used to rotate the color list at Idle time. }
  605.             BEGIN
  606.                 gRotateOn := NOT  gRotateOn;            { invert the state of menu option. }
  607.             END;        { kRotateColors }
  608.         
  609.         OTHERWISE
  610.             DoMenuCommand := INHERITED  DoMenuCommand (aCmdNumber);    { next guy in chain. }
  611.     END;        { CASE aCmdNumber }
  612. END;        { TFracAppApplication.DoMenuCommand }
  613.  
  614.  
  615.     { When we are switched in we need to reset the color table to our color table. }
  616. PROCEDURE TFracAppApplication.RegainControl(checkClipboard: BOOLEAN); OVERRIDE;
  617.  
  618.     PROCEDURE  SendPound (curGar: TGarDevice);
  619.     
  620.     BEGIN
  621.         curGar.PoundColors;
  622.     END;
  623.  
  624.  
  625. BEGIN
  626.     gBackGround := FALSE;                        { no longer in background. }
  627.     
  628.         { Change the color table on each gDevice in the system that has enough colors to
  629.             support our fancy colors.  If any device has enough colors we will enable the
  630.             rotation flag. }
  631.     gGarList.Each (SendPound);
  632.     
  633.         { Call the inherited routine to do the normal regain control operations as well. }
  634.     INHERITED    RegainControl (checkClipboard);
  635. END;    { TFracAppApplication.RegainControl }
  636.  
  637.     
  638.     { When we are switched out we need to restore the color table to be polite.  When
  639.         the program is closed this routine is called as well, so we don't need to override
  640.         the Close method in order to fix the color table on Quit. }
  641. PROCEDURE TFracAppApplication.AboutToLoseControl(convertClipboard: BOOLEAN); 
  642.         OVERRIDE;
  643.  
  644.     PROCEDURE  SendUnPound (curGar: TGarDevice);
  645.     
  646.     BEGIN
  647.         curGar.UnPoundColors;
  648.     END;
  649.  
  650.  
  651. BEGIN
  652.     gBackGround := TRUE;                            { are going into background. }
  653.     
  654.         { Fix up every gDevice that needs it. }
  655.     gGarList.Each (SendUnPound);
  656.         
  657.         { Call the inherited routine to do the normal work for losing control. }
  658.     INHERITED    AboutToLoseControl (convertClipboard);
  659. END;    { TFracAppApplication.AboutToLoseControl }
  660.  
  661.  
  662.  
  663. {-------------------------------  Document  -------------------------------}
  664.  
  665.     { An auxiliary method to set up the step constants for the fractal calculation.
  666.         It is external since we need to set up the constants when we create a new
  667.         fractal as a zoom in.   Sets up the width/height of fractal, the delta in each
  668.         axis as a real number, and ensures that the starting min/max values for
  669.         the figure are set to supply a 1:1 aspect ratio.  The step constants are 
  670.         zeroed to start the fractal anew.  Allocates no memory. }
  671. PROCEDURE  TFracAppDocument.SetUpConstants;
  672.  
  673. BEGIN
  674.     WITH fFracHeader  DO  BEGIN
  675.         { Set up the iterations by calculating up the step constants, and the 
  676.             edges of the view area in pixels. }
  677.     
  678.         plotWidth := (calcRect.Right - calcRect.Left);
  679.         plotHeight := (calcRect.Bottom - calcRect.Top);        
  680.         deltaP := (realMax-realMin)/(plotWidth-1);
  681.         deltaQ := (imagMax-imagMin)/(plotHeight-1);
  682.     
  683.         { Force aspect ratio 1:1, making delta smallest of two.  This effectively grows
  684.             one side or the other out, like rMax/iMax becoming bigger number. }
  685.         IF deltaP > deltaQ  THEN  BEGIN
  686.             deltaQ := deltaP;
  687.             imagMax := deltaQ * (plotHeight-1) + imagMin;     { new maximum for q }
  688.         END   { grow the q side }
  689.         ELSE  BEGIN
  690.             deltaP := deltaQ;
  691.             realMax := deltaP * (plotWidth-1) + realMin;         { new maximum for p }
  692.         END;  { grow the p side }
  693.     
  694.         { Now start the counters at zero, as the edge of the area to calc. }
  695.         curCol := 0;  curRow := 0;
  696.         
  697.         { And the elapsed time is zero of course, since we are just starting. }
  698.         elapsedTime := 0;
  699.     END;        { WITH fractalDocument }
  700. END;        { SetUpConstants }
  701.  
  702.  
  703.     { Utility method to build the offscreen gDevice and offscreen Port that is used for
  704.         the document data.  This happy fellow will allocate huge old hunks of Ram for
  705.         the document and set up the initial state of the gDevice with the right color
  706.         table and so on.  This is done as a utility routine since we don't know in advance
  707.         how big the gDevice will be, and we want to make it as big as it was when the
  708.         document was saved, reading it from the header.  If we are making a new 
  709.         document, the DoInitialState will call with the screen rectangle. }
  710.         
  711. PROCEDURE  TFracAppDocument.BuildOffWorld (sizeOfDoc: Rect);
  712.  
  713. VAR        oldPerm:             Boolean;
  714.             dummy:                Boolean;
  715.             docW, docH:        LongInt;
  716.             fi:                     FailInfo;
  717.             currDevice:        GDHandle;
  718.             currPort:            GrafPtr;
  719.             Erry:                    OSErr;
  720.                 
  721.         { This is the error handler for when we get errors while making a new document,
  722.             typically like running out of memory.  Since the Free method for the document
  723.             will get called we don't have to chuck the things that normally get killed. 
  724.             Just set allocation back to normal (for the error message itself), the drawing 
  725.             environment back to normal and return.  }
  726.     PROCEDURE DeathBuildOff (error: OSErr; message: LONGINT);
  727.     
  728.     BEGIN
  729.         oldPerm := PermAllocation (oldPerm);    { Set memory back to previous. }
  730.         
  731.         SetGDevice (currDevice);                        { Set device back to main, just in case. }
  732.         SetPort (currPort);
  733.     END;
  734.  
  735. BEGIN
  736.     currDevice := GetGDevice;                        { save current for error handling. }
  737.     GetPort(currPort);
  738.     
  739.     { The memory used creating the view must be out of permanent memory, it is too 
  740.         big.  Any failure to get it from permanent memory will invoke the error handler. }
  741.     oldPerm := PermAllocation (TRUE);
  742.  
  743.     CatchFailures(fi, DeathBuildOff);                { any failures, must be cleaned up. }
  744.  
  745.     { Let's set up the size of the rectangle we are using for the document. }
  746.     docW := sizeOfDoc.right - sizeOfDoc.left;
  747.     docH := sizeOfDoc.bottom - sizeOfDoc.top;
  748.  
  749.  
  750.     { Now try to set up the offscreen bitMap (color).  If we fail we have to split,
  751.         and we might since we may not have 300K or more (basically a full screen
  752.         worth, which is unlikely to be less than 300K) for the pixMap.  Each document
  753.         on screen will have a full pixMap for it.  Allocate a full screen size buffer in
  754.         8 bit depth.  Also make it into a color port so we can draw into it normally and 
  755.         use it as a source for CopyBits.  Requires 8 bits deep for the number of colors,  
  756.         and sets up a buffer with that in mind, that is full docRect size with
  757.         one byte per pixel as 8 bit mode.  This is width x height.  8 bits/byte. }    
  758.     fBigBuff := NewPtr (docW * docH);
  759.     FailMemError;                                                { couldn't get it we die. }
  760.  
  761.  
  762.     { OK, now we get wacko.  We need to create our own gDevice, since we want to have
  763.         an offscreen device.  This needs to be done so that we have full control over the
  764.         color table used, in order to save full 8 bit documents, even if we aren't in 8 bit
  765.         mode when we save.  So...  We will start by creating a NewGDevice, that will 
  766.         allocate a temporary ITable, and PixMap with partial colorTable;  change the 
  767.         fields of the device's pixMap to our bitMap, with right size, depth, and rowbytes; 
  768.         init the fields of that device, including changing the color table to our color 
  769.         table created from our clut;  set that gDevice as the current one; then do the 
  770.         OpenCPort which will use the current gDevice to make its PixMap and color table;
  771.         When we go to draw or save the data in the offscreen buffer,
  772.         we need to set the current device so we use our color table, making all the
  773.         colors come out right.  }
  774.             
  775.     { Now we need to do the piece to make an offscreen gDevice that is not connected
  776.         to the screen.  Allocate a new one, with stub pixMap. }
  777.     fDrawingDevice := NewGDevice (0, -1);            { -1 means unphysical device.  }
  778.     FailNIL (fDrawingDevice);                                { If we failed, error out. }
  779.     
  780.     { Now init all the fields we can in the gDevice Record, since it comes uninitialized. }
  781.     HLock ( Handle(fDrawingDevice) );
  782.     WITH  fDrawingDevice^^  DO  BEGIN
  783.         gdId := 0;                                                        { no ID for search & complement procs }
  784.         gdType := clutType;                                        { color table type fer sure. }
  785.         
  786.         { Get the color table for the offscreen gDevice.  This is a copy of the global
  787.             color table we created early on. }
  788.         DisposCTable (gdPMap^^.pmTable);                { kill the stub that is there. }
  789.         gdPMap^^.pmTable := gOurColors;                    { make a copy of our global color table. }
  790.         Erry := HandToHand (Handle(gdPMap^^.pmTable)); { and stick it into this gDevice too. }
  791.         FailOSErr (Erry);                                            { if not possible, blow out. }
  792.         
  793.         { build a new iTable for this device, based on the new color table.  3 bit res to
  794.             save on memory since we don't need the iTable for our stuff.  If we fail here,
  795.             we have some dumb error code like -151.  This is translated via the ╘errs╒
  796.             resource into the ╘there is not enough memory╒ message instead. }
  797.         MakeITable (gdPMap^^.pmTable, gdITable, 3);
  798.         FailOSErr (QDError);                                        { no memory, we can leave here. }
  799.         
  800.         gdResPref := 3;                                                { preferred resolution in table. }
  801.         gdSearchProc := NIL;                                        { no search proc. }
  802.         gdCompProc := NIL;                                        { no complement proc. }
  803.         { Set the gdFlags to be: color, ramInit, noDriver, screenActive }
  804.         gdFlags := 2**0 + 2**10 + 2**14 + 2**15;    { set each bit we need. }
  805.         
  806.         { Now set up the fields in the offscreen PixMap correctly. }
  807.         gdPMap^^.baseAddr := fBigBuff;                        { The base address is our buffer. }
  808.         gdPMap^^.bounds := sizeOfDoc;                        { bounding rectangle to our device. }
  809.         { one byte per pixel horizontally is rowBytes.  + $8000 to make it color port. }
  810.         gdPMap^^.rowBytes := docW + $8000;
  811.         gdPMap^^.pixelSize := 8;
  812.         gdPMap^^.cmpCount := 1;
  813.         gdPMap^^.cmpSize := 8;
  814.         
  815.         gdRect := sizeOfDoc;                                        { the bounding rectangle for gDevice, too. }
  816.     END;        { With fDrawingDevice }
  817.         
  818.         { Now unlock the gDevice handle since it is in the System Heap.  The system
  819.             can use it unlocked as well as locked so we try to help avoid fragmentation. }
  820.     HUnLock ( Handle(fDrawingDevice) );
  821.  
  822.     { Yow, that was rough.  Now we have a fully initialized gDevice offscreen with its
  823.         own colortable.  All color mapping should be done using that color table, and the
  824.         drawing we do to it should make the saved pictures save that color table too. 
  825.         Set to our new device so we OpenCPort with all new parameters. }
  826.     SetGDevice (fDrawingDevice);
  827.     
  828.     { After all of that, we have a gDevice which is complete.  It has the color table we want
  829.         associated with it, from the clut, it has the right portBits.baseAddr and the right
  830.         size.  It is complete, except that we can't draw into it using normal calls.  We thus
  831.         need to make a port that we can use.  We have set the gDevice to be the one we just
  832.         created, and when we OpenCPort we will get a copy of the fields we just set up in
  833.         our new gDevice.  The port is simply an interface into our gDevice for drawing.
  834.         Allocate a port record on the heap as a pointer.  We get the Port record out of 
  835.         permanent memory, but the actual opening of the port must use all the memory
  836.         available to avoid blowing up (system error).  QuickDraw is very unfriendly. 
  837.         After it lives, we need to be sure we still have memory reserve, and if not, we
  838.         exit, killing this document. }
  839.     fDrawingPort := CGrafPtr( NewPtr (SizeOf (CGrafPort)) );    { address of C Port record. }
  840.     FailNil (fDrawingPort);                                    { didn╒t get it, means we die. }
  841.     
  842.     { Now the world is created, put memory allocation back to temporary, so that the
  843.         QD pieces can come out of temp memory as well.  No more permanent blocks are
  844.         allocated by us, except for the port, which cannot fail or we die.  }
  845.     dummy := PermAllocation (FALSE);
  846.  
  847.     OpenCPort (fDrawingPort);                                { make a new port offscreen. }
  848.     FailNoReserve;                                                    { Make reserve, die if we can╒t }
  849.         
  850.         { QuickDraw is most obnoxious about making a port that is bigger than the screen,
  851.             so we need to modify the visRgn to make it as big as our full page document.  It is
  852.             OK to change this ports visRgn since we own it offscreen.  This is in case we
  853.             are opening a document made on a different computer with a bigger screen. }
  854.     RectRgn(fDrawingPort^.visRgn, sizeOfDoc);
  855.  
  856.         { Go whap on the other pieces of the port record to set it up to be offscreen. }    
  857.     fDrawingPort^.portRect := sizeOfDoc;
  858.  
  859.     { OK, we have a nice new color port that is offscreen.  It has a fancy color table that
  860.         came from the clut that will be used for the owning window.  It is 8 bits deep,
  861.         has 256 colors in its color table and has a rect the size passed in.  It has no
  862.         pieces that are related to the main gDevice, so we shouldn't alter that by drawing
  863.         in this port.  }
  864.         
  865.         { Clear the error handler chain, we don't make any more dangerous requests. }
  866.     Success (fi);
  867.  
  868.         { Set the memory allocation to what we started with. }
  869.     oldPerm := PermAllocation (oldPerm);
  870.  
  871.         { Now we have the offscreen PixMap, we need to initialize it to white. }
  872.     SetPort (GrafPtr(fDrawingPort));
  873.     EraseRect (sizeOfDoc);                { clear the bits. }
  874.  
  875.         { We are done drawing and stuff for now, so set the gDevice back to where it was. }
  876.     SetGDevice (currDevice);
  877.     SetPort (currPort);
  878. END;    { BuildOffWorld }
  879.  
  880.  
  881.     { Init for the FracAppDocument itself.  This sets up the Document object. }
  882. PROCEDURE      TFracAppDocument.IFracAppDocument;
  883.  
  884. VAR        dummyTime:        LongInt;        { for picky compiler }
  885.     
  886. BEGIN
  887.     { Set up failure mechanism in case IDocument fails}
  888.     IDocument(kFileType, kSignature, kUsesDataFork,
  889.             NOT kUsesRsrcFork, NOT kDataOpen, NOT kRsrcOpen);
  890.     
  891.     { Set the time in our starting time variable in case we are still calculating.
  892.         Temp var is to make the picky compiler not get worried about the var
  893.         parameter.  This routine can't move memory anyway, but it won't allow
  894.         this use. }
  895.     GetDateTime (dummyTime);
  896.     fStartTime := dummyTime;
  897.     
  898.     fBigBuff := NIL;
  899.     fDrawingPort := NIL;                                    { set up in case we fail in here. }
  900.     fDrawingDevice := NIL;
  901. END;    { TFracAppDocument.IFracAppDocument }
  902.  
  903.  
  904.     { Does the work for a New operation, where we start with a new fractal
  905.         that doesn't have any stored data.  This is to set up the view with no
  906.         data and set up the fractal coordinates to the default.  It will use the size
  907.         of the  main screen to make a new document, and create the offscreen 
  908.         world to match.  If the global variable of gRealMin and gRealMax are both
  909.         nonzero, then we want to use the global state being passed us by the 
  910.         New Fractal handler.  This is for the zoom in. }
  911. PROCEDURE TFracAppDocument.DoInitialState;  OVERRIDE;
  912.     
  913. BEGIN
  914.     WITH  fFracHeader  DO BEGIN
  915.         { Start by filling in the fields that never change. }
  916.         fType := kSignature;            { creator of these documents. }
  917.         hdrId := INTEGER ('FA');        { ID of the file, different from other PICT files. }
  918.         version := 1;                        { version 1 files. 0 was old MandibleJug docs. }
  919.         
  920.         done := FALSE;                    { not done, starting brand new document. }
  921.     
  922.         { We start from scratch.  This is the standard set of coordinates to start
  923.             the default Mandelbrot set. 
  924.             Set up the coordinates to do, saving state in header vars. }
  925.         realMin := -2.5;  realMax := 1.5;
  926.         imagMin := -1.5;  imagMax := 1.5;
  927.         
  928.             { If we are supposed to do a zoom in, use those numbers instead. }
  929.         IF (gRealMin <> 0) AND (gRealMax <> 0)  THEN BEGIN
  930.             realMin := gRealMin;        realMax := gRealMax;
  931.             imagMin := gImagMin;    imagMax := gImagMax;
  932.         END;
  933.         
  934.         { Set the fractal rectangle to be the full screen size. }
  935.         calcRect := ScreenBits.bounds;
  936.  
  937.     END;        { With FracHeader }
  938.     
  939.         { Clear the state of the globals so any new documents will not be zoom in types. }
  940.     gRealMin := 0;        gRealMax := 0;
  941.     
  942.         { Build the initial state of the document offscreen gDevice & port }
  943.     BuildOffWorld (fFracHeader.calcRect);
  944.     
  945.         { Set up the rest of the constants that are used in the fractal, including
  946.             the deltas in each axis and the step constants for stepping through
  947.             each point in the fractal plane. }
  948.     SetUpConstants;
  949. END;        { TFracAppDocument.DoInitialState }
  950.  
  951.  
  952. PROCEDURE TFracAppDocument.DoMakeViews(forPrinting: BOOLEAN); OVERRIDE;
  953.  
  954. VAR        aFracAppView:    TFracAppView;
  955.  
  956. BEGIN
  957.     { Create a new view (failing if we can't), get a rectangle with
  958.         the appropriate extent, and initialize the view. }
  959.     New(aFracAppView);
  960.     FailNil(aFracAppView);
  961.  
  962.     { Initialize the view for use as a drawing environment. }
  963.     aFracAppView.IFracAppView (SELF, fFracHeader.calcRect);
  964.  
  965.     {save a reference to the view in a TFracAppDocument field, for use
  966.         by DoMakeWindows}
  967.     fFracAppView := aFracAppView;
  968. END;    { TFracAppDocument.DoMakeViews }
  969.  
  970.  
  971. PROCEDURE TFracAppDocument.DoMakeWindows; OVERRIDE;
  972.  
  973. VAR        aWindow:    TWindow;
  974.     
  975. BEGIN
  976.     { Gets window definition from resource file; the window is to have both horizontal 
  977.         and vertical scrollbars, and is to have my 'fFracAppView' installed in it;
  978.         NewSimpleWindow will exit via the failure mechanism if allocation fails. 
  979.         There is a palette associated with this window by Resource Id, so it will
  980.         automatically get used when the window is created. }
  981.     aWindow := NewSimpleWindow(kFracAppWindowID, NOT kDialogWindow,
  982.                     kWantHScrollBar, kWantVScrollBar, fFracAppView);
  983.  
  984.     SimpleStagger(aWindow, kStaggerAmount, kStaggerAmount, gStaggerCount);
  985. END;    { TFracAppDocument.DoMakeWindows }
  986.  
  987.  
  988.     { This routine will size the current image as it goes to the disk.  It won't actually
  989.         save any data or anything, but will merely watch the bytes go by keeping track
  990.         of how many go by.  The size is used by DoNeedDiskSpace. }
  991. PROCEDURE  PictSizer (dPointer: Ptr; nextHunk: Integer);
  992.  
  993. BEGIN
  994.     gPictSize := gPictSize + nextHunk;
  995. END;
  996.  
  997.  
  998.     { Routine to find out how much disk space will be required to save the data.  
  999.         This does not call the Inherited DoNeedDiskSpace since we don't support
  1000.         printing info here.  The routine will replace the PutPicProc of the port
  1001.         with our PictSizer routine.  When the picture is created here, no bytes 
  1002.         will actually be allocated or saved, we will just watch it go by and 
  1003.         save off the size in the global variable.  That value is returned
  1004.         as the expected document size. }
  1005. PROCEDURE TFracAppDocument.DoNeedDiskSpace(VAR dataForkBytes, 
  1006.             rsrcForkBytes: LONGINT);   OVERRIDE;
  1007.             
  1008. VAR        picPort:            GrafPtr;
  1009.             currDevice:        GDHandle;
  1010.             currPort:            GrafPtr;
  1011.             newGrafs:            CQDProcs;
  1012.             oldProcs:            QDProcsPtr;        { bug in include files, CGrafPort has QDProcs *** }
  1013.             
  1014. BEGIN
  1015.     { Create a picture Item itself, by opening the picture and doing the CopyBits
  1016.         operation to the same port.  That picture will then be packed using the
  1017.         normal packing operation of the Mac.  That block is then the data to be
  1018.         written to the file. }
  1019.         
  1020.     currDevice := GetGDevice;                                { save off current one. }
  1021.     GetPort (currPort);
  1022.     
  1023.     SetGDevice (fDrawingDevice);                            { set to ours for drawing in it. }
  1024.     picPort := GrafPtr (fDrawingPort);                    { the pointer to our port. }
  1025.     SetPort (picPort);                                            { set there to do pict saving. }
  1026.  
  1027.         { Save the pointer to the current CGrafProcs }
  1028.     oldProcs := thePort^.grafProcs;
  1029.     
  1030.         { Set our GrafProc record up to have the standard pieces. }
  1031.     SetStdCProcs(newGrafs);
  1032.     
  1033.         { Change the port to use those GrafProcs instead. }
  1034.     thePort^.grafProcs := @newGrafs;
  1035.     
  1036.         { We are in our offscreen port.  Change the GrafProc pointer for picture saving. }
  1037.     newGrafs.putPicProc := @PictSizer;
  1038.         
  1039.         { Init the size of the pict we are going to save.  Start with picture header. }
  1040.     gPictSize := SIZEOF (Picture);
  1041.     
  1042.         { The current gDevice is our offscreen device.  Now go ahead and open the picture 
  1043.             and build it in RAM.  We would have done this by slices before, but the newer
  1044.             systems have a patch for playing back pictures that minimize the RAM hit, so
  1045.             we don't have to worry about the full screen CopyBits here. }
  1046.     WITH  picPort^  DO BEGIN
  1047.         gPictHandle := OpenPicture (portRect);
  1048.         
  1049.                 { copy all of the image to itself, in an open picture it saves the bits. }
  1050.             CopyBits (portBits, portBits, portRect, portRect, srcCopy, NIL);            
  1051.             
  1052.         ClosePicture;                        { the picture is created, and packed. }
  1053.     END;        { with picPort^ }
  1054.  
  1055.  
  1056.         { Done saving the size of the picture itself.  Now set the GrafProcs back to normal. }
  1057.     thePort^.grafProcs := oldProcs;
  1058.     
  1059.         { Dispose the pict handle, we didn't actually make anything there. }
  1060.     KillPicture (gPictHandle);
  1061.     gPictHandle := NIL;
  1062.     
  1063.     { Set the drawing device back where it belongs, in case of error, we get right device. }
  1064.     SetGDevice (currDevice);                                    { set back to system for normal. }
  1065.     SetPort (currPort);
  1066.  
  1067.     { The picture has been sized.  Now add that in to the total size the file will use on
  1068.         disk, include the header for the file, plus the number of bytes in actual PICT. }
  1069.     dataForkBytes := dataForkBytes + gPictSize + kPICTHeaderSize;
  1070. END;        { DoNeedDiskSpace }
  1071.  
  1072.  
  1073.     { This routine will save the current image as it is created.  As the data requests
  1074.         go by that data will be written to the file.  The data is being created by the
  1075.         OpenPicture/CopyBits in DoWrite, this is the bottleneck for that operation.
  1076.         Any errors found while doing this will make us skip any further requests
  1077.         to write data to the disk.  No memory is allocated.   Communication with 
  1078.         DoWrite is done through globals, since bottlenecks must be at the main
  1079.         level.  The bottleneck must also keep track of how many bytes are written,
  1080.         so that the header on the picture can be fixed up to be correct.  This must
  1081.         be done to avoid creating bogus pictures.  The picSize field of the handle
  1082.         must be updated continuously so that when the picture is done, the ClosePicture
  1083.         can create a valid picture.  The check for the NIL handle is to handle the
  1084.         problem of when the OpenPicture is called.  The proc gets called before 
  1085.         the handle is valid.  Be very careful of these bottleneck things, it is 
  1086.         easy to run into problems that are very hard to figure out.  QuickDraw
  1087.         has no facilities to give you info when things go wrong so it makes it
  1088.         a bit tougher. }
  1089. PROCEDURE  PictWriter (dPointer: Ptr; nextHunk: Integer);
  1090.  
  1091. VAR    longHunk:    LongInt;
  1092.  
  1093. BEGIN
  1094.     IF gPictError = noErr THEN  BEGIN
  1095.         longHunk := nextHunk;
  1096.         gPictError := FSWrite(gPictRefNum, longHunk, dPointer);
  1097.         gPictSize := gPictSize + longHunk;
  1098.         IF gPictHandle <> NIL THEN  gPictHandle^^.picSize := LoWord (gPictSize);
  1099.     END;
  1100. END;
  1101.  
  1102.  
  1103.     { Write the data calculated into the document to the file.  This will make it a real
  1104.         PICT file.  It writes the header first, then the PICT data.  This is so that it 
  1105.         will still be a normal PICT file and can be used by other programs.  
  1106.         The file will be saved using QuickDraw Bottlenecks for the PutPicProc.  
  1107.         As the data requests go by, they will be written to the file, using the
  1108.         PictWriter routine. }
  1109. PROCEDURE TFracAppDocument.DoWrite(aRefNum: INTEGER; makingCopy: BOOLEAN);
  1110.             OVERRIDE;
  1111.  
  1112. VAR        recSize:            LongInt;
  1113.             fi:                     FailInfo;
  1114.             picPort:            GrafPtr;
  1115.             currDevice:        GDHandle;
  1116.             currPort:            GrafPtr;
  1117.             newGrafs:            CQDProcs;
  1118.             oldProcs:            QDProcsPtr;        { bug in include files, CGrafPort has QDProcs *** }
  1119.  
  1120.     PROCEDURE DeathWrite (error: OSErr; message: LONGINT);
  1121.     BEGIN
  1122.         IF gPictHandle <> NIL THEN      KillPicture (gPictHandle);
  1123.         gPictHandle := NIL;
  1124.  
  1125.         thePort^.grafProcs := oldProcs;
  1126.         SetGDevice (currDevice);                { set back to system for normal. }
  1127.         SetPort (currPort);
  1128.     END;
  1129.     
  1130. BEGIN
  1131.         { We have legit data in our document, set the mark in the file to be at the front. }
  1132.     FailOSErr ( SetFPos (aRefNum, fsFromStart, 0) );
  1133.  
  1134.         { Write the FracHeader to the file, it includes the pertinent details about
  1135.             the fractal including the global state for it. }
  1136.     recSize := SIZEOF (FracRecord);                           { our header on fractal files. }
  1137.     FailOSErr ( FSWrite (aRefNum, recSize, @fFracHeader) );
  1138.  
  1139.         { Now we need to write the picture data itself out to the file, after we set the
  1140.             mark to be after the entire header.  Make sure the file is that big before we do it. 
  1141.             Included in this set is the header of the picture itself, the 10 bytes that
  1142.             include the rectangle.  Those bytes will be updated after the picture is
  1143.             written. }
  1144.     FailOSErr ( SetEOF (aRefNum, kPICTHeaderSize+SIZEOF (Picture) ) );
  1145.     FailOSErr ( SetFPos (aRefNum, fsFromStart, kPICTHeaderSize+SIZEOF (Picture) ) );
  1146.     
  1147.         { The file is all set up to go.  We now want to replace the QuickDraw bottleneck
  1148.             and create the actual Picture data. }
  1149.     currDevice := GetGDevice;                                { save off current one. }
  1150.     GetPort (currPort);
  1151.     
  1152.         { If the write of the picture header fails, we want to dispose the handle allocated. }
  1153.     CatchFailures(fi, DeathWrite);
  1154.  
  1155.         { Move over to the offscreen port/device. }
  1156.     SetGDevice (fDrawingDevice);                            { set to ours for drawing in it. }
  1157.     picPort := GrafPtr (fDrawingPort);                    { the pointer to our port. }
  1158.     SetPort (picPort);                                            { set there to do pict saving. }
  1159.  
  1160.         { Save the pointer to the current CGrafProcs }
  1161.     oldProcs := thePort^.grafProcs;
  1162.     
  1163.         { Set our GrafProc record up to have the standard pieces. }
  1164.     SetStdCProcs(newGrafs);
  1165.     
  1166.         { Change the port to use those GrafProcs instead. }
  1167.     thePort^.grafProcs := @newGrafs;
  1168.     
  1169.         { We are in our offscreen port.  Change the GrafProc pointer for picture saving. }
  1170.     newGrafs.putPicProc := @PictWriter;
  1171.         
  1172.         { Tell PictWriter what file to write to, and start the pic size including the
  1173.             picture header.  Start all the pieces off the right way. }
  1174.     gPictRefNum := aRefNum;
  1175.     gPictSize := SIZEOF(Picture);
  1176.     gPictError := noErr;
  1177.     gPictHandle := NIL;
  1178.  
  1179.         { Actually open the picture and do the CopyBits in order to process the picture.
  1180.             The data will be written by PictWriter as it is called by QuickDraw. }
  1181.     WITH  picPort^  DO BEGIN
  1182.         gPictHandle := OpenPicture (portRect);
  1183.             ClipRect(portRect);            { Make it a happier picture. }
  1184.         
  1185.                 { copy all of the image to itself, in an open picture it saves the bits. }
  1186.             CopyBits (portBits, portBits, portRect, portRect, srcCopy, NIL);            
  1187.             
  1188.         ClosePicture;                        { the picture is created, and packed. }
  1189.     END;        { with picPort^ }
  1190.     
  1191.         { Now check for errors during the write operation.  The gPictError field will be
  1192.             nonzero if we failed during the operation. }
  1193.     FailOSErr (gPictError);
  1194.         
  1195.         { Move back to front of file and write the valid picture info to file. }
  1196.     FailOSErr ( SetFPos (aRefNum, fsFromStart, kPICTHeaderSize) );
  1197.     recSize := SIZEOF(Picture);
  1198.     FailOSErr (FSWrite(aRefNum, recSize, Ptr(gPictHandle^)));
  1199.  
  1200.         { Done saving the data of the picture itself.  Now set the GrafProcs back to normal. }
  1201.     thePort^.grafProcs := oldProcs;
  1202.     
  1203.         { Dispose the pict handle, we didn't actually make anything there. }
  1204.     KillPicture (gPictHandle);
  1205.     gPictHandle := NIL;
  1206.  
  1207.         { Set the drawing device back where it belongs, in case of error, we get right device. }
  1208.     SetGDevice (currDevice);                                    { set back to system for normal. }
  1209.     SetPort (currPort);
  1210.  
  1211.         { If we lived through it, clear error handler. }
  1212.     Success (fi);
  1213. END;            { TFracAppDocument.DoWrite }
  1214.  
  1215.  
  1216.     { The bottleneck routine to read the picture from the disk.  This will read the
  1217.         data required, and pass it along to the unpacker.  This makes it possible to
  1218.         avoid using any RAM for the actual reading part, as it is being played back
  1219.         into the offscreen device.  Error handling is somewhat tricky, since we 
  1220.         need to force the picture to finish, and there isn't a really good way to 
  1221.         do this.  The desired attempt here is to pass back a picture is finished 
  1222.         opcode ($00FF) so we can get back to our code to handle the error.  This is 
  1223.         better than no error recovery, but is not guaranteed to work. }
  1224. PROCEDURE  PictReader (dPointer: Ptr; nextHunk: Integer);
  1225.  
  1226. VAR    longHunk:    LongInt;
  1227.         I:                Integer;
  1228.  
  1229. BEGIN
  1230.     IF gPictError = noErr THEN  BEGIN
  1231.         longHunk := nextHunk;
  1232.         gPictError := FSRead(gPictRefNum, longHunk, dPointer);
  1233.     END
  1234.     ELSE            { handle the error situation by passing back $00FF as the data.? }
  1235.         FOR I := 1 to nextHunk  DO BEGIN
  1236.             IF ODD (I) THEN  dPointer^ := $00
  1237.             ELSE  dPointer^ := $FF;
  1238.             dPointer := PTR (ORD4(dPointer) + 1);
  1239.         END;
  1240. END;
  1241.  
  1242.  
  1243.     { Routine to read the data from the data fork of the file into our document so it
  1244.         can be displayed.  The quickdraw bottleneck will be replaced with the
  1245.         PictReader routine, making it read the data from the disk as the picture
  1246.         requests more data.  This obviates the need for an extra handle that is
  1247.         used to play back the picture.  This is done since that extra handle can
  1248.         be on the order of 100K, memory we may not have available. }
  1249. PROCEDURE TFracAppDocument.DoRead(aRefNum: INTEGER; rsrcExists,
  1250.                                                   forPrinting: BOOLEAN);  OVERRIDE;
  1251.  
  1252. VAR        recSize:            LongInt;
  1253.             fi:                     FailInfo;
  1254.             currDevice:        GDHandle;
  1255.             currPort:            GrafPtr;
  1256.             newGrafs:            CQDProcs;
  1257.             oldProcs:            QDProcsPtr;        { bug in include files, CGrafPort has QDProcs *** }
  1258.  
  1259.     PROCEDURE DeathRead (error: OSErr; message: LONGINT);
  1260.     BEGIN
  1261.         IF gPictHandle <> NIL THEN  KillPicture (gPictHandle);
  1262.         gPictHandle := NIL;
  1263.     END;
  1264.     
  1265. BEGIN
  1266.         { The file is open already, we just have to read the data out of it.  The first thing
  1267.             to read is the header we use to describe a fractal.  If we get an error
  1268.             here we need to split since we should always have at least a header.  The fractal
  1269.             header is the global state for the document.  We just read it into the record
  1270.             and use it from there. }
  1271.     FailOSErr ( SetFPos (aRefNum, fsFromStart, 0) );    { starts at first byte of file. }
  1272.     recSize := SIZEOF (FracRecord);                                   { size of header on fractal files. }
  1273.     FailOSErr ( FSRead (aRefNum, recSize, @fFracHeader) );
  1274.     
  1275.         { We have the header for the PICT file.  Now we need to be sure that it is a fractal
  1276.             document, and not something we can't use.  Check the header to be sure, and if
  1277.             not right, error out with a good alert message (using a standard MacApp errcode). }
  1278.     IF fFracHeader.fType <> kSignature  THEN  FailOSErr (errNotMyType);
  1279.  
  1280.         { We have the data from the header, go ahead and set up an offscreen world for this
  1281.             document, using the header rectangle. }
  1282.     BuildOffWorld (fFracHeader.calcRect);
  1283.     
  1284.         { Make sure the file position is right at the start of the picture in the file. }
  1285.     FailOSErr ( SetFPos (aRefNum, fsFromStart, kPICTHeaderSize) );
  1286.  
  1287.         { Allocate a small handle that will be used as the Pict handle for drawing from
  1288.             the disk.  This is just the picture header. }
  1289.     gPictHandle := PicHandle (NewHandle (SIZEOF(Picture)));
  1290.     FailNil (gPictHandle);
  1291.     
  1292.         { If the read of the picture header fails, we want to dispose the handle allocated. }
  1293.     CatchFailures(fi, DeathRead);
  1294.  
  1295.         { Tell PictReader what file to read from. }
  1296.     gPictRefNum := aRefNum;
  1297.     gPictError := noErr;
  1298.     
  1299.         { Now fill in the picture header itself, using the data from the disk. }
  1300.     recSize := SIZEOF(Picture);
  1301.     gPictError := FSRead(aRefNum, recSize, Ptr (gPictHandle^));
  1302.     FailOSErr (gPictError);
  1303.     
  1304.         { That is the only call we can╒t recover from immediately, the rest of the
  1305.             routine is not easy to recover from, so we won╒t go through DeathRead. }
  1306.     Success (fi);
  1307.     
  1308.         { The file position is right at the beginning of the picture data, so we can just
  1309.             install the bottleneck and call DrawPicture to fill our offscreen gDevice
  1310.             with the data that was saved.  Set to that port and gDevice for playback. }
  1311.     currDevice := GetGDevice;                        { save current to get back. }
  1312.     GetPort (currPort);
  1313.     
  1314.     SetGDevice (fDrawingDevice);
  1315.     SetPort (GrafPtr(fDrawingPort));
  1316.  
  1317.         { Save the pointer to the current CGrafProcs }
  1318.     oldProcs := thePort^.grafProcs;
  1319.     
  1320.         { Set our GrafProc record up to have the standard pieces. }
  1321.     SetStdCProcs(newGrafs);
  1322.     
  1323.         { Change the port to use those GrafProcs instead. }
  1324.     thePort^.grafProcs := @newGrafs;
  1325.     
  1326.         { We are in our offscreen port.  Change the GrafProc pointer for picture reading. }
  1327.     newGrafs.getPicProc := @PictReader;
  1328.         
  1329.         { Now we have the buffer and the offscreen port.  We can draw the picture that
  1330.             will be read out of the file into this port in order to init the port for later use in
  1331.             updating the window.  We are already set to draw in the offscreen port.  Do the
  1332.             DrawPicture to have PictReader read the data out of the file while it is being
  1333.             played into the offscreen Port. }
  1334.     DrawPicture(gPictHandle, gPictHandle^^.picFrame);
  1335.     
  1336.         { Done reading the data of the picture itself.  Now set the GrafProcs back to normal. }
  1337.     thePort^.grafProcs := oldProcs;
  1338.     
  1339.         { Bag the handle we made for playing back the picture. }
  1340.     KillPicture (gPictHandle);
  1341.     gPictHandle := NIL;
  1342.  
  1343.         { Set back to the normal drawing environment. }
  1344.     SetGDevice (currDevice);
  1345.     SetPort (currPort);
  1346.         
  1347.         { If we had an error while reading the data, we must error out. }
  1348.     FailOSErr (gPictError);
  1349. END;        { TFracAppDocument.DoRead }
  1350.  
  1351.  
  1352.     { This is typically used in a Revert case which is not really meaningful here, but
  1353.         the structure is the same so we use it anyway.  Frees the data associated with
  1354.         a document, that is strictly program data, not MacApp data. }
  1355. PROCEDURE TFracAppDocument.FreeData; OVERRIDE;
  1356.  
  1357. BEGIN
  1358.         { Kill the bits for the offscreen bitMap if they were allocated. }
  1359.     IF fBigBuff <> NIL  THEN  DisposPtr (fBigBuff);
  1360.         { Close the port: remove from portList, kill visRgn and clipRgn, kill the penPixPat
  1361.             and fill PixPat and back PixPat, kill PixMap handle, kill grafVars handle. }
  1362.     IF fDrawingPort <> NIL  THEN  BEGIN
  1363.         CloseCPort (fDrawingPort);
  1364.         DisposPtr (Ptr (fDrawingPort) );
  1365.     END;
  1366.         { DisposGDevice does: kills the ITable, kills Cursor expanded data and mask if
  1367.             nonzero, calls DisposPixMap if gdPMap is nonzero, then disposes the gDevice
  1368.             handle itself.  DisposPixMap kills the colorTable and the pixMap record. }
  1369.     IF fDrawingDevice <> NIL  THEN DisposGDevice (fDrawingDevice);
  1370. END;    { TFracAppDocument.FreeData }
  1371.  
  1372.             
  1373.     { Free method for the documents themselves.  We need to override so that we
  1374.         can throw away the data object that was read in from the disk if it exists. 
  1375.         Also chuck the gDevice and port used for the document data. }
  1376. PROCEDURE TFracAppDocument.Free; OVERRIDE;
  1377.  
  1378. BEGIN
  1379.     FreeData;
  1380.     
  1381.     INHERITED Free;
  1382. END;    { TFracAppDocument.Free }
  1383.  
  1384.  
  1385.     { The procedure to do the idle time processing in the document.  This will do the    
  1386.         entire fractal calculation so as to be able to do it in the background.  It
  1387.         does it one pixel at a time to avoid any hit on performance for the
  1388.         foreground application.  This is called in response to the DoIdle for the
  1389.         application.  The fIdlePriority is not set for this method, so it won't get
  1390.         time except when the application calls specifically.  It is done this way
  1391.         since otherwise the target chain would need to have each document in
  1392.         the list, which is not desireable for other event handling.  Notably the
  1393.         time keeper in here is not too accurate.  Each pixel takes less than a 
  1394.         tick to calculate, making it a bit tougher.  A way to make it more 
  1395.         accurate would be to figure out the maximum time for a full black
  1396.         document, and divide by the number of pixels in the screen and the
  1397.         number of loops.  That number (in microseconds) could be added each
  1398.         time through the calculation loop to give a more accurate timestamp.
  1399.         This would be wrong if the clock changes, so perhaps it should use the
  1400.         low memory TimeDBRA value as units instead.
  1401.         This is left as an exercise for the reader. }
  1402. PROCEDURE  TFracAppDocument.CalcCity;
  1403.  
  1404. CONST    M                = 100;                    { this decides what 'infinity' is.  If value less than this, loop. }
  1405.             K                = kNumColors;        { number of colors to choose from. Also iterations times.  This
  1406.                                                             is 195 to match the clut created for it. }
  1407.             BlackPen    = 255;                    { entry in our modified color table for black. }
  1408.  
  1409. VAR        currTime:            LongInt;        { temp var for time check. }
  1410.     
  1411.             x,y,x1,y1:            Extended;        { for interim values of current point. }
  1412.             Po,Qo:                Extended;
  1413.     
  1414.             kol:                    Integer;        { color we are currently on. }
  1415.             r:                        Extended;        { 'distance' from root. }
  1416.             currDevice:        GDHandle;        { current gDevice handle, so we can get back there. }
  1417.             currPort:            GrafPtr;
  1418.             drawRect:            Rect;            { for updating the screen as we calculate. }
  1419.  
  1420. BEGIN
  1421.         { Calculate the fractal as we go.  Do next pixel here, based on the state saved
  1422.             in the document object.  When done, the variables are updated to go to the
  1423.             next location to do.  It sets the pixel in the offscreen port to be whatever
  1424.             we calculate it to be.  The buffer will be copied to the screen at update time. 
  1425.             The global state is saved in the FracHeader record in the document object.
  1426.             That state is saved across the use of a document, so it will always be right. }
  1427.     
  1428.         { If we are done, or not started, we can split. }
  1429.     IF  fFracHeader.done THEN  Exit(CalcCity);
  1430.     
  1431.     currDevice := GetGDevice;                    { save off current one. }
  1432.     GetPort (currPort);
  1433.     
  1434.     SetGDevice (fDrawingDevice);                { set to ours for drawing in it. }
  1435.     SetPort (GrafPtr(fDrawingPort));        { draw in offscreen guy. }
  1436.  
  1437.     { Now do the calculation to determine the color of the pixel at the
  1438.         current location.  Uses the header saved state. }
  1439.  
  1440.     With  fFracHeader  DO BEGIN
  1441. (*        x := realMin + curCol * deltaP;                    { Use these for a Julia set calculation. } 
  1442.         y := imagMin + curRow * deltaQ;
  1443.         kol := 0;
  1444.         Po := -0.39054;
  1445.         Qo := 0.58679;
  1446. *)        
  1447.         Po := realMin + curCol * deltaP;                { next starting point  }
  1448.         Qo := imagMin + curRow * deltaQ;
  1449.         kol := 0; 
  1450.         x := 0;                                                        { Mandel set starts with 0 always. }
  1451.         y := 0;                                                        { For Julia set you start with previous number. }
  1452.     END;        { With }
  1453.     
  1454.     REPEAT
  1455.         { the following is for y = X^2 + C for imaginary numbers.  
  1456.         pt1 = x + yi,  C = Po + Qoi,  in pt2 := pt1^2 + C  }
  1457.         
  1458.         x1 := x*x - y*y + Po;
  1459.         y1 :=2*x*y + Qo;
  1460.         
  1461.         kol := kol + 1;
  1462.         x := x1;  y := y1;
  1463.         
  1464.         r := x1*x1 + y1*y1;
  1465.     UNTIL  (r > M) OR (kol > K);                { Until 'distance' > our infinity, or out of colors. }
  1466.     
  1467.     IF kol <= K  THEN                                { r must be > M. }
  1468.         fDrawingPort^.fgColor := kol            { set the color }
  1469.     ELSE                                                    { must be kol > K, ran out of colors. }
  1470.         fDrawingPort^.fgColor := BlackPen;
  1471.     
  1472.         { Move to the pixel we calculated for, then draw the pixel in right color.  This 
  1473.             could be done by setting the bytes in pixel map directly, since we own the
  1474.             PixMap and the buffer. }
  1475.     MoveTo (fFracHeader.curCol, fFracHeader.curRow);
  1476.     Line (0,0);                                            { draw that 'pixel' in the right color }
  1477.     
  1478.  
  1479.         { up the counters to the next pixel location to do. }
  1480.     WITH  fFracHeader  DO BEGIN
  1481.         curCol := curCol + 1;                                    { up the column count. }
  1482.         drawRect := thePort^.portRect;                    { in case we finished a line. }
  1483.         IF curCol >= plotWidth  THEN  BEGIN            { did we run off end of row? }
  1484.                 { Have the line just calculated drawn to window. }
  1485.             drawRect.top := curRow;
  1486.             drawRect.bottom := curRow+1;
  1487.             
  1488.             curCol := 0;                                            { start on the next row. }
  1489.             curRow := curRow + 1;                            { and up the counter of the next row to do. }
  1490.         END;  { start at next row. }
  1491.         
  1492.     END;  { with  fFracHeader }
  1493.  
  1494.         { Check if we are done, and if so, set the flag to stop calculations.  Set the
  1495.             elapsed time counter in the header. }
  1496.     IF fFracHeader.curRow >= fFracHeader.plotHeight  THEN  BEGIN
  1497.         fFracHeader.done := TRUE;
  1498.         GetDateTime(currTime);
  1499.         fFracHeader.elapsedTime := currTime - fStartTime;
  1500.     END;
  1501.     
  1502.     IF NOT EqualRect (thePort^.portRect, drawRect) THEN 
  1503.         fFracAppView.InvalidRect (drawRect);
  1504.  
  1505.     SetGDevice (currDevice);                                { set back to main for normal drawing. }
  1506.     SetPort (currPort);
  1507.  
  1508.     { Now we have changed another point in the document.  We need to mark it as
  1509.         changed so we can save the document. }
  1510.     fChangeCount := fChangeCount + 1;
  1511.     
  1512. END;        { TFracAppDocument.DoIdle } 
  1513.  
  1514.  
  1515. {-------------------------------  View  -------------------------------}
  1516.  
  1517.     { Initialize the view, basically set up the view object and clear the selection. }
  1518. PROCEDURE     TFracAppView.IFracAppView (itsDocument: TFracAppDocument;
  1519.         sizeOfView: Rect);
  1520.  
  1521. BEGIN
  1522.     fSelectionRect := gZeroRect;                    { no selection to start with. }
  1523.     fFracAppDocument := itsDocument;        { save off parent document for convenience. }
  1524.     
  1525.         { This view will be the full size of the screen since we have an offscreen
  1526.             bitMap as the view.  This will be clipped to fit the frame of the window.
  1527.             There is no parent view, and the horizontal and vertical are fixed.  The
  1528.             selection is to be shown, and is initially off. }
  1529.     IView(NIL, itsDocument, sizeOfView, sizeFixed, sizeFixed, TRUE, hlOff);
  1530.  
  1531. END;        { TFracAppView.IFracAppView }
  1532.  
  1533.  
  1534.     { We are going to map our full blown fractal into a sub universe with lesser
  1535.             colors.  We need to do a linear map of the indices we get to whatever
  1536.             is in the color table.  This will make zebra fractals.   This mapping
  1537.             is only done for devices that don╒t have enough colors to handle our
  1538.             normal fractals. }
  1539.         
  1540. FUNCTION SearchLips(VAR RGB: RGBColor;  VAR result: LongInt) : Boolean;
  1541.  
  1542. VAR        I:                        Integer;
  1543.             theIndex:            Integer;
  1544.             
  1545. BEGIN
  1546.     SearchLips := True;        { always say we hit. }
  1547.     
  1548.     { Start the map by finding the input color in the offscreen color table.  Using
  1549.         the global color table makes it easy to get the right colors. }
  1550.  
  1551.     FOR  I := 0 to gOurColors^^.ctSize  DO
  1552.         IF    (gOurColors^^.ctTable[I].rgb.red = RGB.red)  &
  1553.             (gOurColors^^.ctTable [I].rgb.green = RGB.green)  &  
  1554.             (gOurColors^^.ctTable [I].rgb.blue = RGB.blue)  THEN  BEGIN
  1555.                 theIndex := I;
  1556.                 Leave;         { Skip out of the loop, we found it. }
  1557.             END;        { Found index in offscreen table that is the input color. }
  1558.         
  1559.     { Now theIndex has the index value corresponding to the color we are trying to
  1560.         match.  Perform the MOD operation on it to get into the range needed for
  1561.         the current gDevice.  (If you do a DIV instead, you will get a picture without
  1562.         bands, but with the available colors mapped across the range nicely instead. 
  1563.         Multiple bands will be one color.)
  1564.         This MOD uses ctSize+1 since ctSize is a zero-based number, and the MOD
  1565.         needs to be for the number of actual colors available. }
  1566.     result := theIndex MOD (GetGDevice^^.gdPMap^^.pmTable^^.ctSize+1);
  1567.     
  1568. END;    { SearchLips }
  1569.  
  1570.  
  1571.     { Our routine to do the drawing of the fractal.  This is the display routine
  1572.         to take the data out of the offscreen buffer and whip it up to the window,
  1573.         as the current view. The fractal is full screen size, clips without
  1574.         scaling into the window.  It draws to any device connected to the machine
  1575.         and uses fast updates or search procs where needed, for the enough colors
  1576.         or not enough colors cases. }
  1577. PROCEDURE TFracAppView.Draw(area: Rect); OVERRIDE;    
  1578.  
  1579.     PROCEDURE  SetUp (curGar: TGarDevice);
  1580.     
  1581.     BEGIN
  1582.         curGar.SetUpColorMap;
  1583.     END;
  1584.  
  1585.     PROCEDURE  Remove (curGar: TGarDevice);
  1586.     
  1587.     BEGIN
  1588.         curGar.RemoveColorMap;
  1589.     END;
  1590.  
  1591.  
  1592. BEGIN
  1593.     { Make the ctSeed of both the source gDevice and destination match so there is no
  1594.         remapping of the colors that we are using.  The colors are close enough together
  1595.         that even 5 bit resolution provided for the mapping procedure is not sufficient. 
  1596.         The primary reason to do this is to make the blitting faster, since no color 
  1597.         mapping is done.  This gives the fastest updates possible, since color quickdraw
  1598.         does no color mapping, it just moves bytes.  This will only work properly if we
  1599.         have enough colors to do it. If we don╒t have enough, we will set up the color
  1600.         search procs to do the mapping required.  We will also watch the ctSeed of the
  1601.         device to see if we need to set our colors.  If the ctSeed changes, the color
  1602.         table changed, so we need to reset our colors useage.  In order to do this for
  1603.         each gDevice we will use our TList to make it easy.  We also save the current
  1604.         seed for each device before we update to them, then restore that seed after
  1605.         the drawing.  This makes the drawing fast still, but is a more mellow use of the
  1606.         ctSeeds. }
  1607.         
  1608.     gGarList.Each (SetUp);
  1609.  
  1610.     CopyBits ( GrafPtr(fFracAppDocument.fDrawingPort)^.portBits,
  1611.             thePort^.portBits, area, area, srcCopy, Nil);
  1612.  
  1613.     gGarList.Each (Remove);
  1614. END;        { TFracAppView.Draw }
  1615.  
  1616.  
  1617.     { Handle the menu choice for New Fractal out of the Fractal Menu.  This makes a new
  1618.         Fractal based on the current selection.  It does it by calling on the application
  1619.         object to make a new document.  The communication to the DoInitialState is
  1620.         through the global variables. }
  1621. FUNCTION TFracAppView.DoMenuCommand(aCmdNumber: CmdNumber): TCommand;  OVERRIDE;
  1622.  
  1623. VAR        oldDeltaP,
  1624.             oldDeltaQ,
  1625.             oldRealMin,
  1626.             oldImagMin:    Extended;                    { for figuring new fractal area. }
  1627.  
  1628. BEGIN
  1629.     { Assume that we have no command to return, since none of our commands currently
  1630.         change the document. }
  1631.     DoMenuCommand := gNoChanges;            { no command object returned. }
  1632.  
  1633.     { Case off on the various menus.  Currently we have the new fractal item.
  1634.         Any out of that list are handled by Mr. MacApp and we pass it on. }
  1635.     CASE  aCmdNumber  OF
  1636.     
  1637.         kNewFractal:
  1638.             { If the option chosen was the New Fractal item, then we need to start 
  1639.                 up a fresh one based on the selection rectangle.  This new fractal is 
  1640.                 based on parts of the old one, since it is
  1641.                 a zoom in operation.  We make a new document/window/view
  1642.                 as if it were a New operation.  We then change the fields we need to in
  1643.                 that document to make it start calculating based on the selection from
  1644.                 the current view. }
  1645.             
  1646.             BEGIN
  1647.                 { Make a new document and initialize it to the base state.  If we fail in 
  1648.                     opening it, we won't return here, the failure handler will kill it.  We
  1649.                     have nothing else to dispose of, so we don't make a CatchSignals here. 
  1650.                     This will come out of permanent memory.  The aCmdNumber is so that
  1651.                     the new document knows it came from a zoom in operation.  Since this
  1652.                     is somewhat funky, we communicate to the other part of the program
  1653.                     with the global variables.  If nonzero, the code that makes a
  1654.                     new document will know to use these numbers in order to do the zoom. 
  1655.                     This is less than completely desireable, but there are no good places
  1656.                     to override in order to get both the selection rectangle and the new
  1657.                     document objects. }
  1658.     
  1659.                     { The basic fractal has been set up.  We now need to change the calculation
  1660.                         area based on the current selection, in order to effect the zoom in. } 
  1661.                 WITH  fFracAppDocument.fFracHeader DO BEGIN
  1662.                     oldDeltaP := deltaP;                            { from SELF, the old document. }
  1663.                     oldDeltaQ := deltaQ;
  1664.                     oldRealMin := realMin;
  1665.                     oldImagMin := imagMin;
  1666.                 END;
  1667.  
  1668.                 { calculate new min/max for real and imaginary parts based on how far
  1669.                     into the old fractal plane we were.  This is an extended calculation
  1670.                     since our plane is in extendeds.  We get the new locations of min and
  1671.                     max, and save them off.  We reset the deltaP or Q with SetUpConstants,
  1672.                     in order to force a 1:1 ratio, but we need all sides to determine
  1673.                     which one to force.  }
  1674.                 gRealMin := oldRealMin + oldDeltaP * fSelectionRect.left;
  1675.                 gImagMin := oldImagMin + oldDeltaQ * fSelectionRect.top;
  1676.                 gRealMax := oldRealMin + oldDeltaP * fSelectionRect.right;
  1677.                 gImagMax := oldImagMin + oldDeltaQ * fSelectionRect.bottom;
  1678.  
  1679.                 gApplication.OpenNew (aCmdNumber);
  1680.             END;
  1681.             
  1682.         OTHERWISE
  1683.             DoMenuCommand := INHERITED  DoMenuCommand (aCmdNumber);    { next guy in chain. }
  1684.     END;        { CASE on aCmdNumber }
  1685.         
  1686. END;    { TFracAppView.DoMenuCommand }
  1687.  
  1688.  
  1689.     { Set up the New Fractal menus choice in Fractal Menu, based on selection. }
  1690. PROCEDURE TFracAppView.DoSetupMenus;  OVERRIDE;
  1691.  
  1692. BEGIN
  1693.     INHERITED DoSetupMenus;                            { Do mainline stuff first. }
  1694.     
  1695.         { If we have a non-zero selection, then we can enable the menu item to use
  1696.             it as the new fractal dimensions for this document. }
  1697.     Enable (kNewFractal, NOT EmptyRect (fSelectionRect));
  1698. END;    { TFracAppView.DoSetupMenus }
  1699.  
  1700.  
  1701.     { The way to handle mouse events in the content region of the view.  This will
  1702.         pass back the command object to handle tracking the mouse and creating a
  1703.         new selection in preparation for making a new fractal. }
  1704. FUNCTION  TFracAppView.DoMouseCommand(VAR downLocalPoint: Point; 
  1705.                         VAR info: EventInfo;  VAR hysteresis: Point): TCommand;    OVERRIDE;
  1706.                         
  1707. VAR        tracker:    TAreaSelector;
  1708.         
  1709. BEGIN
  1710.     New(tracker);                                        { make a new command object. }
  1711.     FailNIL(tracker);                                    { no memory, trash out. }
  1712.     tracker.IAreaSelector(SELF, downLocalPoint); { Initialize the command object. }
  1713.     DoMouseCommand := tracker;                { return it for later use. }
  1714. END;        { TFracAppView.DoMouseCommand }
  1715.  
  1716.  
  1717.     { Highlight the current selection rectangle if there is one.  This is drawn in scrCopy
  1718.         mode to make it stand out better when it is a final selection.  XOR is used for
  1719.         the rubberband, until mouseUp. }
  1720. PROCEDURE TFracAppView.DoHighLightSelection(fromHL, toHL: HLState); OVERRIDE;
  1721.  
  1722. VAR        selPatHandle:    PatHandle;
  1723.     
  1724. BEGIN
  1725.     IF toHL = hlOn THEN  BEGIN
  1726.         selPatHandle := GetPattern(kSelPattern);    { get the pattern we use. }
  1727.         IF selPatHandle <> NIL  THEN                         { If pattern available, use it. }
  1728.             PenPat (selPatHandle^^);                            { set pen pattern to our selection kind. }
  1729.         PenMode(srcCopy);                                        { copy mode on pattern selection. }
  1730.         
  1731.             { We have a selection, so go ahead and draw the selection rectangle. }
  1732.         FrameRect (fSelectionRect);                        { outline the frame of selection. }
  1733.     END;        { highlight turned on. }
  1734.     
  1735.         { Turning off the highlight, we need to remove the traces of the selection.
  1736.             To do this, redraw that rectangle. }
  1737.     IF toHL = hlOff THEN   Draw (fSelectionRect);    { ReDraw it to clear selection. }
  1738. END;        { TFracAppView.DoHighlightSelection }
  1739.  
  1740.  
  1741. {-------------------------------  Command  -------------------------------}
  1742.  
  1743.     { Initialize the selector object itself.  Sets up the normal fields. }
  1744. PROCEDURE TAreaSelector.IAreaSelector(ownerView: TFracAppView; startPt: Point);
  1745.  
  1746. BEGIN
  1747.     ICommand(cMouseCommand);                    { initialize normal parts of command }
  1748.  
  1749.     fCausesChange := FALSE;                            { just selection, not changing document. }
  1750.     fCanUndo := FALSE;                                    { therefore, no Undo of no change. }
  1751.     fConstrainsMouse := TRUE;                        { do the constrain to match to screen. }
  1752.     fOwnerView := ownerView;                        { save the view for use in tracking. }
  1753. END;    { TAreaSelector.IAreaSelector }
  1754.  
  1755.  
  1756.     { Track the mouse while the button is down.  This is overridden so we can leave
  1757.         the command object as not having changed, ie. so we can pass back the 
  1758.         gNoChanges as the last step since this is not an undoable operation.  It 
  1759.         doesn't change the view, so we don't need to DoIt or Commit. }
  1760. FUNCTION  TAreaSelector.TrackMouse(aTrackPhase: TrackPhase;
  1761.                         VAR anchorPoint, previousPoint, nextPoint: Point;
  1762.                         mouseDidMove: BOOLEAN): TCommand;  OVERRIDE;
  1763.  
  1764. VAR        selPatHandle:        PatHandle;
  1765.  
  1766. BEGIN
  1767.     TrackMouse := SELF;                                { Assume we are not in release phase. }
  1768.     
  1769.     CASE aTrackPhase OF
  1770.         trackPress:
  1771.         BEGIN
  1772.             fOwnerView.DoHighLightSelection (hlOn, hlOff);        { turn off old selection if any. }
  1773.             fOwnerView.fSelectionRect := gZeroRect;                { clear rect, there isn't one. }
  1774.         END;
  1775.  
  1776.         trackRelease:
  1777.         BEGIN
  1778.             Pt2Rect(anchorPoint, nextPoint, fOwnerView.fSelectionRect);
  1779.             fOwnerView.DoHighlightSelection (hlOff, hlOn);        { leave on selection. }
  1780.             TrackMouse := gNoChanges;
  1781.         END;
  1782.     END;    { Case on aTrackPhase }
  1783. END;    { TAreaSelector.TrackMouse }
  1784.  
  1785.  
  1786.     { Track the mouse giving the feedback of a different rectangle kind.  This is so
  1787.         we can use the selection pattern to give a preferred rectangle.  The selection
  1788.         pattern comes out of temporary memory so as to not fail needlessly. }
  1789. PROCEDURE TAreaSelector.TrackFeedback(anchorPoint, nextPoint: Point;
  1790.                         turnItOn, mouseDidMove: BOOLEAN);  OVERRIDE;
  1791.                         
  1792. VAR        selBoy:                    Rect;
  1793.             selPatHandle:        PatHandle;
  1794.             
  1795. BEGIN
  1796.     IF mouseDidMove THEN
  1797.         BEGIN {the pen is already in patXOR mode, black, one wide}
  1798.             selPatHandle := GetPattern(kSelPattern);    { get the pattern we use. }
  1799.             IF selPatHandle <> NIL  THEN                        { use our pattern if available. }
  1800.                 PenPat (selPatHandle^^);                            { set pen pattern to our selection kind. }
  1801.         
  1802.             Pt2Rect(anchorPoint, nextPoint, selBoy);
  1803.             FrameRect(selBoy);
  1804.         END;
  1805. END;    { TAreaSelector.TrackFeedback }
  1806.  
  1807.  
  1808.     { Constrain the mouse to a rectangle that is the same proportion as the screen, so
  1809.         we can make the selection match better without having to guess at the length
  1810.         or width, or scaling the chosen rect to fit the screen.  Small piece chosen will
  1811.         blow up to fit easily.  This will make it easier to choose a selection that 
  1812.         gives a 1:1 aspect ratio.  This also chooses which direction the mouse has
  1813.         moved, deciding which is larger in order to decide the direction to constrain. }
  1814. PROCEDURE TAreaSelector.TrackConstrain(anchorPoint, previousPoint: Point; 
  1815.                             VAR nextPoint: Point);  OVERRIDE;
  1816.             
  1817. VAR        newWidth, newHeight:        LongInt;
  1818.             mouseRatio, plotRatio:        Real;
  1819.             constrainRect:                    Rect;
  1820.     
  1821.     PROCEDURE  ChangeWidth;
  1822.     BEGIN
  1823.         WITH  fOwnerView.fFracAppDocument.fFracHeader  DO BEGIN
  1824.                 { Get the new width as a positive number, a displacement that is constrained. }
  1825.             newWidth := ABS (nextPoint.v - anchorPoint.v) * plotWidth DIV plotHeight;
  1826.                 { Decide which quadrant we are in, moving the right direction. }
  1827.             IF nextPoint.h < anchorPoint.h  THEN newWidth := -newWidth;
  1828.                 { Actually change the final point to pass back. }
  1829.             nextPoint.h := anchorPoint.h + newWidth;                        { add offset to get new pt. }                
  1830.         END;
  1831.     END;
  1832.     
  1833.     PROCEDURE  ChangeHeight;
  1834.     BEGIN
  1835.         WITH  fOwnerView.fFracAppDocument.fFracHeader  DO BEGIN
  1836.             newHeight := ABS (nextPoint.h - anchorPoint.h) * plotHeight DIV plotWidth;
  1837.             IF nextPoint.v < anchorPoint.v  THEN newHeight := -newHeight;
  1838.             nextPoint.v := anchorPoint.v + newHeight;                    { add offset to get new pt. }
  1839.         END;
  1840.     END;
  1841.     
  1842.     PROCEDURE  PinPoint;                    { Pin the rectangle to the edge of the document. }
  1843.     BEGIN
  1844.         WITH  fOwnerView.fFracAppDocument.fFracHeader  DO BEGIN
  1845.             SetRect(constrainRect, 0, 0, plotWidth, plotHeight);
  1846.             nextPoint := Point (PinRect(constrainRect, nextPoint));
  1847.         END;
  1848.     END;
  1849.     
  1850. BEGIN
  1851.     WITH  fOwnerView.fFracAppDocument.fFracHeader  DO BEGIN
  1852.         mouseRatio := ABS ((nextPoint.h - anchorPoint.h)/(nextPoint.v - anchorPoint.v));
  1853.         plotRatio := plotWidth/plotHeight;
  1854.         
  1855.             { The deltaX, deltaY can be thought of as a rect too.  If the ratio of sides on
  1856.                 that rect (width/height) is greater than the ratio of width/height of the
  1857.                 plot rectangle, then we need to grow the height of the rect.  If it is less,
  1858.                 we need to grow the width.  This is a ratio of sides to decide which way
  1859.                 to grow.  We grow to make the new rect still touch the mouse position.
  1860.                 It can be thought of as the rectangle being thicker than tall wanting to 
  1861.                 grow the tall part in a constrained way, and the corollary for the width. }
  1862.         IF mouseRatio > plotRatio  THEN BEGIN        { constrain height to new value. }
  1863.                 ChangeHeight;
  1864.                 PinPoint;
  1865.                 ChangeWidth;
  1866.             END
  1867.         ELSE        BEGIN        { constrain width to new value. }
  1868.                 ChangeWidth;
  1869.                 PinPoint;
  1870.                 ChangeHeight;
  1871.             END;
  1872.     END;    { With }
  1873. END;    { TAreaSelector.TrackConstrain }
  1874.  
  1875.  
  1876. {-------------------------------  GarDevice  -------------------------------}
  1877.  
  1878.     { A routine to initialize all the GarDevice objects desired for the system.  This
  1879.         takes the info passed in and sets up the GarDevice object the right way.  It
  1880.         also adds it to the gGarList TList. }
  1881.  
  1882. PROCEDURE  TGarDevice.IGarDevice (OwnerGDevice: GDHandle);
  1883.  
  1884. BEGIN
  1885.     fDevice := OwnerGDevice;                        { Init the fields to keep track of device. }
  1886.     fColorTable := NIL;
  1887.     fOldSeed := 0;
  1888.     gGarList.InsertLast (SELF);                    { Add this guy into the TList. }
  1889. END;
  1890.  
  1891.  
  1892.     { A routine to pound the color table to be ours if there are enough colors available.
  1893.         This will use the clut resource used for documents to set up the color table to
  1894.         our nice prechosen set of colors.  If there aren't enough colors available to do
  1895.         the full job, we leave it alone, and let the Palette Manager do his thing.   Error
  1896.         handling here is done in a simple fashion.  If we have errors, we leave the 
  1897.         fColorTable = NIL so that we don╒t change the colors.  We won╒t pass back
  1898.         an alert since it would be confusing and we don╒t have a good thing to say.
  1899.         On the SetEntries and protection, we
  1900.         don╒t check errors since they can only cause strange colors to be displayed,
  1901.         not a crash, and it is unclear how to handle errors there without being
  1902.         too obtuse. }
  1903. PROCEDURE TGarDevice.PoundColors;
  1904.  
  1905. VAR        offColorTable:        CTabHandle;
  1906.             I:                            Integer;
  1907.             Erry:                        OSErr;
  1908.             currDevice:            GDHandle;
  1909.             LocalColors:            CTabHandle;
  1910.     
  1911. BEGIN
  1912.     IF fColorTable <> NIL THEN  DisposCTable (fColorTable);
  1913.     fColorTable := NIL;
  1914.     
  1915.     LocalColors := fDevice^^.gdPMap^^.pmTable;            { for convenient use. }
  1916.     
  1917.         { If we have enough colors on this device, we can do our strangeness. }
  1918.     IF LocalColors^^.ctSize > kNumColors THEN BEGIN
  1919.         
  1920.             { Save a copy of the system color table in our object.  This table will be used when
  1921.                 we need to restore the color table upon exit.  If there aren't enough colors for 
  1922.                 our funny business we have the handle to NIL to imply that we didn't change it. }
  1923.         offColorTable := LocalColors;
  1924.         Erry := HandToHand (Handle (offColorTable));    { copy it so we don╒t use actual system handle. }
  1925.         IF Erry <> noErr THEN Exit (PoundColors);            { If it couldn╒t be done, exit with NIL. }
  1926.         
  1927.         fColorTable := offColorTable;                            { Save it off into the GarDevice object. }
  1928.         
  1929.         currDevice := GetGDevice;                                { Save current state. }
  1930.         
  1931.             { Set to the current GarDevice }
  1932.         SetGDevice(fDevice);
  1933.     
  1934.             { We now need to set up the color table in the system to be the way we want it,
  1935.                 so we can avoid problems with programs that use the Palette Manager.  This
  1936.                 involves reserving all of the colors we use so that they won╒t be used by any
  1937.                 other program (including the Menu Manager for the Apple), and protecting the
  1938.                 entries so they cannot be changed by the Palette Manager or other programs. 
  1939.                 Before doing that we need to save the state of the world so we can put it 
  1940.                 back when done or switched out.  We will use the global color table that 
  1941.                 matches all the documents. }
  1942.             { SetEntries is zero based, so we actually set kNumColors+1, and so starting
  1943.                 with 0 makes us do the right number of entries.  The CSpecArray we have
  1944.                 to pass is also conveniently starting at zero, which is why it is done this way. }
  1945.         SetEntries(0, kNumColors+kNumPalette, gOurColors^^.ctTable);
  1946.         
  1947.         For I := 1 to kNumColors  DO
  1948.             ReserveEntry (I, TRUE);        { No one should use our animating colors. }
  1949.     
  1950.             { We changed the ctSeed now, so we need to save it off, so we know when the
  1951.                 color table has changed for real. }
  1952.         fOldSeed := LocalColors^^.ctSeed;
  1953.         
  1954.             { Set back to where we started from. }
  1955.         SetGDevice(currDevice);
  1956.  
  1957.             { If we changed the main device, redraw the menu bar to make colors right. }
  1958.         IF fDevice = GetMainDevice  THEN DrawMenuBar;
  1959.         
  1960.     END;    { enough colors to make it worthwhile. }
  1961. END;    { PoundColors }
  1962.  
  1963.  
  1964.     { When we leave we need to restore the color table to its pristine form.  This is 
  1965.         only done if we have changed the color table to our fancy set of colors.  We use
  1966.         the fColorTable handle as a boolean as well.  If it is NIL, we didn't have a 
  1967.         saved color table to restore.  This routine will be called for each GarDevice in
  1968.         the TList.  Any errors that are run into here are ignored, since it is too late
  1969.         to fix them.  They will not be catastrophic, and in the worst case will munge
  1970.         up the colors in the color table as we exit.  Since we have the temporary
  1971.         memory available we should never have any error here in the first place. }
  1972. PROCEDURE TGarDevice.UnPoundColors;
  1973.  
  1974. VAR        I:                            Integer;
  1975.             currDevice:            GDHandle;
  1976.     
  1977. BEGIN
  1978.         { If we still have enough colors on this gDevice, we can restore the color table.  }
  1979.     IF fColorTable <> NIL THEN BEGIN
  1980.     
  1981.             { Save off the current device state. }
  1982.         currDevice := GetGDevice;
  1983.         
  1984.             { Set us to be on that gDevice. }
  1985.         SetGDevice(fDevice);
  1986.         
  1987.             { Set the color table back to what it was before we came in and took over.  This
  1988.                 means setting the colors back to those we started with, and setting the
  1989.                 useage back to normal. }
  1990.         For I := 1 to kNumColors  DO
  1991.             ReserveEntry (I, FALSE);
  1992.  
  1993.             { and reset the color table to the colors that the system was using. }
  1994.         SetEntries(0, kNumColors+kNumPalette, fColorTable^^.ctTable);
  1995.         
  1996.             { Set back to the device we started on. }
  1997.         SetGDevice(currDevice);
  1998.  
  1999.     END;    { Had some colors to restore. }
  2000. END;    { UnPoundColors }
  2001.  
  2002.  
  2003.     { If there are enough colors on this GarDevice and the check mark was set, we must
  2004.         rotate them colors around.  If this GarDevice has enough colors we do it, 
  2005.         otherwise we skip it. If we have an error while trying to do this, we want to 
  2006.         just skip it, since it is noncritical to the program.  If we use the normal
  2007.         error handler we will end up giving an alert each time this is called which
  2008.         is too many.  In addition, we will use temporary memory for the allocations,
  2009.         so it is virtually impossible for the memory allocations to fail here.  The
  2010.         memory used is returned at the end of the routine, so it is temporary. }
  2011. PROCEDURE TGarDevice.RotateColors;
  2012.  
  2013. VAR        newCTable:            CTabHandle;
  2014.             lastCSpec:            ColorSpec;
  2015.             I:                            Integer;
  2016.             Erry:                        OSErr;
  2017.             currDevice:            GDHandle;
  2018.             LocalColors:            CTabHandle;
  2019.             fi:                            FailInfo;
  2020.             
  2021.         { If we had a memory problem during Rotation we need to exit, setting things
  2022.             back to normal.  We also clear the Rotation flag, to avoid being called again.
  2023.             In order to give a better error message in the alert, we pass our own string
  2024.             description as the message field.  This will look up the STR# in our resource
  2025.             list to use as the message. }
  2026.     PROCEDURE DeathRotate (error: OSErr; message: LONGINT);
  2027.     
  2028.     BEGIN
  2029.         IF newCTable <> NIL  THEN  DisposCTable(newCTable);
  2030.         SetGDevice (currDevice);                        { Set device back to main, just in case. }
  2031.         gRotateOn := FALSE;                                { Turn rotation off. }
  2032.         Failure (error, kBadRotate);                    { give the right error string. }
  2033.     END;
  2034.  
  2035.  
  2036. BEGIN
  2037.         { Get handle to color table so we don't have to use With statements, but still have
  2038.             it dereferenced more effectively. }
  2039.     LocalColors := fDevice^^.gdPMap^^.pmTable;
  2040.     
  2041.         { Check the seed for this device and be sure it matches what we have saved. 
  2042.             When the color depth is changed with an FKEY or by some other code behind
  2043.             our back, we get the chance to rotate the colors around, before we handle 
  2044.             the update event.  In any case, for safety, we want to avoid rotating if the 
  2045.             color table has changed since we last looked at it. }
  2046.     IF fOldSeed <> LocalColors^^.ctSeed THEN  Exit (RotateColors);
  2047.     
  2048.         { See if we have enough colors on the current device to do, do nothing if not. }
  2049.     IF LocalColors^^.ctSize > kNumColors  THEN  BEGIN
  2050.     
  2051.         currDevice := GetGDevice;                                            { Save current gDevice. }
  2052.         SetGDevice(fDevice);                                                    { Set to our current Gar.}
  2053.  
  2054.         CatchFailures(fi, DeathRotate);                                    { any failures, must be cleaned up. }
  2055.     
  2056.             { Get this device╒s color table and copy it temporarily.  If not enough memory
  2057.                 just skip out since it is not catastrophic. }
  2058.         newCTable := LocalColors;                                            { Handle to this CTable. }
  2059.         Erry := HandToHand (Handle(newCTable));                        { Make copy of it. }
  2060.         IF Erry <> noErr THEN  newCTable := NIL;                        { on error, we should skip dispose. }
  2061.         FailOSErr (Erry);                                                            { If we had an error, exit. }
  2062.                 
  2063.             { Move the colors we are going to use down to the zero position so it is easy
  2064.                 to use SetEntries.  We are trashing the color table, but it is a copy.  }
  2065.         lastCSpec := newCTable^^.ctTable[1];                            { pull first one off. }
  2066.         BlockMove (@newCTable^^.ctTable[2], @newCTable^^.ctTable[1], 
  2067.                 (kNumColors) * SIZEOF (ColorSpec) );                    { copy all one entry down. }
  2068.         newCTable^^.ctTable[kNumColors] := lastCSpec;            { put last color back on front. }
  2069.         
  2070.             { Change the color table to new position. }
  2071.         SetEntries (0, kNumColors, newCTable^^.ctTable);    
  2072.         FailOSErr (QDError);
  2073.         
  2074.         DisposCTable(newCTable);
  2075.         
  2076.             { Save the new seed, since we changed it. }
  2077.         fOldSeed := LocalColors^^.ctSeed;
  2078.  
  2079.         SetGDevice(currDevice);                                        { Set back to start. }
  2080.         
  2081.         Success (fi);
  2082.     END;    { enough colors on device to rotate. }
  2083. END;    { RotateColors }
  2084.  
  2085.  
  2086.     { The routine to set up for the CopyBits by changing the color table if needed, and
  2087.         to make the ctSeeds match for fast updates where possible, and to install the
  2088.         color search procs on devices where we don't have enough color.  Called for 
  2089.         each GarDevice in our TList.  Save off the devices' seed value so we can put
  2090.         it back after our update. }
  2091. PROCEDURE  TGarDevice.SetUpColorMap;
  2092.  
  2093. VAR        currDevice:        GDHandle;
  2094.             LocalColors:        CTabHandle;
  2095.     
  2096. BEGIN
  2097.     currDevice := GetGDevice;                                { Save current device. }
  2098.     SetGDevice (fDevice);                                        { Set to this gDevice. }
  2099.     
  2100.     LocalColors := fDevice^^.gdPMap^^.pmTable;        { Handle to color table. }
  2101.     
  2102.         { Check if the color environment changed on this device, and if so, pound
  2103.             our colors into place there.  This handles changes in depth when we are
  2104.             not switched out (FKEYs). }
  2105.     IF (fOldSeed <> LocalColors^^.ctSeed) AND NOT gBackGround  THEN  
  2106.         PoundColors;
  2107.     
  2108.         { During the update process we want to be as fast as possible, so for the 
  2109.             CopyBits we want the ctSeeds to match.  This will save the current seed
  2110.             and restore it when we are done CopyBitsing. }
  2111.     fDrawSeed := LocalColors^^.ctSeed;
  2112.  
  2113.         { If we have enough colors on this device, then make the seeds match so we
  2114.             have a high speed update.  If there isn't enough colors, then set up the search
  2115.             proc so we get the happy MOD color mapping on this device.  If we have enough
  2116.             colors do the slightly naughty operation of changing the devices' seed to 
  2117.             match all the documents.  This is only done in order to make fast updates on
  2118.             devices that have enough colors. }
  2119.     IF  LocalColors^^.ctSize > kNumColors  THEN
  2120.         LocalColors^^.ctSeed := gOurColors^^.ctSeed
  2121.     ELSE
  2122.         AddSearch (@SearchLips);                            { Add our search proc to that device. }
  2123.         
  2124.     SetGDevice (currDevice);                                    { Set back to starting device. }
  2125. END;    { SetUpColorMap }
  2126.  
  2127.  
  2128.     { A routine applied to each GarDevice to remove any color search procs we installed,
  2129.         and restore any seeds we may have changed in order to be speedy. }
  2130. PROCEDURE  TGarDevice.RemoveColorMap;
  2131.     
  2132. VAR        currDevice:        GDHandle;
  2133.     
  2134. BEGIN
  2135.     currDevice := GetGDevice;                                { Save current device. }
  2136.     SetGDevice (fDevice);                                        { Set to this gDevice. }
  2137.     
  2138.         { If there were, restore the seed on the device to what color QD wants it to be. 
  2139.             If there were not enough colors on this device, remove the color mapping proc. }
  2140.     WITH  fDevice^^.gdPMap^^.pmTable^^  DO 
  2141.         IF ctSize > kNumColors  THEN
  2142.             ctSeed := fDrawSeed                                { restore the seed value. }
  2143.         ELSE
  2144.             DelSearch (@SearchLips);                            { Remove our special color mapper. }
  2145.  
  2146.     SetGDevice (currDevice);                                    { Set back to starting device. }
  2147. END;    { RemoveColorMap }
  2148.  
  2149.  
  2150. {$POP}        { Restore the compiler state. }
  2151.