• 20030602: Implemented the MLJpegSource abstract class, a C++ wrapper around the "source manager"
  concept of the IJG library.  MLJpegFileSource, a concrete subclass of MLJpegSource, provides a
  buffered source that can be constructed from an MLPath object.  Why?  After all, the IJG library already
  provides a default source manager based on std::FILE.  Yes, but we'd rather avoid using ANSI file functions
  on Mac OS 9 -- for example, std::fopen() can become ambiguous on OS 9 if two mounted volumes have the same name.

• 20030603: Added rudimentary (but adequate) Exif support to the IJG library-based MLJpegDecompressor class.
  The IJG library can actually gather "APP1" markers found in the JPEG file header into a linked list
  (see jpeg_save_markers()) so it already does half of the job for us.  The other half is just a matter of
  parsing the Exif header and the TIFF-style IFD (Image File Description) directories.  We currently recognize
  only a handful of Exif tags, collect these in a MLPropertyListDict dictionary, and discard all the others.
  Yet the basic parsing infrastructure is there and can be easily extended to support more tags.

• 20030603: Implemented the compression side of the IJG library, in the form of a new MLJpegCompressor class,
  along with the ancillary MLJpegDestination and MLJpegFileDestination helper classes.
  Tried substituting a MLJpegCompressor object for a MLGraphicsExporter in DigitalEmotionPhoto::MakeThumbnail(),
  and everything seems to work peachy-keen.  The resulting JPEG files are even significantly smaller than the
  one created by QuickTime, using comparable quality settings (codecHighQuality = 75%).

