# Absolute Basic Standards
use strict;
use warnings;

# This will be Win32/Win32::GUI app.
use Win32;
use Win32::GUI;

####
# Constants not readily available already.
use constant WM_NOTIFY		=> 78;
# TVN = tv notification
use constant TVN_FIRST 		=> -400;
use constant TVN_BEGINDRAG	=> TVN_FIRST - 7;
use constant TVN_BEGINRDRAG => TVN_FIRST - 8;
# TVIS = tv item state
use constant TVIS_FOCUSED		=> 1;
use constant TVIS_SELECTED		=> 2;
use constant TVIS_CUT			=> 4;
use constant TVIS_DROPHILITED	=> 8;
use constant TVIS_BOLD			=> 16;
use constant TVIS_EXPANDED		=> 32;
use constant TVIS_EXPANDEDONCE	=> 64;
use constant TVIS_OVERLAYMASK	=> 0xF00;
use constant TVIS_STATEIMAGEMASK=> 0xF000;
use constant TVIS_USERMASK		=> 0xF000;

# global dragging flag.
my $fDragging = 0;

#various flags for operation
my $insert_mode 	= 0x00;
my $fixed_roots 	= 1;
my $fixed_parents 	= 0;
my $fixed_depths 	= 0;

# various globals
#my $cur = undef;
my $dragNode=0;
my $liDrag = undef;

#@# drag cursor. outmoded. optional?
#my $drag = new Win32::GUI::Cursor('drag.cur');

###
# Main window
my $win = new Win32::GUI::Window(
	-name	=> 'wTreviewDrag',
	-caption=> 'Treeview Drag. Proper.',
	-size	=> [300,320],
);

#@# as a test. failed.
#$win->ChangeCursor($drag);

###
# image list for the tvw
my $il = new Win32::GUI::ImageList(16,16,0,2,2);
$il->AddBitmap(new Win32::GUI::Bitmap('closedFolder.bmp'));
$il->AddBitmap(new Win32::GUI::Bitmap('openFolder.bmp'));
$il->AddBitmap(new Win32::GUI::Bitmap('item.bmp'));

###
# treeview
my $tvw = $win->AddTreeView(
	-name	=> 'tvwDrag',
	-size	=> [300,300],
	-pos	=> [0,0],
	-lines	=> 1,
	-buttons=> 1,
	-lines 	=> 1,
	-rootlines=>1,
	-imagelist	=> $il,
	-disabledragdrop => 0,
	
	-eventmodel => 'byref',
	# for drag tracking
	-onMouseMove => \&tvwDrag_MouseMove,
	-onMouseRightUp=> \&tvwDrag_MRightUp,
	-onMouseRightDown=> \&tvwDrag_MRightDown,
	# for 'folder' state management
	-onCollapse	=> \&tvwDrag_Collapse,
	-onExpand	=> \&tvwDrag_Expand,
);

#hook to the BeginDrag messages.
$tvw->Hook(TVN_BEGINDRAG,\&tvwDrag_BeginDrag);
$tvw->Hook(TVN_BEGINRDRAG,\&tvwDrag_BeginDrag);

# Fill Treeview for testing.
{
	my $iNode = 0;
	my $sNode = 0;
	for(my $i=0; $i<20; $i++){
		$iNode = $tvw->InsertItem(
			-text	=> "Item $i",
			-image	=> 0,
			-parent	=> 0,
		);
		$tvw->InsertItem(
			-text	=> "SubItem $sNode",
			-image	=> 2,
			-parent	=> $iNode,
		);
		$sNode++;
		$tvw->InsertItem(
					-text	=> "SubItem $sNode",
					-image	=> 2,
					-parent	=> $iNode,
				);
		$sNode++;
	}
}

###
# Run
$win->Show();


Win32::GUI::Dialog();

#####################
# Routines

#change to the open folder image
sub tvwDrag_Expand{
	my $tvw = shift;
	my $node = shift;
	my %info = $tvw->GetItem($node);
	$info{-image}=1;
	$info{-selectedimage}=1;
	$tvw->SetItem($node,%info);
}

#change to the closed folder image
sub tvwDrag_Collapse{
	my $tvw = shift;
	my $node = shift;
	my %info = $tvw->GetItem($node);
	$info{-image}=0;
	$info{-selectedimage}=0;
	$tvw->SetItem($node,%info);
}

# grab proper node ID for dragging (tends to slide if you're fast)
sub tvwDrag_MRightDown{
	my $tvw = shift;
	$dragNode = $tvw->HitTest($win->ScreenToClient(Win32::GUI::GetCursorPos()));
}

