ruby_proctor 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +2 -0
- data/bin/ruby_proctor +11 -0
- data/bin/ruby_proctor.bat +3 -0
- data/bin/ruby_proctor.sh +3 -0
- data/lib/ruby_proctor/configuration.rb +10 -0
- data/lib/ruby_proctor/constants.rb +13 -0
- data/lib/ruby_proctor/exam.rb +20 -0
- data/lib/ruby_proctor/hashify.rb +26 -0
- data/lib/ruby_proctor/interfaces/gui.rb +620 -0
- data/lib/ruby_proctor/interfaces/logo.gif +0 -0
- data/lib/ruby_proctor/interfaces/terminal.rb +161 -0
- data/lib/ruby_proctor/logger.rb +109 -0
- data/lib/ruby_proctor/logging/quiz_log.rb +4 -0
- data/lib/ruby_proctor/processor.rb +126 -0
- data/lib/ruby_proctor/proctor.rb +206 -0
- data/lib/ruby_proctor/question.rb +17 -0
- data/lib/ruby_proctor/string_ext.rb +5 -0
- data/lib/ruby_proctor.rb +16 -0
- metadata +61 -0
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
#gui.rb
|
|
2
|
+
|
|
3
|
+
#Include Dir for OCRA
|
|
4
|
+
$:.unshift File.dirname($0)
|
|
5
|
+
|
|
6
|
+
# includes
|
|
7
|
+
require 'os'
|
|
8
|
+
|
|
9
|
+
if OS.windows?
|
|
10
|
+
require 'rubygems'
|
|
11
|
+
require 'bundler/setup'
|
|
12
|
+
|
|
13
|
+
require 'tk'
|
|
14
|
+
require 'thread'
|
|
15
|
+
|
|
16
|
+
require 'ruby_proctor/constants.rb'
|
|
17
|
+
require 'ruby_proctor/exam.rb'
|
|
18
|
+
require 'ruby_proctor/question.rb'
|
|
19
|
+
require 'ruby_proctor/processor.rb'
|
|
20
|
+
require 'ruby_proctor/proctor.rb'
|
|
21
|
+
require 'ruby_proctor/string_ext.rb'
|
|
22
|
+
|
|
23
|
+
include Constants
|
|
24
|
+
|
|
25
|
+
def ruby_proctor_gui
|
|
26
|
+
proctoring = TkVariable.new # Variable Used to Let Screen Behave Differently
|
|
27
|
+
proctoring.value = false
|
|
28
|
+
|
|
29
|
+
set_filepath = TkVariable.new
|
|
30
|
+
set_filepath.value = ''
|
|
31
|
+
|
|
32
|
+
set_time_limit = TkVariable.new
|
|
33
|
+
set_time_limit.value = ''
|
|
34
|
+
|
|
35
|
+
set_number_questions = TkVariable.new
|
|
36
|
+
set_number_questions.value = TkVariable.new
|
|
37
|
+
# Main Menu
|
|
38
|
+
root = TkRoot.new { title "Ruby Proctor - Main Menu" }
|
|
39
|
+
|
|
40
|
+
logo = TkPhotoImage.new()
|
|
41
|
+
logo.file = __dir__ + "/logo.gif"
|
|
42
|
+
|
|
43
|
+
logo_label = TkLabel.new(root) do
|
|
44
|
+
image logo
|
|
45
|
+
grid('row'=>0, 'column'=>0, 'padx'=>25, 'pady'=>5, 'columnspan'=>1, 'sticky'=>'WE')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
config_quiz = TkButton.new(root) do
|
|
49
|
+
text "Set Options"
|
|
50
|
+
grid('row'=>1, 'column'=>0, 'padx'=>25, 'pady'=>5, 'columnspan'=>1, 'sticky'=>'WE')
|
|
51
|
+
end
|
|
52
|
+
config_quiz.comman = Proc.new {
|
|
53
|
+
configuration(root, set_filepath, set_number_questions, set_time_limit)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Apply Quiz Config Button
|
|
57
|
+
load_button = TkButton.new(root) do
|
|
58
|
+
text "Run Quiz"
|
|
59
|
+
grid('row'=>2, 'column'=>0, 'padx'=>25, 'pady'=>5, 'columnspan'=>1, 'sticky'=>'WE')
|
|
60
|
+
#pack("side" => "bottom", "padx"=> "50", "pady"=> "50")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
load_button.comman = Proc.new {
|
|
64
|
+
processing_window(root, root, set_filepath, set_number_questions, set_time_limit)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
view_log = TkButton.new(root) do
|
|
68
|
+
text "View Scores"
|
|
69
|
+
grid('row'=>3, 'column'=>0, 'padx'=>25, 'pady'=>5, 'columnspan'=>1, 'sticky'=>'WE')
|
|
70
|
+
end
|
|
71
|
+
view_log.comman = Proc.new {
|
|
72
|
+
view_log(root)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
exit = TkButton.new(root) do
|
|
76
|
+
text "Exit"
|
|
77
|
+
grid('row'=>4, 'column'=>0, 'padx'=>25, 'pady'=>5, 'columnspan'=>1, 'sticky'=>'WE')
|
|
78
|
+
end
|
|
79
|
+
exit.comman = Proc.new {
|
|
80
|
+
root.destroy()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
root.update()
|
|
84
|
+
root['geometry'] = calc_center_geometry(root, root.winfo_width(), root.winfo_height)
|
|
85
|
+
|
|
86
|
+
Tk.mainloop
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def configuration(root, set_filepath, set_number_questions, set_time_limit)
|
|
90
|
+
|
|
91
|
+
new_filepath = TkVariable.new
|
|
92
|
+
new_filepath.value = set_filepath.value
|
|
93
|
+
new_time_limit = TkVariable.new
|
|
94
|
+
new_time_limit.value = set_time_limit.value
|
|
95
|
+
new_number_questions = TkVariable.new
|
|
96
|
+
new_number_questions.value = set_number_questions.value
|
|
97
|
+
|
|
98
|
+
configuration_top = TkToplevel.new { title "Ruby Proctor - Quiz Configuration" }
|
|
99
|
+
configuration_top.grab_set()
|
|
100
|
+
|
|
101
|
+
# configuration_top.protocol("WM_DELETE_WINDOW", Proc.new {
|
|
102
|
+
# put("test!")
|
|
103
|
+
# })
|
|
104
|
+
|
|
105
|
+
#Tk.root.protocol “WM_DELETE_WINDOW”, proc {puts “foo”}
|
|
106
|
+
|
|
107
|
+
file_entry = TkEntry.new(configuration_top) do
|
|
108
|
+
grid('row'=>0, 'column'=>0, 'padx'=>5, 'pady'=>5, 'columnspan'=>4, 'sticky'=>'WE')
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
button = TkButton.new(configuration_top) do
|
|
112
|
+
text "Open"
|
|
113
|
+
grid('row'=>0, 'column'=>4, 'padx'=>5, 'pady'=>5)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
button.comman = Proc.new {
|
|
117
|
+
l_value = Tk.getOpenFile
|
|
118
|
+
if !l_value.empty?
|
|
119
|
+
new_filepath.value = l_value
|
|
120
|
+
end
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
file_entry.textvariable = new_filepath
|
|
124
|
+
|
|
125
|
+
# Number of Questions
|
|
126
|
+
lb1 = TkLabel.new(configuration_top) do
|
|
127
|
+
text 'Number of Questions: '
|
|
128
|
+
#background "yellow"
|
|
129
|
+
#foreground "blue"
|
|
130
|
+
grid('row'=>1, 'column'=>0)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
number_questions_entry = TkEntry.new(configuration_top) do
|
|
134
|
+
grid('row'=>1, 'column'=>1, 'padx'=>5, 'pady'=>5)
|
|
135
|
+
#pack("side" => "left", "padx"=> "50", "pady"=> "50")
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
number_questions_entry.textvariable = new_number_questions
|
|
139
|
+
|
|
140
|
+
# Time Limit
|
|
141
|
+
lb2 = TkLabel.new(configuration_top) do
|
|
142
|
+
text 'Time Limit (Minutes): '
|
|
143
|
+
#background "yellow"
|
|
144
|
+
# foreground "blue"
|
|
145
|
+
grid('row'=>1, 'column'=>2)
|
|
146
|
+
end
|
|
147
|
+
time_limit_entry = TkEntry.new(configuration_top) do
|
|
148
|
+
grid('row'=>1, 'column'=>3, 'padx'=>5, 'pady'=>5)
|
|
149
|
+
#pack("side" => "left", "padx"=> "50", "pady"=> "50")
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
time_limit_entry.textvariable = new_time_limit
|
|
153
|
+
|
|
154
|
+
# Apply Quiz Config Button
|
|
155
|
+
load_button = TkButton.new(configuration_top) do
|
|
156
|
+
text "Apply"
|
|
157
|
+
grid('row'=>1, 'column'=>4, 'padx'=>5, 'pady'=>5)
|
|
158
|
+
#pack("side" => "bottom", "padx"=> "50", "pady"=> "50")
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
load_button.comman = Proc.new {
|
|
162
|
+
|
|
163
|
+
if validate(new_filepath, new_number_questions, new_time_limit)
|
|
164
|
+
configuration_top.destroy();
|
|
165
|
+
set_filepath.value = new_filepath.value
|
|
166
|
+
set_number_questions.value = new_number_questions.value
|
|
167
|
+
set_time_limit.value = new_time_limit.value
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
#processing_window(root, configuration_top, filepath, number_questions, time_limit)
|
|
171
|
+
}
|
|
172
|
+
configuration_top.update()
|
|
173
|
+
configuration_top['geometry'] = calc_center_geometry(configuration_top, configuration_top.winfo_width(), configuration_top.winfo_height)
|
|
174
|
+
#file_entry.textvariable = new_filepath
|
|
175
|
+
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def validate(filepath, number_questions, time_limit)
|
|
180
|
+
|
|
181
|
+
if number_questions
|
|
182
|
+
if number_questions.to_s.is_integer?
|
|
183
|
+
if number_questions.to_i <= 0 || number_questions.to_i > Constants::QUIZ_MAX_QUESTIONS
|
|
184
|
+
Tk.messageBox(
|
|
185
|
+
'type' => 'ok',
|
|
186
|
+
'icon' => 'info',
|
|
187
|
+
'title' => 'Number of Questions outside of Range',
|
|
188
|
+
'message' => 'Number of questions must be greater than 0 but no more than 10,000. Specifying more questions than the provided answer key file has will just use all questions available, no repeats'
|
|
189
|
+
)
|
|
190
|
+
return false
|
|
191
|
+
return false
|
|
192
|
+
end
|
|
193
|
+
elsif (number_questions != '')
|
|
194
|
+
Tk.messageBox(
|
|
195
|
+
'type' => 'ok',
|
|
196
|
+
'icon' => 'info',
|
|
197
|
+
'title' => 'Invalid Number of Questions',
|
|
198
|
+
'message' => 'Make sure # of Questions is a valid integer!'
|
|
199
|
+
)
|
|
200
|
+
return false
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
if time_limit
|
|
205
|
+
if time_limit.to_s.is_integer?
|
|
206
|
+
if time_limit.to_i <= 0
|
|
207
|
+
Tk.messageBox(
|
|
208
|
+
'type' => 'ok',
|
|
209
|
+
'icon' => 'info',
|
|
210
|
+
'title' => 'Time Limit was Less than or equal to 0',
|
|
211
|
+
'message' => 'Time Limit must be a positive number (If you want Unlimited Time, leave input blank)'
|
|
212
|
+
)
|
|
213
|
+
return false
|
|
214
|
+
end
|
|
215
|
+
elsif (time_limit != '')
|
|
216
|
+
Tk.messageBox(
|
|
217
|
+
'type' => 'ok',
|
|
218
|
+
'icon' => 'info',
|
|
219
|
+
'title' => 'Time Limit must be a number greater than 0',
|
|
220
|
+
'message' => 'Time Limit must be a number greater than 0 *Leave Minutes Blank if you want unlimited time)'
|
|
221
|
+
)
|
|
222
|
+
return false
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
if(!File.exist?(filepath))
|
|
227
|
+
Tk.messageBox(
|
|
228
|
+
'type' => 'ok',
|
|
229
|
+
'icon' => 'info',
|
|
230
|
+
'title' => 'File Not Found',
|
|
231
|
+
'message' => 'File was not found, please make sure your filepath is valid!'
|
|
232
|
+
)
|
|
233
|
+
return false
|
|
234
|
+
end
|
|
235
|
+
return true
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def calc_center_geometry(win, window_width, window_height)
|
|
239
|
+
screen_width = win.winfo_screenwidth()
|
|
240
|
+
screen_height = win.winfo_screenheight()
|
|
241
|
+
|
|
242
|
+
x = ((screen_width/2) - (window_width/2)).to_i
|
|
243
|
+
y = ((screen_height/2) - (window_height/2)).to_i
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
"%sx%s+%i+%i" % [window_width, window_height, x, y]
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def processing_window(root, configuration_top, filepath, number_questions, time_limit)
|
|
250
|
+
|
|
251
|
+
if validate(filepath, number_questions, time_limit)
|
|
252
|
+
processing_top = TkToplevel.new {
|
|
253
|
+
title "Ruby Proctor - Processing"
|
|
254
|
+
resizable false, false
|
|
255
|
+
overrideredirect 1
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
processing_top['geometry'] = calc_center_geometry(processing_top, 100, 30)
|
|
259
|
+
|
|
260
|
+
loading_label = TkLabel.new(processing_top) do
|
|
261
|
+
text 'Loading ...'
|
|
262
|
+
background "blue"
|
|
263
|
+
foreground "white"
|
|
264
|
+
grid('row'=>1, 'column'=>2, 'ipadx'=>25, 'ipady'=>5)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Loading Indicator
|
|
268
|
+
# loading_top = TkToplevel.new { title "Processing Quiz ..." }
|
|
269
|
+
#progress_bar = Tk::ProgressBar.new(loading_top)
|
|
270
|
+
# progress_bar.pack("side" => 'bottom')
|
|
271
|
+
#progress_bar.mode = indeterminate
|
|
272
|
+
|
|
273
|
+
# Ruby is really weird with Local & Instance Variables
|
|
274
|
+
if (number_questions.value.empty?)
|
|
275
|
+
processor = Processor.new(filepath, -1)
|
|
276
|
+
else
|
|
277
|
+
processor = Processor.new(filepath, number_questions)
|
|
278
|
+
end
|
|
279
|
+
# Try to Process, and cleanly display any exceptions
|
|
280
|
+
Thread.new {
|
|
281
|
+
begin
|
|
282
|
+
exam = processor.process()
|
|
283
|
+
|
|
284
|
+
if (time_limit.value.empty?)
|
|
285
|
+
exam(exam, -1, root)
|
|
286
|
+
else
|
|
287
|
+
exam(exam, time_limit.to_i, root)
|
|
288
|
+
end
|
|
289
|
+
rescue => e
|
|
290
|
+
Tk.messageBox(
|
|
291
|
+
'type' => 'ok',
|
|
292
|
+
'icon' => 'error',
|
|
293
|
+
'title' => 'Processing Exception Occurred',
|
|
294
|
+
'message' => 'Error Processing Exam File: ' + e.message
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
puts e.backtrace
|
|
298
|
+
ensure
|
|
299
|
+
processing_top.destroy()
|
|
300
|
+
end
|
|
301
|
+
}
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def exam(exam, time_limit, root)
|
|
306
|
+
# # Start Officiating Exam
|
|
307
|
+
# proctor = Proctor.new(exam, time)
|
|
308
|
+
# proctor.officiate_exam
|
|
309
|
+
|
|
310
|
+
time_left = TkVariable.new
|
|
311
|
+
start_time = Time.now
|
|
312
|
+
|
|
313
|
+
question_window = TkToplevel.new { }
|
|
314
|
+
question_window.grab_set()
|
|
315
|
+
|
|
316
|
+
thread = false
|
|
317
|
+
if (time_limit && time_limit > 0)
|
|
318
|
+
thread = Thread.new {
|
|
319
|
+
time_limit.downto(0) do |i|
|
|
320
|
+
time_left.value = i
|
|
321
|
+
|
|
322
|
+
if (i > 0)
|
|
323
|
+
sleep 60
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
proctor = Proctor.new(exam, time_limit)
|
|
328
|
+
proctor.grade_exam(start_time)
|
|
329
|
+
|
|
330
|
+
question_window.destroy()
|
|
331
|
+
|
|
332
|
+
logger = Logger.new
|
|
333
|
+
logger.write_to_log(exam)
|
|
334
|
+
|
|
335
|
+
display_results(exam.results)
|
|
336
|
+
|
|
337
|
+
}
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
display_question(exam, 0, question_window, true, start_time, time_left, thread, time_limit)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def display_question(exam, question_num, question_window, initialize, start_time, time_left, timer_thread, time_limit)
|
|
344
|
+
|
|
345
|
+
human_question_num = question_num + 1
|
|
346
|
+
|
|
347
|
+
question_window['title'] = "Ruby Proctor - Quiz Question #" + human_question_num.to_s
|
|
348
|
+
|
|
349
|
+
if !initialize
|
|
350
|
+
question_window.winfo_children().each { |widgets|
|
|
351
|
+
widgets.destroy()
|
|
352
|
+
}
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# Timer
|
|
356
|
+
if (timer_thread)
|
|
357
|
+
time_left_num = TkLabel.new(question_window) {
|
|
358
|
+
text "Time Left (Minutes): "
|
|
359
|
+
pack('side' => 'top', 'padx'=>5, 'pady'=>5)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
time_left_text = TkEntry.new(question_window) {
|
|
363
|
+
textvariable time_left
|
|
364
|
+
pack('side' => 'top', 'padx'=>5, 'pady'=>5)
|
|
365
|
+
state 'disabled'
|
|
366
|
+
justify 'center'
|
|
367
|
+
}
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
TkSeparator.new(question_window) do
|
|
371
|
+
pack('fill' => 'x')
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
#Question
|
|
375
|
+
question_label = TkLabel.new(question_window) do
|
|
376
|
+
text human_question_num.to_s + ". " + exam.questions[question_num].question
|
|
377
|
+
#background "yellow"
|
|
378
|
+
#foreground "blue"
|
|
379
|
+
anchor 'w'
|
|
380
|
+
pack('side' => 'top', 'fill' => 'x', 'padx'=>5, 'pady'=>5)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
answer = TkVariable.new
|
|
384
|
+
answer.value = exam.questions[question_num].selected_answer
|
|
385
|
+
answer_num = 1
|
|
386
|
+
|
|
387
|
+
exam.questions[question_num].answers.each do |choice|
|
|
388
|
+
TkRadioButton.new(question_window) {
|
|
389
|
+
text answer_num.to_s + ". " + choice
|
|
390
|
+
variable answer
|
|
391
|
+
value answer_num
|
|
392
|
+
anchor 'w'
|
|
393
|
+
pack('side' => 'top', 'fill' => 'x')
|
|
394
|
+
}
|
|
395
|
+
answer_num += 1
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
TkSeparator.new(question_window) do
|
|
399
|
+
pack('fill' => 'x')
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
previous_button = TkButton.new(question_window) {
|
|
403
|
+
text 'Back'
|
|
404
|
+
pack('side' => 'left', 'fill' => 'x', 'padx'=>5, 'pady'=>5)
|
|
405
|
+
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
previous_button.comman = Proc.new {
|
|
409
|
+
exam.questions[question_num].selected_answer = answer.value
|
|
410
|
+
display_question(exam, question_num - 1, question_window, false, start_time, time_left, timer_thread, time_limit)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
next_button = TkButton.new(question_window) {
|
|
414
|
+
text 'Next'
|
|
415
|
+
pack('side' => 'left', 'fill' => 'x', 'padx'=>5, 'pady'=>5)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
next_button.comman = Proc.new {
|
|
419
|
+
exam.questions[question_num].selected_answer = answer.value
|
|
420
|
+
display_question(exam, question_num + 1, question_window, false, start_time, time_left, timer_thread, time_limit)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (1 == human_question_num)
|
|
424
|
+
previous_button['state'] = 'disabled'
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
if (exam.questions.length == human_question_num)
|
|
428
|
+
next_button['state'] = 'disabled'
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
submit_button = TkButton.new(question_window) {
|
|
432
|
+
text 'Submit'
|
|
433
|
+
pack('side' => 'right', 'fill' => 'x', 'padx'=>5, 'pady'=>5)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
submit_button.comman = Proc.new {
|
|
437
|
+
#display_question(exam, question_num + 1)
|
|
438
|
+
answer = Tk.messageBox(
|
|
439
|
+
'type' => 'yesno',
|
|
440
|
+
'icon' => 'question',
|
|
441
|
+
'title' => 'Ready to Submit?',
|
|
442
|
+
'message' => 'Are You Wanting to Submit this Quiz?'
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
if (answer)
|
|
446
|
+
if (timer_thread)
|
|
447
|
+
timer_thread.kill()
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
proctor = Proctor.new(exam, time_limit)
|
|
451
|
+
proctor.grade_exam(start_time)
|
|
452
|
+
|
|
453
|
+
question_window.destroy()
|
|
454
|
+
|
|
455
|
+
logger = Logger.new
|
|
456
|
+
logger.write_to_log(exam)
|
|
457
|
+
|
|
458
|
+
display_results(exam.results)
|
|
459
|
+
|
|
460
|
+
end
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
#if question_num == 0
|
|
464
|
+
question_window['geometry'] = ""
|
|
465
|
+
question_window.update()
|
|
466
|
+
question_window['geometry'] = calc_center_geometry(question_window, question_window.winfo_width(), question_window.winfo_height)
|
|
467
|
+
|
|
468
|
+
#end
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
def display_results(results)
|
|
472
|
+
results_win = TkToplevel.new { title "Ruby Proctor - Quiz Results" }
|
|
473
|
+
results_win.grab_set()
|
|
474
|
+
|
|
475
|
+
display_result_attribute(results_win, 0, "Number of Correct Questions", results.num_correct.to_s + ' / ' + results.total_questions.to_s)
|
|
476
|
+
display_result_attribute(results_win, 1, "Grade %", results.grade.to_s)
|
|
477
|
+
display_result_attribute(results_win, 2, "Letter Grade", results.letter_grade)
|
|
478
|
+
display_result_attribute(results_win, 3, "Time Started", results.time_started)
|
|
479
|
+
display_result_attribute(results_win, 4, "Time Completed", results.time_completed)
|
|
480
|
+
display_result_attribute(results_win, 5, "Time Elapsed", results.time_elapsed)
|
|
481
|
+
|
|
482
|
+
if (results.time_left)
|
|
483
|
+
display_result_attribute(results_win, 6, "Time Left", results.time_left)
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
TkSeparator.new(results_win) do
|
|
487
|
+
grid('row'=>7, 'column'=>0, 'columnspan'=>2, 'sticky'=>'WE')
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
ok_button = TkButton.new(results_win) {
|
|
491
|
+
text 'OK'
|
|
492
|
+
grid('row'=>8, 'column'=>0, 'padx'=>10, 'pady'=>10, 'columnspan'=>2, 'sticky'=>'WE')
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
ok_button.comman = Proc.new {
|
|
496
|
+
results_win.destroy()
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
results_win.update()
|
|
500
|
+
results_win['geometry'] = calc_center_geometry(results_win, results_win.winfo_width(), results_win.winfo_height)
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
def view_log(root)
|
|
504
|
+
|
|
505
|
+
begin
|
|
506
|
+
logger = Logger.new
|
|
507
|
+
|
|
508
|
+
if (File.exist?(logger.file_path))
|
|
509
|
+
|
|
510
|
+
quiz_attempts = logger.read_from_log
|
|
511
|
+
|
|
512
|
+
log_top = TkToplevel.new { title "Ruby Proctor - Quiz Log" }
|
|
513
|
+
log_top.grab_set()
|
|
514
|
+
|
|
515
|
+
# Create Frame
|
|
516
|
+
list_frame = TkFrame.new(log_top) do
|
|
517
|
+
padx 10
|
|
518
|
+
pady 10
|
|
519
|
+
pack('fill' => 'x')
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
list = TkListbox.new(list_frame) do
|
|
523
|
+
width 0
|
|
524
|
+
height 10
|
|
525
|
+
setgrid 1
|
|
526
|
+
selectmode 'browse'
|
|
527
|
+
pack('side' => 'left','fill' => 'x', 'expand'=>1)
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
list.bind("Double-Button-1", Proc.new {
|
|
531
|
+
if (list.curselection().length > 0)
|
|
532
|
+
display_results(quiz_attempts[list.curselection()[0]])
|
|
533
|
+
else
|
|
534
|
+
Tk.messageBox(
|
|
535
|
+
'type' => 'ok',
|
|
536
|
+
'icon' => 'error',
|
|
537
|
+
'title' => 'No Quiz File Selected!',
|
|
538
|
+
'message' => 'Cannot Open, No Quiz File Selected'
|
|
539
|
+
)
|
|
540
|
+
end
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
scrollbar = TkScrollbar.new(list_frame) {
|
|
544
|
+
command Proc.new {|*args|
|
|
545
|
+
list.yview(*args)
|
|
546
|
+
}
|
|
547
|
+
pack('side'=>'left', 'fill'=>'y')
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
list['yscrollcommand'] = Proc.new {|*args|
|
|
551
|
+
scrollbar.set(*args)
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
quiz_num = 1
|
|
555
|
+
for attempt in quiz_attempts do
|
|
556
|
+
list.insert(quiz_num - 1, quiz_num.to_s + ". - " + attempt.quiz_name)
|
|
557
|
+
quiz_num += 1
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
open_button = TkButton.new(log_top) {
|
|
561
|
+
text 'Open Results'
|
|
562
|
+
pack('side' => 'right', 'padx'=>5, 'pady'=>5)
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
open_button.comman = Proc.new {
|
|
566
|
+
if (list.curselection().length > 0)
|
|
567
|
+
display_results(quiz_attempts[list.curselection()[0]])
|
|
568
|
+
else
|
|
569
|
+
Tk.messageBox(
|
|
570
|
+
'type' => 'ok',
|
|
571
|
+
'icon' => 'error',
|
|
572
|
+
'title' => 'No Quiz File Selected!',
|
|
573
|
+
'message' => 'Cannot Open, No Quiz File Selected'
|
|
574
|
+
)
|
|
575
|
+
end
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
log_top.update()
|
|
579
|
+
log_top['geometry'] = calc_center_geometry(log_top, 10, 10)
|
|
580
|
+
else
|
|
581
|
+
Tk.messageBox(
|
|
582
|
+
'type' => 'ok',
|
|
583
|
+
'icon' => 'error',
|
|
584
|
+
'title' => 'Unable to Read Log File',
|
|
585
|
+
'message' => "Unable to Read Log File, File Doesn't Exist!"
|
|
586
|
+
)
|
|
587
|
+
end
|
|
588
|
+
rescue => e
|
|
589
|
+
Tk.messageBox(
|
|
590
|
+
'type' => 'ok',
|
|
591
|
+
'icon' => 'error',
|
|
592
|
+
'title' => 'Logging Exception Occurred',
|
|
593
|
+
'message' => 'Error Reading Log File: ' + e.message
|
|
594
|
+
)
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
def display_result_attribute(win, row, name, value)
|
|
599
|
+
|
|
600
|
+
tk_value = TkVariable.new
|
|
601
|
+
tk_value.value = value
|
|
602
|
+
|
|
603
|
+
# Number of Questions
|
|
604
|
+
TkLabel.new(win) do
|
|
605
|
+
text name + ":"
|
|
606
|
+
#background "yellow"
|
|
607
|
+
#foreground "blue"
|
|
608
|
+
grid('row'=>row, 'column'=>0, 'padx'=>5, 'pady'=>5)
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
entry = TkEntry.new(win) do
|
|
612
|
+
grid('row'=>row, 'column'=>1, 'padx'=>5, 'pady'=>5)
|
|
613
|
+
#pack("side" => "left", "padx"=> "50", "pady"=> "50")
|
|
614
|
+
textvariable tk_value
|
|
615
|
+
state 'disabled'
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
# entry.state('disabled')
|
|
619
|
+
end
|
|
620
|
+
end
|
|
Binary file
|