-
Notifications
You must be signed in to change notification settings - Fork 1
/
client-gui.rkt
816 lines (759 loc) · 31.9 KB
/
client-gui.rkt
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
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
#lang racket/base
(require racket/class racket/unit racket/file racket/gui/base net/sendurl
mrlib/switchable-button mrlib/bitmap-label drracket/tool framework
"info.rkt" "client.rkt" "this-collection.rkt")
(provide tool@)
(define uninstalled? #f)
(define server:port (#%info-lookup 'server:port (lambda () #f)))
(define-values (server port-no)
(if server:port
(let ([m (regexp-match #rx"^([^:]+):([0-9]+)$" server:port)])
(unless m
(error 'handin-client
"Bad configuration ~s, expecting \"server:port\""
server:port))
(values (cadr m) (string->number (caddr m))))
(values #f #f)))
(define handin-name (#%info-lookup 'name))
(define web-menu-name (#%info-lookup 'web-menu-name (lambda () #f)))
(define web-address (#%info-lookup 'web-address (lambda () #f)))
(define password-keep-minutes
(#%info-lookup 'password-keep-minutes (lambda () #f)))
(define handin-dialog-name (string-append handin-name " Handin"))
(define button-label/h (string-append handin-name " Handin"))
(define button-label/r (string-append handin-name " Retrieve"))
(define manage-dialog-name (string-append handin-name " Handin Account"))
(define updater?
(#%info-lookup 'enable-auto-update (lambda () #f)))
(define multifile?
(#%info-lookup 'enable-multifile-handin (lambda () #f)))
(define preference-key (make-my-key 'submit:username))
(preferences:set-default preference-key "" string?)
(define (remembered-user)
(preferences:get preference-key))
(define (remember-user user)
(preferences:set preference-key user))
(define remembered-assignment (make-parameter #f))
(define (connect) (handin-connect server port-no))
;; parameter-like procedure that keeps the password cached for a few minutes
(define cached-password
(let ([passwd #f]
[timer #f])
(define protect
(let ([s (make-semaphore 1)])
(lambda (thunk)
(dynamic-wind (lambda () (semaphore-wait s))
thunk
(lambda () (semaphore-post s))))))
(case-lambda
[() passwd]
[(new)
(protect (lambda ()
(when (and password-keep-minutes
(not (equal? 0 password-keep-minutes))
(not (equal? passwd new)))
(when timer (kill-thread timer))
(set! passwd new)
(set! timer (thread
(lambda ()
(sleep (* 60 password-keep-minutes))
(protect (lambda ()
(set! passwd #f)
(set! timer #f)))))))))])))
;; a password entry box that uses the one cached above
(define cached-passwd%
(class text-field%
(define cached (cached-password))
;; use this instead of a cached password -- to avoid copy/pastes
;; of a password, and to hide its length
(define fake-value "CACHED PASSWD")
(define/override (get-value)
(or cached (super get-value)))
(define/override (on-focus on?)
(if on?
;; got focus -- clear out bogus contents, if any
(when cached (send this set-value "") (set! cached #f))
;; lost focus -- remember a new password, or restore it
(let ([p (super get-value)])
(cond [(and p (not (string=? "" p)))
;; don't behave as if we have a cache: don't clear
;; the value now, or if we get the focus later
(set! cached #f)
(cached-password p)]
[(cached-password)
=> (lambda (p)
(set! cached p)
(send this set-value fake-value))]))))
(super-new [init-value (if cached fake-value "")]
[style '(single password)])))
(provide handin-frame%)
(define handin-frame%
(class dialog%
(inherit show is-shown? center)
(super-new [label handin-dialog-name])
(init-field content on-retrieve)
(define mode
(cond [(and content on-retrieve) #f]
[content 'submit]
[on-retrieve 'retrieve]
[else (error 'handin-frame "bad initial values")]))
(define status
(new message%
[label (format "Making secure connection to ~a..." server)]
[parent this]
[stretchable-width #t]))
(define username
(new text-field%
[label "Username:"]
[init-value (remembered-user)]
[parent this]
[callback (lambda (t e) (activate-ok))]
[stretchable-width #t]))
(define passwd
(new cached-passwd%
[label "Password:"]
[parent this]
[callback (lambda (t e) (activate-ok))]
[stretchable-width #t]))
(define assignment
(new choice%
[label "Submit to:"]
[choices null]
[parent this]
[callback (lambda (c e)
(remembered-assignment
(send assignment get-string
(send assignment get-selection))))]
[stretchable-width #t]))
(define button-panel
(new horizontal-pane%
[parent this]
[stretchable-height #f]))
(make-object vertical-pane% button-panel) ; spacer
(define retrieve?
(new check-box%
[label "Retrieve"]
[parent button-panel]
[callback (lambda _
(define r? (send retrieve? get-value))
(send ok set-label
(if r? button-label/r button-label/h)))]
[value (eq? 'retrieve mode)]
[style (if mode '(deleted) '())]))
(define (submit-file)
(define final-message "Handin successful.")
(submit-assignment
connection
(send username get-value)
(send passwd get-value)
(send assignment get-string (send assignment get-selection))
content
;; on-commit
(lambda ()
(semaphore-wait commit-lock)
(send status set-label "Committing...")
(set! committing? #t)
(semaphore-post commit-lock))
;; message/message-final/message-box handlers
(lambda (msg) (send status set-label msg))
(lambda (msg) (set! final-message msg))
(lambda (msg styles) (message-box "Handin" msg this styles)))
(queue-callback
(lambda ()
(when abort-commit-dialog (send abort-commit-dialog show #f))
(send status set-label final-message)
(set! committing? #f)
(done-interface))))
(define (retrieve-file)
(let ([buf (retrieve-assignment
connection
(send username get-value)
(send passwd get-value)
(send assignment get-string (send assignment get-selection)))])
(queue-callback
(lambda ()
(done-interface)
(do-cancel-button)
(on-retrieve buf)))))
(define ok
(new button%
[label (case mode
[(submit) button-label/h]
[(retrieve) button-label/r]
[else (string-append " " button-label/h " ")])] ; can change
[parent button-panel]
[style '(border)]
[callback
(lambda (b e)
(disable-interface)
(send status set-label "Handing in...")
(parameterize ([current-custodian comm-cust])
(thread
(lambda ()
(remember-user (send username get-value))
(with-handlers ([void (lambda (exn)
(report-error "Handin failed." exn))])
(if (send retrieve? get-value)
(retrieve-file)
(submit-file)))))))]))
(define ok-can-enable? #f)
(define (activate-ok)
(send ok enable (and ok-can-enable?
(not (string=? "" (send username get-value)))
(not (string=? "" (send passwd get-value))))))
(define cancel
(new button%
[label "Cancel"]
[parent button-panel]
[callback (lambda (b e) (do-cancel-button))]))
(define (do-cancel-button)
(let ([go? (begin
(semaphore-wait commit-lock)
(if committing?
(begin
(semaphore-post commit-lock)
(send abort-commit-dialog show #t)
continue-abort?)
#t))])
(when go?
(custodian-shutdown-all comm-cust)
(show #f))))
(define continue-abort? #f)
(define abort-commit-dialog
(let ([d (make-object dialog% "Commit in Progress")])
(make-object message% "The commit action is in progress." d)
(make-object message% "Cancelling now may or may not work." d)
(make-object message% "Cancel anyway?" d)
(let ([b (new horizontal-panel%
[parent d]
[stretchable-height #f]
[alignment '(center center)])])
(make-object button% "Continue Commit" d
(lambda (b e) (send d show #f)))
(make-object button% "Try to Cancel" d
(lambda (b e)
(set! continue-abort? #t) (send d show #f))))))
(define interface-widgets
(list ok username passwd assignment retrieve?))
(define (disable-interface)
(for ([x (in-list interface-widgets)]) (send x enable #f)))
(define (enable-interface)
(for ([x (in-list interface-widgets)]) (send x enable #t) ))
(define (done-interface)
(send cancel set-label "Close")
(send cancel focus))
(define (report-error tag exn)
(queue-callback
(lambda ()
(let* ([msg (if (exn? exn)
(let ([s (exn-message exn)])
(if (string? s) s (format "~.s" s)))
(format "~.s" exn))]
[retry? (regexp-match #rx"bad username or password for" msg)])
(custodian-shutdown-all comm-cust)
(set! committing? #f)
(disable-interface)
(send status set-label tag)
(when (is-shown?)
(message-box "Server Error" msg this)
(if retry?
(begin (init-comm) (semaphore-post go-sema) (enable-interface))
(done-interface)))))))
(define go-sema #f)
(define commit-lock #f)
(define committing? #f)
(define connection #f)
(define comm-cust #f)
(define (init-comm)
(set! go-sema (make-semaphore 1))
(set! commit-lock (make-semaphore 1))
(set! comm-cust (make-custodian))
(parameterize ([current-custodian comm-cust])
(thread
(lambda ()
(let/ec break
(with-handlers ([void
(lambda (exn)
(report-error "Connection failed." exn)
(break))])
(semaphore-wait go-sema)
(let* ([h (connect)]
[l (retrieve-active-assignments h)]
[n (cond [(member (remembered-assignment) l)
=> (lambda (r) (- (length l) (length r)))]
[else 0])])
(when (null? l)
(handin-disconnect h)
(error 'handin "there are no active assignments"))
(set! connection h)
(for ([assign (in-list l)]) (send assignment append assign))
(send assignment enable #t)
(send assignment set-selection n)
(set! ok-can-enable? #t)
(activate-ok)
(send status set-label
(format "Connected securely for ~a." handin-name)))))))))
(define/augment (on-close)
(inner (void) on-close)
(do-cancel-button))
(init-comm)
(send (cond [(string=? "" (send username get-value)) username]
[(string=? "" (send passwd get-value)) passwd]
[else ok])
focus)
(send ok enable #f) ; disable after focus possibly sent to it
(send assignment enable #f)
(center)
(show #t)))
(provide manage-handin-dialog%)
(define manage-handin-dialog%
(class dialog% (init [parent #f])
(inherit show is-shown? center)
(super-new [label manage-dialog-name]
[alignment '(left center)]
[parent parent])
(define user-fields (get-user-fields parent))
;; === utilities ===
(define (mk-txt label parent activate-ok)
(new text-field%
[label label]
[parent parent]
[callback (lambda (t e) (activate-ok))]
[stretchable-width #t]))
(define (mk-passwd label parent activate-ok)
(new text-field%
[label label]
[parent parent]
[callback (lambda (t e) (activate-ok))]
[style '(single password)]
[stretchable-width #t]))
(define (non-empty? . ts)
(andmap (lambda (t) (not (string=? "" (send t get-value)))) ts))
(define (same-value t1 t2)
(string=? (send t1 get-value) (send t2 get-value)))
(define (report-error tag exn)
(queue-callback
(lambda ()
(custodian-shutdown-all comm-cust)
(send status set-label tag)
(when (is-shown?)
(message-box
"Server Error"
(if (exn? exn)
(let ([s (exn-message exn)]) (if (string? s) s (format "~.s" s)))
(format "~.s" exn))
this)
(set! comm-cust (make-custodian))))))
(define comm-cust (make-custodian))
(define/augment (on-close)
(inner (void) on-close)
(custodian-shutdown-all comm-cust))
;; Too-long fields can't damage the server, but they might result in
;; confusing errors due to safety cut-offs on the server side.
(define (check-length field size name k)
(when ((string-length (send field get-value)) . > . size)
(message-box "Error"
(format "The ~a must be no longer than ~a characters."
name size))
(k (void))))
(define (do-change/add new? username)
(let/ec break
(check-length username 50 "Username" break)
(let* ([pw1 (if new? new-passwd add-passwd)]
[pw2 (if new? new-passwd2 add-passwd2)]
[l1 (regexp-replace #rx" *:$" (send pw1 get-label) "")]
[l2 (regexp-replace #rx" *:$" (send pw2 get-label) "")])
(check-length pw1 50 l1 break)
;; not really needed, but leave just in case
(unless (string=? (send pw1 get-value) (send pw2 get-value))
(message-box
"Password Error"
(format "The \"~a\" and \"~a\" passwords are not the same." l1 l2))
(break (void))))
(for ([t (in-list (if new? add-user-fields change-user-fields))]
[f (in-list (or user-fields '()))])
(check-length t 100 f break))
(send tabs enable #f)
(parameterize ([current-custodian comm-cust])
(thread
(lambda ()
(with-handlers ([void (lambda (exn)
(send tabs enable #t)
(report-error
(format "~a failed."
(if new? "Creation" "Update"))
exn))])
(remember-user (send username get-value))
(send status set-label "Making secure connection...")
(let ([h (connect)])
(define (run proc . fields)
(apply proc h
(let loop ([x fields])
(if (list? x) (map loop x) (send x get-value)))))
(send status set-label
(if new? "Creating user..." "Updating server..."))
(if new?
(run submit-addition username add-passwd add-user-fields)
(run submit-info-change username old-passwd new-passwd
change-user-fields)))
(send status set-label "Success.")
(send cancel set-label "Close")))))))
(define (do-retrieve username)
(send tabs enable #f)
(parameterize ([current-custodian comm-cust])
(thread
(lambda ()
(with-handlers ([void (lambda (exn)
(send tabs enable #t)
(report-error "Retrieve failed." exn))])
(remember-user (send username get-value))
(send status set-label "Making secure connection...")
(let ([h (connect)])
(define (run proc . fields)
(apply proc h
(let loop ([x fields])
(if (list? x) (map loop x) (send x get-value)))))
(send status set-label "Retrieving information...")
(let ([vals (run retrieve-user-info username old-passwd)])
(send status set-label "Success, you can now edit fields.")
(send tabs enable #t)
(for ([f change-user-fields]
[val vals])
(send f set-value val))
(activate-change))))))))
;; === toplevel gadgets ===
(define status
(new message%
[label (if user-fields
(format "Manage ~a handin account at ~a."
handin-name server)
"No connection to server!")]
[parent this]
[stretchable-width #t]))
(define (set-active-tab n)
(send new-user-box show #f)
(send old-user-box show #f)
(send un/install-box show #f)
(send (if user-fields
(case n
[(0) new-user-box]
[(1) old-user-box]
[(2) un/install-box]
[else (error "internal error")])
un/install-box)
show #t))
(define tabs
(let* ([names (list (if multifile? "Un/Install" "Uninstall"))]
[names (if user-fields
`("New User" "Change Info" ,@names) names)]
[callback (lambda _ (set-active-tab (send tabs get-selection)))])
(new tab-panel% [parent this] [choices names] [callback callback])))
(define single (new panel:single% [parent tabs]))
(define button-panel
(new horizontal-pane% [parent this] [stretchable-height #f]))
(make-object vertical-pane% button-panel) ; spacer
(define cancel
(new button%
[label "Cancel"] [parent button-panel]
[callback (lambda (b e)
(custodian-shutdown-all comm-cust)
(show #f))]))
;; === change existing info tab ===
(define (activate-change)
(define an-extra-non-empty? (ormap non-empty? change-user-fields))
(send retrieve-old-info-button enable
(non-empty? old-username old-passwd))
(send change-button enable
(and (same-value new-passwd new-passwd2)
(non-empty? old-username old-passwd)
(or (non-empty? new-passwd) an-extra-non-empty?)))
(send change-button set-label
(if an-extra-non-empty? "Change Info" "Set Password")))
(define old-user-box (new vertical-panel%
[parent single] [alignment '(center center)]))
(define old-username (mk-txt "Username:" old-user-box activate-change))
(send old-username set-value (remembered-user))
(define old-passwd
(new cached-passwd%
[label "Old Password:"]
[parent old-user-box]
[callback (lambda (t e) (activate-change))]
[stretchable-width #t]))
(define change-user-fields
(map (lambda (f)
(mk-txt (string-append f ":") old-user-box activate-change))
(or user-fields '())))
(define new-passwd
(mk-passwd "New Password:" old-user-box activate-change))
(define new-passwd2
(mk-passwd "New Password again:" old-user-box activate-change))
(define-values (retrieve-old-info-button change-button)
(let ([p (new horizontal-pane%
[parent old-user-box]
[stretchable-height #f]
[alignment '(center center)])])
(make-object vertical-pane% p)
(values
(begin0 (new button%
[label "Get Current Info"] [parent p]
[callback (lambda (b e) (do-retrieve old-username))])
(make-object vertical-pane% p))
(begin0 (new button%
[label "Set Password"] [parent p] [style '(border)]
[callback (lambda (b e)
(do-change/add #f old-username))])
(make-object vertical-pane% p)))))
;; === register new user tab ===
(define (activate-new)
(send new-button enable
(and (apply non-empty? new-username add-passwd add-passwd2
add-user-fields)
(same-value add-passwd add-passwd2))))
(define new-user-box (new vertical-panel%
[parent single] [alignment '(center center)]))
(define new-username (mk-txt "Username:" new-user-box activate-new))
(send new-username set-value (remembered-user))
(define add-user-fields
(map (lambda (f)
(mk-txt (string-append f ":") new-user-box activate-new))
(or user-fields '())))
(define add-passwd
(mk-passwd "Password:" new-user-box activate-new))
(define add-passwd2
(mk-passwd "Password again:" new-user-box activate-new))
(define new-button (new button%
[label "Add User"] [parent new-user-box]
[callback (lambda (b e)
(do-change/add #t new-username))]
[style '(border)]))
;; === uninstall client, install standalone client ===
(define un/install-box
(new vertical-panel% [parent single] [alignment '(center center)]))
(define uninstall-button
(new button%
[label (format "Uninstall ~a Handin" handin-name)]
[parent un/install-box]
[callback
(lambda (b e)
(with-handlers ([void (lambda (exn)
(report-error "Uninstall failed." exn))])
(delete-directory/files (in-this-collection))
(set! uninstalled? #t)
(send uninstall-button enable #f)
(message-box
"Uninstall"
(format "The ~a tool has been uninstalled. ~a~a"
handin-name
"The Handin button and associated menu items will"
" not appear after you restart DrRacket.")
this)
(send this show #f)))]))
(send uninstall-button enable (not uninstalled?))
(define install-standalone-button
(and multifile?
(new button%
[label (format "Install Standalone ~a Handin" handin-name)]
[parent un/install-box]
[callback
(lambda (b e)
(define (launcher sym)
(dynamic-require `launcher sym))
(let* ([exe (let-values
([(dir name dir?)
(split-path
((launcher 'mred-program-launcher-path)
(format "~a Handin" handin-name)))])
(path->string name))]
[dir (get-directory
(format "Choose a directory to create the ~s~a"
exe " executable in")
#f)])
(when (and dir (directory-exists? dir))
(parameterize ([current-directory dir])
(when (or (not (file-exists? exe))
(eq? 'ok
(message-box
"File Exists"
(format
"The ~s executable already exists, ~a"
exe "it will be overwritten")
this '(ok-cancel caution))))
((launcher 'make-mred-launcher)
(list "-le-" (format "~a/handin-multi"
this-collection-name)
"(multifile-handin)")
(build-path dir exe))
(message-box "Standalone Executable"
(format "~s created" exe)
this)
(send this show #f))))))])))
;; === initialize the whole thing ===
(activate-new)
(activate-change)
(center)
(queue-callback
(lambda ()
(define n (cond [(not user-fields) 0]
[(equal? "" (remembered-user)) 0]
[else 1]))
(set-active-tab n)
(send tabs set-selection n)))
(show #t)))
;; A simple dialog during connection, with an option to cancel (used
;; by `get-user-fields' below, since its value is needed to
;; construct the above dialog).
(define connection-dialog%
(class dialog% (init receiver [parent #f])
(inherit show is-shown? center)
(super-new [label manage-dialog-name]
[alignment '(right center)]
[parent parent])
(define status
(new message% [label "Connecting to server..."]
[parent this]
[stretchable-width #t]))
(define comm-cust (make-custodian))
(define/augment (on-close)
(inner (void) on-close)
(custodian-shutdown-all comm-cust))
(define button
(new button% [label "Cancel"] [parent this]
[callback (lambda (b e)
(custodian-shutdown-all comm-cust)
(show #f))]
[style '(border)]))
(send button focus)
(parameterize ([current-custodian comm-cust])
(thread
(lambda ()
(unless (with-handlers ([void (lambda (_) #f)])
(receiver (connect)) #t)
(begin (send status set-label "Connection failure!")
;; (send button enable #f)
(sleep 5)))
(queue-callback (lambda () (show #f))))))
(center)
(show #t)))
(define cached-user-fields #f)
(define (get-user-fields parent)
(unless cached-user-fields
(new connection-dialog%
[receiver (lambda (h)
(set! cached-user-fields (retrieve-user-fields h)))]
[parent parent]))
cached-user-fields)
(define (scale-by-half file)
(let* ([bm (make-object bitmap% file 'unknown/mask)]
[w (send bm get-width)]
[h (send bm get-height)]
[bm2 (make-object bitmap% (quotient w 2) (quotient h 2))]
[mbm2 (and (send bm get-loaded-mask)
(make-object bitmap% (quotient w 2) (quotient h 2)))]
[mdc (make-object bitmap-dc% bm2)])
(send mdc draw-bitmap-section-smooth bm
0 0 (quotient w 2) (quotient h 2)
0 0 w h)
(send mdc set-bitmap #f)
(when mbm2
(send mdc set-bitmap mbm2)
(send mdc draw-bitmap-section-smooth (send bm get-loaded-mask)
0 0 (quotient w 2) (quotient h 2)
0 0 w h)
(send mdc set-bitmap #f)
(send bm2 set-loaded-mask mbm2))
bm2))
(define handin-icon
(scale-by-half (in-this-collection (car (#%info-lookup 'drracket-tool-icons)))))
(define (editors->string editors)
(let* ([base (make-object editor-stream-out-bytes-base%)]
[stream (make-object editor-stream-out% base)])
(write-editor-version stream base)
(write-editor-global-header stream)
(for ([ed (in-list editors)]) (send ed write-to-file stream))
(write-editor-global-footer stream)
(send base get-bytes)))
(define (string->editor! str defs)
(let* ([base (make-object editor-stream-in-bytes-base% str)]
[stream (make-object editor-stream-in% base)])
(read-editor-version stream base #t)
(read-editor-global-header stream)
(send* defs (begin-edit-sequence #f)
(erase) (read-from-file stream)
(end-edit-sequence))
(read-editor-global-footer stream)))
(define tool@
(unit
(import drracket:tool^)
(export drracket:tool-exports^)
(define phase1 void)
(define phase2
(if updater?
(dynamic-require `(lib "updater.rkt" ,this-collection-name) 'bg-update)
void))
(define tool-button-label (bitmap-label-maker button-label/h handin-icon))
(define (make-new-unit-frame% super%)
(class super%
(inherit get-button-panel
get-definitions-text
get-interactions-text)
(super-instantiate ())
(define/override (file-menu:between-open-and-revert file-menu)
;; super adds a separator, add this and another sep after that
(super file-menu:between-open-and-revert file-menu)
(new menu-item%
[label (format "Manage ~a Handin Account..." handin-name)]
[parent file-menu]
[callback (lambda (m e)
(new manage-handin-dialog% [parent this]))])
(when multifile?
(new menu-item%
[label (format "Submit multiple ~a Files..." handin-name)]
[parent file-menu]
[callback (lambda (m e)
((dynamic-require
`(lib "handin-multi.rkt" ,this-collection-name)
'multifile-handin)))]))
(when updater?
(new menu-item%
[label (format "Update ~a plugin..." handin-name)]
[parent file-menu]
[callback
(lambda (m e)
((dynamic-require `(lib "updater.rkt" ,this-collection-name)
'update)
#f #t))])) ; no parent
(new separator-menu-item% [parent file-menu]))
(define/override (help-menu:after-about menu)
(when web-menu-name
(new menu-item%
[label web-menu-name]
[parent menu]
[callback (lambda (item evt) (send-url web-address))]))
(super help-menu:after-about menu))
#;(define client-panel
(new panel:vertical-discrete-sizes% (parent (get-button-panel))))
#;(define client-button
(new switchable-button%
[label button-label/h]
[bitmap handin-icon]
[parent client-panel]
[callback
(lambda (button)
(let ([content (editors->string
(list (get-definitions-text)
(get-interactions-text)))])
(new handin-frame%
[parent this]
[content content]
[on-retrieve
(lambda (buf)
(string->editor!
buf
(send (drracket:unit:open-drscheme-window)
get-editor)))])))]))
#;(inherit register-toolbar-button)
#;(register-toolbar-button client-button #:number -1000)
#;(send (get-button-panel) change-children
(lambda (l) (cons client-panel (remq client-panel l))))))
(when (and server port-no)
(drracket:get/extend:extend-unit-frame make-new-unit-frame% #f))))