MDL-42023 assign: Edit PDF plugin - Damyon's contributions
[moodle.git] / mod / assign / feedback / editpdf / yui / src / editor / js / editor.js
CommitLineData
5c386472
DW
1// This file is part of Moodle - http://moodle.org/
2//
3// Moodle is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7//
8// Moodle is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
15
16/**
17 * Provides an in browser PDF editor.
18 *
19 * @module moodle-assignfeedback_editpdf-editor
20 */
21
22/**
23 * EDITOR
24 * This is an in browser PDF editor.
25 *
26 * @namespace M.assignfeedback_editpdf.editor
27 * @class Editor
28 * @constructor
29 * @extends Y.Base
30 */
31EDITOR = function() {
32 EDITOR.superclass.constructor.apply(this, arguments);
33};
34EDITOR.prototype = {
35
36 // Instance variables.
37 /**
38 * The dialogue used for all action menu displays.
39 * @property type
40 * @type M.core.dialogue
41 * @protected
42 */
43 dialogue : null,
44
45 /**
46 * The number of pages in the pdf.
47 * @property type
48 * @type int
49 * @protected
50 */
51 pagecount : 0,
52
53 /**
54 * The active page in the editor.
55 * @property type
56 * @type int
57 * @protected
58 */
59 currentpage : 0,
60
61 /**
62 * A list of page objects. Each page has a list of comments and annotations.
63 * @property type
64 * @type array
65 * @protected
66 */
67 pages : [],
68
69 /**
70 * The yui node for the loading icon.
71 * @property type
72 * @type Y.Node
73 * @protected
74 */
75 loadingicon : null,
76
77 /**
78 * Image object of the current page image.
79 * @property type
80 * @type Image
81 * @protected
82 */
83 pageimage : null,
84
85 /**
86 * YUI Graphic class for drawing shapes.
87 * @property type
88 * @type Y.Graphic
89 * @protected
90 */
91 graphic : null,
92
93 /**
94 * Info about the current edit operation.
95 * @property currentedit
96 * @type M.assignfeedback_editpdf.edit
97 * @protected
98 */
99 currentedit : new M.assignfeedback_editpdf.edit(),
100
101 /**
102 * Current drawable.
103 * @property currentdrawable
104 * @type M.assignfeedback_editpdf.drawable (or false)
105 * @protected
106 */
107 currentdrawable : false,
108
109 /**
110 * Current drawables.
111 * @property drawables
112 * @type array(M.assignfeedback_editpdf.drawable)
113 * @protected
114 */
115 drawables : [],
116
117 /**
118 * Current comment when the comment menu is open.
119 * @property currentcomment
120 * @type M.assignfeedback_editpdf.comment
121 * @protected
122 */
123 currentcomment : null,
124
125 /**
126 * Current annotation when the select tool is used.
127 * @property currentannotation
128 * @type M.assignfeedback_editpdf.annotation
129 * @protected
130 */
131 currentannotation : null,
132
133 /**
134 * Last selected annotation tool
135 * @property lastannotationtool
136 * @type String
137 * @protected
138 */
139 lastanntationtool : "pen",
140
141 /**
142 * The users comments quick list
143 * @property quicklist
144 * @type M.assignfeedback_editpdf.quickcommentlist
145 * @protected
146 */
147 quicklist : null,
148
149 /**
150 * The search comments window.
151 * @property searchcommentswindow
152 * @type M.core.dialogue
153 * @protected
154 */
155 searchcommentswindow : null,
156
157
158 /**
159 * The selected stamp picture.
160 * @property currentstamp
161 * @type String
162 * @protected
163 */
164 currentstamp : null,
165
166 /**
167 * The stamps.
168 * @property stamps
169 * @type Array
170 * @protected
171 */
172 stamps : [],
173
174 /**
175 * Called during the initialisation process of the object.
176 * @method initializer
177 */
178 initializer : function() {
179 var link;
180
181 link = Y.one('#' + this.get('linkid'));
182
183 if (link) {
184 link.on('click', this.link_handler, this);
185 link.on('key', this.link_handler, 'down:13', this);
186
187 this.currentedit.start = false;
188 this.currentedit.end = false;
189 this.quicklist = new M.assignfeedback_editpdf.quickcommentlist(this);
190 }
191 },
192
193 /**
194 * Called to show/hide buttons and set the current colours/stamps.
195 * @method refresh_button_state
196 */
197 refresh_button_state : function() {
198 var button, currenttoolnode, imgurl;
199
200 // Initalise the colour buttons.
201 button = Y.one(SELECTOR.COMMENTCOLOURBUTTON);
202
203 imgurl = M.util.image_url('background_colour_' + this.currentedit.commentcolour, 'assignfeedback_editpdf');
204 button.one('img').setAttribute('src', imgurl);
205
206 if (this.currentedit.commentcolour === 'clear') {
207 button.one('img').setStyle('borderStyle', 'dashed');
208 } else {
209 button.one('img').setStyle('borderStyle', 'solid');
210 }
211
212 button = Y.one(SELECTOR.ANNOTATIONCOLOURBUTTON);
213 imgurl = M.util.image_url('colour_' + this.currentedit.annotationcolour, 'assignfeedback_editpdf');
214 button.one('img').setAttribute('src', imgurl);
215
216 currenttoolnode = Y.one(TOOLSELECTOR[this.currentedit.tool]);
217 currenttoolnode.addClass('assignfeedback_editpdf_selectedbutton');
218 currenttoolnode.setAttribute('aria-pressed', 'true');
219
220 button = Y.one(SELECTOR.STAMPSBUTTON);
221 button.one('img').setAttrs({'src': this.get_stamp_image_url(this.currentedit.stamp),
222 'height': '16',
223 'width': '16'});
224 },
225
226 /**
227 * Called to get the bounds of the drawing region.
228 * @method get_canvas_bounds
229 */
230 get_canvas_bounds : function() {
231 var canvas = Y.one(SELECTOR.DRAWINGCANVAS),
232 offsetcanvas = canvas.getXY(),
233 offsetleft = offsetcanvas[0],
234 offsettop = offsetcanvas[1],
235 width = parseInt(canvas.getStyle('width'), 10),
236 height = parseInt(canvas.getStyle('height'), 10);
237
238 return new M.assignfeedback_editpdf.rect(offsetleft, offsettop, width, height);
239 },
240
241 /**
242 * Called to translate from window coordinates to canvas coordinates.
243 * @method get_canvas_coordinates
244 * @param M.assignfeedback_editpdf.point point in window coordinats.
245 */
246 get_canvas_coordinates : function(point) {
247 var bounds = this.get_canvas_bounds(),
248 newpoint = new M.assignfeedback_editpdf.point(point.x - bounds.x, point.y - bounds.y);
249
250 bounds.x = bounds.y = 0;
251
252 newpoint.clip(bounds);
253 return newpoint;
254 },
255
256 /**
257 * Called to translate from canvas coordinates to window coordinates.
258 * @method get_window_coordinates
259 * @param M.assignfeedback_editpdf.point point in window coordinats.
260 */
261 get_window_coordinates : function(point) {
262 var bounds = this.get_canvas_bounds(),
263 newpoint = new M.assignfeedback_editpdf.point(point.x + bounds.x, point.y + bounds.y);
264
265 return newpoint;
266 },
267
268 /**
269 * Called to open the pdf editing dialogue.
270 * @method link_handler
271 */
272 link_handler : function(e) {
273 var drawingcanvas;
274 e.preventDefault();
275
276 if (!this.dialogue) {
277 this.dialogue = new M.core.dialogue({
278 headerContent: this.get('header'),
279 bodyContent: this.get('body'),
280 footerContent: this.get('footer'),
281 width: '840px',
282 visible: true
283 });
284
285 this.dialogue.centerDialogue();
286 // Add custom class for styling.
287 this.dialogue.get('boundingBox').addClass(CSS.DIALOGUE);
288
289 this.loadingicon = Y.one(SELECTOR.LOADINGICON);
290
291 drawingcanvas = Y.one(SELECTOR.DRAWINGCANVAS);
292 this.graphic = new Y.Graphic({render : SELECTOR.DRAWINGCANVAS});
293
294 if (!this.get('readonly')) {
295 drawingcanvas.on('gesturemovestart', this.edit_start, null, this);
296 drawingcanvas.on('gesturemove', this.edit_move, null, this);
297 drawingcanvas.on('gesturemoveend', this.edit_end, null, this);
298
299 this.refresh_button_state();
300 }
301 } else {
302 this.dialogue.show();
303 }
304
305 this.load_all_pages();
306 },
307
308 /**
309 * Called to load the information and annotations for all pages.
310 * @method load_all_pages
311 */
312 load_all_pages : function() {
313 var ajaxurl = AJAXBASE,
314 config;
315
316 config = {
317 method: 'get',
318 context: this,
319 sync: false,
320 data : {
321 'sesskey' : M.cfg.sesskey,
322 'action' : 'loadallpages',
323 'userid' : this.get('userid'),
324 'attemptnumber' : this.get('attemptnumber'),
325 'assignmentid' : this.get('assignmentid')
326 },
327 on: {
328 success: function(tid, response) {
329 this.all_pages_loaded(response.responseText);
330 },
331 failure: function(tid, response) {
332 return M.core.exception(response.responseText);
333 }
334 }
335 };
336
337 Y.io(ajaxurl, config);
338 },
339
340 /**
341 * The info about all pages in the pdf has been returned.
342 * @param string The ajax response as text.
343 * @protected
344 * @method all_pages_loaded
345 */
346 all_pages_loaded : function(responsetext) {
347 var data, i, j, comment, error;
348 try {
349 data = Y.JSON.parse(responsetext);
350 if (data.error || !data.pagecount) {
351 this.dialogue.hide();
352 // Display alert dialogue.
353 error = new M.core.alert({ message: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf') });
354 error.show();
355 return;
356 }
357 } catch (e) {
358 this.dialogue.hide();
359 // Display alert dialogue.
360 error = new M.core.alert({ title: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf')});
361 error.show();
362 return;
363 }
364
365 this.pagecount = data.pagecount;
366 this.pages = data.pages;
367
368 for (i = 0; i < this.pages.length; i++) {
369 for (j = 0; j < this.pages[i].comments.length; j++) {
370 comment = this.pages[i].comments[j];
371 this.pages[i].comments[j] = new M.assignfeedback_editpdf.comment(this,
372 comment.gradeid,
373 comment.pageno,
374 comment.x,
375 comment.y,
376 comment.width,
377 comment.colour,
378 comment.rawtext);
379 }
380 for (j = 0; j < this.pages[i].annotations.length; j++) {
381 data = this.pages[i].annotations[j];
382 this.pages[i].annotations[j] = this.create_annotation(data.type, data);
383 }
384 }
385
386 // Update the ui.
387 this.quicklist.load();
388 this.setup_navigation();
389 this.setup_toolbar();
390 this.change_page();
391 },
392
393 /**
394 * Get the full pluginfile url for an image file - just given the filename.
395 *
396 * @public
397 * @method get_stamp_image_url
398 * @param string filename
399 */
400 get_stamp_image_url : function(filename) {
401 var urls = this.get('stampfiles'),
402 fullurl = '';
403
404 Y.Array.each(urls, function(url) {
405 if (url.indexOf(filename) > 0) {
406 fullurl = url;
407 }
408 }, this);
409
410 return fullurl;
411 },
412
413 /**
414 * Attach listeners and enable the color picker buttons.
415 * @protected
416 * @method setup_toolbar
417 */
418 setup_toolbar : function() {
419 var toolnode,
420 commentcolourbutton,
421 annotationcolourbutton,
422 searchcommentsbutton,
423 currentstampbutton,
424 stampfiles,
425 picker,
426 filename;
427
428 searchcommentsbutton = Y.one(SELECTOR.SEARCHCOMMENTSBUTTON);
429 searchcommentsbutton.on('click', this.open_search_comments, this);
430 searchcommentsbutton.on('key', this.open_search_comments, 'down:13', this);
431
432 if (this.get('readonly')) {
433 return;
434 }
435 // Setup the tool buttons.
436 Y.each(TOOLSELECTOR, function(selector, tool) {
437 toolnode = Y.one(selector);
438 toolnode.on('click', this.handle_tool_button, this, tool);
439 toolnode.on('key', this.handle_tool_button, 'down:13', this, tool);
440 toolnode.setAttribute('aria-pressed', 'false');
441 }, this);
442
443 // Set the default tool.
444
445 commentcolourbutton = Y.one(SELECTOR.COMMENTCOLOURBUTTON);
446 picker = new M.assignfeedback_editpdf.colourpicker({
447 buttonNode: commentcolourbutton,
448 colours: COMMENTCOLOUR,
449 iconprefix: 'background_colour_',
450 callback: function (e) {
451 var colour = e.target.getAttribute('data-colour');
452 if (!colour) {
453 colour = e.target.ancestor().getAttribute('data-colour');
454 }
455 this.currentedit.commentcolour = colour;
456 this.handle_tool_button(e, "comment");
457 },
458 context: this
459 });
460
461 annotationcolourbutton = Y.one(SELECTOR.ANNOTATIONCOLOURBUTTON);
462 picker = new M.assignfeedback_editpdf.colourpicker({
463 buttonNode: annotationcolourbutton,
464 iconprefix: 'colour_',
465 colours: ANNOTATIONCOLOUR,
466 callback: function (e) {
467 var colour = e.target.getAttribute('data-colour');
468 if (!colour) {
469 colour = e.target.ancestor().getAttribute('data-colour');
470 }
471 this.currentedit.annotationcolour = colour;
472 if (this.lastannotationtool) {
473 this.handle_tool_button(e, this.lastannotationtool);
474 } else {
475 this.handle_tool_button(e, "pen");
476 }
477 },
478 context: this
479 });
480
481 stampfiles = this.get('stampfiles');
482 if (stampfiles.length <= 0) {
483 Y.one(SELECTOR.STAMPSBUTTON).hide();
484 Y.one(TOOLSELECTOR.stamp).hide();
485 } else {
486 filename = stampfiles[0].substr(stampfiles[0].lastIndexOf('/') + 1);
487 this.currentedit.stamp = filename;
488 currentstampbutton = Y.one(SELECTOR.STAMPSBUTTON);
489
490 picker = new M.assignfeedback_editpdf.stamppicker({
491 buttonNode: currentstampbutton,
492 stamps: stampfiles,
493 callback: function(e) {
494 var stamp = e.target.getAttribute('data-stamp'),
495 filename;
496
497 if (!stamp) {
498 stamp = e.target.ancestor().getAttribute('data-stamp');
499 }
500 filename = stamp.substr(stamp.lastIndexOf('/'));
501 this.currentedit.stamp = filename;
502 this.refresh_button_state();
503 },
504 context: this
505 });
506 this.refresh_button_state();
507 }
508 },
509
510 /**
511 * Change the current tool.
512 * @protected
513 * @method handle_tool_button
514 */
515 handle_tool_button : function(e, tool) {
516 var currenttoolnode;
517
518 e.preventDefault();
519
520 // Change style of the pressed button.
521 currenttoolnode = Y.one(TOOLSELECTOR[this.currentedit.tool]);
522 currenttoolnode.removeClass('assignfeedback_editpdf_selectedbutton');
523 currenttoolnode.setAttribute('aria-pressed', 'false');
524 this.currentedit.tool = tool;
525 if (tool !== "comment" && tool !== "select") {
526 this.lastannotationtool = tool;
527 }
528 this.refresh_button_state();
529 },
530
531 /**
532 * JSON encode the current page data - stripping out drawable references which cannot be encoded.
533 * @protected
534 * @method stringify_current_page
535 * @return string
536 */
537 stringify_current_page : function() {
538 var comments = [],
539 annotations = [],
540 page,
541 i = 0;
542
543 for (i = 0; i < this.pages[this.currentpage].comments.length; i++) {
544 comments[i] = this.pages[this.currentpage].comments[i].clean();
545 }
546 for (i = 0; i < this.pages[this.currentpage].annotations.length; i++) {
547 annotations[i] = this.pages[this.currentpage].annotations[i].clean();
548 }
549
550 page = { comments : comments, annotations : annotations };
551
552 return Y.JSON.stringify(page);
553 },
554
555 /**
556 * Generate a drawable from the current in progress edit.
557 * @protected
558 * @method get_current_drawable
559 */
560 get_current_drawable : function() {
561 var comment,
562 annotation,
563 drawable = false;
564
565 if (!this.currentedit.start || !this.currentedit.end) {
566 return false;
567 }
568
569 if (this.currentedit.tool === 'comment') {
570 comment = new M.assignfeedback_editpdf.comment(this);
571 drawable = comment.draw_current_edit(this.currentedit);
572 } else {
573 annotation = this.create_annotation(this.currentedit.tool, {});
574 if (annotation) {
575 drawable = annotation.draw_current_edit(this.currentedit);
576 }
577 }
578
579 return drawable;
580 },
581
582 /**
583 * Redraw the active edit.
584 * @protected
585 * @method redraw_active_edit
586 */
587 redraw_current_edit : function() {
588 if (this.currentdrawable) {
589 this.currentdrawable.erase();
590 }
591 this.currentdrawable = this.get_current_drawable();
592 },
593
594 /**
595 * Event handler for mousedown or touchstart.
596 * @protected
597 * @param Event
598 * @method edit_start
599 */
600 edit_start : function(e) {
601 var canvas = Y.one(SELECTOR.DRAWINGCANVAS),
602 offset = canvas.getXY(),
603 scrolltop = canvas.get('docScrollY'),
604 scrollleft = canvas.get('docScrollX'),
605 point = {x : e.clientX - offset[0] + scrollleft,
606 y : e.clientY - offset[1] + scrolltop},
607 selected = false,
608 lastannotation;
609
610 // Ignore right mouse click.
611 if (e.button === 3) {
612 return;
613 }
614
615 if (this.currentedit.starttime) {
616 return;
617 }
618
619 this.currentedit.starttime = new Date().getTime();
620 this.currentedit.start = point;
621 this.currentedit.end = {x : point.x, y : point.y};
622
623 if (this.currentedit.tool === 'select') {
624 x = this.currentedit.end.x;
625 y = this.currentedit.end.y;
626 annotations = this.pages[this.currentpage].annotations;
627 // Find the first annotation whose bounds encompass the click.
628 Y.each(annotations, function(annotation) {
629 if (((x - annotation.x) * (x - annotation.endx)) <= 0 &&
630 ((y - annotation.y) * (y - annotation.endy)) <= 0) {
631 selected = annotation;
632 }
633 });
634
635 if (selected) {
636 lastannotation = this.currentannotation;
637 this.currentannotation = selected;
638 if (lastannotation && lastannotation !== selected) {
639 // Redraw the last selected annotation to remove the highlight.
640 if (lastannotation.drawable) {
641 lastannotation.drawable.erase();
642 this.drawables.push(lastannotation.draw());
643 }
644 }
645 // Redraw the newly selected annotation to show the highlight.
646 if (this.currentannotation.drawable) {
647 this.currentannotation.drawable.erase();
648 }
649 this.drawables.push(this.currentannotation.draw());
650 }
651 }
652 if (this.currentannotation) {
653 // Used to calculate drag offset.
654 this.currentedit.annotationstart = { x : this.currentannotation.x,
655 y : this.currentannotation.y };
656 }
657 },
658
659 /**
660 * Event handler for mousemove.
661 * @protected
662 * @param Event
663 * @method edit_move
664 */
665 edit_move : function(e) {
666 var bounds = this.get_canvas_bounds(),
667 canvas = Y.one(SELECTOR.DRAWINGCANVAS),
668 clientpoint = new M.assignfeedback_editpdf.point(e.clientX + canvas.get('docScrollX'),
669 e.clientY + canvas.get('docScrollY')),
670 point = this.get_canvas_coordinates(clientpoint);
671
672 // Ignore events out of the canvas area.
673 if (point.x < 0 || point.x > bounds.width || point.y < 0 || point.y > bounds.height) {
674 return;
675 }
676
677 if (this.currentedit.tool === 'pen') {
678 this.currentedit.path.push(point);
679 }
680
681 if (this.currentedit.tool === 'select') {
682 if (this.currentannotation && this.currentedit) {
683 this.currentannotation.move( this.currentedit.annotationstart.x + point.x - this.currentedit.start.x,
684 this.currentedit.annotationstart.y + point.y - this.currentedit.start.y);
685 }
686 } else {
687 if (this.currentedit.start) {
688 this.currentedit.end = point;
689 this.redraw_current_edit();
690 }
691 }
692 },
693
694 /**
695 * Event handler for mouseup or touchend.
696 * @protected
697 * @param Event
698 * @method edit_end
699 */
700 edit_end : function() {
701 var duration,
702 comment,
703 annotation;
704
705 duration = new Date().getTime() - this.currentedit.start;
706
707 if (duration < CLICKTIMEOUT || this.currentedit.start === false) {
708 return;
709 }
710
711 if (this.currentedit.tool === 'comment') {
712 if (this.currentdrawable) {
713 this.currentdrawable.erase();
714 }
715 this.currentdrawable = false;
716 comment = new M.assignfeedback_editpdf.comment(this);
717 comment.init_from_edit(this.currentedit);
718 this.pages[this.currentpage].comments.push(comment);
719 this.drawables.push(comment.draw(true));
720 } else {
721 annotation = this.create_annotation(this.currentedit.tool, {});
722 if (annotation) {
723 if (this.currentdrawable) {
724 this.currentdrawable.erase();
725 }
726 this.currentdrawable = false;
727 annotation.init_from_edit(this.currentedit);
728 this.pages[this.currentpage].annotations.push(annotation);
729 this.drawables.push(annotation.draw());
730 }
731 }
732
733
734 // Save the changes.
735 this.save_current_page();
736
737 // Reset the current edit.
738 this.currentedit.starttime = 0;
739 this.currentedit.start = false;
740 this.currentedit.end = false;
741 this.currentedit.path = [];
742 },
743
744 /**
745 * Factory method for creating annotations of the correct subclass.
746 * @public
747 * @method create_annotation
748 */
749 create_annotation : function(type, data) {
750 data.type = type;
751 data.editor = this;
752 if (type === "line") {
753 return new M.assignfeedback_editpdf.annotationline(data);
754 } else if (type === "rectangle") {
755 return new M.assignfeedback_editpdf.annotationrectangle(data);
756 } else if (type === "oval") {
757 return new M.assignfeedback_editpdf.annotationoval(data);
758 } else if (type === "pen") {
759 return new M.assignfeedback_editpdf.annotationpen(data);
760 } else if (type === "highlight") {
761 return new M.assignfeedback_editpdf.annotationhighlight(data);
762 } else if (type === "stamp") {
763 return new M.assignfeedback_editpdf.annotationstamp(data);
764 }
765 return false;
766 },
767
768 /**
769 * Save all the annotations and comments for the current page.
770 * @protected
771 * @method save_current_page
772 */
773 save_current_page : function() {
774 var ajaxurl = AJAXBASE,
775 config;
776
777 config = {
778 method: 'post',
779 context: this,
780 sync: false,
781 data : {
782 'sesskey' : M.cfg.sesskey,
783 'action' : 'savepage',
784 'index' : this.currentpage,
785 'userid' : this.get('userid'),
786 'attemptnumber' : this.get('attemptnumber'),
787 'assignmentid' : this.get('assignmentid'),
788 'page' : this.stringify_current_page()
789 },
790 on: {
791 success: function(tid, response) {
792 var jsondata;
793 try {
794 jsondata = Y.JSON.parse(response.responseText);
795 if (jsondata.error) {
796 return new M.core.ajaxException(jsondata);
797 }
798 Y.one(SELECTOR.UNSAVEDCHANGESDIV).addClass('haschanges');
799 } catch (e) {
800 return new M.core.exception(e);
801 }
802 },
803 failure: function(tid, response) {
804 return M.core.exception(response.responseText);
805 }
806 }
807 };
808
809 Y.io(ajaxurl, config);
810
811 },
812
813 /**
814 * Event handler to open the comment search interface.
815 *
816 * @param Event e
817 * @protected
818 * @method open_search_comments
819 */
820 open_search_comments : function(e) {
821 if (!this.searchcommentswindow) {
822 this.searchcommentswindow = new M.assignfeedback_editpdf.commentsearch({
823 editor : this
824 });
825 }
826
827 this.searchcommentswindow.show();
828 e.preventDefault();
829 },
830
831 /**
832 * Redraw all the comments and annotations.
833 * @protected
834 * @method redraw
835 */
836 redraw : function() {
837 var i,
838 page;
839
840 page = this.pages[this.currentpage];
841 while (this.drawables.length > 0) {
842 this.drawables.pop().erase();
843 }
844
845 for (i = 0; i < page.annotations.length; i++) {
846 this.drawables.push(page.annotations[i].draw());
847 }
848 for (i = 0; i < page.comments.length; i++) {
849 this.drawables.push(page.comments[i].draw(false));
850 }
851 },
852
853 /**
854 * Load the image for this pdf page and remove the loading icon (if there).
855 * @protected
856 * @method change_page
857 */
858 change_page : function() {
859 var drawingcanvas = Y.one(SELECTOR.DRAWINGCANVAS),
860 page,
861 previousbutton,
862 nextbutton;
863
864 previousbutton = Y.one(SELECTOR.PREVIOUSBUTTON);
865 nextbutton = Y.one(SELECTOR.NEXTBUTTON);
866
867 if (this.currentpage > 0) {
868 previousbutton.removeAttribute('disabled');
869 } else {
870 previousbutton.setAttribute('disabled', 'true');
871 }
872 if (this.currentpage < (this.pagecount - 1)) {
873 nextbutton.removeAttribute('disabled');
874 } else {
875 nextbutton.setAttribute('disabled', 'true');
876 }
877
878 page = this.pages[this.currentpage];
879 this.loadingicon.hide();
880 drawingcanvas.setStyle('backgroundImage', 'url("' + page.url + '")');
881
882 this.redraw();
883 },
884
885 /**
886 * Now we know how many pages there are,
887 * we can enable the navigation controls.
888 * @protected
889 * @method setup_navigation
890 */
891 setup_navigation : function() {
892 var pageselect,
893 i,
894 option,
895 previousbutton,
896 nextbutton;
897
898 pageselect = Y.one(SELECTOR.PAGESELECT);
899
900 options = pageselect.all('option');
901 if (options.size() <= 1) {
902 for (i = 0; i < this.pages.length; i++) {
903 option = Y.Node.create('<option/>');
904 option.setAttribute('value', i);
905 option.setHTML((i+1));
906 pageselect.append(option);
907 }
908 }
909 pageselect.removeAttribute('disabled');
910 pageselect.on('change', function() {
911 this.currentpage = pageselect.get('value');
912 this.change_page();
913 }, this);
914
915 previousbutton = Y.one(SELECTOR.PREVIOUSBUTTON);
916 nextbutton = Y.one(SELECTOR.NEXTBUTTON);
917
918 previousbutton.on('click', this.previous_page, this);
919 previousbutton.on('key', this.previous_page, 'down:13', this);
920 nextbutton.on('click', this.next_page, this);
921 nextbutton.on('key', this.next_page, 'down:13', this);
922 },
923
924 /**
925 * Navigate to the previous page.
926 * @protected
927 * @method previous_page
928 */
929 previous_page : function() {
930 this.currentpage--;
931 if (this.currentpage < 0) {
932 this.currentpage = 0;
933 }
934 this.change_page();
935 },
936
937 /**
938 * Navigate to the next page.
939 * @protected
940 * @method next_page
941 */
942 next_page : function() {
943 this.currentpage++;
944 if (this.currentpage >= this.pages.length) {
945 this.currentpage = this.pages.length - 1;
946 }
947 this.change_page();
948 }
949
950
951
952};
953
954Y.extend(EDITOR, Y.Base, EDITOR.prototype, {
955 NAME : 'moodle-assignfeedback_editpdf-editor',
956 ATTRS : {
957 userid : {
958 validator : Y.Lang.isInteger,
959 value : 0
960 },
961 assignmentid : {
962 validator : Y.Lang.isInteger,
963 value : 0
964 },
965 attemptnumber : {
966 validator : Y.Lang.isInteger,
967 value : 0
968 },
969 header : {
970 validator : Y.Lang.isString,
971 value : ''
972 },
973 body : {
974 validator : Y.Lang.isString,
975 value : ''
976 },
977 footer : {
978 validator : Y.Lang.isString,
979 value : ''
980 },
981 linkid : {
982 validator : Y.Lang.isString,
983 value : ''
984 },
985 deletelinkid : {
986 validator : Y.Lang.isString,
987 value : ''
988 },
989 readonly : {
990 validator : Y.Lang.isBoolean,
991 value : true
992 },
993 stampfiles : {
994 validator : Y.Lang.isArray,
995 value : ''
996 }
997 }
998});
999
1000/**
1001 * Assignfeedback edit pdf namespace.
1002 * @static
1003 * @class assignfeedback_editpdf
1004 */
1005M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1006
1007/**
1008 * Editor namespace
1009 * @namespace M.assignfeedback_editpdf.editor
1010 * @class editor
1011 * @static
1012 */
1013M.assignfeedback_editpdf.editor = M.assignfeedback_editpdf.editor || {};
1014
1015/**
1016 * Init function - will create a new instance every time.
1017 * @method init
1018 * @static
1019 * @param {Object} params
1020 */
1021M.assignfeedback_editpdf.editor.init = M.assignfeedback_editpdf.editor.init || function(params) {
1022 return new EDITOR(params);
1023};