#Stringscan: grep-like tool, written in Ruby-Tk. (c) 2017 Kevin Walzer/WordTech Communications LLC. License: MIT license.
#encoding: UTF-8


require 'tk'
#find additional Tcl libraries
Tk::AUTO_PATH.list <<= File.dirname(__FILE__)
require_relative 'tkballoonhelp'  
require 'tkextlib/tile'
require 'find'
require 'tkextlib/tcllib/tablelist_tile'
require 'mime/types'
require 'tkextlib/tkDND'

$platform = Tk.windowingsystem

if $platform == 'aqua'
	TkPackage.require('windowlist')
	TkPackage.require('fullscreen')
	require 'tk/tk_mac'
end
TkPackage.require('regproc')
TkPackage.require('machelp')
TkPackage.require('softwareupdate')
TkPackage.require('xplat')

if $platform == 'aqua'
	$accelkey = 'Command'
else 
	$accelkey = 'Control'
end


class StringscanApp

#here we initialize our app class
def initialize

	$dirname = ""
	$searchterm = ""
	$appname = 'Stringscan'
	$appversion = '1.0'
	Tk.tk_call('machelp::setAppName', $appname, $appversion)
	Tk.tk_call('softwareupdate::setAppName', $appname)
	Tk.tk_call('softwareupdate::setVersion', $appname, $appversion)

	bgerror = Tk.install_cmd(proc{
		|*args|
		puts args
	})
	Tk.ip_eval("proc bgerror {args} {#{bgerror} $args}")

	if $platform == 'aqua'
		##map ruby proc to "odoc" Apple Event
		setDir = Tk.install_cmd(proc{
			|*args|
			filename=(args[0]).delete('{}')
			begin
				if File.directory?(filename)
					$dirname = filename
					$direntry.value = filename
				end
			rescue
				raise
			end
		})    
		Tk.ip_eval("proc ::tk::mac::OpenDocument {args} {#{setDir} $args}")
		Tk.ip_eval("proc ::tk::mac::ShowHelp {} {#{Tk.install_cmd(proc{showHelp})}}")
	end        
	drawgui
	Tk.tk_call('regproc::readLic', $appname, $appversion)
end  

#core method; here we search for a string in text files within a directory and  display a list of matching files in the listbox 
def stringgrep
	$file_list = []
	$grep_list = []
	$lbox.delete(0, 'end')
	Find.find("#{$dirname}") do |path|
		$file_list << path unless FileTest.directory?(path)
	end
	for i in $file_list
	begin
		#we are only reading text files here, not binary. 
		text =  MIME::Types.type_for(i).first.to_s
		type =  text.split('/')[0]
		if type  =~ /text/
			f = File.open(i, "r:iso-8859-1:utf-8")
			result = f.read
			if result =~ /#{$searchterm}/ then
				$lbox.insert('end', "#{i}")
				f.close
			end
		end
	rescue
		#puts "Search term not found.\n"
	end
end
rescue
puts "Directory not found.\n"
puts "Search complete.\n"
end