# start the drag
sub tvwDrag_BeginDrag{
	#@# the TYPE NEEEDS to be CHECKED for WM_NOTIFY!!
	(my $object, my $wParam, my $lParam, my $type, my $msgcode) = @_;
	#Is it a notification?
	if($type == WM_NOTIFY){
		#@# these are seperated, but perform identically atm.
		
		#dragging with left mouse
		if( $msgcode == TVN_BEGINDRAG ){
			print "BeginDrag\n";
			#tvwBeginDrag();
		}
		# dragging with right mouse
		if( $msgcode == TVN_BEGINRDRAG ){
			print "BeginRDrag\n";
			tvwBeginDrag();
		}
	}
}

#monitor the drag
sub tvwDrag_MouseMove{
	if( $fDragging ){
		#Win32::GUI::SetCursor($drag);
		print "tvwDrag MouseMove\n";
		my $x;
		my $y;
		#@#
		($x, $y) = $win->ScreenToClient(Win32::GUI::GetCursorPos());

		#capture drag inside treeview
		printf ("Mouse-client[%s,%s] TVW[W:%s,H:%s]\n",$x,$y,$tvw->Width(), $tvw->Height());
		my $fMoveMouse =0;
		if( $x < $tvw->Left() ){
			$x = $tvw->Left();		
			$fMoveMouse =1;
		}elsif( $x > ($tvw->Left() + $tvw->Width()) ){
			$x = ($tvw->Left() + $tvw->Width());
			$fMoveMouse =1;
		}
		if( $y < 0 ){
			$y = $tvw->Top();
			$fMoveMouse =1;
		}elsif( $y > ($tvw->Top() + $tvw->Height())){
			$y = ($tvw->Top() + $tvw->Height());
			$fMoveMouse =1;
		}
		if($fMoveMouse){
			Win32::GUI::SetCursorPos($tvw->ClientToScreen($x-3,$y-3));
		}else{
			my $hit = $tvw->HitTest($x, $y);
			#$hit  = $tvw->GetLastVisible() if !$hit;
=head *
use GetItemRect to get the bounding rectangle, and then calculate distance to edges,
in order to make use of insertion mark and inserting between items.
=cut
			if( $hit != 0 && $hit != 1 ){
				my @itemRect = $tvw->GetItemRect($hit,1);
				printf("Boundings[L:%s,T:%s,R:%s,B:%s] Mouse[X:%s,Y:%s]\n",@itemRect,$x,$y);
				#high edge is itemRect(1) + int((itemRect(3)-itemRect(1))/5)
				#low  edge is itemRect(3) - int((itemRect(3)-itemRect(1))/5)
				if( $x >= $itemRect[0] &&  $x <= $itemRect[2]){
					if( $y > ( $itemRect[1] + int(($itemRect[3]-$itemRect[1])/5) ) &&
						$y < ( $itemRect[3] - int(($itemRect[3]-$itemRect[1])/5) ) ){
						$tvw->SetInsertMark(0);
						$tvw->SelectDropTarget($hit);
					}elsif( $y < ( $itemRect[1] + int(($itemRect[3]-$itemRect[1])/5) ) ){
						$tvw->SetInsertMark($hit, 0);
						$insert_mode = 0x01;
					}elsif( $y > ( $itemRect[3] - int(($itemRect[3]-$itemRect[1])/5) ) ){
						print 
						$tvw->SetInsertMark($hit, 1);
						$insert_mode = 0x00;
					}
				}else{
					#
					$tvw->SetInsertMark(0);
					$tvw->SelectDropTarget(0);
				}
=head *2
Invalidate the rectangle from below lowest, and above highest.
--
This removes artifacts with light flicker. 
Does not work @ high speed.
ALSO removes the drag image.
=cut
				# calculate the surrouning rectangle
				{
					my ($left, $top, $right, $bottom);
					$left = $tvw->Left();
					$right = $tvw->Left() + $tvw->Width();
					my ($x, $y) = $win->ScreenToClient(Win32::GUI::GetCursorPos());
					$top = $y - ( $tvw->GetItemHeight() * 2); # y - height of item.
					$bottom = $y + ( $tvw->GetItemHeight() *2 ) ; # y+ height of item.
					# invalidate the rectangle to prevent artifacts.
					$win->InvalidateRect($left, $top, $right, $bottom,0);
				}
			}
			if( defined($liDrag) ){Win32::GUI::ImageList::DragMove($x-5,$y-5) };
			
		}
	}
}


#trigger end of drag.
sub tvwDrag_MRightUp{
	print "tvwDrag MouseRightUp ",time,"\n";
	if($fDragging){
		if( defined($liDrag)){
			Win32::GUI::ImageList::DragLeave($tvw);
			Win32::GUI::ImageList::EndDrag();
		}
		tvwEndDrag();
	}
}


