=begin
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
# Designed by Fredo6 - Copyright April 2009

# Permission to use this software for any purpose and without fee is hereby granted
# Distribution of this software for commercial purpose is subject to:
#  - the expressed, written consent of the author
#  - the inclusion of the present copyright notice in all copies.

# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#-----------------------------------------------------------------------------
# Name			:   HoverSelect_Main.rb
# Original Date	:   8 May 2009 - version 1.0
# Description	:   Script to select Edges with the Eraser metaphor
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
=end

module F6_HoverSelect

#---------------------------------------------------------------------------------------------------------------------------
# Overall Configuration for the Tools	
#---------------------------------------------------------------------------------------------------------------------------

MENU_PERSO = [:T_MenuTools_Fredo6Collection, "Tools", :T_MenuPlugins_Fredo6Collection, "Plugins"]

#---------------------------------------------------------------------------------------------------------------------------
#Constants for HoverSelect Module (do not translate here, use Translation Dialog Box instead)	
#---------------------------------------------------------------------------------------------------------------------------

#Menu and Toolbar icons
T6[:HVS_MAIN_Menu] = "Hover Select Edges"
T6[:HVS_MAIN_Tooltip] = "Multi-selection of Edges with Eraser-like metaphor"
HVS_MAIN_Icon = "Main"

# Strings for the application
T6[:MSG_Status_Select] = "Click down and mouse over model to select edges (see options in contextual menu)"
T6[:TIP_Edge_Add] = "Click to Select Edge"
T6[:TIP_Edge_Del] = "Click to Unselect Edge"
T6[:TIP_Vertex_Add] = "Click to Select Edges at vertex"
T6[:TIP_Vertex_Del] = "Click to Unselect Edges at vertex"
T6[:TIP_Face] = "Click to select all edges of the face"
T6[:TIP_Exit] = "Click to Exit tool"
T6[:TIP_Add] = "Add to selection"
T6[:TIP_Remove] = "Remove from selection"

T6[:VCB_Extend_Connected] = "ALL CONNECTED"
T6[:VCB_Extend_Curve] = "CURVE"
T6[:VCB_Extend_Follow] = "FOLLOW"
T6[:VCB_Mode_Erase] = "UNSELECT"
T6[:VCB_Rect_Processing] = "PROCESSING..."

#Contextual menus
T6[:MNU_Done] = "Done and Exit tool (Enter or click in empty space)"
T6[:MNU_Cancel] = "Clear all selection (Esc)"
T6[:MNU_History_Last] = "Undo last selection (ARROW LEFT)"
T6[:MNU_History_Next] = "Redo last selection (ARROW RIGHT)"
T6[:MNU_History_LastBlock] = "Undo selection since last click down (ARROW DOWN)"
T6[:MNU_History_NextBlock] = "Redo last selection from click down (ARROW UP)"
T6[:MNU_Extend_Connected] = "Extend selection to all connected edges (CTRL + SHIFT)"
T6[:MNU_Extend_Curve] = "Extend selection to curve (CTRL)"
T6[:MNU_Extend_Follow] = "Extend selection to cofacial and aligned edges (SHIFT)"
T6[:MNU_EdgePropFilter] = "Filter on Edge properties"
T6[:MNU_EdgePropFilter2] = "Edge Prop. Filter"
T6[:MNU_RemoveFilter] = "Click to remove filter"
T6[:MNU_Init_Clear] = "Automatic Clear at startup"

#Labels for Default Parameters
T6[:DEFAULT_SectionAll] = "Active Options at start up"
T6[:DEFAULT_Flag_InitClear] = "Clear Selection when tool activated"
T6[:DEFAULT_Aperture] = "Aperture for picking in pixel (0 means Sketchup default)"
T6[:DEFAULT_Flag_Modifiers] = "Selection modifiers"
T6[:DEFAULT_Flag_EdgePropFilter] = "Filter for Edge properties"
T6[:DEFAULT_AngleMax] = "Maximum Edge Angle for Follow mode (degree)"
T6[:DEFAULT_RectMaxNum] = "Feedback for Rectangle selection when nb of elements is lower than"

#--------------------------------------------------------------------------------------------------------------
# Plugin Startup Method
#--------------------------------------------------------------------------------------------------------------			 

def F6_HoverSelect.startup

	#Top menu
	MYPLUGIN.declare_topmenu nil, MENU_PERSO
	
	#Declaring Commands
	MYPLUGIN.declare_command(:HVS_MAIN_) { F6_HoverSelect.launch }
	MYPLUGIN.default_icons_visible [:HVS_MAIN_]

	#Common Default Parameters
	MYDEFPARAM.separator :DEFAULT_SectionAll
	MYDEFPARAM.declare :DEFAULT_Flag_InitClear, false, 'B'
	MYDEFPARAM.declare :DEFAULT_Aperture, 0, 'I:>=0<=30'
	MYDEFPARAM.declare :DEFAULT_RectMaxNum, 800, 'I:>=0<=3000'
	MYDEFPARAM.declare :DEFAULT_Flag_Modifiers, '', 'M',  [['X', 'All Connected'], ['C', 'Curve'], ['F', 'Follow']]
	MYDEFPARAM.declare :DEFAULT_Flag_EdgePropFilter, 'P;;S;;M;;H', 'M', 
	                   [['P', 'Plain'], ['S', 'Soft'], ['M', 'Smooth'], ['H', 'Hidden']]
	MYDEFPARAM.declare :DEFAULT_AngleMax, 30.0, 'F:>=0<=60'
					
	#Startup of the Plugin
	MYPLUGIN.go
end

