Minsky
csvImport.tcl
Go to the documentation of this file.
1 # assume canvas.minsky.item is the variable
2 
3 proc separatorToChar {separator} {
4  switch $separator {
5  "<tab>" {return "\t"}
6  "<space>" {return " "}
7  default {return $separator}
8  }
9 }
10 
11 proc separatorFromChar {separatorChar} {
12  switch $separatorChar {
13  "\t" {return "<tab>"}
14  " " {return "<space>"}
15  default {return $separatorChar}
16  }
17 }
18 
19 proc loadDialogFromSpec {} {
20  global csvParms
21  set csvParms(url) [minsky.value.csvDialog.url]
22  set csvParms(separator) [separatorFromChar [minsky.value.csvDialog.spec.separator]]
23  set csvParms(decSeparator) [minsky.value.csvDialog.spec.decSeparator]
24  set csvParms(escape) [minsky.value.csvDialog.spec.escape]
25  set csvParms(quote) [minsky.value.csvDialog.spec.quote]
26  set csvParms(mergeDelimiters) [minsky.value.csvDialog.spec.mergeDelimiters]
27  set csvParms(missingValue) [minsky.value.csvDialog.spec.missingValue]
28  set csvParms(duplicateKeyValue) [minsky.value.csvDialog.spec.duplicateKeyAction]
29  set csvParms(horizontalDimension) [minsky.value.csvDialog.spec.horizontalDimName]
30  set csvParms(horizontalType) [minsky.value.csvDialog.spec.horizontalDimension.type]
31  set csvParms(horizontalFormat) [minsky.value.csvDialog.spec.horizontalDimension.units]
32 }
33 
34 proc CSVImportDialog {} {
35  if {[llength [info commands minsky.canvas.item.valueId]]==0} return
36  getValue [minsky.canvas.item.valueId]
37  global workDir csvParms dataImportDialog
39  if {![winfo exists .wiring.csvImport]} {
40  toplevel .wiring.csvImport
41  minsky.canvas.item.deleteCallback "destroy .wiring.csvImport"
42  if {$dataImportDialog} {bind .wiring.csvImport <Destroy> "canvas.deleteItem; set dataImportDialog 0"}
43 
44  # file/url control
45  frame .wiring.csvImport.fileUrl
46  button .wiring.csvImport.fileUrl.file -text "File" -command {
47  set csvParms(url) [tk_getOpenFile -filetypes {{CSV {.csv}} {All {.*}}} -initialdir $workDir]
48  .wiring.csvImport.fileUrl.load invoke
49  raise .wiring.csvImport
50  }
51  label .wiring.csvImport.fileUrl.orUrl -text "or URL"
52  entry .wiring.csvImport.fileUrl.url -textvariable csvParms(url) -width 100
53  button .wiring.csvImport.fileUrl.load -text "Load" -command {
54  if {[minsky.value.csvDialog.url]!=$csvParms(url)} {
55  minsky.value.csvDialog.url $csvParms(url)
56  minsky.value.csvDialog.guessSpecAndLoadFile
57  loadDialogFromSpec
58  } else {
59  minsky.value.csvDialog.loadFile
60  }
61  minsky.value.csvDialog.requestRedraw
62  # update to calculate tableWidth
63  update
64  .wiring.csvImport.hscroll configure -to [expr int([minsky.value.csvDialog.tableWidth])]
65  }
66  bind .wiring.csvImport.fileUrl.url <Key-Return> ".wiring.csvImport.fileUrl.load invoke"
67 
68  pack .wiring.csvImport.fileUrl.file .wiring.csvImport.fileUrl.orUrl \
69  .wiring.csvImport.fileUrl.url .wiring.csvImport.fileUrl.load -side left
70  pack .wiring.csvImport.fileUrl
71 
72  # delimiters control
73  frame .wiring.csvImport.delimiters
74  label .wiring.csvImport.delimiters.columnarLabel -text "Columnar"
75  ttk::checkbutton .wiring.csvImport.delimiters.columnar -variable csvParms(columnar) -command {
76  minsky.value.csvDialog.spec.columnar $csvParms(columnar)
77  minsky.value.csvDialog.requestRedraw
78  }
79  label .wiring.csvImport.delimiters.separatorLabel -text Separator
80  ttk::combobox .wiring.csvImport.delimiters.separatorValue -values {
81  "," ";" "<tab>" "<space>"} -textvariable csvParms(separator) -width 5
82  bind .wiring.csvImport.delimiters.separatorValue <<ComboboxSelected>> {
83  minsky.value.csvDialog.spec.separator [separatorToChar $csvParms(separator)]}
84  label .wiring.csvImport.delimiters.decSeparatorLabel -text "Decimal Separator"
85  ttk::combobox .wiring.csvImport.delimiters.decSeparatorValue -values {
86  "." ","} -textvariable csvParms(decSeparator) -width 5
87  bind .wiring.csvImport.delimiters.decSeparatorValue <<ComboboxSelected>> {
88  minsky.value.csvDialog.spec.decSeparator $csvParms(decSeparator)}
89  label .wiring.csvImport.delimiters.escapeLabel -text Escape
90  ttk::combobox .wiring.csvImport.delimiters.escapeValue -values {
91  "\\"} -textvariable csvParms(escape) -width 5
92  bind .wiring.csvImport.delimiters.escapeValue <<ComboboxSelected>> {
93  minsky.value.csvDialog.spec.escape $csvParms(escape)}
94  label .wiring.csvImport.delimiters.quoteLabel -text Quote
95  ttk::combobox .wiring.csvImport.delimiters.quoteValue -values {
96  "'" "\"" } -textvariable csvParms(quote) -width 5
97  bind .wiring.csvImport.delimiters.quoteValue <<ComboboxSelected>> {
98  minsky.value.csvDialog.spec.quote $csvParms(quote)}
99  label .wiring.csvImport.delimiters.mergeLabel -text "Merge Delimiters"
100  ttk::checkbutton .wiring.csvImport.delimiters.mergeValue -variable csvParms(mergeDelimiters) -command {
101  minsky.value.csvDialog.spec.mergeDelimiters $csvParms(mergeDelimiters)
102  }
103  label .wiring.csvImport.delimiters.missingLabel -text "Missing Value"
104  ttk::combobox .wiring.csvImport.delimiters.missingValue \
105  -textvariable csvParms(missingValue) -values {nan 0} -width 5
106  bind .wiring.csvImport.delimiters.missingValue <<ComboboxSelected>> {
107  minsky.value.csvDialog.spec.missingValue $csvParms(missingValue)}
108 
109  label .wiring.csvImport.delimiters.colWidthLabel -text "Col Width"
110  spinbox .wiring.csvImport.delimiters.colWidth -from 10 -to 500 -increment 10 \
111  -width 5 -command {adjustColWidth %s} -validate key -validatecommand {adjustColWidth %P}
112  .wiring.csvImport.delimiters.colWidth set 50
113 
114  pack .wiring.csvImport.delimiters.columnarLabel .wiring.csvImport.delimiters.columnar \
115  .wiring.csvImport.delimiters.separatorLabel .wiring.csvImport.delimiters.separatorValue \
116  .wiring.csvImport.delimiters.decSeparatorLabel .wiring.csvImport.delimiters.decSeparatorValue \
117  .wiring.csvImport.delimiters.escapeLabel .wiring.csvImport.delimiters.escapeValue \
118  .wiring.csvImport.delimiters.quoteLabel .wiring.csvImport.delimiters.quoteValue \
119  .wiring.csvImport.delimiters.mergeLabel .wiring.csvImport.delimiters.mergeValue \
120  .wiring.csvImport.delimiters.missingLabel .wiring.csvImport.delimiters.missingValue \
121  .wiring.csvImport.delimiters.colWidthLabel .wiring.csvImport.delimiters.colWidth -side left
122 
123  pack .wiring.csvImport.delimiters
124 
125  # horizontal dimension control
126  frame .wiring.csvImport.horizontalName
127  label .wiring.csvImport.horizontalName.duplicateKeyLabel -text "Duplicate Key Action"
128  ttk::combobox .wiring.csvImport.horizontalName.duplicateKeyValue \
129  -textvariable csvParms(duplicateKeyValue) \
130  -values {throwException sum product min max av} -width 15
131  bind .wiring.csvImport.horizontalName.duplicateKeyValue <<ComboboxSelected>> {
132  minsky.value.csvDialog.spec.duplicateKeyAction $csvParms(duplicateKeyValue)}
133  label .wiring.csvImport.horizontalName.text -text "Horizontal dimension"
134  entry .wiring.csvImport.horizontalName.value -width 30 -textvariable csvParms(horizontalDimension)
135  label .wiring.csvImport.horizontalName.typeLabel -text "Type"
136  ttk::combobox .wiring.csvImport.horizontalName.type \
137  -textvariable csvParms(horizontalType) \
138  -values {string value time} -width 15 -state readonly
139  bind .wiring.csvImport.horizontalName.type <<ComboboxSelected>> {
140  minsky.value.csvDialog.spec.horizontalDimension.type $csvParms(horizontalType)
141  dimFormatPopdown .wiring.csvImport.horizontalName.format $csvParms(horizontalType) {
142  minsky.value.csvDialog.spec.horizontalDimension.units [.wiring.csvImport.horizontalName.format get]
143  }
144  }
145  label .wiring.csvImport.horizontalName.formatLabel -text "Format"
146  ttk::combobox .wiring.csvImport.horizontalName.format \
147  -textvariable csvParms(horizontalFormat) -width 15
148  bind .wiring.csvImport.horizontalName.format <<ComboboxSelected>> {
149  minsky.value.csvDialog.spec.horizontalDimension.units $csvParms(horizontalFormat)}
150 
151  pack .wiring.csvImport.horizontalName.duplicateKeyLabel .wiring.csvImport.horizontalName.duplicateKeyValue -side left
152  pack .wiring.csvImport.horizontalName.text .wiring.csvImport.horizontalName.value -side left
153  pack .wiring.csvImport.horizontalName
154  pack .wiring.csvImport.horizontalName.typeLabel .wiring.csvImport.horizontalName.type .wiring.csvImport.horizontalName.formatLabel .wiring.csvImport.horizontalName.format -side left
155 
156  image create cairoSurface csvDialogTable -surface minsky.value.csvDialog
157  label .wiring.csvImport.table -image csvDialogTable -width 800 -height 300
158  pack .wiring.csvImport.table -fill both -expand 1 -side top
159 
160  scale .wiring.csvImport.hscroll -orient horiz -from -100 -to 1000 -showvalue 0 -command scrollTable
161  pack .wiring.csvImport.hscroll -fill x -expand 1 -side top
162 
163  buttonBar .wiring.csvImport {}
164  # redefine OK command to not delete the the import window on error
165  global csvImportFailed
166  set csvImportFailed 0
167  .wiring.csvImport.buttonBar.ok configure -command csvImportDialogOK -text Import
168  bind .wiring.csvImport.table <Configure> "minsky.value.csvDialog.requestRedraw"
169  bind .wiring.csvImport.table <Button-1> {csvImportButton1 %x %y};
170  bind .wiring.csvImport.table <ButtonRelease-1> {csvImportButton1Up %x %y %X %Y};
171  bind .wiring.csvImport.table <B1-Motion> {
172  minsky.value.csvDialog.xoffs [expr $csvImportPanX+%x];
173  if $movingHeader {
174  set row [minsky.value.csvDialog.rowOver %y]
175  if {$row>=4} {minsky.value.csvDialog.spec.headerRow [expr $row-4]}
176  minsky.value.csvDialog.flashNameRow [expr $row==3]
177  }
178  minsky.value.csvDialog.requestRedraw
179  }
180  }
181 
182  if [string length [minsky.value.csvDialog.url]] {
183  set workDir [file dirname [minsky.value.csvDialog.url]]
184  }
185  .wiring.csvImport.delimiters.colWidth set [minsky.value.csvDialog.colWidth]
186  wm deiconify .wiring.csvImport
187  raise .wiring.csvImport
188  minsky.value.csvDialog.requestRedraw
189 }
190 
191 proc csvImportDialogOK {} {
192  global csvParms dataImportDialog
193  minsky.value.csvDialog.spec.horizontalDimName $csvParms(horizontalDimension)
194  set csvImportFailed [catch {loadVariableFromCSV minsky.value.csvDialog.spec "$csvParms(url)"} err]
195  if $csvImportFailed {
196  toplevel .csvImportError
197  label .csvImportError.errMsg -text $err
198  label .csvImportError.msg -text "Would you like to generate a report?"
199  pack .csvImportError.errMsg .csvImportError.msg -side top
200  buttonBar .csvImportError "global csvParms; doReport {$csvParms(url)}"
201  .csvImportError.buttonBar.ok configure -text "Yes"
202  .csvImportError.buttonBar.cancel configure -text "No"
203  } else {
204  if {$dataImportDialog} {
205  minsky.canvas.item.name [file rootname [file tail [minsky.value.csvDialog.url]]]
206  # don't delete the variable we've so carefully populated
207  bind .wiring.csvImport <Destroy> ""
208  itemFocusFromItem
209  set dataImportDialog 0
210  }
211  catch reset
212  cancelWin .wiring.csvImport
213  }
214 }
215 
216 proc doReport {inputFname} {
217  global workDir
218  set fname [tk_getSaveFile -defaultextension .csv -initialfile [file rootname $inputFname]-error-report.csv -initialdir $workDir]
219  if [string length $fname] {
220  eval minsky.value.csvDialog.reportFromFile {$inputFname} {$fname}
221  }
222 }
223 
224 
225 proc scrollTable v {
226  minsky.value.csvDialog.xoffs [expr -$v]
227  minsky.value.csvDialog.requestRedraw
228 }
229 
230 proc adjustColWidth {w} {
231  minsky.value.csvDialog.colWidth $w
232  minsky.value.csvDialog.requestRedraw
233  return 1
234 }
235 
236 proc csvImportButton1 {x y} {
237  global csvImportPanX mouseSave movingHeader
238  set csvImportPanX [expr [minsky.value.csvDialog.xoffs]-$x]
239  set movingHeader [expr [minsky.value.csvDialog.rowOver $y]-4 == [minsky.value.csvDialog.spec.headerRow]]
240  set mouseSave "$x $y"
241 }
242 
243 proc closeCombo setter {
244  eval $setter \[.wiring.csvImport.text.combo get\]
245  wm withdraw .wiring.csvImport.text
246  minsky.value.csvDialog.requestRedraw
247 }
248 
249 proc setupCombo {getter setter title configure X Y} {
250  wm title .wiring.csvImport.text $title
251  eval .wiring.csvImport.text.combo configure $configure
252  .wiring.csvImport.text.combo set $getter
253  bind .wiring.csvImport.text.combo <<ComboboxSelected>> "closeCombo $setter"
254  bind .wiring.csvImport.text.combo <Return> "closeCombo $setter"
255  wm deiconify .wiring.csvImport.text
256  wm geometry .wiring.csvImport.text +$X+$Y
257  raise .wiring.csvImport.text
258 }
259 
260 proc csvImportButton1Up {x y X Y} {
261  global mouseSave csvParms movingHeader
262  # combobox used for setting dimension type
263  if {![winfo exists .wiring.csvImport.text.combo]} {
264  toplevel .wiring.csvImport.text
265  ttk::combobox .wiring.csvImport.text.combo -values {"string" "value" "time"} -state readonly
266  pack .wiring.csvImport.text.combo
267  wm withdraw .wiring.csvImport.text
268  }
269 
270  set col [minsky.value.csvDialog.columnOver $x]
271  set row [minsky.value.csvDialog.rowOver $y]
272  if {abs([lindex $mouseSave 0]-$x)==0 && abs([lindex $mouseSave 1]-$y)==0} {
273  # mouse click - implement dialog interaction logic
274  switch [minsky.value.csvDialog.rowOver $y] {
275  0 {minsky.value.csvDialog.spec.toggleDimension $col
276  minsky.value.csvDialog.requestRedraw
277  }
278  1 {
279  if {$col<[minsky.value.csvDialog.spec.dimensions.size]} {
280  setupCombo [[minsky.value.csvDialog.spec.dimensions.@elem $col].type] \
281  "minsky.value.csvDialog.spec.dimensions($col).type" \
282  "Dimension type" {-values {"string" "value" "time"} -state readonly} $X $Y
283  }
284  }
285  2 {
286  if {$col<[minsky.value.csvDialog.spec.dimensions.size]} {
287  set units "{}"
288  setupCombo [[minsky.value.csvDialog.spec.dimensions.@elem $col].units] \
289  "minsky.value.csvDialog.spec.dimensions($col).units" \
290  "Dimension units/format" "-values $units -state normal" $X $Y
291  dimFormatPopdown .wiring.csvImport.text.combo [[minsky.value.csvDialog.spec.dimensions.@elem $col].type] "minsky.value.csvDialog.spec.dimensions($col).units \[.wiring.csvImport.text.combo get\]"
292  }
293  }
294  3 {if {$col<[minsky.value.csvDialog.spec.dimensions.size]} {
295  setupCombo [[minsky.value.csvDialog.spec.dimensionNames.@elem $col]] \
296  "minsky.value.csvDialog.spec.dimensionNames($col)" \
297  "Dimension name" "-values \"[minsky.value.csvDialog.headerForCol $col]\" -state normal" $X $Y
298  }}
299  default {
300  minsky.value.csvDialog.spec.setDataArea [expr $row-4] $col
301  minsky.value.csvDialog.requestRedraw
302  }
303  }
304  }
305  if {$movingHeader && $row==3} {
306  # copy columns headers to dimension names
307  set oldHeaderRow [expr [minsky.value.csvDialog.rowOver [lindex $mouseSave 1]]-4]
308  minsky.value.csvDialog.copyHeaderRowToDimNames $oldHeaderRow
309  minsky.value.csvDialog.spec.headerRow $oldHeaderRow
310  minsky.value.csvDialog.flashNameRow 0
311  minsky.value.csvDialog.requestRedraw
312  }
313  set movingHeader 0
314 }