# 	$Id: skin.pl,v 1.5 2006/03/24 02:33:47 brian Exp $	
#
# skin.pl
# (C) 2006 Brian Millham (bmillham@direcway.com)
# This program is free software, licensed under the same terms as perl.
#
# Ideas for including bitmaps in the skin file were borrowed from
# Aldo Calpini's Win32::GUI::BitmapInline module
#
# 

use strict;
use warnings;
use Win32::GUI;
use Win32::API;
use Storable qw(fd_retrieve);
use File::Temp qw(tempfile);
use Compress::Zlib;
use Getopt::Long;

my $isSkin = 0;
my $mult = 0;
my $skinFile;

GetOptions(
	   'skinFile=s' => \$skinFile,
	   );

die "You must supply --skinFile" if !defined($skinFile);

# Load the required API calls

my $SetWindowPos = Win32::API->new("user32","SetWindowPos", "LLLLLLL", "L")
    or die "Failed to load SetWindowPos";

my $ExtCreateRegion = Win32::API->new("gdi32", "ExtCreateRegion", "PNP", "L")
    or die "Failed to import ExtCreateRegion";

# Some constants.  Most are not used at this time.

sub SWP_FRAMECHANGED() {32}
sub SWP_NOMOVE() {2}
sub SWP_NOSIZE() {1}
sub SWP_NOZORDER() {4}
sub SWP_NOACTIVATE() {16}
sub WM_NCLBUTTONDOWN() {161}
sub HTCAPTION() {2}
sub SMTO_NORMAL() {0}
sub RGN_AND() {1}
sub RGN_COPY() {5}
sub RGN_DIFF() {4}
sub RGN_OR() {2}
sub RGN_XOR() {3}
sub NULLREGION() {1}
sub SIMPLEREGION() {2}
sub COMPLEXREGION() {3}
sub BLACKNESS()	{0x42}
sub NOTSRCERASE()	{0x1100A6}
sub NOTSRCCOPY()	{0x330008}
sub SRCERASE()	{0x440328}
sub DSTINVERT()	{0x550009}
sub PATINVERT()	{0x5A0049}
sub SRCINVERT()	{0x660046}
sub SRCAND()	{0x8800C6}
sub MERGEPAINT()	{0xBB0226}
sub MERGECOPY()	{0xC000CA}
sub SRCCOPY() {0xCC0020}
sub SRCPAINT()	{0xEE0086}
sub PATCOPY()	{0xF00021}
sub PATPAINT()	{0xFB0A09}
sub WHITENESS()	{0xFF0062}

# Load the skin region data

my $temphandle = tempfile();

open (F, "<$skinFile");
binmode(F);
my $c;
while(<F>) {
    $c .= $_;
}

# Write the uncompressed skin file to a temp file

print $temphandle uncompress($c);
close(F);

# Move to the beginning of the file
seek($temphandle, 0, 0);

# Get the skin data from the temp file.
my $skinData = fd_retrieve($temphandle);

# Close, (and remove) the temp file

close ($temphandle);

# Create the bitmaps.  Uses ideas from the BitmapInline module

my $closeBtn = getBMP($skinData->{closeBMP});
my $closeBtn1 = getBMP($skinData->{closeBMP});
my $minBtn = getBMP($skinData->{minBMP});
my $minBtn1 = getBMP($skinData->{minBMP});
my $skin = getBMP($skinData->{skinBMP});

# The region for the main app display.
my $appArea = $ExtCreateRegion->Call(0, length($skinData->{appRegion}), $skinData->{appRegion});

# Create the popup menu for use in skin mode

my $popMenu = Win32::GUI::MakeMenu(
				   "Pop" => "Pop",
				   " > &File" => "File",
				   " >> E&xit" => "pop_menu_exit",
				   " > &Skin" => "Skin",
				   " >> &Remove" => "remove_skin_menu",
				   );

# Create the main menu

my $mainMenu = Win32::GUI::MakeMenu(
				   "&File" => "File",
				   " > E&xit" => "pop_menu_exit",
				   );

# Bad name, it's really grey.  This should be defined in
# the skin file

my $whiteBrush = new Win32::GUI::Brush(-style => 0,
					-color => 0xD6D6D6,
					);

# These probably also be defined in the skin file.