#--------------------------------------------------------------------------------------------------------------
# Configuration and Utilities Method at module level
#--------------------------------------------------------------------------------------------------------------			 				   


#--------------------------------------------------------------------------------------------------------------
# Top Calling functions: create the classes and launch the tools
#--------------------------------------------------------------------------------------------------------------			 				   

def F6_HoverSelect.launch
	Sketchup.active_model.select_tool HoverSelectTool.new
end

#--------------------------------------------------------------------------------------------------------------
# Selection Tool for picking up entities
#--------------------------------------------------------------------------------------------------------------			 				   

class HoverSelectTool

#***********************************
# Class Methods
#***********************************

@@persistence = nil

#Initialization of the tool
def initialize
	#Text Initialization
	@status_text = T6[:MSG_Status_Select]
	@text_extend_connected = T6[:VCB_Extend_Connected]
	@text_extend_curve = T6[:VCB_Extend_Curve]
	@text_extend_follow = T6[:VCB_Extend_Follow]
	@text_rect_processing = T6[:VCB_Rect_Processing]
	@text_mode_erase = T6[:VCB_Mode_Erase]
	@tip_edge_add = T6[:TIP_Edge_Add]
	@tip_edge_del = T6[:TIP_Edge_Del]
	@tip_vertex_add = T6[:TIP_Vertex_Add]
	@tip_vertex_del = T6[:TIP_Vertex_Del]
	@tip_add = T6[:TIP_Add]
	@tip_remove = T6[:TIP_Remove]
	@tip_face = T6[:TIP_Face]
	@tip_exit = T6[:TIP_Exit]
	
	@mnu_clear_all = T6[:MNU_Cancel]
	@mnu_done = T6[:MNU_Done]
	@mnu_navig_left = T6[:MNU_History_Last]
	@mnu_navig_right = T6[:MNU_History_Next]
	@mnu_navig_down = T6[:MNU_History_LastBlock]
	@mnu_navig_up = T6[:MNU_History_NextBlock]
	@mnu_connected = T6[:MNU_Extend_Connected]
	@mnu_curve = T6[:MNU_Extend_Curve]
	@mnu_follow = T6[:MNU_Extend_Follow]
	@mnu_init_clear = T6[:MNU_Init_Clear]
		
	#Cursor initialization
	@hotx = 3
	@hoty = 2
	@id_cursor_pick_invalid = MYPLUGIN.create_cursor "Invalid", @hotx, @hoty	
	@id_cursor_pick_edge = MYPLUGIN.create_cursor "Main", @hotx, @hoty	
	@id_cursor_validate = MYPLUGIN.create_cursor "Validate", @hotx, @hoty	
	@id_cursor_edge_add = MYPLUGIN.create_cursor "EdgeAdd", @hotx, @hoty	
	@id_cursor_edge_del = MYPLUGIN.create_cursor "EdgeDel", @hotx, @hoty	
	@id_cursor_vertex_add = MYPLUGIN.create_cursor "VertexAdd", @hotx, @hoty	
	@id_cursor_vertex_del = MYPLUGIN.create_cursor "VertexDel", @hotx, @hoty	
	@id_cursor_edge_face = MYPLUGIN.create_cursor "EdgeFace", @hotx, @hoty	
	@id_cursor_rectangle = MYPLUGIN.create_cursor "Rectangle", @hotx, @hoty	
	@sizecursor = 24
		
	#Drawing elements
	init_palette
	@mark_extended = G6::DrawMark_FourArrows.new
	@mark_follow = G6::DrawMark_H2Arrows.new
	@mark_curve = G6::DrawMark_Curve.new
	
	#Configuration
	@aperture = MYDEFPARAM[:DEFAULT_Aperture]
	@aperture = 5 if @aperture < 5 
	@too_fast = @aperture * 2
	@rect_maxnum = MYDEFPARAM[:DEFAULT_RectMaxNum]
	
	#Restablishing previous parameters
	persistence_restore
end

def make_proc(&proc)
	proc
end
	
#Initialize the button palette
def init_palette
	@palette = F6_HoverSelect::Palette.new
	hsh = { :tooltip => @mnu_clear_all, :draw_proc => :cross_NE3, :main_color => 'red' }
	@palette.declare_button(:pal_clear_all, hsh) { clear_selection }
	@palette.declare_separator
	
	proc = make_proc() { @init_clear }
	hsh = { :height => 32, :width => 56, :value_proc => proc, :tooltip => @mnu_init_clear, :text => "clear at\nstart" }
	@palette.declare_button(:pal_init_clear, hsh) { toggle_init_clear }
	@palette.declare_separator
	
	#Edge filters
	proc = make_proc() { @edge_prop_filter }
	hsh = { :value_proc => proc, :text => T6[:MNU_EdgePropFilter2], :tooltip => T6[:MNU_RemoveFilter], :main_color => 'blue' }
	@palette.declare_edge_prop(:pal_filter, nil, hsh) { filter_edges @palette.button_get_value(:pal_filter) }
	@palette.declare_separator
	
	#Modifiers
	hshb = { :main_color => 'blue' }
	proc = make_proc() { @extend_connected }
	hsh = { :value_proc => proc, :tooltip => @mnu_connected, :draw_proc => :arrow_RULD, :frame_color => 'red' }
	@palette.declare_button(:mod_connected, hsh, hshb) { toggle_extend_connected }
	
	proc = make_proc() { @extend_curve }
	hsh = { :value_proc => proc, :tooltip => @mnu_curve, :draw_proc => :circle_E2, :main_color => 'blue', :draw_scale => 0.75 }
	@palette.declare_button(:mod_curve, hsh, hshb) { toggle_extend_curve }
	
	proc = make_proc() { @extend_follow }
	hsh = { :value_proc => proc, :tooltip => @mnu_follow, :draw_proc => :arrow_RL, :frame_color => 'green' }
	@palette.declare_button(:mod_follow, hsh, hshb) { toggle_extend_follow }
	@palette.declare_separator
	
	#Navigation History
	hsh = { :main_color => 'green', :draw_scale => 0.6 }
	hshb = { :draw_proc => :arrow_LP, :tooltip => @mnu_navig_left }
	@palette.declare_button(:pal_navig_left, hsh, hshb) { unwind_last_from_history }
	hshb = { :draw_proc => :arrow_RP, :tooltip => @mnu_navig_right }
	@palette.declare_button(:pal_navig_right, hsh, hshb) { unwind_next_from_history }
	hshb = { :draw_proc => :arrow_DP, :tooltip => @mnu_navig_down }
	@palette.declare_button(:pal_navig_down, hsh, hshb) { unwind_last_block_from_history }
	hshb = { :draw_proc => :arrow_UP, :tooltip => @mnu_navig_up }
	@palette.declare_button(:pal_navig_up, hsh, hshb) { unwind_next_block_from_history }
	@palette.declare_separator
	
	#Exit
	hsh = { :tooltip => @mnu_done, :draw_proc => :valid }
	@palette.declare_button(:pal_exit, hsh) { execute_action }