def drawgui 
	begin
		Tk.ip_eval("console hide")
	rescue
		puts "could not hide console.\n"
	end

	#image data
	$folderdata = 'R0lGODlhGAAYAIABAAQHB////yH+EUNyZWF0ZWQgd2l0aCBHSU1QACH5BAEKAAEALAAAAAAYABgAAAI1hI8Wy70JgZshJurOtFzmHm0ghJXVZVJkKo7uC8cr28z0g944ojP2/aMFWcNU0SRLxk5KSQEAOw=='

	$glassdata = 'R0lGODlhGAAYAIABAAAAAP///yH+EUNyZWF0ZWQgd2l0aCBHSU1QACH5BAEKAAEALAAAAAAYABgAAAJHjI8Gy+nJYntQWkBVRDLv9zkTNXKleAYhuaBYpr2m7LWVXdOxfvMd67OATpeJcFa89JK/A1O5PMJUySlyZaVis1oel/o9FAAAOw=='

	$folderimage = TkPhotoImage.new(:data => $folderdata)
	$glassimage = TkPhotoImage.new(:data => $glassdata)

	$icondata = 'R0lGODlhQABAAOf/AB8jJSktLyUvLyouMCsvMSwwMi0xMy4yNC8zNS80NjI2ODU5Ozc7PT5APjxBQz5CRUJEQUJGSERJS0hKR0ZLTUBPX0tPUk9QTk5SVFBST0dVZlJUUVBVV1RVU0NZaFJXWVVXVVZYVUdab1dZVlVaXFhaV1lbWFpcWVhcX0tedFtdWi1kplxeW09hbF5fXTZlm1xgYzhloUlkeV9hXkhlhWBiX15jZWFjYE9mgT1opWJkYTRsp2NlYmRlY2VmZGZnZWNoaj9to1FsgU9sjGdpZlRsh2VqbGhqZ2lraElvoGpsaVNwkWttamltcF5we1lwjEZzqm1vbFFzmW5wbWxwc1l0iVh0lXByb25ydVR2nHJzcUt5qk14sHB0d2V3gnN1clx4mUh7uFt6lXV3dHN3ell7oWZ5kFh9nVZ9qXd5dnp5cVKAsXl7eFt/slaAuWN/oGx/il+Ap3h9f3t9enx+e2GCqX1/fH+BfmWGrXeGjIKEgWKJtmmJpIOFgoGFiHKImW6Jn4SGg4eGfnaJlWWLuIWHhHyIlWOOs2mNrluPx2yNtXSMqIiKh3eOnomLiHGQq4uMiXyPm3+PlYyOi4+OhnGSuY6QjXmUqpKRiX6VppCSj3eWspKUkXuXun2YrpSWk3Gd0H+bvpGZoXedyoWcrZeZloKdtH6euZqcmXyhwpyem4yjtImlu4ykwaCin4Gn1YyovqSlooqqxpGoxqaopYiu3Jauv6mrqKOstJqtxZWwx6utqp2wyaGww7Cvpoy13a+xrqeywLK0sa63v6i4y7W3tKy4xaG94aK+1bm7uLC8yrS8xJ3C5L2/vLbBz7rCysPFwr7Hz7zI1sjKx8PM1MHN27zQ6cfP2M7Qzc3S1MfT4cvT28nV483W3tLX2cra7tfZ1s/b6dPb5N7Z2Nvd2tLe7c/f89jg6dXh79/h3uLk4dnl8+nj4uTm49/o8N3p9+3o5ufq5uLr8+rs6PHr6uXu9u3v6+nx+u7x7e3y9fHz8Pjz8fb49Pn7+P3//P///yH+EUNyZWF0ZWQgd2l0aCBHSU1QACH5BAEKAP8ALAAAAABAAEAAAAj+AP8JHEiw4EAECBMiOMBQIUKDECNKnCgw4YEBAhZI+GCjCRk5fgr5ucOmCxASFBQEKPCQokuJCQcowGBETylgxXIWE7brVqyftHYBS0Y02S5NXzAQOIDgpdOKCDBo4XTrFq1SkOZM6XEig9evYDOU4KFFz6dbwHbpwTCg6VOKB+zEUlXoy4wMGzqE6HDixg8kSgIjIcJjxgi9IRJvyHCDjqpbpT4QcPsW4gE2kzIgThxCRxQtW1VwPlEDyRctTG5wXp2hhyVakBhQrkzwMqQNnDuwmHJlhAMDTBUeMIAggokrU1hs5rzhBypVFmbT/mcb994bV2YoaBkRoQIVWmr+rB4PyRWKA9MHHkhzO/EMLRuYOl0oVfR4xXZUkZD+dj0j3CNM8YF8/c3AxHKrbUAHKhHw59QBY/zXwRFEEPgWAgxoYd99IWwACSMEpAdhIRuYoMUDDr5UwBSqceheKRikCNcXjMyAhA/opWfAETy4qFggaeRY2QFTBBLFFBTI6NKOPfoYwhSaFDAdAgYQYABL6QnEpJMh1MCJA0pm+dSWTo6gSXRipvmPAUr0wGUHlnAQJm0KVUnAAHgSoOeeBAQwBxPjjSDooB1Msp+aUMmEARBj6AGJJZBOAsmHhQSixx16FDJDCZx26qkJIUByaJpUMtDEJz3FwgkketDBRhr+aYwxxhe0njbFpiZ8asKuJpwwAiQlzDnRQg8wwpMlX7hg3QglDLrrCdCqoMIJJTwLba/QRluCqMJ2h8AdRtEhaK6c7loDEUzQqoYabMxBhx13pCFttidIay8LJjAyKm0HYFBMMn2EYO21N0TxhQ8dTBDBwgxHIIEeUajAwsQSTzyxCyfo221BBGABDS0z0DvttEp8ccICwTmEUAF2fGExCy7ELPMMKjDywcbqUQGNJSHYKy3FBqOoZAF6jOHCDEgnrTQLhdxcWVTNWGJCxRa7wAISaYDpEgGFsKH010i7UEgIOP8zgE8mXCzz0TOwQfZLBDAyxww11G333TME4vT+UwjAUMwRYCfNhB3cTUQAJHbcfcPii+ugQw197O1UAZqowkLgSLPxQ7cEWHLHDY6D7vjoOtygh+QvGQBM4nfjXUiMTg2giSCk82C77T30oIMecvItATBM2M044z5AEsEA/+jhSjLTTNPMLZAEQtAAnwjCQ+7X5567Dz3woEcHGyNAwi2ik+44D0pggoEf0/TDTzzwxBOPPv6ko4r0ZpfCiPY+9O+/Dz/owffCB4NYzGB0t8MdExhRDH/EYxy8qMQa2qCIXHijHf4AByTMpgpIAPAHIAxhCH1wh97NxwauqAH2tIe9NGCDH+ToxArCkIhXvCIRYViBIrxhD31YggH+tygECIlAxCIW8Qd2MOFLEIACV9xge//zwRemoY9s7MANzEBHPeTxjnVYoxaJyEE05ucHX9ihiEdIIxGOgAQ1ljB8GFCFCEXIhFv0Ixs5KIM57nGPLbpjHegoxzFAsYJo5IMcmrhCGtmIhEYCJjBH4F34IoCKKQzxiHbgBzugcAhnyIOPffwjOszxjV+AIgfk8McttBCYVraSCYIZYH9KkYY1LpIIURCGPXKxBmR04xz1CKY8RElKU24hFPNohx1eyYRmOhOWkcOZAewQiDU6EmvkaAcUEBEOcWwDmMMEZDnC8Y1RvOENOUgHP1TxzHY6M2A4E58llPBIeuoBH1b+PAU3wrGNa3zTHehARzi00YslgCEVa1AGPooxhig49KFHmsIUmBCIt/ENAYhTgjORUIp4EGMLsKgGN7wZjWc8YxnKCEYwzMAHWchiD62IxzTSEFGJ2lSiUWiaylwiPkhc4ZmxaEcu0AALaUijGt3wJjVK+gxj2EIXrNhEGULRDmxo5aYSvYJWizSFhVGAAhIQlgHGEIgpOJQJsYgHL9BwCme49RnR8Oc2thGNVZDiEpcQgxRCwQ6r8karWtUCYK/Ahk/EAhjNgMY0ArBEBLChD1+YaCns4QwoPEIZmF2GZocxDFHAoRGZAEQRnpAEXqgDGmm4ghZWu9panYZWbGD+Ax0CoYkQNVYLXSuSPtKRg0eYohfGMMYwcCGKPBgiEo2oggyKYIUYZCMeu5iDdKdLXenS4brX1cMkbPuS4ZCAEYXQAzjiQQg8LAIQgPjDHwYRCUlIwgkeSAEOhiAFLsBjHoyYFCUoMYn++ve//63tWwKAAAW4wA52vEYOPMHgTGRiEF5oQQU0IAIaDKG5zsDHND6Apw57+MMfLpvZEDCAMcSDHq1IghiqIIQUiMADIqjwEJaQhSQoAh766MMAAMDjHgMAURKJQCz6AY9KvOAJTyjCEqQghSVn4QxBaAM7+nGLEQC5MnNIhgNnsYItKAIPdYhDHOqwhRWEAh79UIfyOnahgiu/RQ/N6Mc+vNGJHaxgB3bOgSKugY9+pAMcluhDLELg5qcU4hbx6Ic+6JENt16DHvTDhyug0ZNS6MGAhX7KJJKhDn3wox+g1kc8isGEDZTiFqrYBSoEzQQIZNopdrBELHaxi1gwogYdEAgEILGLU7uiEKqgg6tfnSYIpEEYp44FJDgxiWETW0xEKEYpaEGL/pZiAs9O0wmkHYtbaIIRrshAticSD3vYYx7qgEgGgFGKbt8im9get0HmF+p4kAMiE6CFKmKRjmzOQd5ZggAqmkEOcsSDCQDPUgNKgQ98AKMBCRfTBTIA8Yhb/OKICggAOw=='
	$icon = TkPhotoImage.new(:data => $icondata)
	
	#initialize variables for entry
	$dirname = TkVariable.new
	$searchterm = TkVariable.new
	
	#top window and frame
	Tk::TkDND::DND
	$root = TkRoot.new {
		title "Stringscan"
	}
	
	if $platform == 'aqua'
		Tk.tk_call('fullscreen::fullscreen', $root)
	end
	
	#menu
	$menubar = TkMenu.new($root)
	TkOption.add '*tearOff', 0
	if $platform == 'aqua'
		$appmenu = TkSysMenu_Apple.new($menubar)
		$menubar.add :cascade, :menu => $appmenu
		$appmenu.add :command, :label => 'About Stringscan', :command=> proc{aboutWindow}
		$appmenu.add :command, :label => 'License', :command=>proc{getReg}
		$appmenu.add :command, :label=>'Check for Updates', :command=>proc{checkUpdate}
		$appmenu.add :separator
	end
	$searchmenu = TkMenu.new($menubar)
	$menubar.add :cascade, :menu => $searchmenu, :label => 'Search'
	$searchmenu.add :command, :label => 'Choose Directory...', 'accelerator' => "#{$accelkey}+O", :command => proc{choosedir}
	$searchmenu.add :command, :label => 'Run Search', 'accelerator' => "#{$accelkey}+R", :command => proc{stringgrep}
	if $platform == 'win32'
		$searchmenu.add :command, :label => "Exit", :command=>proc{exit}, 'accelerator' => "#{$accelkey}+Q"
	end
	if $platform == 'aqua'
		Tk.tk_call("windowlist::windowMenu",  $menubar)
	end
	$helpmenu = TkSysMenu_Help.new($menubar)
	$menubar.add :cascade, :menu => $helpmenu, :label => 'Help'
	if $platform != 'aqua'
		$helpmenu.add :command, :label=>'Stringscan Help', :command=>proc{
			Tk.tk_call('machelp::userhelp')
		}
	end
	$helpmenu.add :command, :label=>'Contact Code by Kevin', :command=>proc{
		Tk.tk_call('xplat::launch', 'mailto:kw@codebykevin.com?subject=Stringscan')
	}
	$helpmenu.add :command, :label=>'Web Site', :command=>proc{
		Tk.tk_call('xplat::launch', 'https://www.codebykevin.com/stringscan.html')
	}

	if $platform != 'aqua'
		$helpmenu.add :command, :label => 'About Stringscan', :command=> proc{aboutWindow}
		$helpmenu.add :command, :label => 'License', :command=>proc{getReg}
		$helpmenu.add :command, :label=>'Check for Updates', :command=>proc{checkUpdate}
			end
	$root['menu'] = $menubar
	$mainframe = Tk::Tile::Frame.new($root).pack('side' => 'top','fill' => 'both','expand' => 'yes')
	
	#button frame and buttons
	$buttonframe = Tk::Tile::Frame.new($mainframe){padding 2}.pack('side' => 'top','fill' => 'both','expand' => 'no')
	choosedirproc = proc{choosedir}
	$choosebutton = Tk::Tile::Button.new($buttonframe) {
		image $folderimage
		takefocus 0
		padding 5
		command  choosedirproc
	}.pack('side' => 'left','fill' => 'both','expand' => 'no')
	Tk::RbWidget::BalloonHelp.new($choosebutton, 'text'=>'Select Directory', 'background'=>'lightyellow', 'relief'=>'flat')
	stringgrepProc = proc {stringgrep}
	$runbutton = Tk::Tile::Button.new($buttonframe) {
		image $glassimage
		takefocus 0
		padding 5
		command stringgrepProc
	}.pack('side' => 'left','fill' => 'both','expand' => 'no')
	Tk::RbWidget::BalloonHelp.new($runbutton, 'text'=>'Run Search', 'background'=>'lightyellow', 'relief'=>'flat')
	$sep = Tk::Tile::Separator.new($mainframe) { orient 'horizontal' }.pack('side'=> 'top', 'fill'=>'both')
	
	#labels and entries
	$topframe = Tk::Tile::Frame.new($mainframe){padding 5}.pack('side' => 'top','fill' => 'both','expand' => 'no')
	$chooselabel = Tk::Tile::Label.new($topframe) {text "Directory:"}.pack('side' => 'left','fill' => 'both','expand' => 'no')
	$direntry = Tk::Tile::Entry.new($topframe) {textvariable $dirname }.pack('side' => 'left','fill' => 'both','expand' => 'no')
	$termlabel = Tk::Tile::Label.new($topframe) {text "Search Term:"}.pack('side' => 'left','fill' => 'both','expand' => 'no')
	$termentry = Tk::Tile::Entry.new($topframe) {textvariable $searchterm}.pack('side' => 'left','fill' => 'both','expand' => 'no')
	$termentry.bind('Return', proc {stringgrep})
	$nextsep = Tk::Tile::Separator.new($mainframe) {orient 'horizontal'}.pack('side'=>'top', 'fill'=>'both')
	
	#listbox, textbox, scrollbars
	$bottomframe = Tk::Frame.new($mainframe).pack('side'=>'bottom','fill' => 'both', 'expand' => 'yes')
	$bottomleftframe = Tk::Tile::Frame.new($bottomframe).pack('side'=>'left','fill' => 'both', 'expand' => 'yes')
	$listlabel = Tk::Tile::Label.new($bottomleftframe) {
		text 'File Name'
		style 'TablelistHeader.TLabel'
		padding 1
	}.pack('side'=>'top', 'fill'=>'both', 'expand' => 'no')
	$lbox = Tk::Listbox.new($bottomleftframe){
		width 50
		height 30
		borderwidth 0
		takefocus 0
		activestyle "none"
		exportselection 0
		selectbackground "RoyalBlue1"
		selectforeground "white"
		dnd_bindtarget('text/uri-list', '<Drop>', '%D') {|d|
			filename=d.join(' ')
			begin
				if File.directory?(filename)
					$dirname = filename
					$direntry.value = filename
				end
			rescue
				raise
			end
		}
	}.pack('side'=>'left','fill' => 'both', 'expand' => 'yes')
	$lbox.bind '<ListboxSelect>', proc {
		begin
			if $lbox.curselection.empty?
				puts "No selection in listbox."
			else
				highlighttext  $lbox.get($lbox.curselection[0])
			end
		rescue
			puts "Cannot get selection in listbox."
		end
	}
	$scrollframe = Tk::Frame.new($bottomleftframe).pack('side'=>'right','fill' => 'y', 'expand' => 'no')
	$listscroll = Tk::Scrollbar.new($scrollframe).pack('side'=>'right','fill' => 'y', 'expand' => 'no')
	$lbox.yscrollbar($listscroll)
	$bottomrightframe  =Tk::Tile::Frame.new($bottomframe).pack('side'=>'right','fill' => 'both', 'expand' => 'yes')
	$tbox = Tk::Text::new($bottomrightframe){
		borderwidth 0.1
		relief 'sunken'
		wrap 'word'
		highlightcolor 'SlateGray3'
		bg 'white'
		state 'disabled'
	}.pack('side'=>'left','fill' => 'both', 'expand' => 'yes')
	$tscrollframe = Tk::Frame.new($bottomrightframe).pack('side'=>'right','fill' => 'y', 'expand' => 'no')
	$tscroll = Tk::Scrollbar.new($tscrollframe).pack('side'=>'right','fill' => 'y', 'expand' => 'no')
	$tbox.yscrollbar($tscroll)
	
	$root.bind("#{$accelkey}-Q", proc{exit})
	$root.bind("#{$accelkey}-q", proc{exit})
	$root.bind("#{$accelkey}-O", proc{choosedir})
	$root.bind("#{$accelkey}-o", proc{choosedir})
	$root.bind("#{$accelkey}-R", proc{stringgrep})
	$root.bind("#{$accelkey}-r", proc{stringgrep})