my $bluePen = new Win32::GUI::Pen(
				  -width => 1,
				  -color => 0xFF0000,
				  );
my $blueBrush = new Win32::GUI::Brush(
				      -style => 0,
				      -color => 0xFF0000,
				      );

# Get the size of the skin
my ($bWidth, $bHeight, $bColors, $bBPP) = $skin->Info;

# Create the window the same size as the skin.
#  This needs changing.  Resizing the window to smaller than
#  the skin size cuts off the skin

my $winSkin = Win32::GUI::Window->new(
				      -menu => $mainMenu,
				      -width  => $bWidth,
				      -height => $bHeight,
				      -name   => "winSkin",
				      -text   => "Skin Test",
				      );

# Add a timer to randomly create data.

my $myTimer = $winSkin->AddTimer("myTimer", 1000);

# Turn the timer off, until skinned.

$myTimer->Interval(0);

# Get the info about the min button, and create the button.
my %minRgnInfo = getRgnInfo($skinData->{minRegion});

my $btnMin = $winSkin->AddButton(-name => "btnMin",
				 -size => [$minRgnInfo{width},
					   $minRgnInfo{height}],
				 -pos => [$minRgnInfo{left},
					  $minRgnInfo{top}],
				 -bitmap => $minBtn1,
				 -tip => "Minimize",
				 -flat => 1,
				 );



# Create DCs to load the BMPs
my $dcSkin = Win32::GUI::DC::CreateCompatibleDC(0);
my $closeSkin = Win32::GUI::DC::CreateCompatibleDC(0);
my $minSkin = Win32::GUI::DC::CreateCompatibleDC(0);

# Select the BMPs as an object in the DCs
$dcSkin->SelectObject($skin);
$closeSkin->SelectObject($closeBtn);
$minSkin->SelectObject($minBtn);

# Create the skin button (only used in non-skinned mode)

my $btnSkin = $winSkin->AddButton(-name => "btnSkin",
				  -text => "Skin",
				  -size => [80, 20],
				  -pos => [($winSkin->Width / 2) - 40, ($winSkin->Height / 2) - 10],
				  );

# These 2 buttons are only used when skinned

my %closeRgnInfo = getRgnInfo($skinData->{closeRegion});

my $btnClose = $winSkin->AddButton(-name => "btnClose",
				   -size => [$closeRgnInfo{width},
					     $closeRgnInfo{height}],
				   -pos => [$closeRgnInfo{left},
					    $closeRgnInfo{top}],
				   -bitmap => $closeBtn1,
				   -tip => "Close",
				   -flat => 1,
				   );
my %appRgnInfo = getRgnInfo($skinData->{appRegion});

# Hide the skin buttons, since we don't start in skin mode
$btnClose->Hide();
$btnMin->Hide();
$winSkin->Show();

Win32::GUI::Dialog();

sub winSkin_Paint
{
    my ($dc) = @_;

    if ($isSkin) {
# Copy the bitmaps into the DC
	$dc->BitBlt(0, 0, $bWidth, $bHeight, $dcSkin, 0, 0, SRCCOPY);

	$dc->BitBlt($closeRgnInfo{left}, $closeRgnInfo{top},
		    $closeRgnInfo{width}, $closeRgnInfo{height},
		    $closeSkin, 0, 0, SRCCOPY);

	$dc->BitBlt($minRgnInfo{left}, $minRgnInfo{top},
		    $minRgnInfo{width}, $minRgnInfo{height},
		    $minSkin, 0, 0, SRCCOPY);

# Write the title to the skin
	$dc->BkMode(1);
	$dc->TextColor(0xFFFFFF);
	$dc->TextOut($skinData->{titleLeft}, $skinData->{titleTop}, $skinData->{appName});

# Fill the app area with the app area brush
	$dc->FillRgn($appArea, $whiteBrush);

# Show the random data
	$dc->SelectObject($bluePen);
	$dc->SelectObject($blueBrush);
	my ($rgnLeft, $rgnTop, $rgnRight, $rgnBottom) = Win32::GUI::Region::GetRgnBox($appArea);
	my $rgnHeight = $rgnBottom - $rgnTop;

# Set the clip region to the app region.  This allow using a standard
# rectangle to show the data.       
	$dc->SelectClipRgn($appArea);
	my $newtop = $rgnBottom - ($rgnHeight * $mult);
	$dc->Rectangle($rgnLeft, $newtop, $rgnRight, $rgnBottom);
	$dc->Validate;
    }
    return(1);
}