end

#Custom drawing for the palette
def drawing_button(symb, dx, dy)
	[]
end

#Activation of the tool
def activate
	@model = Sketchup.active_model
	@selection = @model.selection
	@view = @model.active_view
	@eye = @view.camera.eye
	@ph = @view.pick_helper
	@ip = Sketchup::InputPoint.new		
	@button_down = false
	@ctrl_down = false
	@shift_down = false	
	@timer_toggle = nil
	@mode_erase = false
	@face = nil
	@ls_fullhistory = []
	@nb_fullhistory = 0
	@nb_blockhistory = nil
	@hsh_all_vertices = {}
	@xdown = nil
	@ydown = nil
	
	check_initial_selection	
	
	@view.invalidate
	show_info
end

#Saving parameters across sessions
def persistence_save
	@@persistence = {} unless @@persistence
	type = 'Param'
	@@persistence[type] = {} unless @@persistence[type]
	hsh = @@persistence[type]
	
	#Parameters specific of the tool
	hsh["EdgePropFilter"] = @edge_prop_filter
	wrap_modifiers
	hsh["Modifiers"] = @modifiers
	hsh["InitClear"] = @init_clear
	hsh["AngleMax"] = @angle_max.radians
end

#Restoring parameters across sessions
def persistence_restore
	type ='Param'
	@@persistence = {} unless @@persistence
	hsh = @@persistence[type]
	unless hsh
		hsh = @@persistence[type] = {}
		hsh["EdgePropFilter"] = MYDEFPARAM[:DEFAULT_Flag_EdgePropFilter]
		hsh["Modifiers"] = MYDEFPARAM[:DEFAULT_Flag_Modifiers]
		hsh["InitClear"] = MYDEFPARAM[:DEFAULT_Flag_InitClear]
		hsh["AngleMax"] = MYDEFPARAM[:DEFAULT_AngleMax]
	end	
	@persistence = hsh
	
	@edge_prop_filter = hsh["EdgePropFilter"]
	@modifiers = hsh["Modifiers"]
	@init_clear = hsh["InitClear"]
	@angle_max = hsh["AngleMax"]
	@angle_max = @angle_max.degrees
	split_modifiers
end

#Recreate the toggle flags from the modifier string
def split_modifiers
	@modifiers = "" unless @modifiers
	@extend_connected = (@modifiers =~ /X/i)
	@extend_curve = (@modifiers =~ /C/i)
	@extend_follow = (@modifiers =~ /F/i)
end

#Build the modifier string from the toggle flags 
def wrap_modifiers
	lm = []
	lm.push 'X' if @extend_connected
	lm.push 'C' if @extend_curve
	lm.push 'F' if @extend_follow
	@modifiers = lm.join ';;'
end

#Deactivate tool
def deactivate(view)
	@x = nil
	persistence_save
	view.invalidate
end

#Setting the proper cursor
def onSetCursor
	ic = @palette.onSetCursor
	return (ic != 0) if ic
	if @mode_rectangle
		ic = @id_cursor_rectangle
	elsif @no_entity
		ic = (@button_down) ? @id_cursor_pick_edge : @id_cursor_validate
	elsif @deny_entity
		ic = @id_cursor_pick_invalid
	elsif @mode_erase || (@entity && !@button_down && selection_include?(@entity))
		ic = (@vertex_sel && !@button_down) ? @id_cursor_vertex_del : @id_cursor_edge_del
	elsif @face
		ic0 = (@mode_erase) ? @id_cursor_edge_del : @id_cursor_edge_add
		if (@button_down && !@xdown)
			ic = ic0 
		else	
			ic = @id_cursor_edge_face
		end	
	elsif @entity
		ic = (@vertex_sel && !@button_down) ? @id_cursor_vertex_add : @id_cursor_edge_add
	else
		ic = @id_cursor_pick_edge
	end	
	UI::set_cursor ic
end

#Cancel event
def onCancel(flag, view)
	#User did an Undo
	case flag
	when 1, 2	#Undo or reselect the tool
		activate
		return
	when 0	#user pressed Escape
		if @mode_rectangle
			@mode_rectangle = false
			@button_down = false
		else	
			clear_selection
		end	
	end
	onMouseMove_zero
end

