MDL-61537 assignfeedback_editpdf: Rotate PDF page
[moodle.git] / mod / assign / feedback / editpdf / yui / build / moodle-assignfeedback_editpdf-editor / moodle-assignfeedback_editpdf-editor.js
1 YUI.add('moodle-assignfeedback_editpdf-editor', function (Y, NAME) {
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /* eslint-disable no-unused-vars */
19 /**
20  * A list of globals used by this module.
21  *
22  * @module moodle-assignfeedback_editpdf-editor
23  */
24 var AJAXBASE = M.cfg.wwwroot + '/mod/assign/feedback/editpdf/ajax.php',
25     AJAXBASEPROGRESS = M.cfg.wwwroot + '/mod/assign/feedback/editpdf/ajax_progress.php',
26     CSS = {
27         DIALOGUE: 'assignfeedback_editpdf_widget'
28     },
29     SELECTOR = {
30         PREVIOUSBUTTON:  '.navigate-previous-button',
31         NEXTBUTTON:  ' .navigate-next-button',
32         SEARCHCOMMENTSBUTTON: '.searchcommentsbutton',
33         EXPCOLCOMMENTSBUTTON: '.expcolcommentsbutton',
34         SEARCHFILTER: '.assignfeedback_editpdf_commentsearch input',
35         SEARCHCOMMENTSLIST: '.assignfeedback_editpdf_commentsearch ul',
36         PAGESELECT: '.navigate-page-select',
37         LOADINGICON: '.loading',
38         PROGRESSBARCONTAINER: '.progress-info.progress-striped',
39         DRAWINGREGION: '.drawingregion',
40         DRAWINGCANVAS: '.drawingcanvas',
41         SAVE: '.savebutton',
42         COMMENTCOLOURBUTTON: '.commentcolourbutton',
43         COMMENTMENU: '.commentdrawable a',
44         ANNOTATIONCOLOURBUTTON:  '.annotationcolourbutton',
45         DELETEANNOTATIONBUTTON: '.deleteannotationbutton',
46         WARNINGMESSAGECONTAINER: '.warningmessages',
47         ICONMESSAGECONTAINER: '.infoicon',
48         UNSAVEDCHANGESDIV: '.assignfeedback_editpdf_warningmessages',
49         UNSAVEDCHANGESINPUT: 'input[name="assignfeedback_editpdf_haschanges"]',
50         STAMPSBUTTON: '.currentstampbutton',
51         USERINFOREGION: '[data-region="user-info"]',
52         ROTATELEFTBUTTON: '.rotateleftbutton',
53         ROTATERIGHTBUTTON: '.rotaterightbutton',
54         DIALOGUE: '.' + CSS.DIALOGUE
55     },
56     SELECTEDBORDERCOLOUR = 'rgba(200, 200, 255, 0.9)',
57     SELECTEDFILLCOLOUR = 'rgba(200, 200, 255, 0.5)',
58     COMMENTTEXTCOLOUR = 'rgb(51, 51, 51)',
59     COMMENTCOLOUR = {
60         'white': 'rgb(255,255,255)',
61         'yellow': 'rgb(255,236,174)',
62         'red': 'rgb(249,181,179)',
63         'green': 'rgb(214,234,178)',
64         'blue': 'rgb(203,217,237)',
65         'clear': 'rgba(255,255,255, 0)'
66     },
67     ANNOTATIONCOLOUR = {
68         'white': 'rgb(255,255,255)',
69         'yellow': 'rgb(255,207,53)',
70         'red': 'rgb(239,69,64)',
71         'green': 'rgb(152,202,62)',
72         'blue': 'rgb(125,159,211)',
73         'black': 'rgb(51,51,51)'
74     },
75     CLICKTIMEOUT = 300,
76     TOOLSELECTOR = {
77         'comment': '.commentbutton',
78         'pen': '.penbutton',
79         'line': '.linebutton',
80         'rectangle': '.rectanglebutton',
81         'oval': '.ovalbutton',
82         'stamp': '.stampbutton',
83         'select': '.selectbutton',
84         'drag': '.dragbutton',
85         'highlight': '.highlightbutton'
86     },
87     STROKEWEIGHT = 4;
88 // This file is part of Moodle - http://moodle.org/
89 //
90 // Moodle is free software: you can redistribute it and/or modify
91 // it under the terms of the GNU General Public License as published by
92 // the Free Software Foundation, either version 3 of the License, or
93 // (at your option) any later version.
94 //
95 // Moodle is distributed in the hope that it will be useful,
96 // but WITHOUT ANY WARRANTY; without even the implied warranty of
97 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
98 // GNU General Public License for more details.
99 //
100 // You should have received a copy of the GNU General Public License
101 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
103 /**
104  * Provides an in browser PDF editor.
105  *
106  * @module moodle-assignfeedback_editpdf-editor
107  */
109 /**
110  * Class representing a 2d point.
111  *
112  * @namespace M.assignfeedback_editpdf
113  * @param Number x
114  * @param Number y
115  * @class point
116  */
117 var POINT = function(x, y) {
119     /**
120      * X coordinate.
121      * @property x
122      * @type int
123      * @public
124      */
125     this.x = parseInt(x, 10);
127     /**
128      * Y coordinate.
129      * @property y
130      * @type int
131      * @public
132      */
133     this.y = parseInt(y, 10);
135     /**
136      * Clip this point to the rect
137      * @method clip
138      * @param M.assignfeedback_editpdf.point
139      * @public
140      */
141     this.clip = function(bounds) {
142         if (this.x < bounds.x) {
143             this.x = bounds.x;
144         }
145         if (this.x > (bounds.x + bounds.width)) {
146             this.x = bounds.x + bounds.width;
147         }
148         if (this.y < bounds.y) {
149             this.y = bounds.y;
150         }
151         if (this.y > (bounds.y + bounds.height)) {
152             this.y = bounds.y + bounds.height;
153         }
154         // For chaining.
155         return this;
156     };
157 };
159 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
160 M.assignfeedback_editpdf.point = POINT;
161 // This file is part of Moodle - http://moodle.org/
162 //
163 // Moodle is free software: you can redistribute it and/or modify
164 // it under the terms of the GNU General Public License as published by
165 // the Free Software Foundation, either version 3 of the License, or
166 // (at your option) any later version.
167 //
168 // Moodle is distributed in the hope that it will be useful,
169 // but WITHOUT ANY WARRANTY; without even the implied warranty of
170 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
171 // GNU General Public License for more details.
172 //
173 // You should have received a copy of the GNU General Public License
174 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
176 /**
177  * Provides an in browser PDF editor.
178  *
179  * @module moodle-assignfeedback_editpdf-editor
180  */
182 /**
183  * Class representing a 2d rect.
184  *
185  * @namespace M.assignfeedback_editpdf
186  * @param int x
187  * @param int y
188  * @param int width
189  * @param int height
190  * @class rect
191  */
192 var RECT = function(x, y, width, height) {
194     /**
195      * X coordinate.
196      * @property x
197      * @type int
198      * @public
199      */
200     this.x = x;
202     /**
203      * Y coordinate.
204      * @property y
205      * @type int
206      * @public
207      */
208     this.y = y;
210     /**
211      * Width
212      * @property width
213      * @type int
214      * @public
215      */
216     this.width = width;
218     /**
219      * Height
220      * @property height
221      * @type int
222      * @public
223      */
224     this.height = height;
226     /**
227      * Set this rect to represent the smallest possible rectangle containing this list of points.
228      * @method bounds
229      * @param M.assignfeedback_editpdf.point[]
230      * @public
231      */
232     this.bound = function(points) {
233         var minx = 0,
234             maxx = 0,
235             miny = 0,
236             maxy = 0,
237             i = 0,
238             point;
240         for (i = 0; i < points.length; i++) {
241             point = points[i];
242             if (point.x < minx || i === 0) {
243                 minx = point.x;
244             }
245             if (point.x > maxx || i === 0) {
246                 maxx = point.x;
247             }
248             if (point.y < miny || i === 0) {
249                 miny = point.y;
250             }
251             if (point.y > maxy || i === 0) {
252                 maxy = point.y;
253             }
254         }
255         this.x = minx;
256         this.y = miny;
257         this.width = maxx - minx;
258         this.height = maxy - miny;
259         // Allow chaining.
260         return this;
261     };
263     /**
264      * Checks if rect has min width.
265      * @method has_min_width
266      * @return bool true if width is more than 5px.
267      * @public
268      */
269     this.has_min_width = function() {
270         return (this.width >= 5);
271     };
273     /**
274      * Checks if rect has min height.
275      * @method has_min_height
276      * @return bool true if height is more than 5px.
277      * @public
278      */
279     this.has_min_height = function() {
280         return (this.height >= 5);
281     };
283     /**
284      * Set min. width of annotation bound.
285      * @method set_min_width
286      * @public
287      */
288     this.set_min_width = function() {
289         this.width = 5;
290     };
292     /**
293      * Set min. height of annotation bound.
294      * @method set_min_height
295      * @public
296      */
297     this.set_min_height = function() {
298         this.height = 5;
299     };
300 };
302 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
303 M.assignfeedback_editpdf.rect = RECT;
304 // This file is part of Moodle - http://moodle.org/
305 //
306 // Moodle is free software: you can redistribute it and/or modify
307 // it under the terms of the GNU General Public License as published by
308 // the Free Software Foundation, either version 3 of the License, or
309 // (at your option) any later version.
310 //
311 // Moodle is distributed in the hope that it will be useful,
312 // but WITHOUT ANY WARRANTY; without even the implied warranty of
313 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
314 // GNU General Public License for more details.
315 //
316 // You should have received a copy of the GNU General Public License
317 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
319 /**
320  * Provides an in browser PDF editor.
321  *
322  * @module moodle-assignfeedback_editpdf-editor
323  */
325 /**
326  * EDIT
327  *
328  * @namespace M.assignfeedback_editpdf
329  * @class edit
330  */
331 var EDIT = function() {
333     /**
334      * Starting point for the edit.
335      * @property start
336      * @type M.assignfeedback_editpdf.point|false
337      * @public
338      */
339     this.start = false;
341     /**
342      * Finishing point for the edit.
343      * @property end
344      * @type M.assignfeedback_editpdf.point|false
345      * @public
346      */
347     this.end = false;
349     /**
350      * Starting time for the edit.
351      * @property starttime
352      * @type int
353      * @public
354      */
355     this.starttime = 0;
357     /**
358      * Starting point for the currently selected annotation.
359      * @property annotationstart
360      * @type M.assignfeedback_editpdf.point|false
361      * @public
362      */
363     this.annotationstart = false;
365     /**
366      * The currently selected tool
367      * @property tool
368      * @type String
369      * @public
370      */
371     this.tool = "drag";
373     /**
374      * The currently comment colour
375      * @property commentcolour
376      * @type String
377      * @public
378      */
379     this.commentcolour = 'yellow';
381     /**
382      * The currently annotation colour
383      * @property annotationcolour
384      * @type String
385      * @public
386      */
387     this.annotationcolour = 'red';
389     /**
390      * The current stamp image.
391      * @property stamp
392      * @type String
393      * @public
394      */
395     this.stamp = '';
397     /**
398      * List of points the the current drawing path.
399      * @property path
400      * @type M.assignfeedback_editpdf.point[]
401      * @public
402      */
403     this.path = [];
404 };
406 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
407 M.assignfeedback_editpdf.edit = EDIT;
408 // This file is part of Moodle - http://moodle.org/
409 //
410 // Moodle is free software: you can redistribute it and/or modify
411 // it under the terms of the GNU General Public License as published by
412 // the Free Software Foundation, either version 3 of the License, or
413 // (at your option) any later version.
414 //
415 // Moodle is distributed in the hope that it will be useful,
416 // but WITHOUT ANY WARRANTY; without even the implied warranty of
417 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
418 // GNU General Public License for more details.
419 //
420 // You should have received a copy of the GNU General Public License
421 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
422 /* global SELECTOR */
424 /**
425  * Provides an in browser PDF editor.
426  *
427  * @module moodle-assignfeedback_editpdf-editor
428  */
430 /**
431  * Class representing a drawable thing which contains both Y.Nodes, and Y.Shapes.
432  *
433  * @namespace M.assignfeedback_editpdf
434  * @param M.assignfeedback_editpdf.editor editor
435  * @class drawable
436  */
437 var DRAWABLE = function(editor) {
439     /**
440      * Reference to M.assignfeedback_editpdf.editor.
441      * @property editor
442      * @type M.assignfeedback_editpdf.editor
443      * @public
444      */
445     this.editor = editor;
447     /**
448      * Array of Y.Shape
449      * @property shapes
450      * @type Y.Shape[]
451      * @public
452      */
453     this.shapes = [];
455     /**
456      * Array of Y.Node
457      * @property nodes
458      * @type Y.Node[]
459      * @public
460      */
461     this.nodes = [];
463     /**
464      * Delete the shapes from the drawable.
465      * @protected
466      * @method erase_drawable
467      */
468     this.erase = function() {
469         if (this.shapes) {
470             while (this.shapes.length > 0) {
471                 this.editor.graphic.removeShape(this.shapes.pop());
472             }
473         }
474         if (this.nodes) {
475             while (this.nodes.length > 0) {
476                 this.nodes.pop().remove();
477             }
478         }
479     };
481     /**
482      * Update the positions of all absolutely positioned nodes, when the drawing canvas is scrolled
483      * @public
484      * @method scroll_update
485      * @param scrollx int
486      * @param scrolly int
487      */
488     this.scroll_update = function(scrollx, scrolly) {
489         var i, x, y;
490         for (i = 0; i < this.nodes.length; i++) {
491             x = this.nodes[i].getData('x');
492             y = this.nodes[i].getData('y');
493             if (x !== undefined && y !== undefined) {
494                 this.nodes[i].setX(parseInt(x, 10) - scrollx);
495                 this.nodes[i].setY(parseInt(y, 10) - scrolly);
496             }
497         }
498     };
500     /**
501      * Store the initial position of the node, so it can be updated when the drawing canvas is scrolled
502      * @public
503      * @method store_position
504      * @param container
505      * @param x
506      * @param y
507      */
508     this.store_position = function(container, x, y) {
509         var drawingregion, scrollx, scrolly;
511         drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION);
512         scrollx = parseInt(drawingregion.get('scrollLeft'), 10);
513         scrolly = parseInt(drawingregion.get('scrollTop'), 10);
514         container.setData('x', x + scrollx);
515         container.setData('y', y + scrolly);
516     };
517 };
519 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
520 M.assignfeedback_editpdf.drawable = DRAWABLE;
521 // This file is part of Moodle - http://moodle.org/
522 //
523 // Moodle is free software: you can redistribute it and/or modify
524 // it under the terms of the GNU General Public License as published by
525 // the Free Software Foundation, either version 3 of the License, or
526 // (at your option) any later version.
527 //
528 // Moodle is distributed in the hope that it will be useful,
529 // but WITHOUT ANY WARRANTY; without even the implied warranty of
530 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
531 // GNU General Public License for more details.
532 //
533 // You should have received a copy of the GNU General Public License
534 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
535 /* global STROKEWEIGHT, SELECTOR, SELECTEDBORDERCOLOUR, SELECTEDFILLCOLOUR */
537 /**
538  * Provides an in browser PDF editor.
539  *
540  * @module moodle-assignfeedback_editpdf-editor
541  */
543 /**
544  * Class representing a highlight.
545  *
546  * @namespace M.assignfeedback_editpdf
547  * @class annotation
548  * @constructor
549  */
550 var ANNOTATION = function(config) {
551     ANNOTATION.superclass.constructor.apply(this, [config]);
552 };
554 ANNOTATION.NAME = "annotation";
555 ANNOTATION.ATTRS = {};
557 Y.extend(ANNOTATION, Y.Base, {
558     /**
559      * Reference to M.assignfeedback_editpdf.editor.
560      * @property editor
561      * @type M.assignfeedback_editpdf.editor
562      * @public
563      */
564     editor: null,
566     /**
567      * Grade id
568      * @property gradeid
569      * @type Int
570      * @public
571      */
572     gradeid: 0,
574     /**
575      * Comment page number
576      * @property pageno
577      * @type Int
578      * @public
579      */
580     pageno: 0,
582     /**
583      * X position
584      * @property x
585      * @type Int
586      * @public
587      */
588     x: 0,
590     /**
591      * Y position
592      * @property y
593      * @type Int
594      * @public
595      */
596     y: 0,
598     /**
599      * Ending x position
600      * @property endx
601      * @type Int
602      * @public
603      */
604     endx: 0,
606     /**
607      * Ending y position
608      * @property endy
609      * @type Int
610      * @public
611      */
612     endy: 0,
614     /**
615      * Path
616      * @property path
617      * @type String - list of points like x1,y1:x2,y2
618      * @public
619      */
620     path: '',
622     /**
623      * Tool.
624      * @property type
625      * @type String
626      * @public
627      */
628     type: 'rect',
630     /**
631      * Annotation colour.
632      * @property colour
633      * @type String
634      * @public
635      */
636     colour: 'red',
638     /**
639      * Reference to M.assignfeedback_editpdf.drawable
640      * @property drawable
641      * @type M.assignfeedback_editpdf.drawable
642      * @public
643      */
644     drawable: false,
646     /**
647      * Initialise the annotation.
648      *
649      * @method initializer
650      * @return void
651      */
652     initializer: function(config) {
653         this.editor = config.editor || null;
654         this.gradeid = parseInt(config.gradeid, 10) || 0;
655         this.pageno = parseInt(config.pageno, 10) || 0;
656         this.x = parseInt(config.x, 10) || 0;
657         this.y = parseInt(config.y, 10) || 0;
658         this.endx = parseInt(config.endx, 10) || 0;
659         this.endy = parseInt(config.endy, 10) || 0;
660         this.path = config.path || '';
661         this.type = config.type || 'rect';
662         this.colour = config.colour || 'red';
663         this.drawable = false;
664     },
666     /**
667      * Clean a comment record, returning an oject with only fields that are valid.
668      * @public
669      * @method clean
670      * @return {}
671      */
672     clean: function() {
673         return {
674             gradeid: this.gradeid,
675             x: parseInt(this.x, 10),
676             y: parseInt(this.y, 10),
677             endx: parseInt(this.endx, 10),
678             endy: parseInt(this.endy, 10),
679             type: this.type,
680             path: this.path,
681             pageno: this.pageno,
682             colour: this.colour
683         };
684     },
686     /**
687      * Draw a selection around this annotation if it is selected.
688      * @public
689      * @method draw_highlight
690      * @return M.assignfeedback_editpdf.drawable
691      */
692     draw_highlight: function() {
693         var bounds,
694             drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION),
695             offsetcanvas = this.editor.get_dialogue_element(SELECTOR.DRAWINGCANVAS).getXY(),
696             shape;
698         if (this.editor.currentannotation === this) {
699             // Draw a highlight around the annotation.
700             bounds = new M.assignfeedback_editpdf.rect();
701             bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y),
702                           new M.assignfeedback_editpdf.point(this.endx, this.endy)]);
704             shape = this.editor.graphic.addShape({
705                 type: Y.Rect,
706                 width: bounds.width,
707                 height: bounds.height,
708                 stroke: {
709                    weight: STROKEWEIGHT,
710                    color: SELECTEDBORDERCOLOUR
711                 },
712                 fill: {
713                    color: SELECTEDFILLCOLOUR
714                 },
715                 x: bounds.x,
716                 y: bounds.y
717             });
718             this.drawable.shapes.push(shape);
720             // Add a delete X to the annotation.
721             var deleteicon = Y.Node.create('<img src="' + M.util.image_url('trash', 'assignfeedback_editpdf') + '"/>'),
722                 deletelink = Y.Node.create('<a href="#" role="button"></a>');
724             deleteicon.setAttrs({
725                 'alt': M.util.get_string('deleteannotation', 'assignfeedback_editpdf')
726             });
727             deleteicon.setStyles({
728                 'backgroundColor': 'white'
729             });
730             deletelink.addClass('deleteannotationbutton');
731             deletelink.append(deleteicon);
733             drawingregion.append(deletelink);
734             deletelink.setData('annotation', this);
735             deletelink.setStyle('zIndex', '200');
737             deletelink.on('click', this.remove, this);
738             deletelink.on('key', this.remove, 'space,enter', this);
740             deletelink.setX(offsetcanvas[0] + bounds.x + bounds.width - 18);
741             deletelink.setY(offsetcanvas[1] + bounds.y + 6);
742             this.drawable.nodes.push(deletelink);
743         }
744         return this.drawable;
745     },
747     /**
748      * Draw an annotation
749      * @public
750      * @method draw
751      * @return M.assignfeedback_editpdf.drawable|false
752      */
753     draw: function() {
754         // Should be overridden by the subclass.
755         this.draw_highlight();
756         return this.drawable;
757     },
759     /**
760      * Delete an annotation
761      * @protected
762      * @method remove
763      * @param event
764      */
765     remove: function(e) {
766         var annotations,
767             i;
769         e.preventDefault();
771         annotations = this.editor.pages[this.editor.currentpage].annotations;
772         for (i = 0; i < annotations.length; i++) {
773             if (annotations[i] === this) {
774                 annotations.splice(i, 1);
775                 if (this.drawable) {
776                     this.drawable.erase();
777                 }
778                 this.editor.currentannotation = false;
779                 this.editor.save_current_page();
780                 return;
781             }
782         }
783     },
785     /**
786      * Move an annotation to a new location.
787      * @public
788      * @param int newx
789      * @param int newy
790      * @method move_annotation
791      */
792     move: function(newx, newy) {
793         var diffx = newx - this.x,
794             diffy = newy - this.y,
795             newpath, oldpath, xy,
796             x, y;
798         this.x += diffx;
799         this.y += diffy;
800         this.endx += diffx;
801         this.endy += diffy;
803         if (this.path) {
804             newpath = [];
805             oldpath = this.path.split(':');
806             Y.each(oldpath, function(position) {
807                 xy = position.split(',');
808                 x = parseInt(xy[0], 10);
809                 y = parseInt(xy[1], 10);
810                 newpath.push((x + diffx) + ',' + (y + diffy));
811             });
813             this.path = newpath.join(':');
815         }
816         if (this.drawable) {
817             this.drawable.erase();
818         }
819         this.editor.drawables.push(this.draw());
820     },
822     /**
823      * Draw the in progress edit.
824      *
825      * @public
826      * @method draw_current_edit
827      * @param M.assignfeedback_editpdf.edit edit
828      */
829     draw_current_edit: function(edit) {
830         var noop = edit && false;
831         // Override me please.
832         return noop;
833     },
835     /**
836      * Promote the current edit to a real annotation.
837      *
838      * @public
839      * @method init_from_edit
840      * @param M.assignfeedback_editpdf.edit edit
841      * @return bool if width/height is more than min. required.
842      */
843     init_from_edit: function(edit) {
844         var bounds = new M.assignfeedback_editpdf.rect();
845         bounds.bound([edit.start, edit.end]);
847         this.gradeid = this.editor.get('gradeid');
848         this.pageno = this.editor.currentpage;
849         this.x = bounds.x;
850         this.y = bounds.y;
851         this.endx = bounds.x + bounds.width;
852         this.endy = bounds.y + bounds.height;
853         this.colour = edit.annotationcolour;
854         this.path = '';
855         return (bounds.has_min_width() && bounds.has_min_height());
856     }
858 });
860 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
861 M.assignfeedback_editpdf.annotation = ANNOTATION;
862 // This file is part of Moodle - http://moodle.org/
863 //
864 // Moodle is free software: you can redistribute it and/or modify
865 // it under the terms of the GNU General Public License as published by
866 // the Free Software Foundation, either version 3 of the License, or
867 // (at your option) any later version.
868 //
869 // Moodle is distributed in the hope that it will be useful,
870 // but WITHOUT ANY WARRANTY; without even the implied warranty of
871 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
872 // GNU General Public License for more details.
873 //
874 // You should have received a copy of the GNU General Public License
875 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
876 /* global STROKEWEIGHT, ANNOTATIONCOLOUR */
878 /**
879  * Provides an in browser PDF editor.
880  *
881  * @module moodle-assignfeedback_editpdf-editor
882  */
884 /**
885  * Class representing a line.
886  *
887  * @namespace M.assignfeedback_editpdf
888  * @class annotationline
889  * @extends M.assignfeedback_editpdf.annotation
890  */
891 var ANNOTATIONLINE = function(config) {
892     ANNOTATIONLINE.superclass.constructor.apply(this, [config]);
893 };
895 ANNOTATIONLINE.NAME = "annotationline";
896 ANNOTATIONLINE.ATTRS = {};
898 Y.extend(ANNOTATIONLINE, M.assignfeedback_editpdf.annotation, {
899     /**
900      * Draw a line annotation
901      * @protected
902      * @method draw
903      * @return M.assignfeedback_editpdf.drawable
904      */
905     draw: function() {
906         var drawable,
907             shape;
909         drawable = new M.assignfeedback_editpdf.drawable(this.editor);
911         shape = this.editor.graphic.addShape({
912         type: Y.Path,
913             fill: false,
914             stroke: {
915                 weight: STROKEWEIGHT,
916                 color: ANNOTATIONCOLOUR[this.colour]
917             }
918         });
920         shape.moveTo(this.x, this.y);
921         shape.lineTo(this.endx, this.endy);
922         shape.end();
923         drawable.shapes.push(shape);
924         this.drawable = drawable;
926         return ANNOTATIONLINE.superclass.draw.apply(this);
927     },
929     /**
930      * Draw the in progress edit.
931      *
932      * @public
933      * @method draw_current_edit
934      * @param M.assignfeedback_editpdf.edit edit
935      */
936     draw_current_edit: function(edit) {
937         var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
938             shape;
940         shape = this.editor.graphic.addShape({
941            type: Y.Path,
942             fill: false,
943             stroke: {
944                 weight: STROKEWEIGHT,
945                 color: ANNOTATIONCOLOUR[edit.annotationcolour]
946             }
947         });
949         shape.moveTo(edit.start.x, edit.start.y);
950         shape.lineTo(edit.end.x, edit.end.y);
951         shape.end();
953         drawable.shapes.push(shape);
955         return drawable;
956     },
958     /**
959      * Promote the current edit to a real annotation.
960      *
961      * @public
962      * @method init_from_edit
963      * @param M.assignfeedback_editpdf.edit edit
964      * @return bool true if line bound is more than min width/height, else false.
965      */
966     init_from_edit: function(edit) {
967         this.gradeid = this.editor.get('gradeid');
968         this.pageno = this.editor.currentpage;
969         this.x = edit.start.x;
970         this.y = edit.start.y;
971         this.endx = edit.end.x;
972         this.endy = edit.end.y;
973         this.colour = edit.annotationcolour;
974         this.path = '';
976         return !(((this.endx - this.x) === 0) && ((this.endy - this.y) === 0));
977     }
979 });
981 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
982 M.assignfeedback_editpdf.annotationline = ANNOTATIONLINE;
983 // This file is part of Moodle - http://moodle.org/
984 //
985 // Moodle is free software: you can redistribute it and/or modify
986 // it under the terms of the GNU General Public License as published by
987 // the Free Software Foundation, either version 3 of the License, or
988 // (at your option) any later version.
989 //
990 // Moodle is distributed in the hope that it will be useful,
991 // but WITHOUT ANY WARRANTY; without even the implied warranty of
992 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
993 // GNU General Public License for more details.
994 //
995 // You should have received a copy of the GNU General Public License
996 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
997 /* global STROKEWEIGHT, ANNOTATIONCOLOUR */
999 /**
1000  * Provides an in browser PDF editor.
1001  *
1002  * @module moodle-assignfeedback_editpdf-editor
1003  */
1005 /**
1006  * Class representing a rectangle.
1007  *
1008  * @namespace M.assignfeedback_editpdf
1009  * @class annotationrectangle
1010  * @extends M.assignfeedback_editpdf.annotation
1011  */
1012 var ANNOTATIONRECTANGLE = function(config) {
1013     ANNOTATIONRECTANGLE.superclass.constructor.apply(this, [config]);
1014 };
1016 ANNOTATIONRECTANGLE.NAME = "annotationrectangle";
1017 ANNOTATIONRECTANGLE.ATTRS = {};
1019 Y.extend(ANNOTATIONRECTANGLE, M.assignfeedback_editpdf.annotation, {
1020     /**
1021      * Draw a rectangle annotation
1022      * @protected
1023      * @method draw
1024      * @return M.assignfeedback_editpdf.drawable
1025      */
1026     draw: function() {
1027         var drawable,
1028             bounds,
1029             shape;
1031         drawable = new M.assignfeedback_editpdf.drawable(this.editor);
1033         bounds = new M.assignfeedback_editpdf.rect();
1034         bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y),
1035                       new M.assignfeedback_editpdf.point(this.endx, this.endy)]);
1037         shape = this.editor.graphic.addShape({
1038             type: Y.Rect,
1039             width: bounds.width,
1040             height: bounds.height,
1041             stroke: {
1042                weight: STROKEWEIGHT,
1043                color: ANNOTATIONCOLOUR[this.colour]
1044             },
1045             x: bounds.x,
1046             y: bounds.y
1047         });
1048         drawable.shapes.push(shape);
1049         this.drawable = drawable;
1051         return ANNOTATIONRECTANGLE.superclass.draw.apply(this);
1052     },
1054     /**
1055      * Draw the in progress edit.
1056      *
1057      * @public
1058      * @method draw_current_edit
1059      * @param M.assignfeedback_editpdf.edit edit
1060      */
1061     draw_current_edit: function(edit) {
1062         var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
1063             shape,
1064             bounds;
1066         bounds = new M.assignfeedback_editpdf.rect();
1067         bounds.bound([new M.assignfeedback_editpdf.point(edit.start.x, edit.start.y),
1068                       new M.assignfeedback_editpdf.point(edit.end.x, edit.end.y)]);
1070         // Set min. width and height of rectangle.
1071         if (!bounds.has_min_width()) {
1072             bounds.set_min_width();
1073         }
1074         if (!bounds.has_min_height()) {
1075             bounds.set_min_height();
1076         }
1078         shape = this.editor.graphic.addShape({
1079             type: Y.Rect,
1080             width: bounds.width,
1081             height: bounds.height,
1082             stroke: {
1083                weight: STROKEWEIGHT,
1084                color: ANNOTATIONCOLOUR[edit.annotationcolour]
1085             },
1086             x: bounds.x,
1087             y: bounds.y
1088         });
1090         drawable.shapes.push(shape);
1092         return drawable;
1093     }
1094 });
1096 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1097 M.assignfeedback_editpdf.annotationrectangle = ANNOTATIONRECTANGLE;
1098 // This file is part of Moodle - http://moodle.org/
1099 //
1100 // Moodle is free software: you can redistribute it and/or modify
1101 // it under the terms of the GNU General Public License as published by
1102 // the Free Software Foundation, either version 3 of the License, or
1103 // (at your option) any later version.
1104 //
1105 // Moodle is distributed in the hope that it will be useful,
1106 // but WITHOUT ANY WARRANTY; without even the implied warranty of
1107 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1108 // GNU General Public License for more details.
1109 //
1110 // You should have received a copy of the GNU General Public License
1111 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
1112 /* global STROKEWEIGHT, ANNOTATIONCOLOUR */
1114 /**
1115  * Provides an in browser PDF editor.
1116  *
1117  * @module moodle-assignfeedback_editpdf-editor
1118  */
1120 /**
1121  * Class representing a oval.
1122  *
1123  * @namespace M.assignfeedback_editpdf
1124  * @class annotationoval
1125  * @extends M.assignfeedback_editpdf.annotation
1126  */
1127 var ANNOTATIONOVAL = function(config) {
1128     ANNOTATIONOVAL.superclass.constructor.apply(this, [config]);
1129 };
1131 ANNOTATIONOVAL.NAME = "annotationoval";
1132 ANNOTATIONOVAL.ATTRS = {};
1134 Y.extend(ANNOTATIONOVAL, M.assignfeedback_editpdf.annotation, {
1135     /**
1136      * Draw a oval annotation
1137      * @protected
1138      * @method draw
1139      * @return M.assignfeedback_editpdf.drawable
1140      */
1141     draw: function() {
1142         var drawable,
1143             bounds,
1144             shape;
1146         drawable = new M.assignfeedback_editpdf.drawable(this.editor);
1148         bounds = new M.assignfeedback_editpdf.rect();
1149         bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y),
1150                       new M.assignfeedback_editpdf.point(this.endx, this.endy)]);
1152         shape = this.editor.graphic.addShape({
1153             type: Y.Ellipse,
1154             width: bounds.width,
1155             height: bounds.height,
1156             stroke: {
1157                weight: STROKEWEIGHT,
1158                color: ANNOTATIONCOLOUR[this.colour]
1159             },
1160             x: bounds.x,
1161             y: bounds.y
1162         });
1163         drawable.shapes.push(shape);
1164         this.drawable = drawable;
1166         return ANNOTATIONOVAL.superclass.draw.apply(this);
1167     },
1169     /**
1170      * Draw the in progress edit.
1171      *
1172      * @public
1173      * @method draw_current_edit
1174      * @param M.assignfeedback_editpdf.edit edit
1175      */
1176     draw_current_edit: function(edit) {
1177         var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
1178             shape,
1179             bounds;
1181         bounds = new M.assignfeedback_editpdf.rect();
1182         bounds.bound([new M.assignfeedback_editpdf.point(edit.start.x, edit.start.y),
1183                       new M.assignfeedback_editpdf.point(edit.end.x, edit.end.y)]);
1185         // Set min. width and height of oval.
1186         if (!bounds.has_min_width()) {
1187             bounds.set_min_width();
1188         }
1189         if (!bounds.has_min_height()) {
1190             bounds.set_min_height();
1191         }
1193         shape = this.editor.graphic.addShape({
1194             type: Y.Ellipse,
1195             width: bounds.width,
1196             height: bounds.height,
1197             stroke: {
1198                weight: STROKEWEIGHT,
1199                color: ANNOTATIONCOLOUR[edit.annotationcolour]
1200             },
1201             x: bounds.x,
1202             y: bounds.y
1203         });
1205         drawable.shapes.push(shape);
1207         return drawable;
1208     }
1209 });
1211 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1212 M.assignfeedback_editpdf.annotationoval = ANNOTATIONOVAL;
1213 // This file is part of Moodle - http://moodle.org/
1214 //
1215 // Moodle is free software: you can redistribute it and/or modify
1216 // it under the terms of the GNU General Public License as published by
1217 // the Free Software Foundation, either version 3 of the License, or
1218 // (at your option) any later version.
1219 //
1220 // Moodle is distributed in the hope that it will be useful,
1221 // but WITHOUT ANY WARRANTY; without even the implied warranty of
1222 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1223 // GNU General Public License for more details.
1224 //
1225 // You should have received a copy of the GNU General Public License
1226 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
1227 /* global STROKEWEIGHT, ANNOTATIONCOLOUR */
1229 /**
1230  * Provides an in browser PDF editor.
1231  *
1232  * @module moodle-assignfeedback_editpdf-editor
1233  */
1235 /**
1236  * Class representing a pen.
1237  *
1238  * @namespace M.assignfeedback_editpdf
1239  * @class annotationpen
1240  * @extends M.assignfeedback_editpdf.annotation
1241  */
1242 var ANNOTATIONPEN = function(config) {
1243     ANNOTATIONPEN.superclass.constructor.apply(this, [config]);
1244 };
1246 ANNOTATIONPEN.NAME = "annotationpen";
1247 ANNOTATIONPEN.ATTRS = {};
1249 Y.extend(ANNOTATIONPEN, M.assignfeedback_editpdf.annotation, {
1250     /**
1251      * Draw a pen annotation
1252      * @protected
1253      * @method draw
1254      * @return M.assignfeedback_editpdf.drawable
1255      */
1256     draw: function() {
1257         var drawable,
1258             shape,
1259             first,
1260             positions,
1261             xy;
1263         drawable = new M.assignfeedback_editpdf.drawable(this.editor);
1265         shape = this.editor.graphic.addShape({
1266            type: Y.Path,
1267             fill: false,
1268             stroke: {
1269                 weight: STROKEWEIGHT,
1270                 color: ANNOTATIONCOLOUR[this.colour]
1271             }
1272         });
1274         first = true;
1275         // Recreate the pen path array.
1276         positions = this.path.split(':');
1277         // Redraw all the lines.
1278         Y.each(positions, function(position) {
1279             xy = position.split(',');
1280             if (first) {
1281                 shape.moveTo(xy[0], xy[1]);
1282                 first = false;
1283             } else {
1284                 shape.lineTo(xy[0], xy[1]);
1285             }
1286         }, this);
1288         shape.end();
1290         drawable.shapes.push(shape);
1291         this.drawable = drawable;
1293         return ANNOTATIONPEN.superclass.draw.apply(this);
1294     },
1296     /**
1297      * Draw the in progress edit.
1298      *
1299      * @public
1300      * @method draw_current_edit
1301      * @param M.assignfeedback_editpdf.edit edit
1302      */
1303     draw_current_edit: function(edit) {
1304         var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
1305             shape,
1306             first;
1308         shape = this.editor.graphic.addShape({
1309            type: Y.Path,
1310             fill: false,
1311             stroke: {
1312                 weight: STROKEWEIGHT,
1313                 color: ANNOTATIONCOLOUR[edit.annotationcolour]
1314             }
1315         });
1317         first = true;
1318         // Recreate the pen path array.
1319         // Redraw all the lines.
1320         Y.each(edit.path, function(position) {
1321             if (first) {
1322                 shape.moveTo(position.x, position.y);
1323                 first = false;
1324             } else {
1325                 shape.lineTo(position.x, position.y);
1326             }
1327         }, this);
1329         shape.end();
1331         drawable.shapes.push(shape);
1333         return drawable;
1334     },
1337     /**
1338      * Promote the current edit to a real annotation.
1339      *
1340      * @public
1341      * @method init_from_edit
1342      * @param M.assignfeedback_editpdf.edit edit
1343      * @return bool true if pen bound is more than min width/height, else false.
1344      */
1345     init_from_edit: function(edit) {
1346         var bounds = new M.assignfeedback_editpdf.rect(),
1347             pathlist = [],
1348             i = 0;
1350         // This will get the boundaries of all points in the path.
1351         bounds.bound(edit.path);
1353         for (i = 0; i < edit.path.length; i++) {
1354             pathlist.push(parseInt(edit.path[i].x, 10) + ',' + parseInt(edit.path[i].y, 10));
1355         }
1357         this.gradeid = this.editor.get('gradeid');
1358         this.pageno = this.editor.currentpage;
1359         this.x = bounds.x;
1360         this.y = bounds.y;
1361         this.endx = bounds.x + bounds.width;
1362         this.endy = bounds.y + bounds.height;
1363         this.colour = edit.annotationcolour;
1364         this.path = pathlist.join(':');
1366         return (bounds.has_min_width() || bounds.has_min_height());
1367     }
1370 });
1372 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1373 M.assignfeedback_editpdf.annotationpen = ANNOTATIONPEN;
1374 // This file is part of Moodle - http://moodle.org/
1375 //
1376 // Moodle is free software: you can redistribute it and/or modify
1377 // it under the terms of the GNU General Public License as published by
1378 // the Free Software Foundation, either version 3 of the License, or
1379 // (at your option) any later version.
1380 //
1381 // Moodle is distributed in the hope that it will be useful,
1382 // but WITHOUT ANY WARRANTY; without even the implied warranty of
1383 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1384 // GNU General Public License for more details.
1385 //
1386 // You should have received a copy of the GNU General Public License
1387 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
1388 /* global ANNOTATIONCOLOUR */
1390 /**
1391  * Provides an in browser PDF editor.
1392  *
1393  * @module moodle-assignfeedback_editpdf-editor
1394  */
1396 /**
1397  * Class representing a highlight.
1398  *
1399  * @namespace M.assignfeedback_editpdf
1400  * @class annotationhighlight
1401  * @extends M.assignfeedback_editpdf.annotation
1402  * @module moodle-assignfeedback_editpdf-editor
1403  */
1404 var ANNOTATIONHIGHLIGHT = function(config) {
1405     ANNOTATIONHIGHLIGHT.superclass.constructor.apply(this, [config]);
1406 };
1408 ANNOTATIONHIGHLIGHT.NAME = "annotationhighlight";
1409 ANNOTATIONHIGHLIGHT.ATTRS = {};
1411 Y.extend(ANNOTATIONHIGHLIGHT, M.assignfeedback_editpdf.annotation, {
1412     /**
1413      * Draw a highlight annotation
1414      * @protected
1415      * @method draw
1416      * @return M.assignfeedback_editpdf.drawable
1417      */
1418     draw: function() {
1419         var drawable,
1420             shape,
1421             bounds,
1422             highlightcolour;
1424         drawable = new M.assignfeedback_editpdf.drawable(this.editor);
1425         bounds = new M.assignfeedback_editpdf.rect();
1426         bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y),
1427                       new M.assignfeedback_editpdf.point(this.endx, this.endy)]);
1429         highlightcolour = ANNOTATIONCOLOUR[this.colour];
1431         // Add an alpha channel to the rgb colour.
1433         highlightcolour = highlightcolour.replace('rgb', 'rgba');
1434         highlightcolour = highlightcolour.replace(')', ',0.5)');
1436         shape = this.editor.graphic.addShape({
1437             type: Y.Rect,
1438             width: bounds.width,
1439             height: bounds.height,
1440             stroke: false,
1441             fill: {
1442                 color: highlightcolour
1443             },
1444             x: bounds.x,
1445             y: bounds.y
1446         });
1448         drawable.shapes.push(shape);
1449         this.drawable = drawable;
1451         return ANNOTATIONHIGHLIGHT.superclass.draw.apply(this);
1452     },
1454     /**
1455      * Draw the in progress edit.
1456      *
1457      * @public
1458      * @method draw_current_edit
1459      * @param M.assignfeedback_editpdf.edit edit
1460      */
1461     draw_current_edit: function(edit) {
1462         var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
1463             shape,
1464             bounds,
1465             highlightcolour;
1467         bounds = new M.assignfeedback_editpdf.rect();
1468         bounds.bound([new M.assignfeedback_editpdf.point(edit.start.x, edit.start.y),
1469                       new M.assignfeedback_editpdf.point(edit.end.x, edit.end.y)]);
1471         // Set min. width of highlight.
1472         if (!bounds.has_min_width()) {
1473             bounds.set_min_width();
1474         }
1476         highlightcolour = ANNOTATIONCOLOUR[edit.annotationcolour];
1477         // Add an alpha channel to the rgb colour.
1479         highlightcolour = highlightcolour.replace('rgb', 'rgba');
1480         highlightcolour = highlightcolour.replace(')', ',0.5)');
1482         // We will draw a box with the current background colour.
1483         shape = this.editor.graphic.addShape({
1484             type: Y.Rect,
1485             width: bounds.width,
1486             height: 20,
1487             stroke: false,
1488             fill: {
1489                color: highlightcolour
1490             },
1491             x: bounds.x,
1492             y: edit.start.y - 10
1493         });
1495         drawable.shapes.push(shape);
1497         return drawable;
1498     },
1500     /**
1501      * Promote the current edit to a real annotation.
1502      *
1503      * @public
1504      * @method init_from_edit
1505      * @param M.assignfeedback_editpdf.edit edit
1506      * @return bool true if highlight bound is more than min width/height, else false.
1507      */
1508     init_from_edit: function(edit) {
1509         var bounds = new M.assignfeedback_editpdf.rect();
1510         bounds.bound([edit.start, edit.end]);
1512         this.gradeid = this.editor.get('gradeid');
1513         this.pageno = this.editor.currentpage;
1514         this.x = bounds.x;
1515         this.y = edit.start.y - 10;
1516         this.endx = bounds.x + bounds.width;
1517         this.endy = edit.start.y + 10;
1518         this.colour = edit.annotationcolour;
1519         this.page = '';
1521         return (bounds.has_min_width());
1522     }
1524 });
1526 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1527 M.assignfeedback_editpdf.annotationhighlight = ANNOTATIONHIGHLIGHT;
1528 // This file is part of Moodle - http://moodle.org/
1529 //
1530 // Moodle is free software: you can redistribute it and/or modify
1531 // it under the terms of the GNU General Public License as published by
1532 // the Free Software Foundation, either version 3 of the License, or
1533 // (at your option) any later version.
1534 //
1535 // Moodle is distributed in the hope that it will be useful,
1536 // but WITHOUT ANY WARRANTY; without even the implied warranty of
1537 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1538 // GNU General Public License for more details.
1539 //
1540 // You should have received a copy of the GNU General Public License
1541 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
1542 /* global SELECTOR */
1544 /**
1545  * Provides an in browser PDF editor.
1546  *
1547  * @module moodle-assignfeedback_editpdf-editor
1548  */
1550 /**
1551  * Class representing a stamp.
1552  *
1553  * @namespace M.assignfeedback_editpdf
1554  * @class annotationstamp
1555  * @extends M.assignfeedback_editpdf.annotation
1556  */
1557 var ANNOTATIONSTAMP = function(config) {
1558     ANNOTATIONSTAMP.superclass.constructor.apply(this, [config]);
1559 };
1561 ANNOTATIONSTAMP.NAME = "annotationstamp";
1562 ANNOTATIONSTAMP.ATTRS = {};
1564 Y.extend(ANNOTATIONSTAMP, M.assignfeedback_editpdf.annotation, {
1565     /**
1566      * Draw a stamp annotation
1567      * @protected
1568      * @method draw
1569      * @return M.assignfeedback_editpdf.drawable
1570      */
1571     draw: function() {
1572         var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
1573             drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION),
1574             node,
1575             position;
1577         position = this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(this.x, this.y));
1578         node = Y.Node.create('<div/>');
1579         node.setStyles({
1580             'position': 'absolute',
1581             'display': 'inline-block',
1582             'backgroundImage': 'url(' + this.editor.get_stamp_image_url(this.path) + ')',
1583             'width': (this.endx - this.x),
1584             'height': (this.endy - this.y),
1585             'backgroundSize': '100% 100%',
1586             'zIndex': 50
1587         });
1589         drawingregion.append(node);
1590         node.setX(position.x);
1591         node.setY(position.y);
1592         drawable.store_position(node, position.x, position.y);
1594         // Bind events only when editing.
1595         if (!this.editor.get('readonly')) {
1596             // Pass through the event handlers on the div.
1597             node.on('gesturemovestart', this.editor.edit_start, null, this.editor);
1598             node.on('gesturemove', this.editor.edit_move, null, this.editor);
1599             node.on('gesturemoveend', this.editor.edit_end, null, this.editor);
1600         }
1602         drawable.nodes.push(node);
1604         this.drawable = drawable;
1605         return ANNOTATIONSTAMP.superclass.draw.apply(this);
1606     },
1608     /**
1609      * Draw the in progress edit.
1610      *
1611      * @public
1612      * @method draw_current_edit
1613      * @param M.assignfeedback_editpdf.edit edit
1614      */
1615     draw_current_edit: function(edit) {
1616         var bounds = new M.assignfeedback_editpdf.rect(),
1617             drawable = new M.assignfeedback_editpdf.drawable(this.editor),
1618             drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION),
1619             node,
1620             position;
1622         bounds.bound([edit.start, edit.end]);
1623         position = this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(bounds.x, bounds.y));
1625         node = Y.Node.create('<div/>');
1626         node.setStyles({
1627             'position': 'absolute',
1628             'display': 'inline-block',
1629             'backgroundImage': 'url(' + this.editor.get_stamp_image_url(edit.stamp) + ')',
1630             'width': bounds.width,
1631             'height': bounds.height,
1632             'backgroundSize': '100% 100%',
1633             'zIndex': 50
1634         });
1636         drawingregion.append(node);
1637         node.setX(position.x);
1638         node.setY(position.y);
1639         drawable.store_position(node, position.x, position.y);
1641         drawable.nodes.push(node);
1643         return drawable;
1644     },
1646     /**
1647      * Promote the current edit to a real annotation.
1648      *
1649      * @public
1650      * @method init_from_edit
1651      * @param M.assignfeedback_editpdf.edit edit
1652      * @return bool if width/height is more than min. required.
1653      */
1654     init_from_edit: function(edit) {
1655         var bounds = new M.assignfeedback_editpdf.rect();
1656         bounds.bound([edit.start, edit.end]);
1658         if (bounds.width < 40) {
1659             bounds.width = 40;
1660         }
1661         if (bounds.height < 40) {
1662             bounds.height = 40;
1663         }
1664         this.gradeid = this.editor.get('gradeid');
1665         this.pageno = this.editor.currentpage;
1666         this.x = bounds.x;
1667         this.y = bounds.y;
1668         this.endx = bounds.x + bounds.width;
1669         this.endy = bounds.y + bounds.height;
1670         this.colour = edit.annotationcolour;
1671         this.path = edit.stamp;
1673         // Min width and height is always more than 40px.
1674         return true;
1675     },
1677     /**
1678      * Move an annotation to a new location.
1679      * @public
1680      * @param int newx
1681      * @param int newy
1682      * @method move_annotation
1683      */
1684     move: function(newx, newy) {
1685         var diffx = newx - this.x,
1686             diffy = newy - this.y;
1688         this.x += diffx;
1689         this.y += diffy;
1690         this.endx += diffx;
1691         this.endy += diffy;
1693         if (this.drawable) {
1694             this.drawable.erase();
1695         }
1696         this.editor.drawables.push(this.draw());
1697     }
1699 });
1701 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1702 M.assignfeedback_editpdf.annotationstamp = ANNOTATIONSTAMP;
1703 var DROPDOWN_NAME = "Dropdown menu",
1704     DROPDOWN;
1706 /**
1707  * Provides an in browser PDF editor.
1708  *
1709  * @module moodle-assignfeedback_editpdf-editor
1710  */
1712 /**
1713  * This is a drop down list of buttons triggered (and aligned to) a button.
1714  *
1715  * @namespace M.assignfeedback_editpdf
1716  * @class dropdown
1717  * @constructor
1718  * @extends M.core.dialogue
1719  */
1720 DROPDOWN = function(config) {
1721     config.draggable = false;
1722     config.centered = false;
1723     config.width = 'auto';
1724     config.visible = false;
1725     config.footerContent = '';
1726     DROPDOWN.superclass.constructor.apply(this, [config]);
1727 };
1729 Y.extend(DROPDOWN, M.core.dialogue, {
1730     /**
1731      * Initialise the menu.
1732      *
1733      * @method initializer
1734      * @return void
1735      */
1736     initializer: function(config) {
1737         var button, body, headertext, bb;
1738         DROPDOWN.superclass.initializer.call(this, config);
1740         bb = this.get('boundingBox');
1741         bb.addClass('assignfeedback_editpdf_dropdown');
1743         // Align the menu to the button that opens it.
1744         button = this.get('buttonNode');
1746         // Close the menu when clicked outside (excluding the button that opened the menu).
1747         body = this.bodyNode;
1749         headertext = Y.Node.create('<h3/>');
1750         headertext.addClass('accesshide');
1751         headertext.setHTML(this.get('headerText'));
1752         body.prepend(headertext);
1754         body.on('clickoutside', function(e) {
1755             if (this.get('visible')) {
1756                 // Note: we need to compare ids because for some reason - sometimes button is an Object, not a Y.Node.
1757                 if (e.target.get('id') !== button.get('id') && e.target.ancestor().get('id') !== button.get('id')) {
1758                     e.preventDefault();
1759                     this.hide();
1760                 }
1761             }
1762         }, this);
1764         button.on('click', function(e) {
1765             e.preventDefault(); this.show();
1766         }, this);
1767         button.on('key', this.show, 'enter,space', this);
1768     },
1770     /**
1771      * Override the show method to align to the button.
1772      *
1773      * @method show
1774      * @return void
1775      */
1776     show: function() {
1777         var button = this.get('buttonNode'),
1778             result = DROPDOWN.superclass.show.call(this);
1779         this.align(button, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
1781         return result;
1782     }
1783 }, {
1784     NAME: DROPDOWN_NAME,
1785     ATTRS: {
1786         /**
1787          * The header for the drop down (only accessible to screen readers).
1788          *
1789          * @attribute headerText
1790          * @type String
1791          * @default ''
1792          */
1793         headerText: {
1794             value: ''
1795         },
1797         /**
1798          * The button used to show/hide this drop down menu.
1799          *
1800          * @attribute buttonNode
1801          * @type Y.Node
1802          * @default null
1803          */
1804         buttonNode: {
1805             value: null
1806         }
1807     }
1808 });
1810 Y.Base.modifyAttrs(DROPDOWN, {
1811     /**
1812      * Whether the widget should be modal or not.
1813      *
1814      * Moodle override: We override this for commentsearch to force it always false.
1815      *
1816      * @attribute Modal
1817      * @type Boolean
1818      * @default false
1819      */
1820     modal: {
1821         getter: function() {
1822             return false;
1823         }
1824     }
1825 });
1827 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1828 M.assignfeedback_editpdf.dropdown = DROPDOWN;
1829 var COLOURPICKER_NAME = "Colourpicker",
1830     COLOURPICKER;
1832 /**
1833  * Provides an in browser PDF editor.
1834  *
1835  * @module moodle-assignfeedback_editpdf-editor
1836  */
1838 /**
1839  * COLOURPICKER
1840  * This is a drop down list of colours.
1841  *
1842  * @namespace M.assignfeedback_editpdf
1843  * @class colourpicker
1844  * @constructor
1845  * @extends M.assignfeedback_editpdf.dropdown
1846  */
1847 COLOURPICKER = function(config) {
1848     COLOURPICKER.superclass.constructor.apply(this, [config]);
1849 };
1851 Y.extend(COLOURPICKER, M.assignfeedback_editpdf.dropdown, {
1853     /**
1854      * Initialise the menu.
1855      *
1856      * @method initializer
1857      * @return void
1858      */
1859     initializer: function(config) {
1860         var colourlist = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_menu"/>'),
1861             body;
1863         // Build a list of coloured buttons.
1864         Y.each(this.get('colours'), function(rgb, colour) {
1865             var button, listitem, title, img, iconname;
1867             title = M.util.get_string(colour, 'assignfeedback_editpdf');
1868             iconname = this.get('iconprefix') + colour;
1869             img = M.util.image_url(iconname, 'assignfeedback_editpdf');
1870             button = Y.Node.create('<button><img alt="' + title + '" src="' + img + '"/></button>');
1871             button.setAttribute('data-colour', colour);
1872             button.setAttribute('data-rgb', rgb);
1873             button.setStyle('backgroundImage', 'none');
1874             listitem = Y.Node.create('<li/>');
1875             listitem.append(button);
1876             colourlist.append(listitem);
1877         }, this);
1879         body = Y.Node.create('<div/>');
1881         // Set the call back.
1882         colourlist.delegate('click', this.callback_handler, 'button', this);
1883         colourlist.delegate('key', this.callback_handler, 'down:13', 'button', this);
1885         // Set the accessible header text.
1886         this.set('headerText', M.util.get_string('colourpicker', 'assignfeedback_editpdf'));
1888         // Set the body content.
1889         body.append(colourlist);
1890         this.set('bodyContent', body);
1892         COLOURPICKER.superclass.initializer.call(this, config);
1893     },
1894     callback_handler: function(e) {
1895         e.preventDefault();
1897         var callback = this.get('callback'),
1898             callbackcontext = this.get('context'),
1899             bind;
1901         this.hide();
1903         // Call the callback with the specified context.
1904         bind = Y.bind(callback, callbackcontext, e);
1906         bind();
1907     }
1908 }, {
1909     NAME: COLOURPICKER_NAME,
1910     ATTRS: {
1911         /**
1912          * The list of colours this colour picker supports.
1913          *
1914          * @attribute colours
1915          * @type {String: String} (The keys of the array are the colour names and the values are localized strings)
1916          * @default {}
1917          */
1918         colours: {
1919             value: {}
1920         },
1922         /**
1923          * The function called when a new colour is chosen.
1924          *
1925          * @attribute callback
1926          * @type function
1927          * @default null
1928          */
1929         callback: {
1930             value: null
1931         },
1933         /**
1934          * The context passed to the callback when a colour is chosen.
1935          *
1936          * @attribute context
1937          * @type Y.Node
1938          * @default null
1939          */
1940         context: {
1941             value: null
1942         },
1944         /**
1945          * The prefix for the icon image names.
1946          *
1947          * @attribute iconprefix
1948          * @type String
1949          * @default 'colour_'
1950          */
1951         iconprefix: {
1952             value: 'colour_'
1953         }
1954     }
1955 });
1957 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1958 M.assignfeedback_editpdf.colourpicker = COLOURPICKER;
1959 var STAMPPICKER_NAME = "Colourpicker",
1960     STAMPPICKER;
1962 /**
1963  * Provides an in browser PDF editor.
1964  *
1965  * @module moodle-assignfeedback_editpdf-editor
1966  */
1968 /**
1969  * This is a drop down list of stamps.
1970  *
1971  * @namespace M.assignfeedback_editpdf
1972  * @class stamppicker
1973  * @constructor
1974  * @extends M.assignfeedback_editpdf.dropdown
1975  */
1976 STAMPPICKER = function(config) {
1977     STAMPPICKER.superclass.constructor.apply(this, [config]);
1978 };
1980 Y.extend(STAMPPICKER, M.assignfeedback_editpdf.dropdown, {
1982     /**
1983      * Initialise the menu.
1984      *
1985      * @method initializer
1986      * @return void
1987      */
1988     initializer: function(config) {
1989         var stamplist = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_menu"/>');
1991         // Build a list of stamped buttons.
1992         Y.each(this.get('stamps'), function(stamp) {
1993             var button, listitem, title;
1995             title = M.util.get_string('stamp', 'assignfeedback_editpdf');
1996             button = Y.Node.create('<button><img height="16" width="16" alt="' + title + '" src="' + stamp + '"/></button>');
1997             button.setAttribute('data-stamp', stamp);
1998             button.setStyle('backgroundImage', 'none');
1999             listitem = Y.Node.create('<li/>');
2000             listitem.append(button);
2001             stamplist.append(listitem);
2002         }, this);
2005         // Set the call back.
2006         stamplist.delegate('click', this.callback_handler, 'button', this);
2007         stamplist.delegate('key', this.callback_handler, 'down:13', 'button', this);
2009         // Set the accessible header text.
2010         this.set('headerText', M.util.get_string('stamppicker', 'assignfeedback_editpdf'));
2012         // Set the body content.
2013         this.set('bodyContent', stamplist);
2015         STAMPPICKER.superclass.initializer.call(this, config);
2016     },
2017     callback_handler: function(e) {
2018         e.preventDefault();
2019         var callback = this.get('callback'),
2020             callbackcontext = this.get('context'),
2021             bind;
2023         this.hide();
2025         // Call the callback with the specified context.
2026         bind = Y.bind(callback, callbackcontext, e);
2028         bind();
2029     }
2030 }, {
2031     NAME: STAMPPICKER_NAME,
2032     ATTRS: {
2033         /**
2034          * The list of stamps this stamp picker supports.
2035          *
2036          * @attribute stamps
2037          * @type String[] - the stamp filenames.
2038          * @default {}
2039          */
2040         stamps: {
2041             value: []
2042         },
2044         /**
2045          * The function called when a new stamp is chosen.
2046          *
2047          * @attribute callback
2048          * @type function
2049          * @default null
2050          */
2051         callback: {
2052             value: null
2053         },
2055         /**
2056          * The context passed to the callback when a stamp is chosen.
2057          *
2058          * @attribute context
2059          * @type Y.Node
2060          * @default null
2061          */
2062         context: {
2063             value: null
2064         }
2065     }
2066 });
2068 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
2069 M.assignfeedback_editpdf.stamppicker = STAMPPICKER;
2070 var COMMENTMENUNAME = "Commentmenu",
2071     COMMENTMENU;
2073 /**
2074  * Provides an in browser PDF editor.
2075  *
2076  * @module moodle-assignfeedback_editpdf-editor
2077  */
2079 /**
2080  * COMMENTMENU
2081  * This is a drop down list of comment context functions.
2082  *
2083  * @namespace M.assignfeedback_editpdf
2084  * @class commentmenu
2085  * @constructor
2086  * @extends M.assignfeedback_editpdf.dropdown
2087  */
2088 COMMENTMENU = function(config) {
2089     COMMENTMENU.superclass.constructor.apply(this, [config]);
2090 };
2092 Y.extend(COMMENTMENU, M.assignfeedback_editpdf.dropdown, {
2094     /**
2095      * Initialise the menu.
2096      *
2097      * @method initializer
2098      * @return void
2099      */
2100     initializer: function(config) {
2101         var commentlinks,
2102             link,
2103             body,
2104             comment;
2106         comment = this.get('comment');
2107         // Build the list of menu items.
2108         commentlinks = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_menu"/>');
2110         link = Y.Node.create('<li><a tabindex="-1" href="#">' +
2111                M.util.get_string('addtoquicklist', 'assignfeedback_editpdf') +
2112                '</a></li>');
2113         link.on('click', comment.add_to_quicklist, comment);
2114         link.on('key', comment.add_to_quicklist, 'enter,space', comment);
2116         commentlinks.append(link);
2118         link = Y.Node.create('<li><a tabindex="-1" href="#">' +
2119                M.util.get_string('deletecomment', 'assignfeedback_editpdf') +
2120                '</a></li>');
2121         link.on('click', function(e) {
2122             e.preventDefault();
2123             this.menu.hide();
2124             this.remove();
2125         }, comment);
2127         link.on('key', function() {
2128             comment.menu.hide();
2129             comment.remove();
2130         }, 'enter,space', comment);
2132         commentlinks.append(link);
2134         link = Y.Node.create('<li><hr/></li>');
2135         commentlinks.append(link);
2137         // Set the accessible header text.
2138         this.set('headerText', M.util.get_string('commentcontextmenu', 'assignfeedback_editpdf'));
2140         body = Y.Node.create('<div/>');
2142         // Set the body content.
2143         body.append(commentlinks);
2144         this.set('bodyContent', body);
2146         COMMENTMENU.superclass.initializer.call(this, config);
2147     },
2149     /**
2150      * Show the menu.
2151      *
2152      * @method show
2153      * @return void
2154      */
2155     show: function() {
2156         var commentlinks = this.get('boundingBox').one('ul');
2157             commentlinks.all('.quicklist_comment').remove(true);
2158         var comment = this.get('comment');
2160         comment.deleteme = false; // Cancel the deleting of blank comments.
2162         // Now build the list of quicklist comments.
2163         Y.each(comment.editor.quicklist.comments, function(quickcomment) {
2164             var listitem = Y.Node.create('<li class="quicklist_comment"></li>'),
2165                 linkitem = Y.Node.create('<a href="#" tabindex="-1">' + quickcomment.rawtext + '</a>'),
2166                 deletelinkitem = Y.Node.create('<a href="#" tabindex="-1" class="delete_quicklist_comment">' +
2167                                                '<img src="' + M.util.image_url('t/delete', 'core') + '" ' +
2168                                                'alt="' + M.util.get_string('deletecomment', 'assignfeedback_editpdf') + '"/>' +
2169                                                '</a>');
2170             listitem.append(linkitem);
2171             listitem.append(deletelinkitem);
2173             commentlinks.append(listitem);
2175             listitem.on('click', comment.set_from_quick_comment, comment, quickcomment);
2176             listitem.on('key', comment.set_from_quick_comment, 'space,enter', comment, quickcomment);
2178             deletelinkitem.on('click', comment.remove_from_quicklist, comment, quickcomment);
2179             deletelinkitem.on('key', comment.remove_from_quicklist, 'space,enter', comment, quickcomment);
2180         }, this);
2182         COMMENTMENU.superclass.show.call(this);
2183     }
2184 }, {
2185     NAME: COMMENTMENUNAME,
2186     ATTRS: {
2187         /**
2188          * The comment this menu is attached to.
2189          *
2190          * @attribute comment
2191          * @type M.assignfeedback_editpdf.comment
2192          * @default null
2193          */
2194         comment: {
2195             value: null
2196         }
2198     }
2199 });
2201 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
2202 M.assignfeedback_editpdf.commentmenu = COMMENTMENU;
2203 /* eslint-disable no-unused-vars */
2204 /* global SELECTOR */
2205 var COMMENTSEARCHNAME = "commentsearch",
2206     COMMENTSEARCH;
2208 /**
2209  * Provides an in browser PDF editor.
2210  *
2211  * @module moodle-assignfeedback_editpdf-editor
2212  */
2214 /**
2215  * This is a searchable dialogue of comments.
2216  *
2217  * @namespace M.assignfeedback_editpdf
2218  * @class commentsearch
2219  * @constructor
2220  * @extends M.core.dialogue
2221  */
2222 COMMENTSEARCH = function(config) {
2223     config.draggable = false;
2224     config.centered = true;
2225     config.width = '400px';
2226     config.visible = false;
2227     config.headerContent = M.util.get_string('searchcomments', 'assignfeedback_editpdf');
2228     config.footerContent = '';
2229     COMMENTSEARCH.superclass.constructor.apply(this, [config]);
2230 };
2232 Y.extend(COMMENTSEARCH, M.core.dialogue, {
2233     /**
2234      * Initialise the menu.
2235      *
2236      * @method initializer
2237      * @return void
2238      */
2239     initializer: function(config) {
2240         var editor,
2241             container,
2242             placeholder,
2243             commentfilter,
2244             commentlist,
2245             bb;
2247         bb = this.get('boundingBox');
2248         bb.addClass('assignfeedback_editpdf_commentsearch');
2250         editor = this.get('editor');
2251         container = Y.Node.create('<div/>');
2253         placeholder = M.util.get_string('filter', 'assignfeedback_editpdf');
2254         commentfilter = Y.Node.create('<input type="text" size="20" placeholder="' + placeholder + '"/>');
2255         container.append(commentfilter);
2256         commentlist = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_search"/>');
2257         container.append(commentlist);
2259         commentfilter.on('keyup', this.filter_search_comments, this);
2260         commentlist.delegate('click', this.focus_on_comment, 'a', this);
2261         commentlist.delegate('key', this.focus_on_comment, 'enter,space', 'a', this);
2263         // Set the body content.
2264         this.set('bodyContent', container);
2266         COMMENTSEARCH.superclass.initializer.call(this, config);
2267     },
2269     /**
2270      * Event handler to filter the list of comments.
2271      *
2272      * @protected
2273      * @method filter_search_comments
2274      */
2275     filter_search_comments: function() {
2276         var filternode,
2277             commentslist,
2278             filtertext,
2279             dialogueid;
2281         dialogueid = this.get('id');
2282         filternode = Y.one('#' + dialogueid + SELECTOR.SEARCHFILTER);
2283         commentslist = Y.one('#' + dialogueid + SELECTOR.SEARCHCOMMENTSLIST);
2285         filtertext = filternode.get('value');
2287         commentslist.all('li').each(function(node) {
2288             if (node.get('text').indexOf(filtertext) !== -1) {
2289                 node.show();
2290             } else {
2291                 node.hide();
2292             }
2293         });
2294     },
2296     /**
2297      * Event handler to focus on a selected comment.
2298      *
2299      * @param Event e
2300      * @protected
2301      * @method focus_on_comment
2302      */
2303     focus_on_comment: function(e) {
2304         e.preventDefault();
2305         var target = e.target.ancestor('li'),
2306             comment = target.getData('comment'),
2307             editor = this.get('editor');
2309         this.hide();
2311         comment.pageno = comment.clean().pageno;
2312         if (comment.pageno !== editor.currentpage) {
2313             // Comment is on a different page.
2314             editor.currentpage = comment.pageno;
2315             editor.change_page();
2316         }
2318         comment.node = comment.drawable.nodes[0].one('textarea');
2319         comment.node.ancestor('div').removeClass('commentcollapsed');
2320         comment.node.focus();
2321     },
2323     /**
2324      * Show the menu.
2325      *
2326      * @method show
2327      * @return void
2328      */
2329     show: function() {
2330         var commentlist = this.get('boundingBox').one('ul'),
2331             editor = this.get('editor');
2333         commentlist.all('li').remove(true);
2335         // Rebuild the latest list of comments.
2336         Y.each(editor.pages, function(page) {
2337             Y.each(page.comments, function(comment) {
2338                 var commentnode = Y.Node.create('<li><a href="#" tabindex="-1"><pre>' + comment.rawtext + '</pre></a></li>');
2339                 commentlist.append(commentnode);
2340                 commentnode.setData('comment', comment);
2341             }, this);
2342         }, this);
2344         this.centerDialogue();
2345         COMMENTSEARCH.superclass.show.call(this);
2346     }
2347 }, {
2348     NAME: COMMENTSEARCHNAME,
2349     ATTRS: {
2350         /**
2351          * The editor this search window is attached to.
2352          *
2353          * @attribute editor
2354          * @type M.assignfeedback_editpdf.editor
2355          * @default null
2356          */
2357         editor: {
2358             value: null
2359         }
2361     }
2362 });
2364 Y.Base.modifyAttrs(COMMENTSEARCH, {
2365     /**
2366      * Whether the widget should be modal or not.
2367      *
2368      * Moodle override: We override this for commentsearch to force it always true.
2369      *
2370      * @attribute Modal
2371      * @type Boolean
2372      * @default true
2373      */
2374     modal: {
2375         getter: function() {
2376             return true;
2377         }
2378     }
2379 });
2381 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
2382 M.assignfeedback_editpdf.commentsearch = COMMENTSEARCH;
2383 // This file is part of Moodle - http://moodle.org/
2384 //
2385 // Moodle is free software: you can redistribute it and/or modify
2386 // it under the terms of the GNU General Public License as published by
2387 // the Free Software Foundation, either version 3 of the License, or
2388 // (at your option) any later version.
2389 //
2390 // Moodle is distributed in the hope that it will be useful,
2391 // but WITHOUT ANY WARRANTY; without even the implied warranty of
2392 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
2393 // GNU General Public License for more details.
2394 //
2395 // You should have received a copy of the GNU General Public License
2396 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
2397 /* global SELECTOR, COMMENTCOLOUR, COMMENTTEXTCOLOUR */
2399 /**
2400  * Provides an in browser PDF editor.
2401  *
2402  * @module moodle-assignfeedback_editpdf-editor
2403  */
2405 /**
2406  * Class representing a list of comments.
2407  *
2408  * @namespace M.assignfeedback_editpdf
2409  * @class comment
2410  * @param M.assignfeedback_editpdf.editor editor
2411  * @param Int gradeid
2412  * @param Int pageno
2413  * @param Int x
2414  * @param Int y
2415  * @param Int width
2416  * @param String colour
2417  * @param String rawtext
2418  */
2419 var COMMENT = function(editor, gradeid, pageno, x, y, width, colour, rawtext) {
2421     /**
2422      * Reference to M.assignfeedback_editpdf.editor.
2423      * @property editor
2424      * @type M.assignfeedback_editpdf.editor
2425      * @public
2426      */
2427     this.editor = editor;
2429     /**
2430      * Grade id
2431      * @property gradeid
2432      * @type Int
2433      * @public
2434      */
2435     this.gradeid = gradeid || 0;
2437     /**
2438      * X position
2439      * @property x
2440      * @type Int
2441      * @public
2442      */
2443     this.x = parseInt(x, 10) || 0;
2445     /**
2446      * Y position
2447      * @property y
2448      * @type Int
2449      * @public
2450      */
2451     this.y = parseInt(y, 10) || 0;
2453     /**
2454      * Comment width
2455      * @property width
2456      * @type Int
2457      * @public
2458      */
2459     this.width = parseInt(width, 10) || 0;
2461     /**
2462      * Comment rawtext
2463      * @property rawtext
2464      * @type String
2465      * @public
2466      */
2467     this.rawtext = rawtext || '';
2469     /**
2470      * Comment page number
2471      * @property pageno
2472      * @type Int
2473      * @public
2474      */
2475     this.pageno = pageno || 0;
2477     /**
2478      * Comment background colour.
2479      * @property colour
2480      * @type String
2481      * @public
2482      */
2483     this.colour = colour || 'yellow';
2485     /**
2486      * Reference to M.assignfeedback_editpdf.drawable
2487      * @property drawable
2488      * @type M.assignfeedback_editpdf.drawable
2489      * @public
2490      */
2491     this.drawable = false;
2493     /**
2494      * Boolean used by a timeout to delete empty comments after a short delay.
2495      * @property deleteme
2496      * @type Boolean
2497      * @public
2498      */
2499     this.deleteme = false;
2501     /**
2502      * Reference to the link that opens the menu.
2503      * @property menulink
2504      * @type Y.Node
2505      * @public
2506      */
2507     this.menulink = null;
2509     /**
2510      * Reference to the dialogue that is the context menu.
2511      * @property menu
2512      * @type M.assignfeedback_editpdf.dropdown
2513      * @public
2514      */
2515     this.menu = null;
2517     /**
2518      * Clean a comment record, returning an oject with only fields that are valid.
2519      * @public
2520      * @method clean
2521      * @return {}
2522      */
2523     this.clean = function() {
2524         return {
2525             gradeid: this.gradeid,
2526             x: parseInt(this.x, 10),
2527             y: parseInt(this.y, 10),
2528             width: parseInt(this.width, 10),
2529             rawtext: this.rawtext,
2530             pageno: parseInt(this.pageno, 10),
2531             colour: this.colour
2532         };
2533     };
2535     /**
2536      * Draw a comment.
2537      * @public
2538      * @method draw_comment
2539      * @param boolean focus - Set the keyboard focus to the new comment if true
2540      * @return M.assignfeedback_editpdf.drawable
2541      */
2542     this.draw = function(focus) {
2543         var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
2544             node,
2545             drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION),
2546             container,
2547             label,
2548             marker,
2549             menu,
2550             position,
2551             scrollheight;
2553         // Lets add a contenteditable div.
2554         node = Y.Node.create('<textarea/>');
2555         container = Y.Node.create('<div class="commentdrawable"/>');
2556         label = Y.Node.create('<label/>');
2557         marker = Y.Node.create('<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.5 -0.5 13 13" ' +
2558                 'preserveAspectRatio="xMinYMin meet">' +
2559                 '<path d="M11 0H1C.4 0 0 .4 0 1v6c0 .6.4 1 1 1h1v4l4-4h5c.6 0 1-.4 1-1V1c0-.6-.4-1-1-1z" ' +
2560                 'fill="currentColor" opacity="0.9" stroke="rgb(153, 153, 153)" stroke-width="0.5"/></svg>');
2561         menu = Y.Node.create('<a href="#"><img src="' + M.util.image_url('t/contextmenu', 'core') + '"/></a>');
2563         this.menulink = menu;
2564         container.append(label);
2565         label.append(node);
2566         container.append(marker);
2567         container.setAttribute('tabindex', '-1');
2568         label.setAttribute('tabindex', '0');
2569         node.setAttribute('tabindex', '-1');
2570         menu.setAttribute('tabindex', '0');
2572         if (!this.editor.get('readonly')) {
2573             container.append(menu);
2574         } else {
2575             node.setAttribute('readonly', 'readonly');
2576         }
2577         if (this.width < 100) {
2578             this.width = 100;
2579         }
2581         position = this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(this.x, this.y));
2582         node.setStyles({
2583             width: this.width + 'px',
2584             backgroundColor: COMMENTCOLOUR[this.colour],
2585             color: COMMENTTEXTCOLOUR
2586         });
2588         drawingregion.append(container);
2589         container.setStyle('position', 'absolute');
2590         container.setX(position.x);
2591         container.setY(position.y);
2592         drawable.store_position(container, position.x, position.y);
2593         drawable.nodes.push(container);
2594         node.set('value', this.rawtext);
2595         scrollheight = node.get('scrollHeight');
2596         node.setStyles({
2597             'height': scrollheight + 'px',
2598             'overflow': 'hidden'
2599         });
2600         marker.setStyle('color', COMMENTCOLOUR[this.colour]);
2601         this.attach_events(node, menu);
2602         if (focus) {
2603             node.focus();
2604         } else if (editor.collapsecomments) {
2605             container.addClass('commentcollapsed');
2606         }
2607         this.drawable = drawable;
2610         return drawable;
2611     };
2613     /**
2614      * Delete an empty comment if it's menu hasn't been opened in time.
2615      * @method delete_comment_later
2616      */
2617     this.delete_comment_later = function() {
2618         if (this.deleteme) {
2619             this.remove();
2620         }
2621     };
2623     /**
2624      * Comment nodes have a bunch of event handlers attached to them directly.
2625      * This is all done here for neatness.
2626      *
2627      * @protected
2628      * @method attach_comment_events
2629      * @param node - The Y.Node representing the comment.
2630      * @param menu - The Y.Node representing the menu.
2631      */
2632     this.attach_events = function(node, menu) {
2633         var container = node.ancestor('div'),
2634             label = node.ancestor('label'),
2635             marker = label.next('svg');
2637         // Function to collapse a comment to a marker icon.
2638         node.collapse = function(delay) {
2639             node.collapse.delay = Y.later(delay, node, function() {
2640                 if (editor.collapsecomments) {
2641                     container.addClass('commentcollapsed');
2642                 }
2643             });
2644         };
2646         // Function to expand a comment.
2647         node.expand = function() {
2648             if (node.getData('dragging') !== true) {
2649                 if (node.collapse.delay) {
2650                     node.collapse.delay.cancel();
2651                 }
2652                 container.removeClass('commentcollapsed');
2653             }
2654         };
2656         // Expand comment on mouse over (under certain conditions) or click/tap.
2657         container.on('mouseenter', function() {
2658             if (editor.currentedit.tool === 'comment' || editor.currentedit.tool === 'select' || this.editor.get('readonly')) {
2659                 node.expand();
2660             }
2661         }, this);
2662         container.on('click|tap', function() {
2663             node.expand();
2664             node.focus();
2665         }, this);
2667         // Functions to capture reverse tabbing events.
2668         node.on('keyup', function(e) {
2669             if (e.keyCode === 9 && e.shiftKey && menu.getAttribute('tabindex') === '0') {
2670                 // User landed here via Shift+Tab (but not from this comment's menu).
2671                 menu.focus();
2672             }
2673             menu.setAttribute('tabindex', '0');
2674         }, this);
2675         menu.on('keydown', function(e) {
2676             if (e.keyCode === 9 && e.shiftKey) {
2677                 // User is tabbing back to the comment node from its own menu.
2678                 menu.setAttribute('tabindex', '-1');
2679             }
2680         }, this);
2682         // Comment becomes "active" on label or menu focus.
2683         label.on('focus', function() {
2684             node.active = true;
2685             if (node.collapse.delay) {
2686                 node.collapse.delay.cancel();
2687             }
2688             // Give comment a tabindex to prevent focus outline being suppressed.
2689             node.setAttribute('tabindex', '0');
2690             // Expand comment and pass focus to it.
2691             node.expand();
2692             node.focus();
2693             // Now remove label tabindex so user can reverse tab past it.
2694             label.setAttribute('tabindex', '-1');
2695         }, this);
2696         menu.on('focus', function() {
2697             node.active = true;
2698             if (node.collapse.delay) {
2699                 node.collapse.delay.cancel();
2700             }
2701             this.deleteme = false;
2702             // Restore label tabindex so user can tab back to it from menu.
2703             label.setAttribute('tabindex', '0');
2704         }, this);
2706         // Always restore the default tabindex states when moving away.
2707         node.on('blur', function() {
2708             node.setAttribute('tabindex', '-1');
2709         }, this);
2710         label.on('blur', function() {
2711             label.setAttribute('tabindex', '0');
2712         }, this);
2714         // Collapse comment on mouse out if not currently active.
2715         container.on('mouseleave', function() {
2716             if (editor.collapsecomments && node.active !== true) {
2717                 node.collapse(400);
2718             }
2719         }, this);
2721         // Collapse comment on blur.
2722         container.on('blur', function() {
2723             node.active = false;
2724             node.collapse(800);
2725         }, this);
2727         if (!this.editor.get('readonly')) {
2728             // Save the text on blur.
2729             node.on('blur', function() {
2730                 // Save the changes back to the comment.
2731                 this.rawtext = node.get('value');
2732                 this.width = parseInt(node.getStyle('width'), 10);
2734                 // Trim.
2735                 if (this.rawtext.replace(/^\s+|\s+$/g, "") === '') {
2736                     // Delete empty comments.
2737                     this.deleteme = true;
2738                     Y.later(400, this, this.delete_comment_later);
2739                 }
2740                 this.editor.save_current_page();
2741                 this.editor.editingcomment = false;
2742             }, this);
2744             // For delegated event handler.
2745             menu.setData('comment', this);
2747             node.on('keyup', function() {
2748                 node.setStyle('height', 'auto');
2749                 var scrollheight = node.get('scrollHeight'),
2750                     height = parseInt(node.getStyle('height'), 10);
2752                 // Webkit scrollheight fix.
2753                 if (scrollheight === height + 8) {
2754                     scrollheight -= 8;
2755                 }
2756                 node.setStyle('height', scrollheight + 'px');
2757             });
2759             node.on('gesturemovestart', function(e) {
2760                 if (editor.currentedit.tool === 'select') {
2761                     e.preventDefault();
2762                     if (editor.collapsecomments) {
2763                         node.setData('offsetx', 8);
2764                         node.setData('offsety', 8);
2765                     } else {
2766                         node.setData('offsetx', e.clientX - container.getX());
2767                         node.setData('offsety', e.clientY - container.getY());
2768                     }
2769                 }
2770             });
2771             node.on('gesturemove', function(e) {
2772                 if (editor.currentedit.tool === 'select') {
2773                     var x = e.clientX - node.getData('offsetx'),
2774                         y = e.clientY - node.getData('offsety'),
2775                         newlocation,
2776                         windowlocation,
2777                         bounds;
2779                     if (node.getData('dragging') !== true) {
2780                         // Collapse comment during move.
2781                         node.collapse(0);
2782                         node.setData('dragging', true);
2783                     }
2785                     newlocation = this.editor.get_canvas_coordinates(new M.assignfeedback_editpdf.point(x, y));
2786                     bounds = this.editor.get_canvas_bounds(true);
2787                     bounds.x = 0;
2788                     bounds.y = 0;
2790                     bounds.width -= 24;
2791                     bounds.height -= 24;
2792                     // Clip to the window size - the comment icon size.
2793                     newlocation.clip(bounds);
2795                     this.x = newlocation.x;
2796                     this.y = newlocation.y;
2798                     windowlocation = this.editor.get_window_coordinates(newlocation);
2799                     container.setX(windowlocation.x);
2800                     container.setY(windowlocation.y);
2801                     this.drawable.store_position(container, windowlocation.x, windowlocation.y);
2802                 }
2803             }, null, this);
2804             node.on('gesturemoveend', function() {
2805                 if (editor.currentedit.tool === 'select') {
2806                   &nb