summaryrefslogtreecommitdiff
path: root/lib/simulator/stopwatch.tcl
blob: 70e58c5bdf4449ab86dc2ee8690d18d778b6436a (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
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
#!/usr/bin/tclsh
# Part of MCU 8051 IDE ( http://https://sourceforge.net/projects/mcu8051ide/ )

############################################################################
#    Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 by Martin Ošmera     #
#    martin.osmera@gmail.com                                               #
#                                                                          #
#    Copyright (C) 2014 by Moravia Microsystems, s.r.o.                    #
#    martin.osmera@gmail.com                                #
#                                                                          #
#    This program is free software; you can redistribute it and#or modify  #
#    it under the terms of the GNU General Public License as published by  #
#    the Free Software Foundation; either version 2 of the License, or     #
#    (at your option) any later version.                                   #
#                                                                          #
#    This program is distributed in the hope that it will be useful,       #
#    but WITHOUT ANY WARRANTY; without even the implied warranty of        #
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         #
#    GNU General Public License for more details.                          #
#                                                                          #
#    You should have received a copy of the GNU General Public License     #
#    along with this program; if not, write to the                         #
#    Free Software Foundation, Inc.,                                       #
#    59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             #
############################################################################

# >>> File inclusion guard
if { ! [ info exists _STOPWATCH_TCL ] } {
set _STOPWATCH_TCL _
# <<< File inclusion guard

# --------------------------------------------------------------------------
# DESCRIPTION
# Stopwatch timer for MCU simulator
# --------------------------------------------------------------------------

class Stopwatch {
	## Class variables
	public common stopw_count	0	;# Int: Counter of class instances
	# List: Short names of stopwatch entries
	public common stats_keys {
		US		CC		IC
		IP		PB		IN
		SC		RT		RI
		BR
	}
	#  List: Full names of stopwatch entries
	public common stats_names {
		{Micro seconds}		{Clock cycles}	{Instruction cycles}
		{Instructions passed}	{Program bytes}	{Interrupts}
		{Subprogram calls}	{RET}		{RETI}
		{Breakpoints}
	}

	## Private varibales
	private variable win				;# Widget: Dialog window
	private variable obj_idx			;# Int: Current object number
	private variable entryboxes			;# Array of Widget: Entry boxes
	private variable clearbuttons			;# Array of Widget: Clear buttons
	private variable dialog_opened		0	;# Bool: 1 == Dialog opened; 0 == Dialog closed
	private variable status_bar_lbl			;# Widget: Status bar label widget
	private variable start_stop_button		;# Widget: Button "Start / Stop"
	private variable label_stopped_lbl		;# Widget: Label "Stoppped"
	private variable stopwatch_on		1	;# Bool: Stopwatch running
	private variable window_geometry	{}	;# Geometry: Window geometry

	constructor {} {
		# Configure ttk styles
		if {!$stopw_count} {
			ttk::style configure Stopwatch.TEntry -fieldreadonlybackground {#F8F8F8}
			ttk::style configure Stopwatch_Focused_D.TEntry -fieldbackground {#AAAAFF} -fieldreadonlybackground {#AAAAFF}
			ttk::style configure Stopwatch_Focused_D_Invalid.TEntry -fieldbackground {#AAAAFF} -foreground {#FF0000}
			ttk::style configure Stopwatch_Focused_I.TEntry -fieldbackground {#DDDDFF} -fieldreadonlybackground {#DDDDFF}
		}

		# Increment counter of object instances
		set obj_idx $stopw_count
		incr stopw_count

		# Restore configuration from the previous session
		set i 0
		set window_geometry [lindex ${::CONFIG(STOPWATCH_CONFIG)} $i]
		incr i
		set val [lindex ${::CONFIG(STOPWATCH_CONFIG)} $i]
		if {![string is digit -strict $val]} {
			set val 0
		}
		set ::Stopwatch::text_vars${obj_idx}(stop_sim) $val
		incr i
		foreach key $stats_keys {
			set val [lindex ${::CONFIG(STOPWATCH_CONFIG)} $i]
			if {![string is digit -strict $val]} {
				set val 0
			}
			set ::Stopwatch::text_vars${obj_idx}($key,S) $val
			incr i
		}
	}

	destructor {
		catch {
			array unset ::Stopwatch::text_vars${obj_idx}
		}
	}

	## Close window
	 # @return void
	public method stopwatch_close {} {
		set window_geometry [wm geometry $win]
		destroy $win
		set dialog_opened 0
	}

	## Open window
	 # @return void
	public method stopwatch_invoke_dialog {} {
		if {$dialog_opened} {return}
		set win [toplevel .stopwatch$obj_idx -class {Stopwatch} -bg ${::COMMON_BG_COLOR}]
		set dialog_opened 1
		set stopwatch_on 1

		stopwatch_create_gui
		stopwatch_refresh

		bind $win <Control-Key-q> "destroy $win; break"
		bindtags $win [list $win Toplevel all .]

		wm title $win "[mc {Stopwatch}] - [$this cget -projectName] - MCU 8051 IDE"
		wm iconphoto $win ::ICONS::22::history
		wm protocol $win WM_DELETE_WINDOW "$this stopwatch_close"
		wm resizable $win 0 0
		update
		catch {
			wm geometry $win [regsub {^\=?\d+x\d+} $window_geometry	\
				[regsub {\+\d+\+\d+$} [wm geometry $win] {}]	\
			]
		}
	}

	## Refresh window content (adjust to current simulator state)
	 # @return void
	public method stopwatch_refresh {} {
		if {!$dialog_opened} {return}

		array set run_statistics [$this get_run_statistics]
		set run_statistics(0) [expr {$run_statistics(0) / 1000}]
		set i 0
		set org_O 0
		set stop_a 0
		foreach key $stats_keys {
			# Overall
			set org_O [subst -nocommands "\$::Stopwatch::text_vars${obj_idx}($key,O)"]
			set ::Stopwatch::text_vars${obj_idx}($key,O) $run_statistics($i)

			# Current
			if {$stopwatch_on} {
				incr ::Stopwatch::text_vars${obj_idx}($key,C) [expr {$run_statistics($i) - $org_O}]
				set stop_a [subst -nocommands "\$::Stopwatch::text_vars${obj_idx}($key,S)"]

				# Conditional stop
				if {$stop_a && $stop_a <= [subst -nocommands "\$::Stopwatch::text_vars${obj_idx}($key,C)"]} {
					stopwatch_start_stop
					if {[subst -nocommands "\$::Stopwatch::text_vars${obj_idx}(stop_sim)"]} {
						if {[$this sim_run_in_progress]} {
							$this sim_run
						} elseif {[$this sim_anim_in_progress]} {
							$this sim_animate
						}
					}
				}
			}
			incr i
		}
	}

	## Create window GUI
	 # @return void
	private method stopwatch_create_gui {} {

		## Create bottom frame (status bar)
		set bottom_frame [frame $win.bottom_frame]
		set status_bar_lbl [label $bottom_frame.status_lbl	\
			-anchor w -justify left				\
		]
		pack $status_bar_lbl -fill x -side left


		## Create toolbar
		set top_frame [frame $win.top_frame]
		 # - Button "Start / Stop"
		set button [ttk::button $top_frame.start_but	\
			-command "$this stopwatch_start_stop"	\
			-style ToolButton.TButton		\
			-image ::ICONS::22::player_pause	\
		]
		set start_stop_button $button
		pack $button -side left -anchor w
		DynamicHelp::add $top_frame.start_but -text ""
		local_status_tip $top_frame.start_but [mc "Stop"]
		 # - Separator
		pack [ttk::separator $top_frame.sep0		\
			-orient vertical			\
		] -side left -fill y -anchor w -padx 2
		 # - Button "Save as plain text"
		set button [ttk::button $top_frame.save_but	\
			-command "$this stopwatch_save 1"	\
			-image ::ICONS::22::filesave		\
			-style ToolButton.TButton		\
		]
		pack $button -side left -anchor w
		DynamicHelp::add $top_frame.save_but -text ""
		local_status_tip $top_frame.save_but [mc "Save as plain text"]
		 # - Button "Save as XHTML"
		set button [ttk::button $top_frame.export_but	\
			-command "$this stopwatch_save 0"	\
			-image ::ICONS::22::html		\
			-style ToolButton.TButton		\
		]
		pack $button -side left -anchor w
		DynamicHelp::add $top_frame.export_but -text ""
		local_status_tip $top_frame.export_but [mc "Save as XHTML"]
		 # - Separator
		pack [ttk::separator $top_frame.sep1	\
			-orient vertical		\
		] -side left -fill y -anchor w -padx 2
		 # - Button "Close"
		set button [ttk::button $top_frame.exit_but	\
			-command "$this stopwatch_close"	\
			-image ::ICONS::22::exit		\
			-style ToolButton.TButton		\
		]
		pack $button -side left -anchor w
		DynamicHelp::add $top_frame.exit_but -text ""
		local_status_tip $top_frame.exit_but [mc "Close window"]
		 # - Label "STOPPED"
		set label_stopped_lbl [label $top_frame.label_stopped_lbl	\
			-font [font create					\
				-family {Helvetica}				\
				-size -21					\
				-weight bold					\
			] -text {STOPPED} -fg {#FF0000} -pady 0			\
		]

		## Create main frame
		set main_frame [frame $win.main_frame]
		# Create horizontal headers
		grid [label $main_frame.lbl_h_0	\
			-text [mc "Current"]	\
		] -sticky we -row 0 -column 2
		grid [ttk::button $main_frame.clr_C_but		\
			-image ::ICONS::16::clear_left_r	\
			-command "$this stopwatch_clear_all C"	\
			-style Flat.TButton			\
		] -sticky w -row 0 -column 3
		DynamicHelp::add $main_frame.clr_C_but	\
			-text [mc "Clear all"]
		local_status_tip $main_frame.clr_C_but [mc "Clear all"]
		grid [label $main_frame.lbl_h_1	\
			-text [mc "Stop after"]	\
		] -sticky we -row 0 -column 5
		grid [ttk::button $main_frame.clr_S_but		\
			-style Flat.TButton			\
			-image ::ICONS::16::clear_left_r	\
			-command "$this stopwatch_clear_all S"	\
		]  -sticky w -row 0 -column 6
		DynamicHelp::add $main_frame.clr_S_but	\
			-text [mc "Clear all"]
		local_status_tip $main_frame.clr_S_but [mc "Clear all"]
		grid [label $main_frame.lbl_h_2	\
			-text [mc "Overall"]		\
		] -sticky we -row 0 -column 8 -columnspan 2

		# Create matrix of entryboxes (and vertical headers)
		set row 1
		foreach text $stats_names key $stats_keys {
			# Vertical header
			grid [label $main_frame.lbl_f_$row	\
				-text [mc $text]		\
			] -sticky w -row $row -column 0

			# Create 3 entryboxes and 2 clear buttons
			set col 2
			foreach tp {C S O} {
				# Create clear button
				if {$tp != {O}} {
					set clearbuttons($key,$tp) [ttk::button $main_frame.clrbut_${key}_$tp	\
						-style Flat.TButton						\
						-image ::ICONS::16::clear_left					\
						-command "$this stopwatch_clear_entrybox $key $tp"		\
					]
					DynamicHelp::add $main_frame.clrbut_${key}_$tp	\
						-text [mc "Clear entrybox"]
				}

				# Clear entrybox
				set entrybox [ttk::entry $main_frame.entry_${key}_$tp				\
					-validatecommand "$this stopwatch_entrybox_validator $key $tp %P"	\
					-textvariable ::Stopwatch::text_vars${obj_idx}($key,$tp)		\
					-style Stopwatch.TEntry							\
					-validate key								\
					-width 12								\
				]
				bind $entrybox <Key-Up>			"$this stopwatch_entry_key $key $tp up;		break"
				bind $entrybox <Key-Down>		"$this stopwatch_entry_key $key $tp down;	break"
				bind $entrybox <Key-Left>		"$this stopwatch_entry_key $key $tp left;	break"
				bind $entrybox <Key-Right>		"$this stopwatch_entry_key $key $tp right;	break"
				bind $entrybox <Shift-Key-Left>		"continue"
				bind $entrybox <Shift-Key-Right>	"continue"
				bind $entrybox <Key-Tab>		"$this stopwatch_entry_key $key $tp tab;	break"
				if {!$::MICROSOFT_WINDOWS} {
					bind $entrybox <Key-ISO_Left_Tab> "$this stopwatch_entry_key $key $tp stab;	break"
				}
				bind $entrybox <Key-Return>		"$this stopwatch_entry_key $key $tp enter;	break"
				bind $entrybox <Key-KP_Enter>		"$this stopwatch_entry_key $key $tp enter;	break"
				bind $entrybox <FocusIn>		"$this stopwatch_entry_focus $key $tp 1"
				bind $entrybox <FocusOut>		"$this stopwatch_entry_focus $key $tp 0"
				bindtags $entrybox [list $entrybox TEntry $win all .]

				grid $entrybox -row $row -column $col -sticky we
				set entryboxes($key,$tp) $entrybox
				incr col

				if {$tp == {O}} {
					break
				}

				local_status_tip $clearbuttons($key,$tp) [mc "Clear"]
				grid $clearbuttons($key,$tp) -row $row -column $col -sticky w
				incr col 2
			}

			set ::Stopwatch::text_vars${obj_idx}($key,C) 0
			set ::Stopwatch::text_vars${obj_idx}($key,O) 0
			$entryboxes($key,O) configure -state readonly
			incr row
		}

		# Create checkbutton "Stop simulation"
		grid [checkbutton $main_frame.stop_sim_chb			\
			-variable ::Stopwatch::text_vars${obj_idx}(stop_sim)	\
			-text [mc "Stop simulation"]				\
		] -row $row -column 5 -sticky w -columnspan 2

		# Configure columns in main frame
		grid columnconfigure $main_frame 1 -minsize 10
		grid columnconfigure $main_frame 4 -minsize 10
		grid columnconfigure $main_frame 7 -minsize 10


		# Show dialog frames
		pack $top_frame -anchor w -pady 5 -padx 5 -fill x
		pack $main_frame -fill both -expand 1 -padx 10
		pack $bottom_frame -fill x
	}

	## Entybox event handler for <FocusIn> and <FocusOut>
	 # @parm String key	- Short entry name (from list: $stats_keys)
	 # @parm Char type	- Entry type (C == "Current"; S == "Stop after"; O == "Overall")
	 # @parm Bool focused	- 1 == <FocusIn>; 0 == <FocusOut>
	 # @return void
	public method stopwatch_entry_focus {key type focused} {
		if {$focused} {
			$entryboxes($key,C) configure -style Stopwatch_Focused_I.TEntry
			$entryboxes($key,S) configure -style Stopwatch_Focused_I.TEntry
			$entryboxes($key,$type) configure -style Stopwatch_Focused_D.TEntry
		} else {
			$entryboxes($key,$type) selection clear
			$entryboxes($key,C) configure -style TEntry
			$entryboxes($key,S) configure -style TEntry
			$entryboxes($key,$type) configure -style TEntry
		}
	}

	## Entybox event handler for <Key-Up>, <Key-Down>, <Key-Left>, <Key-Right>, <Key-Tab>,
	 #+ <Key-ISO_Left_Tab>, <Key-Return> and <Key-KP_Enter>
	 # @parm String ekey	- Short entry name (from list: $stats_keys)
	 # @parm Char type	- Entry type (C == "Current"; S == "Stop after"; O == "Overall")
	 # @parm String kkey	- Key name (e.g. "down")
	 # @return void
	public method stopwatch_entry_key {ekey type kkey} {
		set entrybox $entryboxes($ekey,$type)
		set insert [$entrybox index insert]
		set y [lsearch -ascii -exact $stats_keys $ekey]
		set max_y [llength $stats_keys]
		incr max_y -1
		switch -- $type {
			C {set x 0}
			S {set x 1}
			O {set x 2}
		}

		$entrybox selection clear
		switch -- $kkey {
			{up} {
				if {!$y} {
					return
				}
				incr y -1
			}
			{down} {
				if {$y == $max_y} {
					return
				}
				incr y
			}
			{left} {
				if {!$x || $insert} {
					$entrybox icursor [expr {$insert-1}]
					return
				}
				incr x -1
			}
			{right} {
				if {($x == 2) || ($insert != [$entrybox index end])} {
					$entrybox icursor [expr {$insert+1}]
					return
				}
				incr x
			}
			{tab} {
				if {$x == 2} {
					return
				}
				incr x
			}
			{stab} {
				if {!$x} {
					return
				}
				incr x -1
			}
			{enter} {
				if {$y == $max_y} {
					return
				}
				incr y
			}
		}

		set insert [expr {[$entrybox index end] - $insert}]
		set entrybox $entryboxes([lindex $stats_keys $y],[lindex {C S O} $x])
		$entrybox selection range 0 end
		$entrybox icursor [expr {[$entrybox index end] - $insert}]
		focus $entrybox
	}

	## Set local status tip
	 # @parm Widget wdg	- Target widget
	 # @parm String txt	- Status tip text
	 # @return void
	private method local_status_tip {wdg txt} {
		bind $wdg <Enter> [list $status_bar_lbl configure -text $txt]
		bind $wdg <Leave> [list $status_bar_lbl configure -text {}]
	}

	## Validator procedure for all entryboxes in the dialog
	 # @parm String key	- Short entry name (from list: $stats_keys)
	 # @parm Char type	- Entry type (C == "Current"; S == "Stop after"; O == "Overall")
	 # @parm String string	- Suggested content
	 # @return Bool - Validation result (1 == Allowed; 0 == Denied)
	public method stopwatch_entrybox_validator {key type string} {
		# Validate input string
		if {[string length $string] > 19 || ![string is digit $string]} {
			return 0
		}

		# Adjust foreground color for entrybox in column "Current"
		if {$type == {C} && $string != {}} {
			set max [subst -nocommands "\$::Stopwatch::text_vars${obj_idx}($key,S)"]
			if {$max != {} && $max != 0 && $string >= $max} {
				$entryboxes($key,C) configure -style Stopwatch_Focused_D_Invalid.TEntry
			} else {
				$entryboxes($key,C) configure -style Stopwatch_Focused_D.TEntry
			}
		}

		# Adjust clear button
		if {$type == {C} || $type == {S}} {
			if {$string != {} && $string != 0} {
				$clearbuttons($key,$type) configure -state normal
			} else {
				$clearbuttons($key,$type) configure -state disabled
			}
		}

		return 1
	}

	## Clear all entryboxes in the specified column
	 # @parm Char what - Entry type (C == "Current"; S == "Stop after"; O == "Overall")
	 # @return void
	public method stopwatch_clear_all {what} {
		foreach key $stats_keys {
			set ::Stopwatch::text_vars${obj_idx}($key,$what) 0
		}
	}

	## Clear the specified entrybox
	 # @parm String key	- Short entry name (from list: $stats_keys)
	 # @parm Char type	- Entry type (C == "Current"; S == "Stop after"; O == "Overall")
	 # @return void
	public method stopwatch_clear_entrybox {key type} {
		set ::Stopwatch::text_vars${obj_idx}($key,$type) 0
	}

	## Invoke file selection dialog to save content of stopwatch into a file
	 # @parm Bool text__html - File type (1 == Plain text; 0 == XHTML)
	 # @return void
	public method stopwatch_save {text__html} {
		# Determinate list of available file extensions
		if {$text__html} {
			set filetypes [list					\
				[list [::mc "Text files"]	{*.txt}]	\
				[list [::mc "All files"]	{*}]		\
			]
		} else {
			set filetypes [list					\
				[list [::mc "HTML files"]	{*.html}]	\
				[list [::mc "All files"]	{*}]		\
			]
		}

		# Invoke the file selection dialog
		KIFSD::FSD ::fsd	 					\
			-title [mc "Save stopwatch state - MCU 8051 IDE"]	\
			-directory [$this cget -projectPath]			\
			-master $win -filetypes [mc $filetypes]			\
			-defaultmask 0 -multiple 0				\
			-initialfile [$this cget -projectName]

		# Open file after press of OK button
		::fsd setokcmd "
			::fsd deactivate
			$this stopwatch_savefile_proc $text__html	\
				\[file normalize \[file join		\
					\[$this cget -ProjectDir\]	\
					\[::fsd get\]			\
				\]\]
		"

		# Activate the dialog
		::fsd activate
	}

	## Save content of stopwatch into the specified file
	 # @parm Bool text__html	- File type (1 == Plain text; 0 == XHTML)
	 # @parm String filename	- Full name of the target file
	 # @return void
	public method stopwatch_savefile_proc {text__html filename} {
		# Adjust filename extension
		if {![string length [file extension $filename]]} {
			if {$text__html} {
				append filename {.txt}
			} else {
				append filename {.html}
			}
		}

		# Create backup file
		if {[file exists $filename] && [file isfile $filename]} {
			# Ask user for overwrite existing file
			if {[tk_messageBox	\
				-type yesno	\
				-icon question	\
				-parent $win	\
				-title [mc "Overwrite file"]	\
				-message [mc "A file name '%s' already exists. Are you sure you want to overwrite it ?" [file tail $filename]]
				] != {yes}
			} then {
				return
			}
			# Create a backup file
			catch {
				file rename -force $filename "$filename~"
			}
		}

		# Open the specified file
		if {[catch {
			set file [open $filename w 0640]
		}]} then {
			tk_messageBox		\
				-parent .	\
				-type ok	\
				-icon warning	\
				-title [mc "Permission denied"]	\
				-message [mc "Unable to open file:\n'%s'" $filename]
			return
		}

		# Save as plain text
		if {$text__html} {
			set text {}
			append text	[string repeat { } 37] [mc "Current"]		\
					[string repeat { } 10] [mc "Stop after"]	\
					[string repeat { } 13] [mc "Overall"]
			puts $file $text
			foreach text $stats_names key $stats_keys {
				set text [mc $text]
				append text [string repeat { } [expr {24 - [string length $text]}]]
				foreach subkey {C S O} {
					set val [subst -nocommands "\$::Stopwatch::text_vars${obj_idx}($key,$subkey)"]
					append text [string repeat { } [expr {20 - [string length $val]}]] $val
				}
				puts $file $text
			}

			puts $file "\n[mc {Project:}] [$this cget -projectName]"
			puts $file [mc "Generated by %s" "${::APPNAME}  ( http://mcu8051ide.sf.net )"]

		# Save as XHTML
		} else {
			puts $file "<?xml version='1.0' encoding='utf-8' standalone='no'?>"
			puts $file "<!DOCTYPE html PUBLIC"
			puts $file "\t'-//W3C//DTD XHTML 1.1//EN'"
			puts $file "\t'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'>"
			puts $file "<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en'>"
			puts $file "<!-- Creator: ${::APPNAME} -->"
			puts $file "\t<head>"
			puts $file "\t\t<title>[$this cget -projectName] stopwatch state [clock format [clock seconds] -format {%D}]</title>"
			puts $file "\t\t<meta http-equiv=\"Content-Type\" content=\"application/xhtml+xml; charset=UTF-8\" />"
			puts $file "\t\t<meta name=\"Generator\" content=\"${::APPNAME}\" />"
			puts $file "\t\t<style type=\"text/css\">"
			puts $file "\t\t\t.sw_header {"
			puts $file "\t\t\t\tfont-weight: normal;"
			puts $file "\t\t\t}"
			puts $file "\t\t\t.sw_C {"
			puts $file "\t\t\t\tbackground-color: #FFEEEE;"
			puts $file "\t\t\t\tfont-weight: bold;"
			puts $file "\t\t\t\ttext-align: right;"
			puts $file "\t\t\t}"
			puts $file "\t\t\t.sw_S {"
			puts $file "\t\t\t\tbackground-color: #EEFFEE;"
			puts $file "\t\t\t\tfont-weight: bold;"
			puts $file "\t\t\t\ttext-align: right;"
			puts $file "\t\t\t}"
			puts $file "\t\t\t.sw_O {"
			puts $file "\t\t\t\tbackground-color: #EEEEFF;"
			puts $file "\t\t\t\tfont-weight: bold;"
			puts $file "\t\t\t\ttext-align: right;"
			puts $file "\t\t\t}"
			puts $file "\t\t</style>"
			puts $file "\t</head>"
			puts $file "\t<body>"
			puts $file "\t\t<table style=\"border-width: 1px\">"
			puts $file "\t\t\t<col /><col /><col /><col />"
			puts $file "\t\t\t<thead>"
			puts $file "\t\t\t\t<tr class=\"sw_header\"><th>&nbsp;</th><th>[mc {Current}]</th><th>[mc {Stop after}]</th><th>[mc {Overall}]</th></tr>"
			puts $file "\t\t\t</thead>"
			puts $file "\t\t\t<tbody>"
			foreach text $stats_names key $stats_keys {
				puts $file "\t\t\t\t<tr>"
				puts $file "\t\t\t\t\t<td class=\"sw_header\">[mc $text]</td>"
				foreach subkey {C S O} {
					puts -nonewline $file "\t\t\t\t\t<td class=\"sw_$subkey\">"
					puts -nonewline $file [subst -nocommands "\$::Stopwatch::text_vars${obj_idx}($key,$subkey)"]
					puts $file "</td>"
				}
				puts $file "\t\t\t\t</tr>"
			}
			puts $file "\t\t\t</tbody>"
			puts $file "\t\t</table>"

			puts $file "\t\t<p>"
			puts $file "\t\t\t[mc {Project:}] <b>[$this cget -projectName]</b><br />"
			puts $file "\t\t\t[mc {Generated by %s} "${::APPNAME}  ( <a href=\"http://mcu8051ide.sf.net\">http://mcu8051ide.sf.net</a> )"]"
			puts $file "\t\t</p>"

			puts $file "\t</body>"
			puts $file "</html>"
		}

		# Close target file
		close $file
	}

	## Enable / Disable stopwatch (swith between states ON and OFF)
	 # @return void
	public method stopwatch_start_stop {} {
		set stopwatch_on [expr {!$stopwatch_on}]

		# Start
		if {$stopwatch_on} {
			$start_stop_button configure -image ::ICONS::22::player_pause
			local_status_tip $start_stop_button [mc "Stop"]
			pack forget $label_stopped_lbl
		# Stop
		} else {
			$start_stop_button configure -image ::ICONS::22::player_play
			local_status_tip $start_stop_button [mc "Start"]
			pack $label_stopped_lbl -side right -pady 0 -ipady 0
		}
	}

	## Get configuration list (for session save procedure)
	 # @return void
	public method stopwatch_get_config {} {
		set result $window_geometry
		lappend result [subst -nocommands "\$::Stopwatch::text_vars${obj_idx}(stop_sim)"]

		foreach key $stats_keys {
			lappend result [subst -nocommands "\$::Stopwatch::text_vars${obj_idx}($key,S)"]
		}

		return $result
	}
}

# >>> File inclusion guard
}
# <<< File inclusion guard