#Button click - Means that we end the selection
def onLButtonDown(flags, x, y, view)
	return if @palette.onLButtonDown(flags, x, y, view)
	entity = entity_under_mouse(view, x, y)
	@entity_down = @entity
	@button_down = true
	@last_entity = nil
	@xdown = x
	@ydown = y
	@nb_blockhistory = @ls_fullhistory.length
	if entity && selection_include?(entity)
		@mode_erase = true
	else
		@mode_erase = false
	end	
	onMouseMove_zero
end

def cancel_button_down(flags)
	if (flags >= 0) && @button_down && (flags & 1 != 1)
		@button_down = false
		@mode_rectangle = false
		return true
	end	
	false
end	
	
def onMouseLeave(view)
	@palette.onMouseLeave(view)
end

def onMouseEnter(view)
	@palette.onMouseEnter(view)
end	

#Button click - Means that we end the selection
def onLButtonUp(flags, x, y, view)
	return if @palette.onLButtonUp(flags, x, y, view)
	prevbut = @button_down
	@button_down = false
	
	#rectangle mode
	if @mode_rectangle
		set_warning @text_rect_processing
		UI.start_timer(0.3) { rectangle_compute_selection }
		return
	
	#Exit the tool
	elsif @no_entity && @last_entity == nil
		@view.invalidate
		return exit_tool if prevbut
	end	
	
	#selection mode
	entity = entity_under_mouse(view, x, y)
	if close_down_in_pixel(x, y) && !@entity_down
		@face_init = (@face) ? @face : nil
	end	
	@mode_erase = false
	@last_entity = nil
	onMouseMove_zero
end

#Check if the current mouse position is closed from the Click down position
def close_down_in_pixel(x, y)
	return false unless @xdown
	Math.sqrt((x - @xdown) * (x - @xdown) + (y - @ydown) * (y - @ydown)) < 3
end

#Contextual menu
def getMenu(menu)
	#Custom menu item after 
	menu.add_item(@mnu_done) { exit_tool }
	menu.add_separator
	menu.add_item(@mnu_clear_all) { clear_selection }
	
	#Modifiers
	menu.add_separator
	menu.add_item(@mnu_connected) { toggle_extend_connected }
	menu.add_item(@mnu_curve) { toggle_extend_curve }
	menu.add_item(@mnu_follow) { toggle_extend_follow }
	status = (@init_clear) ? T6[:T_DLG_YES] : T6[:T_DLG_NO]
	menu.add_item(T6[:MNU_Init_Clear] + " (BACKSPACE): #{status}") { toggle_init_clear }
	
	#Function Key menus
	menu.add_separator
	menu.add_item(T6[:MNU_EdgePropFilter] + " (TAB): #{G6.edge_filter_text(@edge_prop_filter)}") { ask_prop_filter }

	#Navigation in history 
	menu.add_separator
	menu.add_item(@mnu_navig_left) { unwind_last_from_history }
	menu.add_item(@mnu_navig_right) { unwind_next_from_history }
	menu.add_item(@mnu_navig_down) { unwind_last_block_from_history }
	menu.add_item(@mnu_navig_up) { unwind_next_block_from_history }
	
end

#check initial selection
def check_initial_selection
	return selection_clear if @init_clear
	
	ls = []
	@selection.each do |e|
		ls.push e unless check_entity?(e)
	end
	selection_remove ls, true if ls.length > 0	
end

#Clear selection
def clear_selection
	selection_clear
	onMouseMove_zero
end

#dialog box for Edge property filter
def ask_prop_filter
	result = G6.ask_edge_prop_filter T6[:MNU_EdgePropFilter], @edge_prop_filter
	return unless result
	filter_edges result
end

def filter_edges(filter)
	if filter != @edge_prop_filter
		@edge_prop_filter = filter
		ldel = @selection.find_all { |e| !check_entity?(e) }
		selection_remove ldel if ldel.length > 0
	end	
end

#toggle the auto clear at startup	
def toggle_init_clear
	@init_clear = !@init_clear
end
	
#toggle functions for options
def toggle_plain_edges
	@plain_edges = !@plain_edges
end
	
def toggle_extend_curve
	@extend_curve = ! @extend_curve
	@extend_follow = false
	@extend_connected = false
	onMouseMove_zero
end

def toggle_extend_follow
	@extend_follow = ! @extend_follow
	@extend_curve = false
	@extend_connected = false
	onMouseMove_zero
end

def toggle_extend_connected
	@extend_connected = ! @extend_connected
	@extend_curve = false
	@extend_follow = false
	onMouseMove_zero
end

def toggle_both
	return unless @timer_toggle
	@toggle_now = Time.now.to_f
	save_toggles
	UI.stop_timer @timer_toggle
	@timer_toggle = nil
	if @ctrl_down && @shift_down
		@extend_connected = false
		toggle_extend_connected
	elsif @extend_connected
		@extend_connected = true
		toggle_extend_connected
	elsif @ctrl_down
		toggle_extend_curve
	elsif @shift_down
		toggle_extend_follow
	end
end

def save_toggles
	@old_extend_connected = @extend_connected
	@old_extend_curve = @extend_curve
	@old_extend_follow = @extend_follow
end

def restore_toggles
	return if @toggle_now && Time.now.to_f - @toggle_now < 1.0
	@extend_connected = @old_extend_connected
	@extend_curve = @old_extend_curve
	@extend_follow = @old_extend_follow
end

