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