Invalid Command and files not populating using treeview in TCL - treeview

In my endeavour to create a simple treeview GUI I have come across tree.tcl which is available in the active tcl 8.6 installation.
I've managed to adapt the code to fit my purpose (hardy changed anything) and when I run the code in the same way as the demos for Active TCL 8.6 (via widget) are run the code is running as expected (though I've not tried making any selection of node within tree).
However that is not the case once I copy the code over into my gui application.
The structure is as expected but when I try to expand the nodes I get:
I get invalid command error
ERROR: invalid command name "populateTree"
command bound to event:
"populateTree .fc.tv.tree [.fc.tv.tree focus]"
Now for some reason none of the files within the folders are read i.e. all file types are recognised as Directories hence everything under nodes shown as "dummy"
I'd also like to add filter to only read a specific file type, i.e. *.txt, if I do so then even folders are not read.
i.e foreach f [lsort -dictionary [glob -nocomplain -dir $path *]] to
foreach f [lsort -dictionary [glob -nocomplain -dir $path *.txt]]
I'd be obliged if someone could help.
# temp dir to mimic Network dir
set ::dir "C:/Dev"
proc populateRoots {tree} {
populateTree $tree [$tree insert {} end -text "Network File" \
-values [list $::dir directory]]
}
## Code to populate a node of the tree
proc populateTree {tree node} {
if {[$tree set $node type] ne "directory"} {
return
}
set path [$tree set $node fullpath]
$tree delete [$tree children $node]
foreach f [lsort -dictionary [glob -nocomplain -dir $path *]] {
set type [file type $f]
set id [$tree insert $node end -text [file tail $f] \
-values [list $f $type]]
if {$type eq "directory"} {
## Make it so that this node is openable
$tree insert $id 0 -text dummy ;# a dummy
$tree item $id -text [file tail $f]/
} elseif {$type eq "file"} {
set size [file size $f]
set ttime [file mtime $f]
## Format the file size nicely
if {$size >= 1024*1024*1024} {
set size [format %.1f\ GB [expr {$size/1024/1024/1024.}]]
} elseif {$size >= 1024*1024} {
set size [format %.1f\ MB [expr {$size/1024/1024.}]]
} elseif {$size >= 1024} {
set size [format %.1f\ kB [expr {$size/1024.}]]
} else {
append size " bytes"
}
$tree set $id size $size
}
}
# Stop this code from rerunning on the current node
$tree set $node type processedDirectory
}
# ## Create the tree and set it up
ttk::treeview $tw.tree -columns {fullpath type size date time} -displaycolumns {size date time} \
-yscroll "$tw.vsb set" -xscroll "$tw.hsb set"
ttk::scrollbar $tw.vsb -orient vertical -command "$tw.tree yview"
ttk::scrollbar $tw.hsb -orient horizontal -command "$tw.tree xview"
$tw.tree heading \#0 -text "Directory Structure"
$tw.tree heading size -text "File Size"
$tw.tree column size -stretch 0 -width 70
populateRoots $tw.tree
bind $tw.tree <<TreeviewOpen>> {populateTree %W [%W focus]}
# ## Arrange the tree and its scrollbars in the toplevel
lower [ttk::frame $tw.dummy]
pack $tw.dummy -fill both -expand 1
grid $tw.tree $tw.vsb -sticky nsew -in $tw.dummy
grid $tw.hsb -sticky nsew -in $tw.dummy
grid columnconfigure $tw.dummy 0 -weight 1
grid rowconfigure $tw.dummy 0 -weight 1
Thanks in advance,
George

The issue was that the procedure populateTree needed to be ::populateTree so it could be found within the namespace.
PS: I still can't print selection to console...

Related

Powershell IF conditional isn't firing in the way I expected. Unsure what I'm doing wrong