#Key Down received
def onKeyDown(key, rpt, flags, view)
	key = Traductor.check_key key, flags, false
	case key			
	when CONSTRAIN_MODIFIER_KEY
		@shift_down = true
		if @mode_rectangle
			rectangle_toggle_shift
		else
			@timer_toggle = UI.start_timer(0.5) { toggle_both } unless @timer_toggle
		end	

	when COPY_MODIFIER_KEY
		@ctrl_down = true
		if @mode_rectangle
			rectangle_toggle_ctrl
		else
			@timer_toggle = UI.start_timer(0.5) { toggle_both } unless @timer_toggle
		end	
	
	else
		if @mode_rectangle
			rectangle_toggle_reverse if [VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT].include?(key)
		else
			history_navigate key
		end	
	end
	@moving = false
	view.invalidate	
end

#Key up received
def onKeyUp(key, rpt, flags, view)
	key = Traductor.check_key key, flags, true
	case key			
	when CONSTRAIN_MODIFIER_KEY
		@shift_down = false
		restore_toggles
	when COPY_MODIFIER_KEY
		@ctrl_down = false
		restore_toggles
	when 8
		toggle_init_clear
	when 9
		ask_prop_filter
	end
	view.invalidate	
end

#Return key received
def onReturn(view)
	execute_action
end

#History_navigation
def history_navigate(key)
	case key
	when VK_LEFT
		unwind_last_from_history
	when VK_RIGHT
		unwind_next_from_history
	when VK_DOWN
		unwind_last_block_from_history
	when VK_UP
		unwind_next_block_from_history
	end
end
	
#Drawing method - Used to add cursor indicator
def draw(view)
	#Synchronize draw and move
	@palette.draw view
	@moving = false

	
	return if rectangle_draw(view)
	return unless @x && !@no_entity	
	x = @x
	y = @y + @sizecursor + 6
	if @extend_connected
		@mark_extended.draw_at_xy view, x, y	
	elsif @extend_follow
		@mark_follow.draw_at_xy view, x, y
	elsif @extend_curve
		@mark_curve.draw_at_xy view, x, y	
	end	
end

#Check the validity of entity
def check_class?(entity)
	entity && entity.class == Sketchup::Edge
end

def check_prop?(entity)
	G6.edge_filter?(entity, @edge_prop_filter)
end

def check_entity?(entity)
	check_class?(entity) && check_prop?(entity)
end

#Identify the entity under the mouse
def entity_under_mouse(view, x, y)
	(@aperture) ? @ph.do_pick(x, y, @aperture) : @ph.do_pick(x, y)
	entity = @ph.best_picked
	@deny_entity = false
	@entity = nil
	@no_entity = false
	@face = nil
	@vertex_sel = nil
	if entity
		if check_entity?(entity)
			@ip.pick view, x, y
			@vertex_sel = (@extend_follow || @extend_curve || @extend_connected) ? nil : @ip.vertex
			@entity = entity
		elsif entity.class == Sketchup::Face
			@face = entity
		else
			@deny_entity = true
		end	
	else
		@no_entity = true	
	end
	@entity
end

#---------------------------------------------------------------------------------------------------------------------------
# Mouse Move Management	
#---------------------------------------------------------------------------------------------------------------------------

def set_tooltip(tip, noinfo=false)
	tip = tip + "\nnb of edges: #{@selection.length}" unless noinfo
	@palette.set_tooltip tip
	@view.tooltip = tip
end

def set_warning(tip)
	@palette.set_tooltip tip
	@view.tooltip = tip
end

#Mouse Move method
def onMouseMove_zero
	onMouseMove(-1, @x, @y, @view) if @x
end

def onMouseMove(flags, x, y, view)
	#check if within the palette
	return unless x
	return if @palette.onMouseMove(flags, x, y, view)
	cancel_button_down(flags)
	
	#Synchronize draw and move
	return if @moving
	@moving = true

	@x = x
	@y = y
	
	#Manage rectangle mode
	if @mode_rectangle
		return rectangle_continue(x, y)
	end
	
	#checking Rectangle selection mode
	unless close_down_in_pixel(x, y)
		if @entity_down == nil && @button_down
			return rectangle_start
		else
			@xdown = nil
			@ydown = nil
		end
	end
	
	#Checking if Mouse moving too fast
	fly_over(view, x, y) if @button_down
	
	#checking entity under mouse	
	entity_under_mouse view, x, y 
	
	#Determining the status
	if @no_entity && @last_entity == nil && !@button_down
		ttip = @tip_exit
	elsif @face && !@button_down
		ttip = @tip_face
	else
		ttip = (@mode_erase) ? @tip_remove : @tip_add
	end	
	set_tooltip ttip
	
	if @face_init
		manage_selection()
	elsif @entity
		if @button_down
			manage_selection()
		elsif !@face_init	
			#if @selection.include?(@entity)
			if selection_include?(@entity)
				set_tooltip (@vertex_sel) ? @tip_vertex_del : @tip_edge_del
			else	
				set_tooltip((@vertex_sel) ? @tip_vertex_add : @tip_edge_add)
			end	
			@last_entity = nil
		end	
	end	
	
	@xprev = x
	@yprev = y
	
	onSetCursor
	view.invalidate
	show_info
end	

#Manage selection when mouse passes over point x, y
def fly_over(view, x, y)

	#check whether the next mouse position is far from previous
	d = move_too_fast(x, y)
	return unless d
	
	#SCanning the intermediate position
	aperture = (@aperture) ? @aperture : 5
	n = (d / aperture).ceil
	origin = Geom::Point3d.new @xprev, @yprev, 0
	target = Geom::Point3d.new x, y, 0
	incr = 1.0 / n
	for i in 1..n-1
		step = i * incr
		pt = Geom.linear_combination step, origin, 1 - step, target
		entity_under_mouse view, pt.x, pt.y
		manage_selection if @entity
	end	