end

#select search directory
def choosedir
	$dirname = Tk::chooseDirectory('initialdir'=>Dir.home, 'parent'=>$root)
	$direntry.value = $dirname
end

#read file for display in text widget
def highlighttext(file)
	$tbox.configure('state'=>'normal')
	$tbox.delete('1.0', 'end') 
	f = open(file, 'rb')
	while(!f.eof?)
		$tbox.insert('end', f.read(1000))
	end
	f.close
	$tbox.configure('state'=>'disabled')
	$tbox.tag_configure('search', :background=>'yellow')
	sethighlight
end

#set highlight color for search term
def sethighlight
	$tbox.configure('state'=>'normal')
	$tbox.tag_remove('search', '0.0', 'end')
	return if $searchterm == ""
	cur = '1.0'
	loop {
		cur, len = $tbox.search_with_length("#{$searchterm}", cur, 'end')
		break if cur == ""
		$tbox.tag_add('search', cur, "#{cur} + #{len} char")
		cur = $tbox.index("#{cur} + #{len} char")
	}
	$tbox.configure('state'=>'disabled')
end
#about window for app
def aboutWindow
	Tk::messageBox :type => 'ok',
	:message => 'Stringscan: Text Search Tool',
	:icon => 'info', :title => 'About Stringscan',
	:detail  => "Version 1.0\n(c) 2017 Wordtech Communications LLC",
	:parent => $root
end

#check version of installed software
def checkUpdate 
	Tk.tk_call('softwareupdate::setIcon', $icon)
	Tk.tk_call('softwareupdate::checkVersion', $appname, $appversion)
end

#check version of installed software
def getReg
	Tk.tk_call('regproc::getReg')
end

#user help
def showHelp
	Tk.tk_call('machelp::userhelp')
end
 
          
#end of app class
end

#run app
app = StringscanApp.new
Tk.mainloop