• 20030604: Small revolution in the framework: the MLTime data type, defined in "MLTypes.h" and used to
  represents times and durations, is now a double-precision floating point rather than an unsigned 32-bit
  integer.  Also, on the Mac OS, the MLDispatcher::GetTime() function is now implemented using
  Microseconds() rather than TickCount(), which has a very coarse resolution (1/60 of a second).
  All MLTime values are still expressed in milliseconds.
  On Mac OS 9, the Microseconds() clock and the TickCount() clock (used by the Event Manager to timestamp
  events) can go out of sync, so MLDispatcher now automatically adjusts the mWhen field of MLMessage
  records to compensate for the drift, if any.

  The JPEG compressor and decompressor classes can now yield the cpu to other cooperative threads during
  lengthy operations, by default once every 10 ms (this can be changed using MLJpegCompressor::SetTimeSlice()).
  This feature is controlled by the ML_JPG_TIME_SLICES compile-time switch.
  The default 10 ms time slice appears to be small enough for the importer thread to run quietly in the
  background while keeping the main thread very responsive to user interaction.
  This change addresses [entry #3 of v2 specs].

• 20030605: Integrated latest mods from Andrea (bilinear filter).  "Bilinear" can now be chosen from the
  "Interpolation Method" pop-up menu in the Print Options dialog.  This filter sits between nearest neighbor
  and bicubic, both quality- and speed-wise.

  New build posted to my website:

			<http://www.merzwaren.com/bin/digital-emotion-20030605.sit>

• 20030605: Ideas for making DE even more responsive while the importer thread is running:

	1.	The time slice used by JPEG compressors/decompressors should depend on the thread they're
		running in: a small slice (say, 10 to 50 ms) when running in the importer thread, but a big
		slice, or no slice at all (just hog the cpu until done) when running in the main thread.
		Added the static function MLMacThread::IsMainThread() for this purpose.

	2.	Right now, the main thread yields the cpu to other threads (in MLDispatcher::DispatchMacNullMessage)
		as often as possible.  This may not always be a good idea.  Added a MLDispatcher::SetYieldThrottle()
		method that allows you to specify the minimum period between successive yields.

	3.	MLDispatcher::DispatchMacNullMessage() should call DispatchCursor() *first*, then yield the cpu,
		not the other way around.

  With these changes, the application feels more responsive, although that's just my subjective feeling.

• 20030606: Added MLPath::DeleteFolder().  This method deletes the (empty) directory pointed to by the
  given path.  Once the directory is successfully deleted, the MLPath object points to a missing node
  in the file system tree where the directory used to be, and can be immediately re-used to create a file
  or another directory with the same name.

• 20030606: Implemented the FORMAT button. [entry #10 of v2 specs]
  Clicking this button causes DE to enter a card-erasing mode: all files and directories on volumes mounted
  when this mode is in effect are deleted recursively (but the volumes themselves are not actually formatted).
  When in card-erasing mode, a modal window with a bright yellow background prevents any other activities --
  closing this window exits the mode.

• 20030609: I'm sick of having to click DATABASE and Find All every time I open an album, so I've added a
  new preference key (kKeyPreferencesFindAllOnOpen == "find_all_on_open") that lets me do that automatically.
  The default value is false (as per specs).

• 20030611: Minor cleanup of MLException and related classes: eliminated some unnecessary constructors and
  assignment operators.  Replaced several occurrences of std::sprintf with the safer std::snprintf.

• 20030613: Started working with Leonardo Pellegrini on Windows port of Digital Emotion.
  Added x86 targets to project file and attempted to recompile the ML framework.  Several minor tweaks
  were needed to get most files to compile correctly.  Over the next few days, we're going to need to perform
  major adjustments to some classes (e.g., MLDate), and think about designing and implementing Windows
  counterparts to some Mac-only classes (e.g., the various system controls: buttons, sliders, clocks, etc.)

• 20030616: WARNING: Important change to MLDate, which has the potential for major impact on existing code:
  the default constructor used to initialize the date to the current date (with a potentially expensive and
  unneeded system call); to initialize a date to its undefined/invalid state, you had to use the MLDate(SInt64)
  constructor, passing an explicit zero.  This was a design mistake that I'm trying to undo.  From now on,
  the default MLDate constructor initializes the date to its undefined/invalid state.  To initialize a date
  to the current date/time, you now need to use a static accessor, GetCurrentDate().  In other words, uses
  like this:
  
  			MLDate		currentDate ;

  must be replaced with:
  
  			MLDate		currentDate ( MLDate :: GetCurrentDate ( ) ) ;

• 20030616: Mac (Carbon) implementation of volume-mounted and modifiers-changed messages at the framework level,
  as two new kinds of MLMessage (MLMessage::kVolumeMounted and MLMessage::kModifiersChanged).  The kVolumeMounted
  message is generated when a volume is mounted -- the message parameter is a pointer to an MLPath object
  representing the newly mounted volume.  The kModifiersChanged message is generated when some keyboard
  modifier key (like shift or control) is pressed or raised.
  The same messages will have to be generated on Windows, although the underlying implementation will
  of course be different.  Ditto for Mac (Classic), if we still care to support non-Carbon Mac targets.

• 20030617: Tried recompiling the project with the prerelease C++ compiler from CodeWarrior Pro 9 (the forthcoming
  major revision to everyone's favorite Integrated Development Environment).  Everything compiles mostly fine,
  except one thing: local types can no longer be used as template arguments, so code like the following (from
  MLListView.cpp) will have to be changed:

	void MLStringList :: SortLineStarts ( )
	{
		class ListComp
		{
			const char *	mTextBase ;

		public :
			ListComp ( const char * inTextBase ) :
				mTextBase ( inTextBase )
				{ }

			bool operator ( ) ( UInt32 inOffset1, UInt32 inOffset2 ) const
				{ return ( :: macstrcmp ( mTextBase + inOffset1, mTextBase + inOffset2 ) < 0 ) ; }
		} ;

		std :: sort ( mStartOffsets . Begin ( ), mStartOffsets . End ( ) - 1, ListComp ( mText . CStr ( ) ) ) ;
	}

• 20030617: Icons and frames can now be PNG files -- previously they could only be pictures in Targa
  format.  The plan is to switch to PNG-only, at least for the Windows version, since we want to avoid
  using QuickTime, and we have libpng already integrated in the framework, but no equivalent for Targa
  files.  Also, although Targa and PNG are both lossless formats that support full alpha channels, PNG
  is much more space-efficient than Targa.  PNG is so neat -- gotta love it!

  JPEG thumbnails of icons and frames are now generated using the IJG library.

  In order to avoid relying on the QuickTime-specific data type "CodecQ" to express the quality/size tradeoff,
  MLJpegCompressor::SetQuality() now takes a float parameter in the range 0 (lowest quality -- maximum compression)
  to 1 (highest quality -- lossless or near-lossless compression).

• 20030623: Called Sony Italia in an attempt to obtain the Windows SDK for the Sony UP-D70A printer
  (which is no longer to be found on Sony's website) and maybe some technical support (source code? specs?) for using
  that printer from a Mac OS X application.  Hopefully, they'll get back to me in a few days.

  Fixed bug which could cause DigitalEmotionApp::ImportPictures() to crash when importing corrupt JPEG files --
  a very similar problem in DigitalEmotionImporter was fixed long ago.  I should really merge those codepaths,
  and perform "manual" import in its own thread, just like automatic import.

  It looks like, sometimes, the IJG library tries to read data past the end of a file (this seems to happen
  with corrupt, truncated JPEG files).  To be safe, MLJpegSource::ReadSource() now zeroes the unused portion
  of the returned buffer.

• 20030624: Eventually found out why prints produced by Digital Emotion aren't as good as they were supposed
  to be, even using bicubic interpolation (see 20030520 entry in this log).  It turns out that the print
  canvas is prepared in two phases: first the photo(s) are composed using DigitalEmotionPhoto::ComposeCanvas(),
  then they are placed into the print canvas using Quickdraw's CopyBits() (in the A4 case) or DigitalEmotionAlbum::
  PlaceOntoPrintCanvas().  Well, the high-quality bicubic interpolation was only used during the first
  phase, but got degraded by the subsequent application of inferior scaling algorithms used to place the
  photo onto the print canvas.

  Consolidated MLAffineTransform, MLAffineTransformBilinear and MLAffineTransformBicubic into a single
  class (MLAffineTransform), whose constructor takes an InterpolationType parameter.  I think this makes more
  sense than having three effect classes that only differ in their implementation of EffectLoop32Bit().

  The MLAffineTransformWithAlpha class now takes an InterpolationType parameter, too, and supports bicubic
  interpolation (TODO: bilinear interpolation).  Its MLAffineTransformWithAlpha::EffectLoop32BitBicubic()
  is closely modeled on Andrea's MLAffineTransformBicubic implementation, but blends the transformed image
  into the destination canvas, taking its alpha channel into account.

  Added DigitalEmotionOverlay::SetAlpha() accessor.  When an overlay (icon or text) is selected,
  the 't' and 'o' keys (for 'Transparent' and 'Opaque') can be used to decrease or increase the
  overlay alpha (tranparency).  The default value is 1.0 (opaque) -- an alpha value of 0.0 makes
  the overlay effectively invisible.

  DigitalEmotionLayout::ApplyLayout() and DigitalEmotionOverlay::DrawOverlay() now take an additional
  InterpolationType parameter, the same received by DigitalEmotionPhoto::ComposeCanvas().  This means
  we can now use high-quality interpolation when applying overlays.  Frames, on the other hand, are still
  composed using Quickdraw's CopyBits(), which I think employs an inferior scaling algorithm.

  Fixed bug where grand totals weren't being updated in the Print Log window after completing a print job.

• 20030626: Cleanup of MLMsg.h header: messaging definitions (MLCommand, MLMessageID, MLMessage, etc.) were
  moved to a new MLMessage.h file; the MLCommander class was moved to a new MLCommander.h file, which
  parallels the existing MLCommander.cpp.  The definition of the MLMessage class was moved to its own
  MLMessage.cpp file (from MLDispatcher.cpp).  Finally, MLMsg.h itself and all references to it were removed.
  
  MLMessage now has a richer constructor which allows the specification of a timestamp, a global mouse location,
  etc.  This constructor is rarely used, and only for synthesized messages -- most MLMessage object are constructed
  from MLPlatformMessage structures by MLDispatcher::GetMessage().

  Added MLDispatcher accessors for retrieving the current mouse location in screen coordinates (GetCurrentMouse),
  and the current state of the modifier keys (GetCurrentModifiers).  GetCurrentModifiers returns a bit map
  which can be tested with MLMessage::kShiftModifierMask, MLMessage::kAltModifierMask, etc.

• 20030628: Revised the HTML generation code so that it outputs well-formed XHTML 1.1 (XHTML 1.0 Frameset
  for the index page).  Mostly as a debugging feature, the HTML exporter will now incorporate "validator
  links" in the pages it creates if the "html_include_validator_link" key is set to true in the preferences
  file.  Validator links look like this:

		<a href="http://validator.w3.org/check?url=referer">Check this page</a>

• 20030629: Found and fixed a bug in the HTML exporter which was causing a major performance degradation:
  DigitalEmotionHTMLExporter::CreateThumbnailPage() was calling DigitalEmotionPhoto::ComposeCanvas(), a
  very expensive routine, even when (inMakeThumbnailFile == false).

  A new preference key, "html_include_keywords" (boolean, false by default) controls whether the caption
  below each photo in the generated HTML galleries includes the corresponding keywords, in square brackets.

  The HTML exporter now generates a common, external style sheet that applies to all HTML files, rather
  than embedding a style sheet in each and every page.  This makes it much easier to change the formatting.

• 20030630: Further optimizations of the bicubic interpolation code in MLAffineTransform::EffectLoop32BitBicubic()
  and MLAffineTransformWithAlpha::EffectLoop32BitBicubic().  In particular, the cubic weighting function (cwf)
  is now called 8 times per pixel, rather than 20 times per pixel.  The new implementation processes about
  384 kp/s (kilopixel per second) on my G4/350, vs. about 231 kp/s in the previous implementation.  That's 66%
  faster!  But it's still dog-slow on a full print canvas (3508 x 2550), taking about 22s on my machine.

  Extended the expiration date to September 8, 20003.

• 20030701: New build posted to my web site:

		<http://www.merzwaren.com/bin/digital-emotion-20030701.sit>

• 20030704: Fixed OBO error in DigitalEmotionHTMLExporter::CreateThumbnailPage().  When the photo count
  was an exact multiple of (rowCount * columnCount), we were generating an extra, empty gallery.

• 20030705: Fixed bug in DigitalEmotionApp::DoTrash().  Sometimes, using the TRASH button in PHOTO mode
  would display the warning message twice -- this would only happen if the current album had any layouts.

• 20030714: Integrated recent changes to HTML exporter by Andrea Pelizzari (progress bar, stop button).
  Moved HTML exporter to its own thread.
  
  Started working on a new MLPeriodicThread class meant to simplify the use of cooperative threads.

  The rationale for this class proceeds from the observation that MLMacThread-derived classes are
  usually subclasses of MLPeriodical, too.  This is necessary to make sure the process gets cpu time
  for non-primary cooperative threads even when the main thread sits idle (and would therefore call
  WaitNextEvent with a potentially large sleep value, essentially blocking all threads at the process
  level).  The interaction between the periodical and the thread is clumsy and somewhat confusing.
  Also, by default, MLMacThread creates new threads in suspended state, so that they need to be explicitly
  Schedule()'d.  The new MLPeriodicThread attempts to address these problems by providing a simplified
  interface: subclasses need only implement a single pure virtual entry point:

  		virtual void MLPeriodicThread::DoPeriodicThread() = 0;
  
  And they will start getting cpu time on the next pass through the event loop.  No need to Schedule()
  or Resume() thread execution from the main thread.

  Fixed stupid error in MLDispatcher::DispatchMacNullMessage() which could prevent terminated threads
  from being immediately disposed.  This error was inadvertently introduced along with SetYieldThrottle().

• 20030715: Added two new pieces of metainformation to albums: "category" and "timeframe".  We're going to
  need these to properly import albums into the new online database Leo is building.  Added more fields/combos
  to the Album Options window: title, category, timeframe.

  When we're done erasing (formatting) cards, we now provide visual and aural feedback so that the user
  can safely remove the media.

  Folders generated by the HTML exporter were too cluttered.  To reduce the clutter, we now group all
  files except "index.html" in a subfolder named "Contents".  This is the new directory layout:
  
  
  				html_folder  ----+----  index.html
  				                 |
  				                 +----  Contents ----+----  galleries ----+----  0_0.html
  				                                     |                    |
  				                                     |                    +----  0_1.html
  				                                     |                    |
  				                                     |                    +----  ...
  				                                     |
  				                                     +----  gallery_all_0.html
  				                                     |
  				                                     +----  gallery_all_1.html
  				                                     |
  				                                     +----  ...
  				                                     |
  				                                     +----  manifest.plist
  				                                     |
  				                                     +----  pictures ----+----  00001.jpg
  				                                     |                   |
  				                                     |                   +----  00002.jpg
  				                                     |                   |
  				                                     |                   +----  ...
  				                                     |
  				                                     +----  search.html
  				                                     |
  				                                     +----  stylesheet.css


• 20030716: Fixed a couple of CSS syntax errors introduced yesterday.

  Made new build available on my website:

		<http://www.merzwaren.com/bin/digital-emotion-20030716.sit>

• 20030716: In order to make life easier for the XML importer being developed by Ledsoft for
  the new Photo Emotion website, photo keyword maps can now optionally be exported in a more
  human-readable format, where keywords applied to a photo are represented by a comma-separated
  list of indices, rather than a Base64-encoded binary blob.  For example, we can now use this form:
  
			<key>keyword_map</key>
			<array>
				<integer>2</integer>
				<string>30</string>
				<integer>7</integer>
				<string>9,10</string>
			</array>

  instead of this one:
  
  			<key>keyword_map</key>
			<array>
				<integer>2</integer>
				<data>
				AAAAiiAAAAAAAAAAAAAAAAAAAAAAAAAA
				</data>
				<integer>7</integer>
				<data>
				AAAACgAAAwA=
				</data>
			</array>

  Which form is used can be controlled using the DigitalEmotionPhoto::UseBase64ForKeywords()
  class accessor.  The index-list form is built on a new MLBitVector accessor, GetAsIndexList().
  For the time being, we'll keep on using the Base64 form in albums, but will adopt the index-list
  form for manifest files generated during HTML export.

• 20030717: Marco Silvestri experienced some major disk problems while testing recent builds of DE:
  in more than one case, his hard drive suffered from a corrupt Master Directory Block (error -60),
  it wouldn't mount after a system crash, and he was forced to reformat it.  These severe problems
  appear to be connected to my file copying code, although I fail to see how.  Perhaps he was using
  corrupt memory cards and the card driver somehow misbehaved.
  In an attempt to shed more light on this riddle, I added code to print various driver-related
  pieces of information to the console at volume-mount time, using DriverGestalt.
  Moved a bunch of ugly, mac-specific, low-level debugging code to a new file, DigitalEmotionDebugging.cpp.

  The MLFile::Copy() call in DigitalEmotionImporter::ImportFile() is now protected by a try block,
  so that failing to copy one file does not cancel the whole volume.  Ditto for the Delete() call.

  MLDispatcher now supports different "redraw policies", which can be chosen at runtime using
  MLDispatcher::SetRedrawPolicy().  This is a MacOS-only accessor.  When the default policy
  (kRedrawPolicyNormal) is enacted, the dispatcher redraws invalid portions of a window when it gets
  update events from WaitNextEvent, as usual.  Sometimes, too frequent updates starve the application
  of null and mouse-moved events -- this used to happen with the old Vitaminic application running
  on slow CPUs.  In this case, we can opt for kRedrawPolicyDelayed, a policy where update events
  are fabricated with CheckUpdate(), queued for later processing, and WNE is called with an event
  mask that suppresses update events.  BTW, this policy supersedes the old ML_MACOS_DELAYED_UPDATES
  compile-time switch.  Sometimes, the opposite problem occurs: we can't process update events often
  enough, perhaps because multiple threads keep invalidating multiple windows at once, but each
  update event can refresh only one window at a time.  This happens to be the case with Digital
  Emotion.  Here's where kRedrawPolicyImmediate comes into play: when this policy is chosen,
  with each pass through the event loop, the dispatcher attempts to redraw all invalid windows
  at once.

• 20030718: After much pondering, I've come to realize the severe stability problems recently
  experienced by Marco Silvestri were caused by an unforeseen interaction between Quickdraw and
  the Thread Manager.  Quickdraw was not designed to live in a multi-threaded environment.
  For one thing, a lot of Quickdraw APIs get their graphics port parameter implicitly through a
  global variable -- which I consider to be one of the worst design flaw of this graphics library,
  and a source of countless headaches.  Digital Emotion tries to use Quickdraw from several threads
  simultaneously, but the Thread Manager knows nothing about Quickdraw, and it will not attempt to
  save/restore Quickdraw globals when a thread is switched out or back in.  So, unless special care
  is taken, a thread may yield, be resumed, and find that the current gworld has been changed behind
  its back while it was suspended.  In an attempt to solve this problem as transparently as possible
  for MLMacThread subclasses, I added custom thread switchers that swap Quickdraw globals when a thread
  is switched in and out.  This effectively means that each thread gets its own, private, Quickdraw
  globals.  This change alone seems to cure a lot of problems, although I'm still getting sporadic
  crashes when running importer and exporter threads simultaneously.  Will investigate.

• 20030722: We now call QDError() when UpdateGWorld() or LockPixels() fail, so we can have a better
  understanding of *why* those calls fail.

  More stability fixes in my Quickdraw code:
  
  	1. When the MLUseCanvas stack-based class is used in conjuction with MLOffscreenCanvases, it
  	   calls LockPixels() in the ctor and UnlockPixels() in the dtor.  Unfortunately, LockPixels()
  	   and UnlockPixels() can't be nested, so code like the following can be dangerous:
  	   
  	   		void foo ( MLOffscreenCanvas & inCanvas, const MLRect & inRect )
  	   		{
  	   			MLUseCanvas		usingCanvas ( inCanvas ) ;
  	   			void *			rawBits = inCanvas . GetBits ( ) ;
  	   			
  	   			inCanvas . DrawRect ( inRect, MLColor :: cWhite ) ;
  	   			
  	   			DoSomethingWithTheBits ( rawBits ) ;
  	   		}

	   Since MLCanvas::DrawRect itself uses MLUseCanvas internally, by the time DrawRect()
	   returns, the pixmap is unlocked, and rawBits may be a stale pointer.
	   Fix: add a mPixelLockCount member to MLOffscreenCanvas that keeps track of the number of
	   nested calls to MLOffscreenCanvas::BeginDrawing().  MLOffscreenCanvas::EndDrawing() will
	   only unlock the pixels when the lock count drops to zero.

	2. Sometimes thread A may yield, and be switched out, before it sets up a gworld for its
	   own use.  In this scenario, my custom thread switcher will save whatever gworld is current
	   in thread A's local storage.  This gworld may have been created by thread B, which may
	   eventually release it.  When this happens, and thread A is switched back in, the thread
	   switcher will call SetGWorld() on a disposed gworld, and Quickdraw will get very upset.
	   Fix: add an MLMacThread::GWorldDied() class-wide (static) notification method that immediately
	   eliminates all references to dead gworlds; call this method from the MLOffscreenCanvas dtor.

  Working on the 2nd screen code [entry #1 of v2 specs].

• 20030723: Implemented thumbnail printing [entry #11 of v2 specs].  The thumbnail sheet is printed
  in portrait orientation and holds 24 numbered thumbnails by default (6 rows, 4 columns).  The row
  and column counts can be changed setting the appropriate keys in the preferences file.  A header
  at the top of the sheet shows the album title, description, location and time frame (in a single
  line).

  Change in print queue management: previously, print jobs would be removed from the print queue as
  soon as a printer became available.  Now, print jobs are removed from the queue only after the
  corresponding spool file has been uploaded to the printer and the print command issued.  This
  means that the relative entry in the Print Queue window doesn't go away until Digital Emotion
  is really finished with the job.  DigitalEmotionAlbum::UnqueueNextPrintJob() was renamed
  DigitalEmotionAlbum::GetNextPrintJob().

• 20030808: It is now possible to open an album by double-clicking the manifest.plist file within.
  Implemented the Mode menu.  Added "New Album..." and "Open Album..." commands to the File menu, so
  now you can switch albums without quitting the application.  Added a Working Set menu with commands
  to manage the working set, and to switch between working sets:
  
  		Clean
  		Find
  		-----
  		Sort by Import Date
  		Sort by Shoot Date
  		-----
  		Previous Working Set
  		Next Working Set

  The sort commands are new and are implemented by a new DigitalEmotionAlbum::SortWorkingSet()
  method, which takes a DigitalEmotionAlbum::ESortKey parameter.  Currently, the only supported
  sort keys are import date (the default) and shoot date.  More keys may be added in the future.
  This feature was not in the official wish list, but I find it useful for my own albums.

  MLDispatcher fix: accept update events even when the redraw policy is kRedrawPolicyImmediate, or
  windows won't get redrawn while the process is in the background.

• 20030818: Started working on the Keyword Editor.  This is a new facility that will allow
  addition, deletion, renaming and reordering of keywords and keyword families associated with
  the current album, using a simple, but powerful, drag-and-drop interface.

• 20030819: Added a new mWASTELibVersion field to SystemInfo.

  MLDate::GetDateString() and MLDate::GetTimeString() should return MLUniString's, not MLString's,
  since those are locale-dependent strings that may contain non-ASCII characters, and we should
  try to limit the use of MLString's in the framework to ASCII-only strings (and eventually phase
  them out altogether in favor of MLUniString's).

  HTML exporter bug fix: generated index files could be corrupt if date strings contained non-ASCII
  characters.

  Major overhaul to MLListView so that it now uses UTF-16 strings internally.  This puts MLListView
  on par with the majority of visual classes dealing with user-visible text.  In particular,
  MLListView::SetListText() now takes a MLUniString.  The sorting function used by MLListView is now
  MLUniString::CaselessCompare() rather than the mac-specific macstrcmp() (which was based on
  CompareText).  This will make the Windows port easier.

  MLDrag now has specialized methods for working with UTF-16 strings: HasUniStringFlavor(),
  GetUniStringFlavor(), AddUniStringFlavor() and SetUniStringFlavor().  MLDrag::HasMacFlavor()
  takes an additional, optional output parameter (bool * outIsPromised) that is set to true
  if the specified flavor has been promised by the sender, but not actually delivered yet.
  This feature relies on GetFlavorFlags() setting the flavorDataPromised bit correctly, so it
  only works on Mac OS X 10.1 or newer (see <http://developer.apple.com/technotes/tn/tn2029.html#DRAGMGR>).

• 20030821: MLBitVector revision.  Fixed bug in MLBitVector::Resize(), added MLBitVector::Insert(bool, UInt32)
  and MLBitVector::RemoveAt(UInt32).  The new insertion/removal methods are used by
  DigitalEmotionPhoto::MakeRoomForNewKeyword(), DigitalEmotionPhoto::MoveKeyword() and
  DigitalEmotionPhoto::RemoveKeyword() to keep keyword maps in sync with changes made through
  the Keyword Editor.

• 20030822: Finishing touches to the Keyword Editor.  You can now invoke the Keyword Editor from
  the Keyword window displayed after a photo volume has been imported, to add familes and keywords
  on the fly.  I think this fully implements [entry #4 of v2 specs].  As for [entry #8 of v2 specs]
  ("ability to choose which Common Keyword families to use when a new album is created"), I think
  the Keyword Editor makes this item obsolete, as it's now very easy to delete any family at any
  time, and all Common Keywords are automatically added to every new album.

  Some more work on activation codes.  Added a DigitalEmotionApp::VerifyActivationCode() placeholder
  which will parse the current activation code (a string saved in the preferences under the key "activation_code"),
  make sure it is consistent with the SystemInfo structure collected at startup, and extract an
  expiration date from the activation code, which is then passed to the existing CheckExpirationDate().

  Implemented registration window and wired its code to the existing (but so far unused) HTTP code.

• 20030824: MLInternetConfig revision.  Several Get accessors now return default values if the corresponding
  preference key is not present in the Internet Config database.  Previously, they would throw OS
  exceptions (-666 == icPrefNotFoundErr), and this caused my registration code to fail miserably on
  machines where the kICOrganization key was missing.  The accessors in question are:
  
  		GetRealName					default value: empty string
  		GetOrganization				default value: empty string
  		GetMailAddress				default value: empty string
  		GetMailAccount				default value: empty string
  		GetMailPassword				default value: empty string
  		GetDownloadFolder			default value: desktop folder

• 20030825: "TN" was missing from the Italian province list, for some reason.

• 20030826: Revised DigitalEmotionApp::SendRegistration() so that it supports HTTP redirection (3xx status codes).
  This allows me to have DE send its first registration request to a very stable Apache server I control, which
  will redirect the query to a PC running IIS, hosted by Ledsoft.

  Added MLBufferStream::Rewind() and MLBufferStream::Reset().  The first function, meant for input streams, rewinds
  the position of the stream to offset zero; the second function, meant for output streams, rewinds the stream
  and also clears the buffer.

• 20030827: Working on the remote activation algorithm.

• 20030828: Again on the remote activation algorithm.  Experimented with HTTP compression.

• 20030830: The 2nd screen now works in 4/9/16 mode.  This completes [entry #1 of v2 specs].
  Started work on the last missing entry of the v2 specs, i.e., #6: export of master files for backup
  purposes.

• 20030831: Finished work on the export feature.  This basically completes version 2 of Digital Emotion.

  Added a new boolean preference key (kKeyPreferencesSlideShowUseMaster == "slide_show_use_master") that
  determines whether masters or scaled-down pictures are used to compose canvases used for the slide show.
  The default value is false, since slide shows are going to be projected on a low-resolution (640x480)
  second monitor most of the time.  Setting this preference to true (and kKeyPreferencesSlideShowInterpolationType
  to kInterpolationBicubic == 2) provides the maximum possible preview quality, but at the expense of a
  significant slowdown.

  Removed superfluous close box from HTML progress window.

• 20030909: Marco Silvestri filed a list of 9 bugs/enhancement requests.  Most are minor quirks, easily fixed.
  A few of them, though, will require some effort.  Here's the complete list:

		1.	Make lists auto-scrollable, i.e., make it possible to drag items to out-of-view portions of lists.
		2.	Once the master exporter is used, the EXPORT button remains inactive.
		3.	Find All should deselect all.
		4.	When the layout mode is not PHOTO, the activation state of some buttons may go out of sync.
		5.	Newly created layouts should not be automatically selected.
		6.	Make more room for photo labels in the 16-photo viewer.  Labels above 999 don't fit.
		7.	Exception when the printer is out of ink.
		8.	Switching to the Finder (with cmd-H) and back doesn't hide the menu bar in OS 9.
		9.	Execute "manual" import in a separate thread, just like automatic import.

• 20030911: Addressed entries #2, #3, #4, #5 and #6 of [20030909 wish list].
  Refactored the class hierarchy by inserting an extra base class DigitalEmotionExporter between
  MLPeriodicThread and the two concrete exporter classes (DigitalEmotionMasterExporter, DigitalEmotionHTMLExporter).
  The Stop button in the Export Progress window now works with the master exporter as well.

• 20030913: "Manual" imports are now executed in their own separate thread, rather than in the main thread.
  The real work is now performed by a new DigitalEmotionManualImporter class (an MLPeriodicThread).
  Since this class has a lot of code in common with the existing DigitalEmotionImporter class, the common
  code was factored out to a new DigitalEmotionImporterBase from which both classes inherit.
  This addresses [entry #9 of 20030909 wish list].

• 20030914: The HTML exporter will no longer add title attributes to img tags for photos that don't have
  a valid shoot date.

• 20031001: Moved a bunch of code to get info about the hardware, OS, etc. to a new "MLMacSystemInfo.cpp" file.

  Added new preference keys to control the fonts used in various UI widgets:
  
  		font_edit_fields				[string]
  		font_lists						[string]
  		font_popup_menus				[string]
  		font_size_edit_fields			[integer]
  		font_size_lists					[integer]
  		font_size_popup_menus			[integer]

  The first three keys specify font names, or lists of font names, separated by commas, e.g.:
  
  		<key>font_lists</key>
  		<string>Lucida Grande, Geneva</string>

  Digital Emotion will scan the list and pick the first available font.
  The empty string stands for the so-called "application" font (usually Geneva on OS 9, Lucida Grande on OS X).
  The special name "$SYSTEM" stands for the system font (usually Charcoal on OS 9, Lucida Grande on OS X).
  The "font_size_*" keys specify the corresponding point sizes.  Specifying a zero or negative size instructs
  DE to use the default font size (as returned by the Carbon API GetDefFontSize()).

• 20031002: New build posted to my web site:

		<http://www.merzwaren.com/bin/digital-emotion-20031002.sit>

• 20031016: Phone conversation with Marco Silvestri.  There seems to be a hard-to-reproduce, sporadic problem
  with keywords sometimes getting mixed up for no apparent reason.  I'll investigate it, but I'd rather have
  a reproducible test case to work on.  More wish list items:

	+ The screen position of all windows (including Find, Set Keywords, Keyword Editor) should be persistent.
	+ The Set Keywords window currently doesn't offer a way to inspect *all* the keywords associated with a
	  picture -- it shows just the first keyword for each family.  Devise a way to show all the keywords.
	+ Add a way to jump to a picture given its serial number.  Better yet, add a way to define a working
	  set by entering the serial numbers of its pictures, separated by commas or in range form.  For example,
	  entering "1-4, 12, 18-20" should create a working set made of pictures number 1, 2, 3, 4, 12, 18, 19 and 20.
	+ Shadow text.
	+ Picture selection rings should appear on the second (customer) monitor as well.
	+ The Print Queue window should report the picture serial number(s) corresponding to each job.

• 20031017: Started working again on the Windows port.  I merged the old Win32 branch we forked off in May
  back into the main codebase.  We now have a single project with PowerPC and x86 targets.  The x86 target
  compiles and links, but I had to comment out a bunch of stuff, so I don't think the executable can take
  off at this stage.

  I rewrote the entire DigitalEmotionScanner hierarchy so that it uses x-platform types (MLPath, MLUniString)
  instead of mac-specific ones (FSRef, HFSUniStr255).  The base class is somewhat less flexible than
  before, but we'll gain Windows compatibility (almost) for free.  TODO: test these changes.

  In various places I was using the MLFont(FMFontFamily, UInt16, UInt16) ctor, which is mac-only, to
  create fonts from the default system or application typefaces.  I replaced those ctors with
  MLFont(const char *, UInt16, UInt16), which is x-platform, and modified the MLFont implementation so
  that it special-cases the font names "$SYSTEM" and "$APPLICATION" and maps them to the corresponding
  system and application fonts.  This special-casing needs to be done on the Win32 side as well.

  Added hand-made implementations for missing, non-standard strcpyupr() and strncpyupr() functions (why
  were these used in the first place?)

• 20031019: Added libpng 1.2.5 and zlib 1.1.4 to the project, and #defined ML_PNG_NATIVE_SUPPORT to 1 in
  the prefix files.  This causes MLOffscreenCanvas to rely on libpng (rather than QuickTime) to load PNG
  files.

		<http://www.libpng.org/pub/png/libpng.html>
		<http://www.gzip.org/zlib/>

• 20031020: Working on the Windows port.  Added some missing MLDate member implementations (GetDateString()
  and GetTimeString()).  Fixed some problems with the Win32 implementation of MLFont.  Changed MLJpegDecompressor
  so that it uses 32-bit deep surfaces on Win32 as well (it used to create 24-bit surfaces -- no alpha channel).
  This is needed so that interpolation code in MLEffect.cpp can work without changes.
  Added an "x86.final" target to the project that compiles with full optimizations on.

• 20031022: More work on the Windows port.

• 20031030: Rewritten the Win32 implementation of MLCanvas::DrawRect() so that it more closely mimics the
  mac semantics.  In particular, DrawRect() now honors the pen width set with MLUsePen -- previously, it
  would call the GDI API FrameRect(), which always draws a one-pixel thick outline.

  Incorporated new MLSystemInfo class from Leonardo.  This is a x-platform reimplementation of MLMacSystemInfo.

  Dug out the old MLWin32View class, a convenient adapter that allows Windows controls (buttons, edit boxes,
  scrollbars, sliders, progress bars, etc.) to be used as regular views.  A MLWin32View is both an MLView and
  an MLWindowProcHandler.  Incorporated MLWinEditBox, a subclass of MLWin32View.  Added a new MLWinSlider class,
  also a subclass of MLWin32View.

• 20031031: Leonardo renamed MLWin32View to MLWinControl (to make its relationship with MLMacControl more obvious)
  and added more subclasses: MLWinScrollbar, MLWinTab and MLWinProgressBar.  I added MLWinButton, modeled after
  MLMacButton.  In several of these controls, the underlying child HWND sends various notification messages
  (WM_COMMAND, WM_NOTIFY, WM_HSCROLL, etc.) to its parent window, which are handled by MLWindow::WindowProc().
  MLWindow::WindowProc(), in turn, retrieves a pointer to the C++ wrapper object around the child HWND, and
  redirects the notification to it, suitably converted into a virtual member call (MLWinControl::HandleCommand(),
  MLWinControl::HandleNotification(), MLWinControl::HandleScroll(), etc.).  The concrete control subclasses
  can then override the relevant methods, and behave like their Macintosh counterparts, by broadcasting
  a kCommandSlider command (MLWinSlider), performing an MLAction (MLWinButton), what have you.  This strategy seems
  to work pretty neatly.

• 20031103: The MLWinThread class is now on par with its Macintosh counterpart -- it mimics the semantics of
  Thread Manager cooperative threads by using Windows fibers with a simple round-robin scheduler.
  Threads can be "stopped" (made ineligible for scheduling) by calling MLWinThread::Unschedule(), or resumed
  to their "running" status by calling MLWinThread::Schedule().  Of course, thread switching can only happen
  when MLWinThread::Yield() is explicitly called by the current thread.  Just like in the Macintosh implementation,
  a private MLWinThread ctor is used to create a wrapper around the main application thread -- a singleton which
  is created before main() is entered, and never destroyed.  This ctor calls ConvertThreadToFiber(), thus making
  it possible for the regular ctor to call CreateFiber().

  MLWinThread.[h/cpp] defines a thread/periodical hybrid, MLPeriodicThread, which is almost identical to the
  Macintosh implementation.  I'll merge MLMacThread.[h/cpp] and MLWinThread.[h/cpp] as soon as I have a chance.

  Bug fix: MLOffscreenCanvas::ConstructFromPNGStream() would incorrectly call png_set_background() when reading
  images with an alpha channel into a 32-bit deep surface.

  Bug fix: the Win32 implementation MLFile::Rename() was badly broken -- it would rename files *and* move them
  to the current directory.

  Fixed endian issues in MLBitVector::MLBitVector(const MLBuffer&) and MLBitVector::GetAsBuffer().

• 20031104: Fixed fiber termination issue in MLWinThread.  If a fiber proc terminates normally (i.e., if
  MLWinThread::CommonEntryPoint() returns), Windows will terminate the thread that owns the fiber.  Since
  our implementation spins all fibers off the main thread, this means exiting a fiber proc would immediately
  kill the process.  So we now destroy fibers from the main fiber and the MLWinThread destructor (only
  called in the context of the main fiber) the now calls DeleteFiber().

  MLPeriodical now has a new virtual method Terminate(), invoked by the dispatcher when the periodical has
  stopped running and has just been removed from the dispatcher list.  The default implementation does nothing.
  MLPeriodicThread overrides Terminate() to delete itself, which destroys the underlying fiber.

  Worked around a tricky problem with MLPath::EnforceInvariants() and volumes that have just been mounted.
  MLPath::EnforceInvariants() relied on the dwAttributes field of the SHFILEINFO structure filled in by SHGetFileInfo()
  to determine whether a given pathname referred to a directory or not.  Unfortunately, SHGetFileInfo() would sometimes
  fail for files living on recently mounted volumes not yet detected by the shell.  We now use GetFileAttributes()
  exclusively to determine whether a given path refers to a directory.  We only use SHGetFileInfo() to detect
  shortcuts (symbolic links).

• 20031106: More tweaks to MLWinThread.

  Fixed broken Win32 implementation of MLFile::Copy() (the second parameter to CopyFile() must be the
  pathname of the destination *file*, not the destination directory).

  Until we incorporate code to read TIFF files (libtiff?), the Windows version can only import JPEGs -- various
  places in the code where we accept TIFF files (such as DigitalEmotionImporterBase::IsGraphicsFile() and
  DigitalEmotionApp::ImportPictures()) must be protected by #if ML_USE_QUICKTIME guards.

		<http://www.libtiff.org/>

  DigitalEmotionPhoto::MakeThumbnail() can now use an alternate "high-quality scaling" codepath, where the
  source canvas is scaled down using our built-in bicubic interpolation algorithm.  The old codepath relies on
  MLCanvas::Blit(), implemented on top of the system-provided pixel-scaling API: CopyBits (MacOS/Quickdraw)
  or StretchBlt (Win32/GDI).  Unfortunately, while CopyBits() yields passable results, StretchBlt() does a
  really lousy job of downscaling.

  Digital Emotion would fail to load an existing album if it couldn't recreate missing thumbnails (if any) for all
  photos.  Now it will just silently skip photos for which thumbnails can't be re-generated.  This can happen on
  Windows when importing an album created on the mac, containing TIFF masters whose corresponding thumbnails
  have been deliberately removed.

• 20031107: Fixed endianness issue in DigitalEmotionPrintJob::CreateSpoolFile().

  TODO: Provide Win32 implementation of MLDispatcher::GetCurrentModifiers().

  Modified MLMacPopupMenu so that it can create and manage its own MLMenu, rather than relying on client code
  to attach a separately created MLMenu object to it.  The rationale for this change is to make it easier to
  adapt the existing initialization code to the Windows implementation, which will likely use drop-down list
  controls to mimic mac popup menu controls.

  Incorporated initial version of MLWinDropDownList class from Leonardo.

• 20031110: Added MLFont::ListFonts(); Win32 implementation of MLDispatcher::GetCurrentModifiers().
  Explicit CFRelease() calls in various places have been replaced by implicit calls to
  AutoHandle<CFString>::~AutoHandle<CFString>().

• 20031111: Better handling of libpng error and warnings: instead of setting up a jmp_buf structure for
  libpng to call jongjmp() with, we now install a custom error callback which throws a C++ exception
  with an informative message.  We also install a custom warning callback which writes warning messages to
  the console in debug builds.

  MLOffscreenCanvas::ConstructFromPNGStream() would refuse to load PNG files with bit depths other than 8,
  for no good reason at all (since libpng handles all expansion details).  This would prevent the Album Options
  window from loading correctly, since one of its combo buttons references a 1 bit-deep PNG (triangle.png).

• 20031112: (Win32) MLSystemInfo::GetEthernetAddress() no longer throws an exception on a machine lacking an
  Ethernet card.

  (Win32) MLFont::ListFonts() now sorts the returned list alphabetically, and only includes TrueType ANSI fonts.

  (Win32) The MLWinDropDownList control now includes a vertical scrollbar if the items don't fit a fixed (200 pixels)
  height.  Some cosmetic issues were fixed as well.  Added a new MLWinControl::ResizeControl() accessor that changes
  the bounds of the underlying HWND without affecting the MLView bounds.  This is needed for controls such as
  MLWinDropDownList where the two rectangles don't match.

  (Win32) The Find and Print Options windows now work.

• 20031113: ML Framework change: when a MLMacPopupMenu broadcasts a kCommandPopupChanged message to notify its listeners
  that a new menu item has been selected, the new item index is now zero-based rather than one-based, for consistency with
  the various MLMenu accessors and with the general rule used in the framework that sets of N items are numbered
  0 to (N - 1).  Also, to get and set the current selection of a MLMacPopupMenu object, it is now recommended to use
  GetSelection() and SetSelection(), instead of the lower-level GetValue()/SetValue() inherited from the MLMacControl
  base class.  GetSelection()/SetSelection() are zero-based, whereas GetValue()/SetValue() are one-based, according
  to the Macintosh Control Manager convention.

  MLMacPopupMenu no longer exposes its private MLMenu object: it is no longer possible to obtain a reference to it
  through GetMenu(), nor to specify an existing MLMenu to the MLMacPopupMenu ctor.

  MLMacControl now supports the cloning mechanism implemented by several other MLView subclasses: calling Clone()
  on a MLMacControl makes a more or less identical copy of the object.

  Renamed GetValue()/SetValue() accessors in MLWinDropDownList to GetSelection()/SetSelection() and made them zero-based.

• 20031119: Reworked the InterfaceLib shim I use to trick the Sony support library for the UP-D70 printer to work in a
  Carbon application.  The new shim has a new entry point (SetSCSIActionHook) that allows Digital Emotion to install a
  hook to monitor calls to SCSIAction() made by the Sony library.  #Defining TRACE_SCSI_CALLS to 1 causes Digital Emotion
  to install a SCSIAction hook (TracingSCSIAction) that logs calls to the debug console.  The purpose of this machinery
  is, of course, to reverse-engineer the Sony library in order to write a compatible Win32 implementation.

• 20031125: MLUniString::InitFromUTF8String() now supports characters beyond the BMP (Basic Multilingual Plane) and is
  stricter about malformed UTF-8 sequences.

• 20031127: Started working on a Win32 reimplementation of the Sony "UPLib" library that allows communication with the
  Sony printer via SCSI.  This reimplementation will be based on the Win32 ASPI (Advanced SCSI2 Programming Interface)
  layer, originally developed by Adaptec, maker of the popular PowerDomain 2930 card I've been using to connect my test unit.

	<http://www.hochfeiler.it/alvise/ASPI_1.HTM>
	<http://www.ncf.carleton.ca/~aa571/aspi.htm>
	<http://www.adaptec.com/worldwide/product/proddetail.html?sess=no&language=English+US&cat=%2fProducts%2fAPD-2930U&prodkey=APD-2930U>

  A new class, tentatively named ASPI (will rename to MLWinASPI soon), provides a convenient C++ wrapper around WNASPI32.DLL.
  Another class written on top of this (UPImpl) provides the low-level implementation of several calls in "UPLib".
  Finally, the relevant UPLib C entry points have been reimplemented on Windows through the UPImpl class.

  The official SCSI2 specification can be found here:
  
  	<http://www.micro-magic.com/ftp/scsi2.pdf>

• 20031130: Incorporated several changes from Leo, including a new MLWinDateTimePicker class that provides a Win32 counterpart
  to MLMacClockControl.  The kModifiersChanged message is now implemented for Win32 as well, but only for the Alt key.

• 20031202 (my birthday): A more accurate reverse-engineering of the Sony communication protocol, thanks to a revised
  TracingSCSIAction() function, along with a somewhat better understanding of the SCSI2 standard, allowed me to correct
  several errors in the layout of CDBs (Command Descriptor Blocks) sent to the printer at various steps of the printing
  process.  I'm happy to announce I was finally able to print the very first test page from the Windows version of Digital
  Emotion running on Windows XP.  For what it's worth, it was a reproduction of Francis Bacon's "Study after Velazquez's
  Portrait of Pope Innocent X".

  Renamed the ASPI C++ wrapper to MLWinASPI and moved it into its own files (MLWinASPI.[h/cpp]).

  The problem with ASPI is that 1. It is no longer supported by Microsoft on NT-class operating systems; and 2. there
  are a zillion different implementations around.  The one we're using on our test PC is the latest version from
  Adaptec (version 4.72).  This appears to work seamlessly with the Adaptec PowerDomain 2930 PCI card I borrowed from
  my PowerMac G4, but unfortunately, it doesn't seem to work as smoothly with Leo's Tecram 390 PCI card, and doesn't
  work at all with RATOC Systems FR1SX FireWire-to-UltraSCSI converter.
  
  	<http://www.ratocsystems.com/english/products/subpages/firerex1.html>

  I fully expect this ASPI implementation to be incompatible with USB-to-SCSI converters as well, making it unsuitable
  to drive Sony printers from a modern portable machine (which is likely to feature IEEE 1394 or USB 2.0 ports, but
  no SCSI connectors).  The solution to this problem appears to involve a make-vs-buy decision: either 1. we write an
  alternate implementation based on Windows XP's native SCSI layer (SPTI = SCSI Pass-Through Interface), or 2. we buy
  one of the commercially available SPTI-based ASPI layers, such as the ones marketed by Nexitech or RecDev:

	<http://www.nexitech.com/products.htm>
	<http://www.recdev.de/addon.html#aspi>

  There are a number of interesting Usenet threads on ASPI/SPTI issues, like this:

	<http://groups.google.com/groups?selm=bgtnu1%24em8%241%40slb9.atl.mindspring.net>

• 20031204: Added a new MLDate::GetCompileDate() static accessor, which returns the date/time when the function was
  compiled (obtained by parsing the ANSI C __DATE__ and __TIME__ macros).  Digital Emotion will now refuse to run if
  the current time is less than the compile time.  This makes setting back the system clock to prevent Digital Emotion
  from expiring a little bit harder.  Fixed a bug in the Win32 implementation of MLDate::GetCurrentDate(): this accessor
  should return the current *local* time, not the current UTC time.  Changed the Win32 implementation of MLDate::LocalToUTC()
  and MLDate::UTCToLocal() so that it doesn't depend on TzSpecificLocalTimeToSystemTime/SystemToToTzSpecificLocalTime,
  since these are not available on all Windows systems.  Instead, we just add/subtract the Bias field of the TIME_ZONE_INFORMATION
  record returned by GetTimeZoneInformation.

  Galleries created by the HTML exporter now have Previous/Next buttons both at the top and at the bottom of the gallery.

• 20031205: Updated zlib to version 1.2.1, announced today by Mark Adler.

• 20031209: Experimented with a test version of the NexiTech ASPI emulation layer, made available by Don Matthews for
  evaluation purposes.  This ASPI implementation works beautifully with the Ratoc adapter, and with the Tecram PCI card
  as well, although in the latter case I had to lower the SCSI transfer buffer size from 64K (which I think is the
  theoretical maximum for the UP_CMD_SEND_IMAGE_DATA command) to just 24K.

  Added a couple of new keys to the preferences file: "scsi_transfer_buffer_size" (an integer specifying the maximum
  size of data chunks pumped through the SCSI bus, in KB, min: 1, max: 64, default: 64), and "aspi_layer_name" (the
  name of the ASPI DLL to load, default: "wnaspint").

• 20031215: Minor optimizations to MLString::Replace() and MLUniString::Replace().

• 20031217: New albums wouldn't inherit Common Keywords in some cases.  This was a thread synchronization issue.
  Ported the TCP/IP code to Windows.  Added MLWinSocket.[cpp|h] and ws2_32.dll to the project.  Slight tweaks to
  MLHTTPRequest.[cpp|h] to make it cross-platform.

• 20031218: Fixed long-standing, subtle bug involving negative numbers in MLString::IntToString() and MLUniString::IntToString().
  Fixed broken Win32 implementation of MLSystemInfo::GetOSName().  Win32 implementation of MLSystemInfo::GetRAMSize() now
  uses GlobalMemoryStatusEx() instead of GlobalMemoryStatus() -- this allows us to correctly recognize systems with more than
  4 GB of RAM.  Fixed bug with redirects of HTTP GET requests in DigitalEmotionApp::SendRegistration().
  The remote activation code now works correctly on Windows, but should nonetheless be modified so that we have alternate
  ways to uniquely identify PCs that don't have an Ethernet card.

• 20031225: Made HTML exporter more flexible by adding several accessors to fine-tune the quality of the generated thumbnails.
  Almost all of the new accessors have a corresponding preference key.  Here's a table of new keys, their meanings, and their
  default values:

    +-----------------------------+--------------------------------------------+------------------+
    |   key                       |   meaning                                  |   default value  |
    +-----------------------------+--------------------------------------------+------------------+
    |   html_use_masters          |   generate HTML thumbnails from            |   false          |
    |                             |   masters (true) or from working           |                  |
    |                             |   images (false)?                          |                  |
    +-----------------------------+--------------------------------------------+------------------+
    |   html_apply_layouts        |   include overlays in HTML thumbnails?     |   false          |
    +-----------------------------+--------------------------------------------+------------------+
    |   html_high_quality_scaling |   use high-quality bicubic interpolation   |   true on Win32  |
    |                             |   to scale down source images?             |   false on Mac   |
    +-----------------------------+--------------------------------------------+------------------+
    |   html_jpeg_quality         |   JPEG compression quality of thumbnails   |   0.75           |
    |                             |   (valid range is 0.0 to 1.0)              |                  |
    +-----------------------------+--------------------------------------------+------------------+

  "html_use_masters" should be set to true when generating relatively large HTML thumbnails (say, 400 pixel tall/wide or more).
  Doing so slows down the generation process, of course.
  "html_high_quality_scaling" is true by default on Win32 because the system-provided scaling algorithm is pretty lousy.

  Corrected a bug in the MLJpegCompressor class: SetQuality() had no effect, since DoExport() would reset it to its
  default value (0.75) anyway.  This happened because we would call jpeg_set_defaults() in DoExport() -- now we call
  jpeg_set_defaults() in the ctor instead.

  Experimented a bit with the "optimize_coding" option of the IJG compressor object.  Turning this option on (it's off by default)
  causes the JPEG compressor to calculate optimal Huffman tables for the source image, instead of using default tables.
  This will reduce the JPEG file size slightly without affecting its quality, at the expense of an extra pass (slower compression).
  According to a simple test I've performed (compressing 77 1024x768 pixel real-world pictures shot by my Sanyo camera down to
  400x300 pixel thumbnails), the average file size reduction is just 3.5%, ranging from about 2.25% to 6%.  Probably not enough
  to justify the extra time.

  Fixed a bug where MLMessage::IsFunctionKey() did not recognize arrow keys on the Macintosh.

• 20031230: The warning dialog that appears when a build is about to expire now offers the option to connect to the activation
  server, to check whether a renewed license is available.  The serial number must be reentered anyway.

  Made the activation code stored in the preferences.plist file somewhat more resilient to tampering.

• 20040101: New Macintosh build posted to my web site:

		<http://www.merzwaren.com/bin/digital-emotion-mac-20040101.sit>

• 20040105: On Mac OS X, MLSystemInfo::GetRAMSize() now attempts to determine the available physical RAM using the
  gestaltPhysicalRAMSizeInMegabytes Gestalt selector.  This allows us to get correct results on machines with more
  than 4 GB of RAM.

• 20040107: Implemented an auto-update function.  When Digital Emotion connects to the activation server to obtain or
  renew a license, it's now returned the version and the URL of the most recent public build.  If a newer version is
  available, Digital Emotion offers to download it.  If the user clicks OK, a DigitalEmotionDownloader thread is spawned
  which proceeds to download the update archive to a temporary folder.  When the download terminates successfully, the
  archive is moved to the desktop and a post-processing application is invoked to decompress it (the post-processing
  part is currently only implemented on the Macintosh).

  The XML document returned by the activation server includes two new keys ("current_version" and "current_version_url")
  and looks like this:

    <?xml version='1.0' encoding='utf-8' ?>
    <plist>
    <dict>
        <key>activation_code</key>
        <string>1864BA5F-53D3BD3D-12E92D40-C73F37B5-52F9DD85</string>
        <key>message</key>
        <string>Product successfully activated.</string>
        <key>current_version</key>
        <string>0x01206003</string> <!-- 1.2b3 -->
        <key>current_version_url</key>
        <string>http://www.merzwaren.com/bin/digital-emotion-mac-20040101.sit</string>
    </dict>
    </plist>

• 20040114: Following changes made to the code that prepares print canvases (see entry 20030624 of this log), the
  Orientation parameter to DigitalEmotionPrintJob::CreateSpoolFile() (and DigitalEmotionAlbum::CreatePrintJob()) is always
  kOrientationLandscape, so it's redundant.  I'm removing this parameter and the unused code path that it controlled.

• 20040114: Fixed a long-standing, crashing bug sometimes observed when quitting Digital Emotion with a non-empty print
  queue.  The problem was that the DigitalEmotionPrintJobManager thread would sometimes terminate prematurely because of
  an uncaught exception, and the current album would keep a stale reference to the thread, which it would attempt to use
  in the dtor.  The job manager thread now calls DigitalEmotionAlbum::JobManagerDied() before exiting.

• 20040114: Framework change: the MacOS implementation of MLWindow::GetFrontWindow() now uses ActiveNonFloatingWindow()
  (Carbon targets) or FrontNonFloatingWindow() (classic targets) instead of FrontWindow(), following a recommendation by
  Eric Schlegel on the Carbon mailing list.  FrontWindow() will likely return the menu bar WindowRef in the next major
  revision of Mac OS X (10.4?), which is not what we want.

• 20040116: Overhauled the printing code in order to support printing through standard Windows printer drivers.

  I introduced two new abstract base classes, DigitalEmotionPrinterClass and DigitalEmotionPrinter.  DigitalEmotionPrinter
  represents a single printing device.  DigitalEmotionPrinterClass is a factory class representing a family of similar
  or identical devices, it has a LookForPrinters() method which can be used to discover devices, and CountPrinters()/GetNthPrinter()
  methods which can be used to enumerate discovered devices.

  The existing SonyUPD70A_Printer and SonyUPD70A_PrinterManager classes are now concrete implementations of DigitalEmotionPrinter
  and DigitalEmotionPrinterClass, respectively.

  Two new classes, WinPrinter and WinPrinterManager, are concrete implementations of the same abstract bases which
  provide convenient wrappers around the Win32/GDI printing model, or the subset needed by Digital Emotion to print
  bitmap pictures.

  This is just a first attempt at unifying two rather different printing models, the original one supported by Digital
  Emotion for Macintosh, and the native Windows printing model.  It's still far from perfect.  In particular, I haven't
  yet addressed the issues of managing a unified print queue and job logging in the GDI implementation.  OTOH, I've been
  pressured to provide a version of Digital Emotion for Windows that can print to Kodak printers for which we haven't
  developed a custom module yet.

• 20040121: Implemented About Box using the HIAboutBox API (Mac OS X Jaguar only).
  MLSystemInfo::GetClockSpeed() will now use the gestaltProcClkSpeedMHz selector on Mac OS X, if available.

• 20040129: MLDispatcher::PostMessage() used to be implemented in two different ways on Mac and Windows.  On Mac, it
  used a private message queue (MLQueue<MLMessage>) where custom messages would be enqueued and later dequeued by
  MLDispatcher::GetMessage().  On Windows, it would post a Windows custom message to the main thread using the Windows
  API PostThreadMessage().  Unfortunately, this disparity of implementation led to subtle discrepancies in behavior:
  for instance, MLApplication::MessageBox() would silenty discard queued messages on Windows (but not on Mac).
  This side effect is documented, BTW:
  
  	<http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/WinUI/WindowsUserInterface/Windowing/MessagesandMessageQueues/MessagesandMessageQueuesReference/MessagesandMessageQueuesFunctions/PostThreadMessage.asp>

  To avoid this problem, we now use a completely uniform implementation on both platforms, based on the Mac code
  (private message queue).

  Incorporated a completely rewritten implementation of the Win32 code that generates MLMessage :: kVolumeMounted messages,
  courtesy of Leo.  The new implementation is based on SHCNE_MEDIAINSERTED shell notifications.

  [Win32] Added MLDll::GetFunctionByOrdinal(), and MLDll::GetFunctionByOrdinalNoThrow() functions.  These allow loading
  of unnamed functions from a DLL.  It turns out we needed this functionality to load SHChangeNotifyRegister() from older
  versions of SHELL32.DLL, when this function was undocumented and unnamed.

• 20040204: Implemented a "manual registration" feature.  This is meant to be a fall-back mechanism to register Digital
  Emotion on machines that don't have an Internet connection, not even sporadic.  Clicking the "Register Manually" button
  on the Registration window will display a longish (49 characters) "manual registration" string that encodes the most
  relevant information that would be sent to the activation server via HTTP if an Internet connection were available.
  This manual registration string can be then communicated by non-Internet means (e.g., phone or fax) to an activation
  center which will hand back an activation code for the product.

  Fixed a cosmetic bug in MLListView on Windows.  This turned out to be caused by a bogus clipping region.  Setting the
  clipping region to a reasonable value at window creation time cured the problem.

• 20040205: Reworked MLWinEditBox so that it behaves more like its Macintosh counterpart (MLMacText).  In particular,
  MLWinEditBox now implements SetEditFieldBehavior() and AttachFocusRing(), it sends kCommandSubmitField when appropriate,
  and it reacts correctly to DoFocusAcquired() and DoFocusLost() notifications.

• 20040206: More fixes to MLWinEditBox.  First half-decent implementation of the MLWinScrollbar class -- previously, this
  class was little more than a placeholder; it didn't even inherit from MLAbsScrollBar as it should, so it didn't work at
  all in conjuction with panoramas.  Correctly implementing MLWinScrollbar::SetProportion() for Win32 scrollbars turned
  out to be harder than it appeared at first.  The MLAbsScrollBar::SetProportion(UInt32 inVisibleSize, UInt32 inTotalSize)
  function is meant to be used to support system-defined proportional scroll bars.  Its Macintosh implementation is based
  on the SetControlViewSize() API.  Its initial Windows implementation, based on the SetScrollInfo() API called with the
  SIF_PAGE selector, didn't work quite right.  The problem turned out to be that, in the case of proportional scrollbars,
  Windows assumes the scrollable range (max - min) to be the whole height (or width, in case of horizontal scroll bars) of
  the scrollable view, while MLPanorama sets the scrollable range to the total height *minus* the visible height, which
  seems to make more sense to me.  The weird thing is that if I stick to non-proportional scrollbars (i.e., if I never
  use the SIF_PAGE selector), my idea of what the scrollable range should be seems to work just fine.

• 20040209: The MLMacComboBox class now works on Windows, too, and has been renamed to MLComboBox.  It's basically unchanged
  except, on the Windows side, we use MLWinEditBox instead of MLMacText for the edit field.  I do resort to a very ugly
  kludge to make sure the combo box list (which is just an MLView, not an HWND) doesn't get overdrawn by edit fields behind
  it in z-order.  Like its Macintosh counterpart, MLWinEditBox now broadcasts kCommandTextChanged messages when the user types
  into it -- this is required for MLComboBox auto-completion to work correctly.  As a result of all these changes, the Album
  Options window now works correctly on Windows, too.

  Implemented the color picker dialog on Windows.  This is the dialog that comes up when you double-click a color well in
  the Text palette, allowing you to change one of the 13 predefined colors.

• 20040224: Began integrating the latest version of libtiff (3.6.1) into the ML framework.

		<http://www.libtiff.org/>

  The following macros are #defined in prefix files to cause the corresponding codecs to be compiled in:

		CCITT_SUPPORT			CCITT Group 3 and 4						(compression codes 2, 3, 4, 32771)
		PACKBITS_SUPPORT		Macintosh PackBits						(compression code 32773)
		LZW_SUPPORT				Lempel-Ziv & Welch						(compression code 5)
		THUNDER_SUPPORT			4-bit RLE scheme from ThunderScan		(compression code 32809)
		NEXT_SUPPORT			2-bit scheme							(compression code 32766)
		LOGLUV_SUPPORT			LogLuv
		JPEG_SUPPORT			JPEG-in-TIFF							(compression code 7)
		ZIP_SUPPORT				experimental Deflate scheme				(compression code 32946)

  Introduced a new MLTiff class as an interface between libtiff and the ML framework.
  A new compile-time switch, ML_TIFF_NATIVE_SUPPORT, controls whether MLOffscreenCanvas uses libtiff to load TIFF files.
  By default, ML_TIFF_NATIVE_SUPPORT is #defined to (!ML_USE_QUICKTIME).
  Like MLJpg (the analogous glue class for the IJG library), MLTiff has a SetOrientation() method that allows me to
  load a TIFF file rotated by 90, 180 or 270 degrees.

• 20040225: A new preferences key, DigitalEmotionApp::kKeyPreferencesUse2ndScreen ("use_2nd_screen") can be set to
  false (default is true) to prevent Digital Emotion from using the second display, even if it's present.

• 20040301: Incorporated a first batch of changes from Alberto Barbati.  Alberto is rewriting the Win32 drag-and-drop
  code, which was previously broken and only partially implemented.  Some pieces of the puzzle are still missing,
  though, notably MLDrag::Start().  I asked Alberto to take care of those as well.

• 20040301: Added MLMessage::kEnter to the MLMessage::EFunctionKey enumeration, and modified MLMessage::IsFunctionKey()
  so that this key (the Enter key on the numeric keypad) is correctly recognized on both platforms.  This key generates
  ASCII 3 on Mac OS, and the VK_RETURN virtual key code on Windows, where it can be told from the regular Return key
  by testing bit 24 of the message LPARAM.

• 20040302: Second and final batch of changes from Alberto Barbati.  Now we can rearrange lists by dragging lines,
  move lines between lists, and even drag text from other programs (such as MS Word) into Digital Emotion views.

• 20040313: Reimplemented Win32 version of MLFileDialog::OpenDialog() so that the filter string is set correctly.
  MLFileDialog::SetValidExtensions() now takes an optional array of human descriptions, currently only used on
  Windows, used to build the file type drop-down list.  Each string in the string array passed to SetValidExtensions()
  can now represent multiple alternate extensions at once, separated by semicolons, e.g., ".jpg;.jpe;.jpeg".

• 20040315: Added MLApplication::PlaySound() function to play WAVE files (or memory-resident WAVE buffers), implemented
  on both Windows (using ::PlaySound()) and Mac (using the QuickTime WAVE importer).  This supersedes the Mac-only
  kCommandPlayMacSound.  Replaced the Mac-only "importer complete" sound resource that plays when an importer thread
  is done with a WAVE file ("cashreg.wav") in the media folder.

• 20040329: Finally got around to implementing MLAffineTransformWithAlpha::EffectLoop32BitBilinear().

• 20040331: Revised MLJpegDecompressor class.  Added an option to skip Exif parsing when this is not needed.

  More importantly, the IJG library supports direct downscaling of JPEG (DCT) data (with scaling ratios of 1/2, 1/4 and 1/8)
  without the need for separate decode and spatial downsampling steps.  This means that the IJG library can generate a
  1/N (with N = 2, 4 or 8) scaled-down image directly from a JPEG file, and it can do this much faster than it would
  take to fully decode the JPEG file and then scale down the resulting full-size image.  So far, the MLJpegDecompressor
  class hadn't been exploiting this feature.  Today I added a new SetScalingRatio() method that can be called before
  the DoImport() step to instruct the IJG library to generate a scaled-down image.  This feature is used in
  DigitalEmotionPhoto::LoadMasterCanvas() to speed up the creation of "working" images from large masters.

• 20040401: For even faster direct downscaling of JPEGs, I applied Guido Vollbeding's patch to the IJG library, based on
  ideas from Rakesh Dugad and Narendra Ahuja, and described here:

		<http://jpegclub.org/jidctred/>
		<http://vision.ai.uiuc.edu/~dugad/research/dct/index.html>

  The only affected source file is "jidctred.c" -- the patched file is actually smaller than the original, and generates
  tighter code.