end

#Check if mouse move is too fast
def move_too_fast(x, y)
	return nil unless @xprev
	d = Math.sqrt((x - @xprev) * (x - @xprev) + (y - @yprev) * (y - @yprev))
	(d > @too_fast) ? d : nil
end

#---------------------------------------------------------------------------------------------------------------------------
# Exceution of actions	
#---------------------------------------------------------------------------------------------------------------------------

def selection_add(ls, nostore=false)
	if ls.length > 0
		ls = ls.find_all { |e| e.valid? && !@selection.include?(e)}
		return if ls.length == 0
		ls.uniq!
		store_for_history 'A', ls unless nostore
		@selection.add ls
	end	
end

def selection_remove(ls, nostore=false)
	ls = ls.find_all { |e| e.valid? && @selection.include?(e)}
	if ls.length > 0
		store_for_history 'E', ls unless nostore
		@selection.remove ls
	end	
end

def selection_clear
	selection_remove @selection.to_a 
end

def selection_include?(entity)
	(entity.valid?) ? @selection.include?(entity) : false
end

#Action to be executed
def execute_action
	exit_tool
end

#exit the tool
def exit_tool
	@model.select_tool nil
end

#---------------------------------------------------------------------------------------------------------------------------
# Management of Edge selection	
#---------------------------------------------------------------------------------------------------------------------------

#Manage the selection for adding or removing edges to the selection
def manage_selection
	return if @last_entity && @last_entity == @entity
	
	#Erase mode
	if @mode_erase
		ls = (@vertex_sel && @last_entity == nil) ? @vertex_sel.edges.to_a : [@entity]
		selection_remove ls
		@last_entity = @entity
		return
	end

	#Connected extension
	if @face_init
		if @extend_connected
			ls = @face_init.all_connected.find_all { |e| check_entity?(e) }
		else
			ls = @face_init.edges.find_all { |e| check_entity?(e) }
		end	
		@face_init = nil
	else
		ls = (@vertex_sel && @last_entity == nil) ? @vertex_sel.edges.to_a : [@entity]
	end	

	if @entity
		if @extend_connected
			ls |= edge_all_connected(@entity).find_all { |e| check_entity?(e) }
		elsif @extend_curve	
			curve = @entity.curve
			ls = curve.edges if curve
		elsif @extend_follow	
			curve = @entity.curve
			ls = curve.edges if curve
			ls |= follow_extend @entity
		end	
		@last_entity = @entity
	end
		
	#adding to the selection
	selection_add ls
end	

#Compute the deges connected to an edge.
#This function is required because 'all_connected' method has a side effect to deactivate the visibility of the selection
def edge_all_connected(edge)
	hsh_v = {}
	hsh_e = {}
	vstart = edge.start
	all_v = [vstart]
	lst_v = [vstart]
	hsh_v[vstart.entityID] = vstart
	while true
		new_v = []
		lst_v.each do |vertex|
			vertex.edges.each do |e|
				v = e.other_vertex vertex
				unless hsh_v[v.entityID]
					new_v.push v 
					hsh_v[v.entityID] = v
				end	
			end
		end	
		break if new_v.length == 0
		lst_v = new_v.clone
		all_v += new_v
	end
	
	all_v.each do |v|
		v.edges.each do |e|
			hsh_e[e.entityID] = e
		end
	end	
	hsh_e.values
end

#extend selection in follow mode for edge at vertex
def follow_extend(edge)
	@common_normal = nil
	follow_extend_at_vertex(edge, edge.start) + follow_extend_at_vertex(edge, edge.end)
end

#extend selection in follow mode for edge at vertex
def follow_extend_at_vertex(edge, vertex)
	anglemax = @angle_max
	ls_edges = []
	edgenext = edge
	while edgenext
		len = vertex.edges.length
		break if len == 1
		if len == 2
			ls = [[0, vertex.edges.to_a.find { |ee| ee != edgenext }]]
		else	
			ls = []
			vertex.edges.each do |e|
				next if e == edgenext
				next unless check_entity?(e)
				an = Math::PI - angle_edge(edgenext, e, vertex)
				next if an > anglemax
				if @common_normal
					ls.push [an, e] if edge_normal(e, edgenext).parallel?(@common_normal)
					next
				elsif e.common_face(edgenext)
					ls.push [an, e]
				elsif an < anglemax
					ls.push [an, e]
				end	
			end
		end
		break if ls.length == 0
		
		ls.sort! { |a1, a2| a1[0] <=> a2[0] } if ls.length > 1
		e = ls[0][1]
		break if e == edge
		@common_normal = edge_normal(e, edgenext) unless @common_normal && @common_normal.valid?
		edgenext = e
		vertex = e.other_vertex(vertex)
		ls_edges.push edgenext
	end
	
	return ls_edges
end

def angle_edge(e1, e2, vertex)
	v1 = e1.other_vertex vertex
	v2 = e2.other_vertex vertex
	vec1 = vertex.position.vector_to v1
	vec2 = vertex.position.vector_to v2
	vec1.angle_between vec2
end

def edge_normal(e1, e2)
	vec1 = e1.start.position.vector_to e1.end.position
	vec2 = e2.start.position.vector_to e2.end.position
	vec1 * vec2
end

#---------------------------------------------------------------------------------------------------------------------------
# Status bar management	
#---------------------------------------------------------------------------------------------------------------------------

#Parse the VCB text as an angle (degree, radians, grade) or slope
def parse_as_angle(text)
	dangle = Traductor.string_to_angle_degree text, true
	return nil unless dangle
	dangle = dangle.modulo 180
	dangle.degrees
end