# Generic BeginDrag, mouse button isolated.
sub tvwBeginDrag{
	# begin mouse capture
	$tvw->SetCapture();
	# manage the drag image creation.
	$liDrag = $tvw->CreateDragImage($dragNode);
	print "Drag Image: $liDrag\n";
	if( defined($liDrag)){
		Win32::GUI::ImageList::BeginDrag($liDrag,0,0,0);
		Win32::GUI::ImageList::DragEnter($tvw,50,50);
	}
	#set drag flag
	$fDragging = 1;
}

# Generic EndDrag, mouse button isolated.
sub tvwEndDrag{
	$fDragging = 0;
	print "tvwEndDrag, PUT\n";
	#my $node = $tvw->HitTest($win->ScreenToClient(Win32::GUI::GetCursorPos()));
	#$node = $tvw->GetLastVisible() if $node;
	#$tvw->SelectDropTarget($node);
	$tvw->Move($dragNode,$insert_mode);
	$tvw->SelectDropTarget(0);
	$tvw->SetInsertMark(0);
	$tvw->ReleaseCapture();
}



#@######################
# Insertion of new routines to Treeview class
package Win32::GUI::TreeView;
use constant TVIS_EXPANDED		=> 32;

sub HasChildren{
	my $self = shift;
	my $node = shift;
	
	my %info = $self->GetItem($node);
	
	return $info{-children};
}

sub IsSubitem{
	my $tvw = shift;
	my $node = shift;
	my $parent = shift;
	
	while($node){
		$node = $tvw->GetParent($node);
		if($parent == $node){
			return 1;
		}
	}
	
	return 0;
	
}

############
## Procedure for Drag n Drop move of TreeView Items.
=head *
needs flags for: 
0 insertion
0 fixed parents
0 fixed 'root' items
=cut
#
sub Move{
	my $tvw = shift;
	my $source = shift;
	my $insertion = shift;
	
	my $parent;
	
	if( $source != 0 ){
		my %item = $tvw->GetItem($source);
		my $target = $tvw->GetDropHilight();
		if( $target ){
			my %titem = $tvw->GetItem($target);
			#fixed_roots (no moving/adding of 'root' items)
			if( $fixed_roots && $item{-parent} == 0 ){
				if( $titem{-parent} != $item{-parent} ){
					printf("move-fail roots1[%s,%s]\n",$titem{-parent},$titem{-image});
					return undef;
				}
			}
			#target is a standalone/no children
			if(!$tvw->HasChildren($target) && $titem{-image} != 1 && $titem{-image} != 0){
				$parent = $tvw->GetParent($target);
				%titem = $tvw->GetItem($parent);
				print "No children, Getting Parent\n";
			}
			if( $source != $target && !$tvw->IsSubitem($target,$source)){
				#FIXED_ROOTS CHECKING
				if( $fixed_roots && $titem{-parent} == 0 ){
					if( $item{-parent} == 0 && $titem{-parent} == 0){
						# allow re-arrange of roots.
						$parent = 0;
						#$insertion = 0x00;
					}elsif( $titem{-image} != 0 && $titem{-image} != 1 ){
						# deny addition to a roots
						printf("move-fail roots2[%s,%s]\n",$titem{-parent},$titem{-image});
						return undef;
					}
				}
				if( $fixed_parents ){
					if( $item{-parent} != $tvw->GetParent($target) ){
						print "#fail fixed parents\n";
						return undef;
					}
				}
				$item{-parent}=defined($parent)?$parent:$target;
				if( !defined($insertion) ){
					$insertion = $insert_mode;
				}
				#0xFFFF0001: at the beginning of the list
				#0xFFFF0002: at the end of the list
				#0xFFFF0003: in alphabetical order
				#0x00000000: after target
				#0x00000001: before target
				if( $insertion == 0x00 ){
					$item{-item}=$target;
				}elsif( $insertion == 0x01 ){
					if( $tvw->GetPrevSibling($target) ){
						$item{-item}=$tvw->GetPrevSibling($target);
					}else{
						print "Move to Top\n";
						$item{-item}=0xFFFF0001;
					}	
				}else{
					$item{-item}=$insertion;
				}

				my $nnode = $tvw->InsertItem(%item);
				## parse all children, and their children.
				if( $tvw->HasChildren($source) ){
					my $tFixedParents = $fixed_parents;
					$fixed_parents = 0;
					my $child = $tvw->GetChild($source);
					$tvw->SelectDropTarget($nnode);
					while($child){
						my %info = $tvw->GetItem($child);
						#printf("Parent: %s Child:%s\n",$item{-text},$info{-text});

						$tvw->Move($child);
						$child = $tvw->GetChild($source);
						$tvw->SelectDropTarget($nnode);
					}				
					$fixed_parents = $tFixedParents;
				}
				# expand the new location if the old was expanded.
				if( $item{-state} & TVIS_EXPANDED ){
					$tvw->Expand($nnode)
				}
				$tvw->DeleteItem($source);
			}else{
				return undef;
			}
		}else{
			return undef;
		}
	}
}

#