sub winSkin_MouseRightDown
{
    if ($isSkin) {
# Right mouse in skin mode, show the popup menu
	my ($x, $y) = Win32::GUI::GetCursorPos();
	$winSkin->TrackPopupMenu($popMenu->{Pop});
    }
}

sub btnSkin_Click
{
    if (!$isSkin) {
	addRegion();
	$btnSkin->Hide;
	$btnClose->Show();
	$btnMin->Show();
	$myTimer->Interval(1000);
    }
}

sub btnMin_Click
{
    $winSkin->Minimize();
}

	
sub btnClose_Click
{
    return(-1);
}

sub winSkin_Terminate
{
    return(-1);
}

sub pop_menu_exit_Click
{
    winSkin_Terminate();
}

sub remove_skin_menu_Click
{
    removeRegion();
    $btnClose->Hide();
    $btnMin->Hide();
    $myTimer->Interval(0);
    $btnSkin->Show;
}

sub winSkin_MouseDown
{
    return if !$isSkin;

# Allow the skin to be dragged
    Win32::GUI::SendMessage($winSkin, WM_NCLBUTTONDOWN, HTCAPTION, 0);
}


sub removeRegion
{
# Remove the region
    $winSkin->SetWindowRgn(0, 1);

# Restore the caption and borders
    $winSkin->Change(-pushstyle => WS_CAPTION);
    $winSkin->Change(-pushstyle => WS_SIZEBOX);

# Restore the menu
    $winSkin->SetMenu($mainMenu);

# Force the window redraw
    $SetWindowPos->Call($winSkin->{-handle}, 0, 0, 0, 0, 0,
			SWP_FRAMECHANGED|SWP_NOMOVE|SWP_NOSIZE|
			SWP_NOACTIVATE|SWP_NOZORDER);
    $isSkin = 0;
}

sub addRegion
{
# Remove the caption and the borders

    $winSkin->Change(-popstyle => WS_CAPTION);
    $winSkin->Change(-popstyle => WS_SIZEBOX);

# Remove the menu
    $winSkin->SetMenu(0);

    $SetWindowPos->Call($winSkin->{-handle}, 0, 0, 0, 0, 0,
			SWP_FRAMECHANGED|SWP_NOMOVE|SWP_NOSIZE|
			SWP_NOACTIVATE|SWP_NOZORDER);

# Create the region
    my $rgnSkin = $ExtCreateRegion->Call(0, length($skinData->{skinRegion}), $skinData->{skinRegion});

# Set the region
    $winSkin->SetWindowRgn($rgnSkin, 1);

# 
    $isSkin = 1;
#    return $appTmp;
}

sub getBMP
{
    my ($b) = @_;

# This is based on the BitmapInline code!

    open(B, ">~$$.tmp");
    binmode(B);
    print B $b;
    close(B);
    undef ($b);
    my $bmp = Win32::GUI::Bitmap->new("~$$.tmp");
    unlink("~$$.tmp");
    return $bmp;
}

sub getRgnInfo
{
    my ($rgn) = @_;

# Decode the region information

    my ($dwSize, $iType, $nCount, $nRgnSize, $left, $top, $right, $bottom, $b) = unpack("LLLLLLLLC*", $rgn);

    my %rInfo = (
		 'dwSize' => $dwSize,
		 'iType' => $iType,
		 'nCount' => $nCount,
		 'nRgnSize' => $nRgnSize,
		 'left' => $left,
		 'top' => $top,
		 'right' => $right,
		 'width' => $right - $left,
		 'height' => $bottom - $top,
		 'bottom' => $bottom,
		 'buffer' => $b,
		 );
    return %rInfo;
}

sub myTimer_Timer
{
# Create random data to show in the app window.
    $mult = rand(100) / 100;
    $winSkin->InvalidateRect(1);
}

# $Log: skin.pl,v $
# Revision 1.5  2006/03/24 02:33:47  brian
# Added header comments, and credits for borrowed code.
#
# Revision 1.4  2006/03/24 02:19:24  brian
# Added comments.
#