#Text typed in the VCB
def onUserText(text, view)
	angle = parse_as_angle(text)
	return UI.beep unless angle
	@angle_max = angle
	show_info
end

#Manage the status bar
def show_info()
	title = @status_text
	Sketchup.set_status_text title + ' --> ' + G6.edge_filter_text(@edge_prop_filter)
	svalue = ""
	label = ""
	if @extend_connected	
		label = @text_extend_connected
	elsif @extend_follow
		label = @text_extend_follow
	elsif @extend_curve
		label = @text_extend_curve
	end	
	if @mode_erase
		label = @text_mode_erase
	elsif @rect_processing
		label = @text_rect_processing
	end
	text_angle = (@extend_follow) ? sprintf("%3.1f", @angle_max.radians) + " deg." : ""
	
	Sketchup.set_status_text label, SB_VCB_LABEL
	Sketchup.set_status_text text_angle, SB_VCB_VALUE
end

#---------------------------------------------------------------------------------------------------------------------------
# History Management	
#---------------------------------------------------------------------------------------------------------------------------

#Store the change of selection for history purpose
def store_for_history(code, ls)
	n = @ls_fullhistory.length
	if @nb_fullhistory < n
		@ls_fullhistory[@nb_fullhistory..-1] = []
	end
	@ls_fullhistory.push [code, ls]
	@nb_fullhistory = @ls_fullhistory.length
end

#Undo previous selection from History
def unwind_last_from_history	
	return UI.beep if @nb_fullhistory == 0 
	@nb_fullhistory -= 1
	ll = @ls_fullhistory[@nb_fullhistory]
	if ll[0] == 'A'
		selection_remove ll[1], true
	else
		selection_add ll[1], true
	end	
end

#Redo previous selection from History
def unwind_next_from_history	
	return UI.beep if @nb_fullhistory >= @ls_fullhistory.length
	ll = @ls_fullhistory[@nb_fullhistory]
	if ll[0] == 'A'
		selection_add ll[1], true
	else
		selection_remove ll[1], true
	end	
	@nb_fullhistory += 1
end

#undo the selection performed since last click down
def unwind_last_block_from_history	
	nh = @ls_fullhistory.length
	nb = @nb_blockhistory
	if nb == nil || nh == 0 || nb >= nh
		@nb_blockhistory = nil
		return UI.beep
	end	
	ladd = []
	ldel = []
	for i in nb..nh-1
		ll = @ls_fullhistory[i]
		if ll[0] == 'A'
			ldel |= ll[1]
		else
			ladd |= ll[1]	
		end	
	end
	selection_add ladd, true if ladd.length > 0
	selection_remove ldel, true if ldel.length > 0
	@nb_fullhistory = @nb_blockhistory
end

#Redo the selection performed since last click down
def unwind_next_block_from_history	
	nh = @ls_fullhistory.length
	nb = @nb_blockhistory
	if nb == nil || nh == 0 || nb >= nh
		@nb_blockhistory = nil
		return UI.beep
	end	
	ladd = []
	ldel = []
	for i in nb..nh-1
		ll = @ls_fullhistory[i]
		if ll[0] == 'A'
			ladd |= ll[1]
		else
			ldel |= ll[1]	
		end	
	end
	selection_remove ldel, true if ldel.length > 0
	selection_add ladd, true if ladd.length > 0
	@nb_fullhistory = nh
end

#---------------------------------------------------------------------------------------------------------------------------
# Rectangle Selection	
#---------------------------------------------------------------------------------------------------------------------------

#draw the selection rectangle
def rectangle_draw(view)
	return false unless @mode_rectangle
	return true unless @pt_rect
	view.drawing_color = (@rect_erase) ? 'red' : 'green'
	view.line_width = (@rect_hidden) ? 4 : 2
	if @rect_reverse
		view.line_stipple = '-'
	else
		view.line_stipple = ''
	end
	
	view.draw2d GL_LINE_STRIP, @pt_rect
	
	view.line_stipple = ''
	if @rect_erase
		view.line_width = 3
		view.drawing_color = 'black'	
	else
		view.line_width = 3
		view.drawing_color = 'blue'	
	end
	view.drawing_color = (@rect_erase) ? 'lightcoral' : 'lime'	
	view.draw2d GL_LINES, @entities_pairs if @entities_pairs if @entities_pairs && @entities_pairs.length > 1
	
	true
end

#Start Rectangle Selection mode
def rectangle_start
	@mode_rectangle = true
	@rect_erase = @ctrl_down
	@rect_hidden = @shift_down
	@rect_inv = false
	origin = Geom::Point3d.new(@xdown, @ydown, 0) 
	@pt_rect = [origin, origin.clone, origin.clone, origin.clone, origin]
	if entities_too_big?
		@nb_all_entities = @rect_maxnum + 1
		@lst_all_entities = nil	
	end	
	rectangle_tooltip
	@view.invalidate
end

#Continue Rectangle Selection mode
def rectangle_continue(x, y)
	@mode_rectangle = true
	@pt_rect[1].y = y
	@pt_rect[2].x = x
	@pt_rect[2].y = y
	@pt_rect[3].x = x
	@rect_reverse = (@pt_rect[0].x > @pt_rect[2].x)
	@rect_reverse = !@rect_reverse if @rect_inv
	rectangle_tooltip
	rectangle_eval_selection if @nb_all_entities < @rect_maxnum
	@view.invalidate
end

def rectangle_tooltip
	tip = "Selection by rectangle:"
	tip += (@rect_reverse) ? " PARTIAL" : " TOTAL"
	tip += (@rect_hidden) ? " - All" : " - Visible"
	tip += (@rect_erase) ? " - REMOVE" : " - ADD"
	set_tooltip tip, false