I am writing a simple script that makes use of 7zip's command-line to extract archives within folders and then delete the original archives.
There is a part of my script that isn't behaving how I would expect it to. I can't get my if statement to trigger correctly. Here's a snippet of the code:
if($CurrentRar.Contains(".part1.rar")){
[void] $RarGroup.Add($CurrentRar)
# Value of CurrentRar:
# Factory_Selection_2.part1.rar
$CurrentRarBase = $CurrentRar.TrimEnd(".part1.rar")
# Value: Factory_Selection_2
for ($j = 1; $j -lt $AllRarfiles.Count; $j++){
$NextRar = $AllRarfiles[$j].Name
# Value: Factory_Selection_2.part2.rar
if($NextRar.Contains("$CurrentRarBase.part$j.rar")){
Write-Host "Test Hit" -ForegroundColor Green
# Never fires, and I have no idea why
# [void] $RarGroup.Add($NextRar)
}
}
$RarGroups.Add($RarGroup)
}
if($NextRar.Contains("$CurrentRarBase.part$j.rar")) is the line that I can't get to fire.
If I shorten it to if($NextRar.Contains("$CurrentRarBase.part")), it fires true. But as soon as I add the inline $j it always triggers false. I've tried casting $j to string but it still doesn't work. Am I missing something stupid?
Appreciate any help.
The issue seems to be your for statement and the fact that an array / list is zero-indexed (means they start with 0).
In your case, the index 0 of $AllRarfiles is probably the part1 and your for statement starts with 1, but the file name of index 1 does not contain part1 ($NextRar.Contains("$CurrentRarBase.part$j.rar"), but part2 ($j + 1).
As table comparison
Index / $j
Value
Built string for comparison (with Index)
0
Factory_Selection_2.part1.rar
Factory_Selection_2.part0.rar
1
Factory_Selection_2.part2.rar
Factory_Selection_2.part1.rar
2
Factory_Selection_2.part3.rar
Factory_Selection_2.part2.rar
3
Factory_Selection_2.part4.rar
Factory_Selection_2.part3.rar
Another simpler approach
Since it seems you want to group split RAR files which belong together, you could also use a simpler approach with Group-Object
# collect and group all RAR files.
$rarGroups = Get-ChildItem -LiteralPath 'C:\somewhere\' -Filter '*.rar' | Group-Object -Property { $_.Name -replace '\.part\d+\.rar$' }
# do some stuff afterwards
foreach($rarGroup in $rarGroups){
Write-Verbose -Verbose "Processing RAR group: $($rarGroup.Name)"
foreach($rarFile in $rarGroup.Group) {
Write-Verbose -Verbose "`tCurrent RAR file: $($rarFile.Name)"
# do some stuff per file
}
}

tcl insert string at certain line in file

I want to insert one string at the certain line ,and I know the line number.
ex.
#Aa_version = Aa/45.21-a32_1
#Aa_version = Aa/47.21-a33_1
Aa_version = Aa/45.27-a57_2 ->I can get this line number n
and I want to insert one line Aa/49.27-a54_1 at line n+1
and put Aa_version = Aa/45.27-a57_2 -> #Aa_version = Aa/45.27-a57_2
output is like
#Aa_version = Aa/45.21-a32_1
#Aa_version = Aa/47.21-a33_1
#Aa_version = Aa/45.27-a57_2
Aa_version = Aa/49.27-a54_1
and my code is
set Aa ""
set fp [open $file "r+"]
set lines [split [read $fp] \n]
set idx [lsearch -regexp $lines {^Aa_version} ]
regexp {Aa(.+)} [lindex $lines $idx] Aa_version
set old_version "#$Aa_version"
set newAa [gets stdin]
set new_version "Aa_version =$newAa "
puts $old_version ->replace $Aa_version
puts $new_version
close $fp
How can I put them at the correct line
thanks
I think it's easier and cleaner to handle the input file a line at a time and look at each one in turn instead of reading it all in one single go and then finding, altering and inserting elements in a list:
set fp [open $file]
while {[gets $fp line] >= 0} {
# If the line starts with Aa_version...
if {[string match "Aa_version*" $line]} {
# Comment it out
puts "#$line"
# And read the new version and write it out
set newAa [gets stdin]
puts "Aa_version = $newAa"
# Copy the rest of the file to standard output and exit the loop
chan copy $fp stdout
break
} else {
puts $line
}
}
close $fp
But if you want to keep the current list based approach, lreplace is your friend:
set fp [open $file]
set lines [split [read $fp] \n]
close $fp
set idx [lsearch -glob $lines "Aa_version*"]
# If a match was found...
if {$idx >= 0} {
# Read the new verson
set newAa [gets stdin]
# Replace the version element with two new elements:
# Commented out previous version, and new version
set lines [lreplace $lines $idx $idx \
"#[lindex $lines $idx]" "Aa_version = $newAa"]
}
puts [join $lines \n]

prevent ttk::treeview from selecting on expanding/collapsing subtrees

so i'm having a simple treeview, with some expandible subtrees, and a listener for selection changes:
ttk::treeview .tree
pack .tree -expand 1 -fill both
.tree tag configure disabled -foreground grey
.tree tag bind all <<TreeviewSelect>> "puts selected"
set node [.tree insert {} end -text "Node 0" -tags all]
.tree insert $node end -text "A" -tags "all"
.tree insert $node end -text "B" -tags "all"
.tree insert $node end -text "C" -tags "all"
set node [.tree insert {} end -text "Node 1" -tags all -open 1]
.tree insert $node end -text "X" -tags "all"
.tree insert $node end -text "Y" -tags "all"
.tree insert $node end -text "Z" -tags "all"
It seems that tcl/tk will automatically (de)select a subtree-node ("Node 0" resp "Node 1") and call the <<TreeviewSelect>> listener, if the user tries to expand/collapse the subtree (by clicking on the Expander icon left of the node element).
Is there any way to allow expansion/callapsing of the subtree without selecting the item at the same time?
My dev platform is Tcl/Tk-8.6 (on Debian/stretch), but any solution is supposed to work cross-platform.
Something like this works.
package require Tk
variable vars
proc sel { args } {
variable vars
if { $vars(skip) && $vars(oldsel) ne {} } {
.tree selection set $vars(oldsel)
set vars(skip) false
return
}
set vars(skip) false
set vars(oldsel) [.tree selection]
}
proc op { args } {
variable vars
set vars(skip) true
}
proc cl { args } {
variable vars
set vars(skip) true
}
ttk::treeview .tree
set id1 [.tree insert {} end -id id1 -text id1]
set id1-1 [.tree insert $id1 end -id id1-1 -text id1-1]
set id1-2 [.tree insert $id1 end -id id1-2 -text id1-2]
set id2 [.tree insert {} end -id id2 -text id2]
set id2-1 [.tree insert $id2 end -id id2-1 -text id2-1]
set id2-2 [.tree insert $id2 end -id id2-2 -text id2-2]
bind .tree <<TreeviewSelect>> sel
bind .tree <<TreeviewOpen>> op
bind .tree <<TreeviewClose>> cl
pack .tree
set vars(oldsel) {}

Increase performance for checking file delimiters

After spending some time looking for the most clearcut way to check if the body of a file has the same amount of delimiters as the header I came up with this code:
Param #user enters the directory path and delimiter they are checking for
(
[string]$source,
[string]$delim
)
#try {
$lineNum = 1
$thisOK = 0
$badLine = 0
$noDelim = 0
$archive = ("*archive*","*Archive*","*ARCHIVE*");
foreach ($files in Get-ChildItem $source -Exclude $archive) #folder directory may have sub folders, as a temp workaround just made sure to exclude any folder with archive
{
$read2 = New-Object System.IO.StreamReader($files.FullName)
$DataLine = (Get-Content $files.FullName)[0]
$validCount = ([char[]]$DataLine -eq $delim).count #count of delimeters in the header
$lineNum = 1 #used to write to host which line is bad in file
$thisOK = 0 #used for if condition to let the host know that the file has delimeters that line up with header
$badLine = 0 #used so the write-host doesnt meet the if condition and write the file is ok after throwing an error
while (!$read2.EndOfStream)
{
$line = $read2.ReadLine()
$total = $line.Split($delim).Length - 1;
if ($total -eq $validCount)
{
$thisOK = 1
}
elseif ($total -ne $validCount)
{
Write-Output "Error on line $lineNum for file $files. Line number $lineNum has $total delimeters and the header has $validCount"
$thisOK = 0
$badLine = 1
break; #break or else it will repeat each line that is bad
}
$lineNum++
}
if ($thisOK = 1 -and $badLine -eq 0 -and $validCount -ne 0)
{
Write-Output "$files is ok"
}
if ($validCount -eq 0)
{
Write-Output "$files does not contain entered delimeter: $delim"
}
$read2.Close()
$read2.Dispose()
} #end foreach loop
#} catch {
# $ErrorMessage = $_.Exception.Message
# $FailedItem = $_.Exception.ItemName
#}
It works for what I have tested so far. However, when it comes to larger files, it takes considerably longer. I was wondering what I can do or change for this code to make it process these text/CSV files more quickly?
Also, my try..catch statements are commented out since the script doesn't seem to run when I include them - no error just enters a new command line. As a thought I was looking to incorporate a simple GUI for other users to double check.
Sample file:
HeaderA|HeaderB|HeaderC|HeaderD //header line
DataLnA|DataLnBBDataLnC|DataLnD|DataLnE //bad line
DataLnA|DataLnB|DataLnC|DataLnD| //bad line
DataLnA|DataLnB|DataLnC|DataLnD //good line
Now that I look at it, I guess there could be an issue where there are the correct amount if delimeters but the columns mismatch like this:
HeaderA|HeaderB|HeaderC|HeaderD
DataLnA|DataLnBDataLnC|DataLnD|
The main problem that I see is that you are reading the file twice -- once with the call to Get-Content, which reads the entire file into memory, and a second time with your while loop. You can double the speed of your process by replacing this line:
$DataLine = (Get-Content $files.FullName)[0] #inefficient
with this:
$DataLine = Get-Content $files.FullName -First 1 #efficient

How to split a variable in two args with exec in TCL?

I want to create a simple Console in Tcl/Tk
I have two problems. First changing every * with a [glob *] but also, when my entry contains "ls -a" it doesn't understand that ls is the command and -a the first arg.
How can I manage to do that ?
Thanks
proc execute {} {
# ajoute le contenu de .add_frame.add_entry
set value [.add_frame.add_entry get]
if {[string compare "$value" ""] == 1} {
.text insert end "\n\n% $value\n"
.text insert end [exec $value]
.add_frame.add_entry delete 0 end
}
}
frame .add_frame
label .add_frame.add_label -text "Nouvel élément : "
entry .add_frame.add_entry
button .add_frame.add_button -text "Executer" -command execute
button .add_frame.exit_button -text "Quitter" -command exit
bind .add_frame.add_entry <Return> execute
bind .add_frame.add_entry <KP_Enter> execute
bind . <Escape> exit
bind . <Control-q> exit
pack .add_frame.add_label -side left
pack .add_frame.exit_button -side right
pack .add_frame.add_button -side right
pack .add_frame.add_entry -fill x -expand true
pack .add_frame -side top -fill x
text .text
.text insert end "% Tcl/Tk Console"
pack .text -side bottom -fill both -expand true
The simple answer in Tcl 8.5 is to use this:
exec {*}$value
In 8.4 and before, that syntax didn't exist. That meant that many people wrote this:
eval exec $value
But in reality, the safe version was one of these:
eval exec [lrange $value 0 end]
eval [linsert $value 0 exec]
Of course, if the $value is coming straight from the user then you're better off using a system shell to evaluate it since more users expect that sort of syntax:
exec /usr/bin/bash -c $value

Resources