aboutsummaryrefslogtreecommitdiff
path: root/advtrains/locale/gui
blob: 7f455e48acb511f4912f5fdbcdd950d2fe0b3f83 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
#!/usr/bin/tclsh
package require Tk

# Auxiliary functions
proc maybe_lindex {lst idx fallback} {
	set val [lindex $lst $idx]
	if {$val eq {}} {
		return $fallback
	} else {
		return $val
	}
}

proc firstupper {str} {
	return [string cat [string toupper [string index $str 0]] [string range $str 1 end]]
}

# CLI arguments
# NOTE: This will likely be changed in the future
set translationDomain [maybe_lindex $argv 0 "advtrains"]
set translationDir [maybe_lindex $argv 1 "."]

# Translation file I/O, etc

set translationTemplatePath [file join $translationDir "template.txt"]

proc translationFilePaths {} {
	global translationDir translationDomain
	return [glob -path [file join $translationDir $translationDomain] ".*.tr"]
}

proc readTranslationTemplate {} {
	global translationTemplate translationTemplatePath
	set translationTemplate [list]
	set handle [open $translationTemplatePath "r"]
	fconfigure $handle -translation lf
	while {[gets $handle line] >= 0} {
		if {$line eq ""} {
			lappend translationTemplate $line
		} elseif {[string match {#*} $line]} {
			lappend translationTemplate $line
		} elseif {[regexp {^(.+[^@])=.+$} $line x str]} {
			lappend translationTemplate $str
		}
	}
	close $handle
}

proc readTranslationFiles {} {
	global translationData translationLangs
	array set translationData [list]
	set translationLangs [list]
	foreach fn [translationFilePaths] {
		if {[regexp {\.([^.]+)\.tr$} $fn x lang]} {
			set handle [open $fn "r"]
			fconfigure $handle -translation lf
			while {[gets $handle line] >= 0} {
				if {[regexp {^([^#].+[^@])=(.+)$} $line x ori tr]} {
					set translationData($lang,$ori) $tr
				}
			}
			lappend translationLangs $lang
			close $handle
		}
	}
	set translationLangs [lsort $translationLangs]
}

proc readTranslations {} {
	readTranslationTemplate
	readTranslationFiles
}

proc writeTranslationTemplate {} {
	global translationTemplate translationTemplatePath
	set handle [open $translationTemplatePath "w"]
	fconfigure $handle -translation lf
	foreach line $translationTemplate {
		if {$line eq ""} {
			puts $handle ""
		} elseif {[string match {#*} $line]} {
			puts $handle $line
		} else {
			puts $handle "$line=$line"
		}
	}
	close $handle
}

proc writeTranslationFiles {} {
	global translationDir translationDomain translationData translationLangs translationTemplate
	foreach lang $translationLangs {
		set handle [open [file join $translationDir "$translationDomain.$lang.tr"] "w"]
		fconfigure $handle -translation lf
		foreach i $translationTemplate {
			if {$i eq ""} {
				puts $handle ""
			} elseif {[string match {#*} $i]} {
				puts $handle $i
			} else {
				puts $handle [format "%s=%s" $i [getTranslationString $lang $i]]
			}
		}
		close $handle
	}
}

proc writeTranslations {} {
	writeTranslationTemplate
	writeTranslationFiles
}

proc hasTranslationString {lang str} {
	return [expr {[getTranslationString $lang $str] ne $str}]
}

proc maybeGetTranslationString {lang str} {
	set tr [getTranslationString $lang $str]
	if {$tr eq $str} {
		return {}
	} else {
		return $tr
	}
}

proc getTranslationString {lang str} {
	global translationData
	if {[info exists translationData($lang,$str)]} {
		return $translationData($lang,$str)
	} else {
		return $str
	}
}

proc setTranslationString {lang ori tr} {
	global translationData
	set translationData($lang,$ori) $tr
}

proc rewordTranslationString {old new} {
	global translationData translationLangs
	foreach lang $translationLangs {
		if {[hasTranslationString $lang $old]} {
			setTranslationString $lang $new [getTranslationString $lang $old]
		} else {
			setTranslationString $lang $new $new
		}
	}
}

# GUI helpers
proc gAddTab {basename title elems dummies} {
	global gMainNotebook
	gAddFrame $basename $elems $dummies
	$gMainNotebook add "$gMainNotebook.$basename" -text $title
}

proc gAddFrame {basename elems dummies} {
	global gMainNotebook
	set varbase [string cat "g" [firstupper $basename]]
	set varname "${varbase}Frame"
	set pathname "$gMainNotebook.$basename"
	global $varname
	set $varname $pathname
	ttk::frame $pathname -padding 5
	foreach i $elems {
		set varname [string cat $varbase [firstupper $i]]
		global $varname
		set $varname "$pathname.$i"
	}
	foreach i $dummies {
		set varname [string cat $varbase [firstupper $i]]
		global $varname
		set $varname {}
	}
}

proc gReloadTranslations {} {
	readTranslations
	gTrLoadTranslations
	gTmLoadTranslations
}

proc gAddMenu {varname parent menuname entries} {
	global $varname
	set path "${parent}.${menuname}"
	set $varname $path
	$parent configure -menu [menu $path -tearoff false]
	foreach i $entries {
		$path add {*}$i
	}
	return $parent
}
set gMenuItemNotImplemented [list command -state disabled -label "(Not implemented)"]

proc gNotImplemented {args} {
	tk_messageBox -message "Not implemented" -icon error -type ok
}

# Main window

set gMainFrame ".f"
set gMainFrameWidgetCount 0
foreach i [list readBtn writeBtn notebookTopSeparator notebook] {
	set [string cat "gMain" [firstupper $i]] "$gMainFrame.$i"
}

wm title . "Advtrains Translation File Editor"
grid rowconfigure . 0 -weight 1
grid columnconfigure . 0 -weight 1

ttk::frame $gMainFrame
grid $gMainFrame -column 0 -row 0 -sticky nsew
grid rowconfigure $gMainFrame 2 -weight 1

foreach i [list \
	[ttk::button $gMainReadBtn -text "Reload translation files" -command gReloadTranslations] \
	[ttk::button $gMainWriteBtn -text "Write changes" -command writeTranslations] \
] {
	grid $i -column $gMainFrameWidgetCount -row 0 -sticky ns
	incr gMainFrameWidgetCount
}

grid columnconfigure $gMainFrame $gMainFrameWidgetCount -weight 1

ttk::separator $gMainNotebookTopSeparator -orient horizontal
grid $gMainNotebookTopSeparator -column 0 -row 1 -columnspan [expr {1+$gMainFrameWidgetCount}] -sticky ew

ttk::notebook $gMainNotebook
grid $gMainNotebook -column 0 -row 2 -columnspan [expr {1+$gMainFrameWidgetCount}] -sticky nsew

# Translation Manager
# FIXME?: it seems like Tcl requires -textvariable globals to be set first before they are accessible

gAddTab tr "Translations" \
	[list langSelect refLangSelect readBtn writeBtn treeviewFrame origTextLabel origTextField translatedTextLabel translatedTextField refTextLabel refTextField] \
	[list langValue refLangValue origTextValue translatedTextValue refTextValue]
set gTrTreeview "$gTrTreeviewFrame.main"
set gTrTreeviewScrollbar "$gTrTreeviewFrame.scrollbar"

grid rowconfigure $gTrFrame 0 -weight 1
grid columnconfigure $gTrFrame 2 -weight 1

ttk::frame $gTrTreeviewFrame -borderwidth 1 -relief sunken
grid $gTrTreeviewFrame -column 0 -row 0 -columnspan 3 -sticky nsew
grid rowconfigure $gTrTreeviewFrame 0 -weight 1
grid columnconfigure $gTrTreeviewFrame 0 -weight 1
ttk::treeview $gTrTreeview -selectmode browse -columns {translation} -yscrollcommand {$gTrTreeviewScrollbar set}
grid $gTrTreeview -column 0 -row 0 -sticky nsew
bind $gTrTreeview <<TreeviewSelect>> gTrTreeviewSelectionCallback
ttk::scrollbar $gTrTreeviewScrollbar -orient vertical -command {$gTrTreeview yview}
grid $gTrTreeviewScrollbar -column 1 -row 0 -sticky ns
$gTrTreeview heading #0 -text "Original text"

lmap i [list \
	[list \
		[ttk::label $gTrOrigTextLabel -text "Original" -anchor w] \
		{} \
		[ttk::entry $gTrOrigTextField -state readonly -textvariable gTrOrigTextValue] \
	] \
	[list \
		[ttk::label $gTrTranslatedTextLabel -text "Translation" -anchor w] \
		[ttk::combobox $gTrLangSelect -exportselection false -state readonly -textvariable gTrLangValue] \
		[ttk::entry $gTrTranslatedTextField -textvariable gTrTranslatedTextValue] \
	] \
	[list \
		[ttk::label $gTrRefTextLabel -text "Reference" -anchor w] \
		[ttk::combobox $gTrRefLangSelect -exportselection false -state readonly -textvariable gTrRefLangValue] \
		[ttk::entry $gTrRefTextField -state readonly -textvariable gTrRefTextValue] \
	] \
] row [list 1 2 3] {
	lmap item $i col [list 0 1 2] {
		if {$item eq ""} {
			continue
		}
		grid $item -column $col -row $row -sticky nswe
	}
	grid rowconfigure $gTrFrame $row -uniform bottom
	incr gTrRowCount
}

bind $gTrLangSelect <<ComboboxSelected>> gTrLoadTranslationsToTreeview
trace add variable gTrTranslatedTextValue write gTrApplyTranslationString
bind $gTrRefLangSelect <<ComboboxSelected>> gTrTreeviewSelectionCallback

proc gTrLoadTranslationsToTreeview {} {
	global translationTemplate gTrTreeview gTrLangValue
	$gTrTreeview heading translation -text "Translation: $gTrLangValue"
	set prevFocus [$gTrTreeview item [$gTrTreeview focus] -text]
	array set openedSections [list]
	foreach i [$gTrTreeview children {}] {
		if {[$gTrTreeview item $i -open]} {
			set openedSections([$gTrTreeview item $i -text]) 1
		}
	}
	$gTrTreeview delete [$gTrTreeview children {}]
	set parent {}
	foreach i [lrange $translationTemplate 1 end] {
		if {[regexp {^\#+\s*(.+)} $i x comment]} {
			set parent [$gTrTreeview insert {} end -text $comment -tags {notranslate}]
			if {[info exists openedSections($comment)]} {
				$gTrTreeview item $parent -open true
			}
			if {$comment eq $prevFocus} {
				$gTrTreeview focus $parent
				$gTrTreeview selection set $parent
			}
		} elseif {$i ne ""} {
			set last [$gTrTreeview insert $parent end -text $i -values [list [maybeGetTranslationString $gTrLangValue $i]]]
			if {$i eq $prevFocus} {
				$gTrTreeview focus $last
				$gTrTreeview selection set $last
			}
		}
	}
	gTrTreeviewSelectionCallback
}

proc gTrTreeviewSelectionCallback {} {
	global gTrOrigTextValue gTrTreeview gTrLangSelect gTrLangValue gTrRefLangSelect gTrRefLangValue gTrTranslatedTextField gTrTranslatedTextValue gTrRefTextField gTrRefTextValue
	set focused [$gTrTreeview focus]
	set gTrOrigTextValue [$gTrTreeview item $focused -text]
	if {$focused eq ""} {
		$gTrTranslatedTextField state disabled
		$gTrRefTextField state disabled
		set gTrTranslatedTextValue "Select an entry to translate"
		set gTrRefTextValue ""
	} elseif {[$gTrTreeview tag has notranslate $focused]} {
		$gTrTranslatedTextField state disabled
		$gTrRefTextField state disabled
		set gTrTranslatedTextValue "Category names cannot be translated"
		set gTrRefTextValue ""
	} else {
		$gTrTranslatedTextField state !disabled
		set gTrTranslatedTextValue [maybeGetTranslationString $gTrLangValue $gTrOrigTextValue]
		set gTrRefTextValue [maybeGetTranslationString $gTrRefLangValue $gTrOrigTextValue]
		if {$gTrRefLangValue eq ""} {
			$gTrRefTextField state disabled
			set gTrRefTextValue ""
		} elseif {$gTrRefTextValue eq ""} {
			$gTrRefTextField state disabled
			set gTrRefTextValue "The selected string is not yet translated to the reference language"
		} else {
			$gTrRefTextField state !disabled
		}
	}
	$gTrLangSelect selection clear
	$gTrRefLangSelect selection clear
}

proc gTrLoadTranslations {} {
	global translationLangs gTrLangSelect gTrLangValue gTrRefLangSelect gTrRefLangValue
	set prevLang $gTrLangValue
	set prevRefLang $gTrRefLangValue
	$gTrLangSelect configure -values $translationLangs
	$gTrRefLangSelect configure -values [linsert $translationLangs 0 ""]
	if {[llength $translationLangs] < 1} {
		tk_messageBox -icon error type ok -title "Error" -message "No translation files present."
		exit 1
	}
	if {$prevLang ni $translationLangs} {
		$gTrLangSelect current 0
	}
	if {$prevRefLang ni $translationLangs} {
		$gTrRefLangSelect current 0
	}
	gTrLoadTranslationsToTreeview
}

proc gTrApplyTranslationString args {
	global gTrTreeview gTrLangValue gTrOrigTextValue gTrTranslatedTextValue
	set focused [$gTrTreeview focus]
	if {![$gTrTreeview tag has notranslate $focused]} {
		setTranslationString $gTrLangValue $gTrOrigTextValue $gTrTranslatedTextValue
		$gTrTreeview item $focused -values [list $gTrTranslatedTextValue]
	}
}

# Translation Template Editor

gAddTab tm "Template" \
	[list addStringMenuBtn addHeadingMenuBtn removeMenuBtn moveMenuBtn textField setMenuBtn treeviewFrame] \
	[list textValue]
set gTmTreeview "$gTmTreeviewFrame.main"
set gTmTreeviewScrollbar "$gTmTreeviewFrame.scrollbar"

set gTmRowCount 0
foreach i [list \
	[gAddMenu gTmAddStringMenu [ttk::menubutton $gTmAddStringMenuBtn -text "Add string"] "menu" [list \
		[list command -label "Insert before current entry" -command {gTmInsertStringAt true 0}] \
		[list command -label "Insert after current entry" -command {gTmInsertStringAt true 1}] \
		[list command -label "Insert as the first entry" -command {gTmInsertStringAt false 0}] \
		[list command -label "Insert as the last entry" -command {gTmInsertStringAt false end}] \
	]] \
	[gAddMenu gTmAddHeadingMenu [ttk::menubutton $gTmAddHeadingMenuBtn -text "Add heading"] "menu" [list \
		[list command -label "Insert before current section" -command {gTmInsertHeadingAt true 0}] \
		[list command -label "Insert after current section" -command {gTmInsertHeadingAt true 1}] \
	]] \
	[gAddMenu gTmRemoveMenu [ttk::menubutton $gTmRemoveMenuBtn -text "Remove"] "menu" [list \
		[list command -label "Remove entry" -command gTmDeleteEntry] \
		[list command -label "Remove section" -command gTmDeleteEntry] \
		[list command -label "Merge with previous section" -command gTmMergeWithPrevious] \
	]] \
	[gAddMenu gTmMoveMenu [ttk::menubutton $gTmMoveMenuBtn -text "Move"] "menu" [list \
		[list command -label "Up" -command {gTmMoveInSection true -1}] \
		[list command -label "Down" -command {gTmMoveInSection true 1}] \
		[list command -label "To first" -command {gTmMoveInSection false 0}] \
		[list command -label "To last" -command {gTmMoveInSection false end}] \
	]] \
] {
	grid $i -column 1 -row $gTmRowCount -sticky we
	incr gTmRowCount
}

ttk::frame $gTmTreeviewFrame -borderwidth 1 -relief sunken
grid $gTmTreeviewFrame -column 0 -row 0 -rowspan [expr {1+$gTmRowCount}] -sticky nsew
grid rowconfigure $gTmTreeviewFrame 0 -weight 1
grid columnconfigure $gTmTreeviewFrame 0 -weight 1
grid rowconfigure $gTmFrame $gTmRowCount -weight 1
grid columnconfigure $gTmFrame 0 -weight 1
ttk::treeview $gTmTreeview -selectmode browse -show tree -yscrollcommand {$gTmTreeviewScrollbar set}
bind $gTmTreeview <<TreeviewSelect>> gTmTreeviewSelectionCallback
grid $gTmTreeview -column 0 -row 0 -sticky nsew
ttk::scrollbar $gTmTreeviewScrollbar -orient vertical -command {$gTmTreeview yview}
grid $gTmTreeviewScrollbar -column 1 -row 0 -sticky ns
incr gTmRowCount

ttk::entry $gTmTextField -textvariable gTmTextValue
grid $gTmTextField -column 0 -row $gTmRowCount -sticky nsew

gAddMenu gTmSetMenu [ttk::menubutton $gTmSetMenuBtn -text "Set"] "menu" [list \
	[list command -label "Set heading" -command gTmSetEntry] \
	[list command -label "Set and copy translations" -command gTmSetEntryAndCopy] \
	[list command -label "Set without copying translations" -command gTmSetEntry] \
]
grid $gTmSetMenuBtn -column 1 -row $gTmRowCount -sticky nsew

proc gTmLoadTranslationsToTreeview {} {
	global translationTemplate gTmTreeview
	set prevFocus [$gTmTreeview item [$gTmTreeview focus] -text]
	array set openedSections [list]
	foreach i [$gTmTreeview children {}] {
		if {[$gTmTreeview item $i -open]} {
			set openedSections([$gTmTreeview item $i -text]) 1
		}
	}
	$gTmTreeview delete [$gTmTreeview children {}]
	set parent {}
	foreach i [lrange $translationTemplate 1 end] {
		if {[regexp {^\#+\s*(.+)} $i x comment]} {
			set parent [$gTmTreeview insert {} end -text $comment -tags {heading}]
			if {[info exists openedSections($comment)]} {
				$gTmTreeview item $parent -open true
			}
			if {$comment eq $prevFocus} {
				$gTmTreeview focus $parent
				$gTmTreeview selection set $parent
			}
		} elseif {$i ne ""} {
			set last [$gTmTreeview insert $parent end -text $i]
			if {$i eq $prevFocus} {
				$gTmTreeview focus $last
				$gTmTreeview selection set $last
			}
		}
	}
	gTmTreeviewSelectionCallback
}

proc gTmTreeviewSelectionCallback {} {
	global gTmTreeview gTmTextField gTmTextValue gTmAddStringMenu gTmAddHeadingMenu gTmRemoveMenu gTmMoveMenu gTmSetMenu
	set focused [$gTmTreeview focus]
	if {$focused eq {}} {
		$gTmTextField state disabled
		set gTmTextValue "Select an entry to edit"
		foreach m [list $gTmAddStringMenu $gTmAddHeadingMenu $gTmRemoveMenu $gTmMoveMenu $gTmSetMenu] {
			for {set i 0} {$i <= [$m index end]} {incr i} { $m entryconfigure $i -state disabled}
		}
	} else {
		$gTmTextField state !disabled
		set headingSetState disabled
		set entrySetState normal
		if {[$gTmTreeview tag has heading $focused]} {
			set headingSetState normal
			set entrySetState disabled
		}
		foreach ent [list \
			[list $gTmAddStringMenu \
				[list 0 -state $entrySetState] \
				[list 1 -state $entrySetState] \
				[list 2 -state normal] \
				[list 3 -state normal] \
			] \
			[list $gTmAddHeadingMenu \
				[list 0 -state normal] \
				[list 1 -state normal] \
			] \
			[list $gTmMoveMenu \
				[list 0 -state normal] \
				[list 1 -state normal] \
				[list 2 -state normal] \
				[list 3 -state normal] \
			] \
			[list $gTmRemoveMenu \
				[list 0 -state $entrySetState] \
				[list 1 -state $headingSetState] \
				[list 2 -state $headingSetState] \
			] \
			[list $gTmSetMenu \
				[list 0 -state $headingSetState] \
				[list 1 -state $entrySetState] \
				[list 2 -state $entrySetState] \
			] \
		] {
			set m [lindex $ent 0]
			lmap i [lrange $ent 1 end] {
				$m entryconfigure {*}$i
			}
		}
		set gTmTextValue [$gTmTreeview item $focused -text]
	}
}

proc gTmUpdateTemplateAux {parent} {
	global translationTemplate gTmTreeview
	foreach i [$gTmTreeview children $parent] {
		set tval [$gTmTreeview item $i -text]
		if {[$gTmTreeview tag has heading $i]} {
			lappend translationTemplate {}
			set tval "# $tval"
		}
		lappend translationTemplate $tval
		gTmUpdateTemplateAux $i
	}
}

proc gTmUpdateTemplate {} {
	global translationTemplate translationDomain
	set translationTemplate [list "# textdomain: $translationDomain"]
	gTmUpdateTemplateAux {}
	gTrLoadTranslations
	gTmLoadTranslationsToTreeview
}

proc gTmSetEntry {} {
	global gTmTreeview gTmTextValue
	$gTmTreeview item [$gTmTreeview focus] -text $gTmTextValue
	gTmUpdateTemplate
}

proc gTmSetEntryAndCopy {} {
	global gTmTreeview gTmTextValue
	set focus [$gTmTreeview focus]
	rewordTranslationString [$gTmTreeview item $focus -text] $gTmTextValue
	gTmSetEntry
}

proc gTmInsertStringAt {relative idx} {
	global gTmTreeview
	set focus [$gTmTreeview focus]
	set parent [$gTmTreeview parent $focus]
	set relidx [$gTmTreeview index $focus]
	if {[$gTmTreeview tag has heading $focus]} {
		set parent $focus
		set relindex 0
	}
	set newidx $idx
	if {$relative} {
		set newidx [expr {$idx+$relidx}]
	}
	set item [$gTmTreeview insert $parent $newidx -text {}]
	$gTmTreeview focus $item
	$gTmTreeview selection set $item
}

proc gTmInsertHeadingAt {relative idx} {
	global gTmTreeview
	set focus [$gTmTreeview focus]
	if {![$gTmTreeview tag has heading $focus]} {
		set focus [$gTmTreeview parent $focus]
	}
	set parent [$gTmTreeview parent $focus]
	set relidx [$gTmTreeview index $focus]
	if {$focus eq {}} {
		set parent {}
		set relidx 0
	}
	set newidx $idx
	if {$relative} {
		set newidx [expr {$idx+$relidx}]
	}
	set item [$gTmTreeview insert $parent $newidx -text {} -tags {heading}]
	$gTmTreeview focus $item
	$gTmTreeview selection set $item
}

proc gTmMoveInSection {relative idx} {
	global gTmTreeview
	set focus [$gTmTreeview focus]
	set parent [$gTmTreeview parent $focus]
	set relidx [$gTmTreeview index $focus]
	set newidx $idx
	if {$relative} {
		set newidx [expr {$idx+$relidx}]
	}
	$gTmTreeview move $focus $parent $newidx
	gTmUpdateTemplate
}

proc gTmDeleteEntry {} {
	global gTmTreeview
	$gTmTreeview delete [$gTmTreeview focus]
	gTmUpdateTemplate
}

proc gTmMergeWithPrevious {} {
	global gTmTreeview
	set parent [$gTmTreeview focus]
	set prev [$gTmTreeview prev $parent]
	set newindex end
	if {$prev eq {}} {
		set prev [$gTmTreeview parent $parent]
		set newindex 0
	}
	foreach i [$gTmTreeview children $parent] {
		$gTmTreeview move $i $prev $newindex
	}
	$gTmTreeview delete $parent
	gTmUpdateTemplate
}

proc gTmLoadTranslations {} {
	gTmLoadTranslationsToTreeview
}

# Initialization

gReloadTranslations