end

def rectangle_toggle_shift
	@rect_hidden = !@rect_hidden
	rectangle_tooltip
	onMouseMove_zero
end

def rectangle_toggle_ctrl
	@rect_erase = !@rect_erase
	rectangle_tooltip
	onMouseMove_zero
end

def rectangle_toggle_reverse
	@rect_inv = !@rect_inv
	rectangle_tooltip
	onMouseMove_zero
end

#Compute the selection with the rectangle
def rectangle_compute_selection
	@rect_processing = true
	show_info
	@view.invalidate
	entities_build_list
	rectangle_eval_selection
	if @entities_rect.length > 0
		if @rect_erase
			selection_remove @entities_rect
		else	
			selection_add @entities_rect
		end	
	end	
	@rect_processing = false
	@mode_rectangle = false
	@rect_erase = false	
	@rect_hidden = false		
	@pt_rect = nil
	@entities_pairs = nil
	show_info
	onMouseMove_zero
end

#velauate the selections enclosed within a rectangle
def rectangle_eval_selection
	@entities_rect = []
	@entities_pairs = []
	xmin = [@pt_rect[0].x, @pt_rect[2].x].min
	xmax = [@pt_rect[0].x, @pt_rect[2].x].max
	ymin = [@pt_rect[0].y, @pt_rect[2].y].min
	ymax = [@pt_rect[0].y, @pt_rect[2].y].max
	@lst_all_entities.each do |entity|
		next unless check_prop?(entity)
		line = edge_cross_rectangle(@view, entity, @pt_rect, xmin, xmax, ymin, ymax)
		if line && edge_visible?(entity)
			@entities_rect.push entity
			if selection_include?(entity)
				@entities_pairs += line if @rect_erase
			else
				@entities_pairs += line unless @rect_erase
			end	
		end	
	end
end

def cross_rectangle?(edge, xmin, xmax, ymin, ymax)
	ptbeg = @view.screen_coords(edge.start.position)
	ptend = @view.screen_coords(edge.end.position)
	return nil if ptbeg.x < xmin && ptend.x < xmin
	return nil if ptbeg.y < ymin && ptend.y < ymin
	return nil if ptbeg.x > xmax && ptend.x > xmax
	return nil if ptbeg.y > ymax && ptend.y > ymax
	ptbeg.z = ptend.z = 0
	[ptbeg, ptend]
end

def fully_within_rectangle?(edge, xmin, xmax, ymin, ymax)
	ptbeg = @view.screen_coords(edge.start.position)
	return nil if ptbeg.x < xmin || ptbeg.y < ymin || ptbeg.x > xmax || ptbeg.y > ymax
	ptend = @view.screen_coords(edge.end.position)
	return nil if ptend.x < xmin || ptend.y < ymin || ptend.x > xmax || ptend.y > ymax
	ptbeg.z = ptend.z = 0
	[ptbeg, ptend]
end

#Build List of edges in the model
def entities_build_list
	return if @lst_all_entities
	w = @view.vpwidth
	h = @view.vpheight
	@hsh_all_vertices = {}
	nbmax = 0
	@lst_all_entities = @model.active_entities.find_all do |e| 
		check_class?(e) && cross_rectangle?(e, 0, w, 0, h)
	end	
	@nb_all_entities = @lst_all_entities.length
	false
end

#Test List of edges in the model
def entities_too_big?
	return false if @lst_all_entities
	w = @view.vpwidth
	h = @view.vpheight
	@hsh_all_vertices = {}
	nbmax = 0
	@lst_all_entities = []
	@model.active_entities.each do |e|
		return true if nbmax > @rect_maxnum
		if check_class?(e) && cross_rectangle?(e, 0, w, 0, h)
			@lst_all_entities.push e 
			nbmax += 1
		end	
	end	
	@nb_all_entities = @lst_all_entities.length
	false
end

#Refresh the screen coords of edges when view is changed
def edge_cross_rectangle(view, edge, ptrect, xmin, xmax, ymin, ymax)
	return fully_within_rectangle?(edge, xmin, xmax, ymin, ymax) unless @rect_reverse
	line = cross_rectangle?(edge, xmin, xmax, ymin, ymax)
	return nil unless line
	iref = nil
	for i in 0..3
		ptproj = ptrect[i].project_to_line line
		vecref = ptrect[i].vector_to ptproj
		if vecref.valid?
			iref = i+1 
			break
		end	
	end
	return nil unless iref
	
	psref = nil
	for i in iref..3
		ptproj = ptrect[i].project_to_line line
		vec = ptrect[i].vector_to(ptproj)
		next unless vec.valid?
		return line if vec % vecref <= 0
	end
	nil
end

def edge_visible?(edge)
	return true if @rect_hidden
	status1 = vertex_visible(edge.start)
	status2 = vertex_visible(edge.end)
	case (status1 + status2)
	when 0
		return false
	when 2
		return true
	end
	point_visible?(Geom.linear_combination(0.5, edge.start.position, 0.5, edge.end.position))
end

def vertex_visible(vertex)
	status = @hsh_all_vertices[vertex.entityID]
	return status if status
	@hsh_all_vertices[vertex.entityID] = (point_visible?(vertex.position)) ? 1 : 0
end

def point_visible?(pt)
	ll = @model.raytest [@eye, @eye.vector_to(pt)]
	ll[0] == pt
end

#Change of view - Resetting the parameters for rectangle selection
def resume(view)
	@lst_all_entities = nil
	@eye = @view.camera.eye
end

end	#class HoverSelectionTool
	
end	#End Module F6_HoverSelect

