on-demand release 4.2dev+
[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/>.
423 /**
424  * Provides an in browser PDF editor.
425  *
426  * @module moodle-assignfeedback_editpdf-editor
427  */
429 /**
430  * Class representing a drawable thing which contains both Y.Nodes, and Y.Shapes.
431  *
432  * @namespace M.assignfeedback_editpdf
433  * @param M.assignfeedback_editpdf.editor editor
434  * @class drawable
435  */
436 var DRAWABLE = function(editor) {
438     /**
439      * Reference to M.assignfeedback_editpdf.editor.
440      * @property editor
441      * @type M.assignfeedback_editpdf.editor
442      * @public
443      */
444     this.editor = editor;
446     /**
447      * Array of Y.Shape
448      * @property shapes
449      * @type Y.Shape[]
450      * @public
451      */
452     this.shapes = [];
454     /**
455      * Array of Y.Node
456      * @property nodes
457      * @type Y.Node[]
458      * @public
459      */
460     this.nodes = [];
462     /**
463      * Delete the shapes from the drawable.
464      * @protected
465      * @method erase_drawable
466      */
467     this.erase = function() {
468         if (this.shapes) {
469             while (this.shapes.length > 0) {
470                 this.editor.graphic.removeShape(this.shapes.pop());
471             }
472         }
473         if (this.nodes) {
474             while (this.nodes.length > 0) {
475                 this.nodes.pop().remove();
476             }
477         }
478     };
480     /**
481      * Update the positions of all absolutely positioned nodes, when the drawing canvas is scrolled
482      * @public
483      * @method scroll_update
484      * @param scrollx int
485      * @param scrolly int
486      */
487     this.scroll_update = function(scrollx, scrolly) {
488         var i, x, y;
489         for (i = 0; i < this.nodes.length; i++) {
490             x = this.nodes[i].getData('x');
491             y = this.nodes[i].getData('y');
492             if (x !== undefined && y !== undefined) {
493                 this.nodes[i].setX(parseInt(x, 10) - scrollx);
494                 this.nodes[i].setY(parseInt(y, 10) - scrolly);
495             }
496         }
497     };
499     /**
500      * Store the initial position of the node, so it can be updated when the drawing canvas is scrolled
501      * @public
502      * @method store_position
503      * @param container
504      * @param x
505      * @param y
506      */
507     this.store_position = function(container, x, y) {
508         var drawingregion, scrollx, scrolly;
510         drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION);
511         scrollx = parseInt(drawingregion.get('scrollLeft'), 10);
512         scrolly = parseInt(drawingregion.get('scrollTop'), 10);
513         container.setData('x', x + scrollx);
514         container.setData('y', y + scrolly);
515     };
516 };
518 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
519 M.assignfeedback_editpdf.drawable = DRAWABLE;
520 // This file is part of Moodle - http://moodle.org/
521 //
522 // Moodle is free software: you can redistribute it and/or modify
523 // it under the terms of the GNU General Public License as published by
524 // the Free Software Foundation, either version 3 of the License, or
525 // (at your option) any later version.
526 //
527 // Moodle is distributed in the hope that it will be useful,
528 // but WITHOUT ANY WARRANTY; without even the implied warranty of
529 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
530 // GNU General Public License for more details.
531 //
532 // You should have received a copy of the GNU General Public License
533 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
535 /**
536  * Provides an in browser PDF editor.
537  *
538  * @module moodle-assignfeedback_editpdf-editor
539  */
541 /**
542  * Class representing a highlight.
543  *
544  * @namespace M.assignfeedback_editpdf
545  * @class annotation
546  * @constructor
547  */
548 var ANNOTATION = function(config) {
549     ANNOTATION.superclass.constructor.apply(this, [config]);
550 };
552 ANNOTATION.NAME = "annotation";
553 ANNOTATION.ATTRS = {};
555 Y.extend(ANNOTATION, Y.Base, {
556     /**
557      * Reference to M.assignfeedback_editpdf.editor.
558      * @property editor
559      * @type M.assignfeedback_editpdf.editor
560      * @public
561      */
562     editor: null,
564     /**
565      * Grade id
566      * @property gradeid
567      * @type Int
568      * @public
569      */
570     gradeid: 0,
572     /**
573      * Comment page number
574      * @property pageno
575      * @type Int
576      * @public
577      */
578     pageno: 0,
580     /**
581      * X position
582      * @property x
583      * @type Int
584      * @public
585      */
586     x: 0,
588     /**
589      * Y position
590      * @property y
591      * @type Int
592      * @public
593      */
594     y: 0,
596     /**
597      * Ending x position
598      * @property endx
599      * @type Int
600      * @public
601      */
602     endx: 0,
604     /**
605      * Ending y position
606      * @property endy
607      * @type Int
608      * @public
609      */
610     endy: 0,
612     /**
613      * Path
614      * @property path
615      * @type String - list of points like x1,y1:x2,y2
616      * @public
617      */
618     path: '',
620     /**
621      * Tool.
622      * @property type
623      * @type String
624      * @public
625      */
626     type: 'rect',
628     /**
629      * Annotation colour.
630      * @property colour
631      * @type String
632      * @public
633      */
634     colour: 'red',
636     /**
637      * Reference to M.assignfeedback_editpdf.drawable
638      * @property drawable
639      * @type M.assignfeedback_editpdf.drawable
640      * @public
641      */
642     drawable: false,
644     /**
645      * Initialise the annotation.
646      *
647      * @method initializer
648      * @return void
649      */
650     initializer: function(config) {
651         this.editor = config.editor || null;
652         this.gradeid = parseInt(config.gradeid, 10) || 0;
653         this.pageno = parseInt(config.pageno, 10) || 0;
654         this.x = parseInt(config.x, 10) || 0;
655         this.y = parseInt(config.y, 10) || 0;
656         this.endx = parseInt(config.endx, 10) || 0;
657         this.endy = parseInt(config.endy, 10) || 0;
658         this.path = config.path || '';
659         this.type = config.type || 'rect';
660         this.colour = config.colour || 'red';
661         this.drawable = false;
662     },
664     /**
665      * Clean a comment record, returning an oject with only fields that are valid.
666      * @public
667      * @method clean
668      * @return {}
669      */
670     clean: function() {
671         return {
672             gradeid: this.gradeid,
673             x: parseInt(this.x, 10),
674             y: parseInt(this.y, 10),
675             endx: parseInt(this.endx, 10),
676             endy: parseInt(this.endy, 10),
677             type: this.type,
678             path: this.path,
679             pageno: this.pageno,
680             colour: this.colour
681         };
682     },
684     /**
685      * Draw a selection around this annotation if it is selected.
686      * @public
687      * @method draw_highlight
688      * @return M.assignfeedback_editpdf.drawable
689      */
690     draw_highlight: function() {
691         var bounds,
692             drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION),
693             offsetcanvas = this.editor.get_dialogue_element(SELECTOR.DRAWINGCANVAS).getXY(),
694             shape;
696         if (this.editor.currentannotation === this) {
697             // Draw a highlight around the annotation.
698             bounds = new M.assignfeedback_editpdf.rect();
699             bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y),
700                           new M.assignfeedback_editpdf.point(this.endx, this.endy)]);
702             shape = this.editor.graphic.addShape({
703                 type: Y.Rect,
704                 width: bounds.width,
705                 height: bounds.height,
706                 stroke: {
707                    weight: STROKEWEIGHT,
708                    color: SELECTEDBORDERCOLOUR
709                 },
710                 fill: {
711                    color: SELECTEDFILLCOLOUR
712                 },
713                 x: bounds.x,
714                 y: bounds.y
715             });
716             this.drawable.shapes.push(shape);
718             // Add a delete X to the annotation.
719             var deleteicon = Y.Node.create('<img src="' + M.util.image_url('trash', 'assignfeedback_editpdf') + '"/>'),
720                 deletelink = Y.Node.create('<a href="#" role="button"></a>');
722             deleteicon.setAttrs({
723                 'alt': M.util.get_string('deleteannotation', 'assignfeedback_editpdf')
724             });
725             deleteicon.setStyles({
726                 'backgroundColor': 'white'
727             });
728             deletelink.addClass('deleteannotationbutton');
729             deletelink.append(deleteicon);
731             drawingregion.append(deletelink);
732             deletelink.setData('annotation', this);
734             deletelink.on('click', this.remove, this);
735             deletelink.on('key', this.remove, 'space,enter', this);
737             deletelink.setX(offsetcanvas[0] + bounds.x + bounds.width - 18);
738             deletelink.setY(offsetcanvas[1] + bounds.y + 6);
739             this.drawable.nodes.push(deletelink);
740         }
741         return this.drawable;
742     },
744     /**
745      * Draw an annotation
746      * @public
747      * @method draw
748      * @return M.assignfeedback_editpdf.drawable|false
749      */
750     draw: function() {
751         // Should be overridden by the subclass.
752         this.draw_highlight();
753         return this.drawable;
754     },
756     /**
757      * Delete an annotation
758      * @protected
759      * @method remove
760      * @param event
761      */
762     remove: function(e) {
763         var annotations,
764             i;
766         e.preventDefault();
768         annotations = this.editor.pages[this.editor.currentpage].annotations;
769         for (i = 0; i < annotations.length; i++) {
770             if (annotations[i] === this) {
771                 annotations.splice(i, 1);
772                 if (this.drawable) {
773                     this.drawable.erase();
774                 }
775                 this.editor.currentannotation = false;
776                 this.editor.save_current_page();
777                 return;
778             }
779         }
780     },
782     /**
783      * Move an annotation to a new location.
784      * @public
785      * @param int newx
786      * @param int newy
787      * @method move_annotation
788      */
789     move: function(newx, newy) {
790         var diffx = newx - this.x,
791             diffy = newy - this.y,
792             newpath, oldpath, xy,
793             x, y;
795         this.x += diffx;
796         this.y += diffy;
797         this.endx += diffx;
798         this.endy += diffy;
800         if (this.path) {
801             newpath = [];
802             oldpath = this.path.split(':');
803             Y.each(oldpath, function(position) {
804                 xy = position.split(',');
805                 x = parseInt(xy[0], 10);
806                 y = parseInt(xy[1], 10);
807                 newpath.push((x + diffx) + ',' + (y + diffy));
808             });
810             this.path = newpath.join(':');
812         }
813         if (this.drawable) {
814             this.drawable.erase();
815         }
816         this.editor.drawables.push(this.draw());
817     },
819     /**
820      * Draw the in progress edit.
821      *
822      * @public
823      * @method draw_current_edit
824      * @param M.assignfeedback_editpdf.edit edit
825      */
826     draw_current_edit: function(edit) {
827         var noop = edit && false;
828         // Override me please.
829         return noop;
830     },
832     /**
833      * Promote the current edit to a real annotation.
834      *
835      * @public
836      * @method init_from_edit
837      * @param M.assignfeedback_editpdf.edit edit
838      * @return bool if width/height is more than min. required.
839      */
840     init_from_edit: function(edit) {
841         var bounds = new M.assignfeedback_editpdf.rect();
842         bounds.bound([edit.start, edit.end]);
844         this.gradeid = this.editor.get('gradeid');
845         this.pageno = this.editor.currentpage;
846         this.x = bounds.x;
847         this.y = bounds.y;
848         this.endx = bounds.x + bounds.width;
849         this.endy = bounds.y + bounds.height;
850         this.colour = edit.annotationcolour;
851         this.path = '';
852         return (bounds.has_min_width() && bounds.has_min_height());
853     }
855 });
857 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
858 M.assignfeedback_editpdf.annotation = ANNOTATION;
859 // This file is part of Moodle - http://moodle.org/
860 //
861 // Moodle is free software: you can redistribute it and/or modify
862 // it under the terms of the GNU General Public License as published by
863 // the Free Software Foundation, either version 3 of the License, or
864 // (at your option) any later version.
865 //
866 // Moodle is distributed in the hope that it will be useful,
867 // but WITHOUT ANY WARRANTY; without even the implied warranty of
868 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
869 // GNU General Public License for more details.
870 //
871 // You should have received a copy of the GNU General Public License
872 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
874 /**
875  * Provides an in browser PDF editor.
876  *
877  * @module moodle-assignfeedback_editpdf-editor
878  */
880 /**
881  * Class representing a line.
882  *
883  * @namespace M.assignfeedback_editpdf
884  * @class annotationline
885  * @extends M.assignfeedback_editpdf.annotation
886  */
887 var ANNOTATIONLINE = function(config) {
888     ANNOTATIONLINE.superclass.constructor.apply(this, [config]);
889 };
891 ANNOTATIONLINE.NAME = "annotationline";
892 ANNOTATIONLINE.ATTRS = {};
894 Y.extend(ANNOTATIONLINE, M.assignfeedback_editpdf.annotation, {
895     /**
896      * Draw a line annotation
897      * @protected
898      * @method draw
899      * @return M.assignfeedback_editpdf.drawable
900      */
901     draw: function() {
902         var drawable,
903             shape;
905         drawable = new M.assignfeedback_editpdf.drawable(this.editor);
907         shape = this.editor.graphic.addShape({
908         type: Y.Path,
909             fill: false,
910             stroke: {
911                 weight: STROKEWEIGHT,
912                 color: ANNOTATIONCOLOUR[this.colour]
913             }
914         });
916         shape.moveTo(this.x, this.y);
917         shape.lineTo(this.endx, this.endy);
918         shape.end();
919         drawable.shapes.push(shape);
920         this.drawable = drawable;
922         return ANNOTATIONLINE.superclass.draw.apply(this);
923     },
925     /**
926      * Draw the in progress edit.
927      *
928      * @public
929      * @method draw_current_edit
930      * @param M.assignfeedback_editpdf.edit edit
931      */
932     draw_current_edit: function(edit) {
933         var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
934             shape;
936         shape = this.editor.graphic.addShape({
937            type: Y.Path,
938             fill: false,
939             stroke: {
940                 weight: STROKEWEIGHT,
941                 color: ANNOTATIONCOLOUR[edit.annotationcolour]
942             }
943         });
945         shape.moveTo(edit.start.x, edit.start.y);
946         shape.lineTo(edit.end.x, edit.end.y);
947         shape.end();
949         drawable.shapes.push(shape);
951         return drawable;
952     },
954     /**
955      * Promote the current edit to a real annotation.
956      *
957      * @public
958      * @method init_from_edit
959      * @param M.assignfeedback_editpdf.edit edit
960      * @return bool true if line bound is more than min width/height, else false.
961      */
962     init_from_edit: function(edit) {
963         this.gradeid = this.editor.get('gradeid');
964         this.pageno = this.editor.currentpage;
965         this.x = edit.start.x;
966         this.y = edit.start.y;
967         this.endx = edit.end.x;
968         this.endy = edit.end.y;
969         this.colour = edit.annotationcolour;
970         this.path = '';
972         return !(((this.endx - this.x) === 0) && ((this.endy - this.y) === 0));
973     }
975 });
977 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
978 M.assignfeedback_editpdf.annotationline = ANNOTATIONLINE;
979 // This file is part of Moodle - http://moodle.org/
980 //
981 // Moodle is free software: you can redistribute it and/or modify
982 // it under the terms of the GNU General Public License as published by
983 // the Free Software Foundation, either version 3 of the License, or
984 // (at your option) any later version.
985 //
986 // Moodle is distributed in the hope that it will be useful,
987 // but WITHOUT ANY WARRANTY; without even the implied warranty of
988 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
989 // GNU General Public License for more details.
990 //
991 // You should have received a copy of the GNU General Public License
992 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
994 /**
995  * Provides an in browser PDF editor.
996  *
997  * @module moodle-assignfeedback_editpdf-editor
998  */
1000 /**
1001  * Class representing a rectangle.
1002  *
1003  * @namespace M.assignfeedback_editpdf
1004  * @class annotationrectangle
1005  * @extends M.assignfeedback_editpdf.annotation
1006  */
1007 var ANNOTATIONRECTANGLE = function(config) {
1008     ANNOTATIONRECTANGLE.superclass.constructor.apply(this, [config]);
1009 };
1011 ANNOTATIONRECTANGLE.NAME = "annotationrectangle";
1012 ANNOTATIONRECTANGLE.ATTRS = {};
1014 Y.extend(ANNOTATIONRECTANGLE, M.assignfeedback_editpdf.annotation, {
1015     /**
1016      * Draw a rectangle annotation
1017      * @protected
1018      * @method draw
1019      * @return M.assignfeedback_editpdf.drawable
1020      */
1021     draw: function() {
1022         var drawable,
1023             bounds,
1024             shape;
1026         drawable = new M.assignfeedback_editpdf.drawable(this.editor);
1028         bounds = new M.assignfeedback_editpdf.rect();
1029         bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y),
1030                       new M.assignfeedback_editpdf.point(this.endx, this.endy)]);
1032         shape = this.editor.graphic.addShape({
1033             type: Y.Rect,
1034             width: bounds.width,
1035             height: bounds.height,
1036             stroke: {
1037                weight: STROKEWEIGHT,
1038                color: ANNOTATIONCOLOUR[this.colour]
1039             },
1040             x: bounds.x,
1041             y: bounds.y
1042         });
1043         drawable.shapes.push(shape);
1044         this.drawable = drawable;
1046         return ANNOTATIONRECTANGLE.superclass.draw.apply(this);
1047     },
1049     /**
1050      * Draw the in progress edit.
1051      *
1052      * @public
1053      * @method draw_current_edit
1054      * @param M.assignfeedback_editpdf.edit edit
1055      */
1056     draw_current_edit: function(edit) {
1057         var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
1058             shape,
1059             bounds;
1061         bounds = new M.assignfeedback_editpdf.rect();
1062         bounds.bound([new M.assignfeedback_editpdf.point(edit.start.x, edit.start.y),
1063                       new M.assignfeedback_editpdf.point(edit.end.x, edit.end.y)]);
1065         // Set min. width and height of rectangle.
1066         if (!bounds.has_min_width()) {
1067             bounds.set_min_width();
1068         }
1069         if (!bounds.has_min_height()) {
1070             bounds.set_min_height();
1071         }
1073         shape = this.editor.graphic.addShape({
1074             type: Y.Rect,
1075             width: bounds.width,
1076             height: bounds.height,
1077             stroke: {
1078                weight: STROKEWEIGHT,
1079                color: ANNOTATIONCOLOUR[edit.annotationcolour]
1080             },
1081             x: bounds.x,
1082             y: bounds.y
1083         });
1085         drawable.shapes.push(shape);
1087         return drawable;
1088     }
1089 });
1091 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1092 M.assignfeedback_editpdf.annotationrectangle = ANNOTATIONRECTANGLE;
1093 // This file is part of Moodle - http://moodle.org/
1094 //
1095 // Moodle is free software: you can redistribute it and/or modify
1096 // it under the terms of the GNU General Public License as published by
1097 // the Free Software Foundation, either version 3 of the License, or
1098 // (at your option) any later version.
1099 //
1100 // Moodle is distributed in the hope that it will be useful,
1101 // but WITHOUT ANY WARRANTY; without even the implied warranty of
1102 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1103 // GNU General Public License for more details.
1104 //
1105 // You should have received a copy of the GNU General Public License
1106 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
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/>.
1222 /**
1223  * Provides an in browser PDF editor.
1224  *
1225  * @module moodle-assignfeedback_editpdf-editor
1226  */
1228 /**
1229  * Class representing a pen.
1230  *
1231  * @namespace M.assignfeedback_editpdf
1232  * @class annotationpen
1233  * @extends M.assignfeedback_editpdf.annotation
1234  */
1235 var ANNOTATIONPEN = function(config) {
1236     ANNOTATIONPEN.superclass.constructor.apply(this, [config]);
1237 };
1239 ANNOTATIONPEN.NAME = "annotationpen";
1240 ANNOTATIONPEN.ATTRS = {};
1242 Y.extend(ANNOTATIONPEN, M.assignfeedback_editpdf.annotation, {
1243     /**
1244      * Draw a pen annotation
1245      * @protected
1246      * @method draw
1247      * @return M.assignfeedback_editpdf.drawable
1248      */
1249     draw: function() {
1250         var drawable,
1251             shape,
1252             first,
1253             positions,
1254             xy;
1256         drawable = new M.assignfeedback_editpdf.drawable(this.editor);
1258         shape = this.editor.graphic.addShape({
1259            type: Y.Path,
1260             fill: false,
1261             stroke: {
1262                 weight: STROKEWEIGHT,
1263                 color: ANNOTATIONCOLOUR[this.colour]
1264             }
1265         });
1267         first = true;
1268         // Recreate the pen path array.
1269         positions = this.path.split(':');
1270         // Redraw all the lines.
1271         Y.each(positions, function(position) {
1272             xy = position.split(',');
1273             if (first) {
1274                 shape.moveTo(xy[0], xy[1]);
1275                 first = false;
1276             } else {
1277                 shape.lineTo(xy[0], xy[1]);
1278             }
1279         }, this);
1281         shape.end();
1283         drawable.shapes.push(shape);
1284         this.drawable = drawable;
1286         return ANNOTATIONPEN.superclass.draw.apply(this);
1287     },
1289     /**
1290      * Draw the in progress edit.
1291      *
1292      * @public
1293      * @method draw_current_edit
1294      * @param M.assignfeedback_editpdf.edit edit
1295      */
1296     draw_current_edit: function(edit) {
1297         var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
1298             shape,
1299             first;
1301         shape = this.editor.graphic.addShape({
1302            type: Y.Path,
1303             fill: false,
1304             stroke: {
1305                 weight: STROKEWEIGHT,
1306                 color: ANNOTATIONCOLOUR[edit.annotationcolour]
1307             }
1308         });
1310         first = true;
1311         // Recreate the pen path array.
1312         // Redraw all the lines.
1313         Y.each(edit.path, function(position) {
1314             if (first) {
1315                 shape.moveTo(position.x, position.y);
1316                 first = false;
1317             } else {
1318                 shape.lineTo(position.x, position.y);
1319             }
1320         }, this);
1322         shape.end();
1324         drawable.shapes.push(shape);
1326         return drawable;
1327     },
1330     /**
1331      * Promote the current edit to a real annotation.
1332      *
1333      * @public
1334      * @method init_from_edit
1335      * @param M.assignfeedback_editpdf.edit edit
1336      * @return bool true if pen bound is more than min width/height, else false.
1337      */
1338     init_from_edit: function(edit) {
1339         var bounds = new M.assignfeedback_editpdf.rect(),
1340             pathlist = [],
1341             i = 0;
1343         // This will get the boundaries of all points in the path.
1344         bounds.bound(edit.path);
1346         for (i = 0; i < edit.path.length; i++) {
1347             pathlist.push(parseInt(edit.path[i].x, 10) + ',' + parseInt(edit.path[i].y, 10));
1348         }
1350         this.gradeid = this.editor.get('gradeid');
1351         this.pageno = this.editor.currentpage;
1352         this.x = bounds.x;
1353         this.y = bounds.y;
1354         this.endx = bounds.x + bounds.width;
1355         this.endy = bounds.y + bounds.height;
1356         this.colour = edit.annotationcolour;
1357         this.path = pathlist.join(':');
1359         return (bounds.has_min_width() || bounds.has_min_height());
1360     }
1363 });
1365 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1366 M.assignfeedback_editpdf.annotationpen = ANNOTATIONPEN;
1367 // This file is part of Moodle - http://moodle.org/
1368 //
1369 // Moodle is free software: you can redistribute it and/or modify
1370 // it under the terms of the GNU General Public License as published by
1371 // the Free Software Foundation, either version 3 of the License, or
1372 // (at your option) any later version.
1373 //
1374 // Moodle is distributed in the hope that it will be useful,
1375 // but WITHOUT ANY WARRANTY; without even the implied warranty of
1376 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1377 // GNU General Public License for more details.
1378 //
1379 // You should have received a copy of the GNU General Public License
1380 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
1382 /**
1383  * Provides an in browser PDF editor.
1384  *
1385  * @module moodle-assignfeedback_editpdf-editor
1386 */
1388 /**
1389  * Class representing a highlight.
1390  *
1391  * @namespace M.assignfeedback_editpdf
1392  * @class annotationhighlight
1393  * @extends M.assignfeedback_editpdf.annotation
1394  * @module moodle-assignfeedback_editpdf-editor
1395  */
1396 var ANNOTATIONHIGHLIGHT = function(config) {
1397     ANNOTATIONHIGHLIGHT.superclass.constructor.apply(this, [config]);
1398 };
1400 ANNOTATIONHIGHLIGHT.NAME = "annotationhighlight";
1401 ANNOTATIONHIGHLIGHT.ATTRS = {};
1403 Y.extend(ANNOTATIONHIGHLIGHT, M.assignfeedback_editpdf.annotation, {
1404     /**
1405      * Draw a highlight annotation
1406      * @protected
1407      * @method draw
1408      * @return M.assignfeedback_editpdf.drawable
1409      */
1410     draw: function() {
1411         var drawable,
1412             shape,
1413             bounds,
1414             highlightcolour;
1416         drawable = new M.assignfeedback_editpdf.drawable(this.editor);
1417         bounds = new M.assignfeedback_editpdf.rect();
1418         bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y),
1419                       new M.assignfeedback_editpdf.point(this.endx, this.endy)]);
1421         highlightcolour = ANNOTATIONCOLOUR[this.colour];
1423         // Add an alpha channel to the rgb colour.
1425         highlightcolour = highlightcolour.replace('rgb', 'rgba');
1426         highlightcolour = highlightcolour.replace(')', ',0.5)');
1428         shape = this.editor.graphic.addShape({
1429             type: Y.Rect,
1430             width: bounds.width,
1431             height: bounds.height,
1432             stroke: false,
1433             fill: {
1434                 color: highlightcolour
1435             },
1436             x: bounds.x,
1437             y: bounds.y
1438         });
1440         drawable.shapes.push(shape);
1441         this.drawable = drawable;
1443         return ANNOTATIONHIGHLIGHT.superclass.draw.apply(this);
1444     },
1446     /**
1447      * Draw the in progress edit.
1448      *
1449      * @public
1450      * @method draw_current_edit
1451      * @param M.assignfeedback_editpdf.edit edit
1452      */
1453     draw_current_edit: function(edit) {
1454         var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
1455             shape,
1456             bounds,
1457             highlightcolour;
1459         bounds = new M.assignfeedback_editpdf.rect();
1460         bounds.bound([new M.assignfeedback_editpdf.point(edit.start.x, edit.start.y),
1461                       new M.assignfeedback_editpdf.point(edit.end.x, edit.end.y)]);
1463         // Set min. width of highlight.
1464         if (!bounds.has_min_width()) {
1465             bounds.set_min_width();
1466         }
1468         highlightcolour = ANNOTATIONCOLOUR[edit.annotationcolour];
1469         // Add an alpha channel to the rgb colour.
1471         highlightcolour = highlightcolour.replace('rgb', 'rgba');
1472         highlightcolour = highlightcolour.replace(')', ',0.5)');
1474         // We will draw a box with the current background colour.
1475         shape = this.editor.graphic.addShape({
1476             type: Y.Rect,
1477             width: bounds.width,
1478             height: 20,
1479             stroke: false,
1480             fill: {
1481                color: highlightcolour
1482             },
1483             x: bounds.x,
1484             y: edit.start.y - 10
1485         });
1487         drawable.shapes.push(shape);
1489         return drawable;
1490     },
1492     /**
1493      * Promote the current edit to a real annotation.
1494      *
1495      * @public
1496      * @method init_from_edit
1497      * @param M.assignfeedback_editpdf.edit edit
1498      * @return bool true if highlight bound is more than min width/height, else false.
1499      */
1500     init_from_edit: function(edit) {
1501         var bounds = new M.assignfeedback_editpdf.rect();
1502         bounds.bound([edit.start, edit.end]);
1504         this.gradeid = this.editor.get('gradeid');
1505         this.pageno = this.editor.currentpage;
1506         this.x = bounds.x;
1507         this.y = edit.start.y - 10;
1508         this.endx = bounds.x + bounds.width;
1509         this.endy = edit.start.y + 10;
1510         this.colour = edit.annotationcolour;
1511         this.page = '';
1513         return (bounds.has_min_width());
1514     }
1516 });
1518 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1519 M.assignfeedback_editpdf.annotationhighlight = ANNOTATIONHIGHLIGHT;
1520 // This file is part of Moodle - http://moodle.org/
1521 //
1522 // Moodle is free software: you can redistribute it and/or modify
1523 // it under the terms of the GNU General Public License as published by
1524 // the Free Software Foundation, either version 3 of the License, or
1525 // (at your option) any later version.
1526 //
1527 // Moodle is distributed in the hope that it will be useful,
1528 // but WITHOUT ANY WARRANTY; without even the implied warranty of
1529 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1530 // GNU General Public License for more details.
1531 //
1532 // You should have received a copy of the GNU General Public License
1533 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
1535 /**
1536  * Provides an in browser PDF editor.
1537  *
1538  * @module moodle-assignfeedback_editpdf-editor
1539  */
1541 /**
1542  * Class representing a stamp.
1543  *
1544  * @namespace M.assignfeedback_editpdf
1545  * @class annotationstamp
1546  * @extends M.assignfeedback_editpdf.annotation
1547  */
1548 var ANNOTATIONSTAMP = function(config) {
1549     ANNOTATIONSTAMP.superclass.constructor.apply(this, [config]);
1550 };
1552 ANNOTATIONSTAMP.NAME = "annotationstamp";
1553 ANNOTATIONSTAMP.ATTRS = {};
1555 Y.extend(ANNOTATIONSTAMP, M.assignfeedback_editpdf.annotation, {
1556     /**
1557      * Draw a stamp annotation
1558      * @protected
1559      * @method draw
1560      * @return M.assignfeedback_editpdf.drawable
1561      */
1562     draw: function() {
1563         var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
1564             drawingcanvas = this.editor.get_dialogue_element(SELECTOR.DRAWINGCANVAS),
1565             node,
1566             position;
1568         position = this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(this.x, this.y));
1569         node = Y.Node.create('<div/>');
1570         node.addClass('annotation');
1571         node.addClass('stamp');
1572         node.setStyles({
1573             'position': 'absolute',
1574             'display': 'inline-block',
1575             'backgroundImage': 'url(' + this.editor.get_stamp_image_url(this.path) + ')',
1576             'width': (this.endx - this.x),
1577             'height': (this.endy - this.y),
1578             'backgroundSize': '100% 100%'
1579         });
1581         drawingcanvas.append(node);
1582         node.setX(position.x);
1583         node.setY(position.y);
1584         drawable.store_position(node, position.x, position.y);
1586         // Bind events only when editing.
1587         if (!this.editor.get('readonly')) {
1588             // Pass through the event handlers on the div.
1589             node.on('gesturemovestart', this.editor.edit_start, null, this.editor);
1590             node.on('gesturemove', this.editor.edit_move, null, this.editor);
1591             node.on('gesturemoveend', this.editor.edit_end, null, this.editor);
1592         }
1594         drawable.nodes.push(node);
1596         this.drawable = drawable;
1597         return ANNOTATIONSTAMP.superclass.draw.apply(this);
1598     },
1600     /**
1601      * Draw the in progress edit.
1602      *
1603      * @public
1604      * @method draw_current_edit
1605      * @param M.assignfeedback_editpdf.edit edit
1606      */
1607     draw_current_edit: function(edit) {
1608         var bounds = new M.assignfeedback_editpdf.rect(),
1609             drawable = new M.assignfeedback_editpdf.drawable(this.editor),
1610             drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION),
1611             node,
1612             position;
1614         bounds.bound([edit.start, edit.end]);
1615         position = this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(bounds.x, bounds.y));
1617         node = Y.Node.create('<div/>');
1618         node.addClass('annotation');
1619         node.addClass('stamp');
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         });
1629         drawingregion.append(node);
1630         node.setX(position.x);
1631         node.setY(position.y);
1632         drawable.store_position(node, position.x, position.y);
1634         drawable.nodes.push(node);
1636         return drawable;
1637     },
1639     /**
1640      * Promote the current edit to a real annotation.
1641      *
1642      * @public
1643      * @method init_from_edit
1644      * @param M.assignfeedback_editpdf.edit edit
1645      * @return bool if width/height is more than min. required.
1646      */
1647     init_from_edit: function(edit) {
1648         var bounds = new M.assignfeedback_editpdf.rect();
1649         bounds.bound([edit.start, edit.end]);
1651         if (bounds.width < 40) {
1652             bounds.width = 40;
1653         }
1654         if (bounds.height < 40) {
1655             bounds.height = 40;
1656         }
1657         this.gradeid = this.editor.get('gradeid');
1658         this.pageno = this.editor.currentpage;
1659         this.x = bounds.x;
1660         this.y = bounds.y;
1661         this.endx = bounds.x + bounds.width;
1662         this.endy = bounds.y + bounds.height;
1663         this.colour = edit.annotationcolour;
1664         this.path = edit.stamp;
1666         // Min width and height is always more than 40px.
1667         return true;
1668     },
1670     /**
1671      * Move an annotation to a new location.
1672      * @public
1673      * @param int newx
1674      * @param int newy
1675      * @method move_annotation
1676      */
1677     move: function(newx, newy) {
1678         var diffx = newx - this.x,
1679             diffy = newy - this.y;
1681         this.x += diffx;
1682         this.y += diffy;
1683         this.endx += diffx;
1684         this.endy += diffy;
1686         if (this.drawable) {
1687             this.drawable.erase();
1688         }
1689         this.editor.drawables.push(this.draw());
1690     }
1692 });
1694 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1695 M.assignfeedback_editpdf.annotationstamp = ANNOTATIONSTAMP;
1696 var DROPDOWN_NAME = "Dropdown menu",
1697     DROPDOWN;
1699 /**
1700  * Provides an in browser PDF editor.
1701  *
1702  * @module moodle-assignfeedback_editpdf-editor
1703  */
1705 /**
1706  * This is a drop down list of buttons triggered (and aligned to) a button.
1707  *
1708  * @namespace M.assignfeedback_editpdf
1709  * @class dropdown
1710  * @constructor
1711  * @extends M.core.dialogue
1712  */
1713 DROPDOWN = function(config) {
1714     config.draggable = false;
1715     config.centered = false;
1716     config.width = 'auto';
1717     config.visible = false;
1718     config.footerContent = '';
1719     DROPDOWN.superclass.constructor.apply(this, [config]);
1720 };
1722 Y.extend(DROPDOWN, M.core.dialogue, {
1723     /**
1724      * Initialise the menu.
1725      *
1726      * @method initializer
1727      * @return void
1728      */
1729     initializer: function(config) {
1730         var button, body, headertext, bb;
1731         DROPDOWN.superclass.initializer.call(this, config);
1733         bb = this.get('boundingBox');
1734         bb.addClass('assignfeedback_editpdf_dropdown');
1736         // Align the menu to the button that opens it.
1737         button = this.get('buttonNode');
1739         // Close the menu when clicked outside (excluding the button that opened the menu).
1740         body = this.bodyNode;
1742         headertext = Y.Node.create('<h3/>');
1743         headertext.addClass('accesshide');
1744         headertext.setHTML(this.get('headerText'));
1745         body.prepend(headertext);
1747         body.on('clickoutside', function(e) {
1748             if (this.get('visible')) {
1749                 // Note: we need to compare ids because for some reason - sometimes button is an Object, not a Y.Node.
1750                 if (e.target.get('id') !== button.get('id') && e.target.ancestor().get('id') !== button.get('id')) {
1751                     e.preventDefault();
1752                     this.hide();
1753                 }
1754             }
1755         }, this);
1757         button.on('click', function(e) {
1758             e.preventDefault(); this.show();
1759         }, this);
1760         button.on('key', this.show, 'enter,space', this);
1761     },
1763     /**
1764      * Override the show method to align to the button.
1765      *
1766      * @method show
1767      * @return void
1768      */
1769     show: function() {
1770         var button = this.get('buttonNode'),
1771             result = DROPDOWN.superclass.show.call(this);
1772         this.align(button, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
1774         return result;
1775     }
1776 }, {
1777     NAME: DROPDOWN_NAME,
1778     ATTRS: {
1779         /**
1780          * The header for the drop down (only accessible to screen readers).
1781          *
1782          * @attribute headerText
1783          * @type String
1784          * @default ''
1785          */
1786         headerText: {
1787             value: ''
1788         },
1790         /**
1791          * The button used to show/hide this drop down menu.
1792          *
1793          * @attribute buttonNode
1794          * @type Y.Node
1795          * @default null
1796          */
1797         buttonNode: {
1798             value: null
1799         }
1800     }
1801 });
1803 Y.Base.modifyAttrs(DROPDOWN, {
1804     /**
1805      * Whether the widget should be modal or not.
1806      *
1807      * Moodle override: We override this for commentsearch to force it always false.
1808      *
1809      * @attribute Modal
1810      * @type Boolean
1811      * @default false
1812      */
1813     modal: {
1814         getter: function() {
1815             return false;
1816         }
1817     }
1818 });
1820 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1821 M.assignfeedback_editpdf.dropdown = DROPDOWN;
1822 var COLOURPICKER_NAME = "Colourpicker",
1823     COLOURPICKER;
1825 /**
1826  * Provides an in browser PDF editor.
1827  *
1828  * @module moodle-assignfeedback_editpdf-editor
1829  */
1831 /**
1832  * COLOURPICKER
1833  * This is a drop down list of colours.
1834  *
1835  * @namespace M.assignfeedback_editpdf
1836  * @class colourpicker
1837  * @constructor
1838  * @extends M.assignfeedback_editpdf.dropdown
1839  */
1840 COLOURPICKER = function(config) {
1841     COLOURPICKER.superclass.constructor.apply(this, [config]);
1842 };
1844 Y.extend(COLOURPICKER, M.assignfeedback_editpdf.dropdown, {
1846     /**
1847      * Initialise the menu.
1848      *
1849      * @method initializer
1850      * @return void
1851      */
1852     initializer: function(config) {
1853         var colourlist = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_menu"/>'),
1854             body;
1856         // Build a list of coloured buttons.
1857         Y.each(this.get('colours'), function(rgb, colour) {
1858             var button, listitem, title, img, iconname;
1860             title = M.util.get_string(colour, 'assignfeedback_editpdf');
1861             iconname = this.get('iconprefix') + colour;
1862             img = M.util.image_url(iconname, 'assignfeedback_editpdf');
1863             button = Y.Node.create('<button><img alt="' + title + '" src="' + img + '"/></button>');
1864             button.setAttribute('data-colour', colour);
1865             button.setAttribute('data-rgb', rgb);
1866             button.setAttribute('role', 'menuitem');
1867             button.setStyle('backgroundImage', 'none');
1868             listitem = Y.Node.create('<li/>');
1869             listitem.append(button);
1870             listitem.setAttribute('role', 'none');
1871             colourlist.append(listitem);
1872         }, this);
1874         body = Y.Node.create('<div/>');
1876         // Set the call back.
1877         colourlist.delegate('click', this.callback_handler, 'button', this);
1878         colourlist.delegate('key', this.callback_handler, 'down:13', 'button', this);
1880         // Set the accessible header text.
1881         this.set('headerText', M.util.get_string('colourpicker', 'assignfeedback_editpdf'));
1883         // Set the body content.
1884         body.append(colourlist);
1885         this.set('bodyContent', body);
1887         COLOURPICKER.superclass.initializer.call(this, config);
1888     },
1889     callback_handler: function(e) {
1890         e.preventDefault();
1892         var callback = this.get('callback'),
1893             callbackcontext = this.get('context'),
1894             bind;
1896         this.hide();
1898         // Call the callback with the specified context.
1899         bind = Y.bind(callback, callbackcontext, e);
1901         bind();
1902     }
1903 }, {
1904     NAME: COLOURPICKER_NAME,
1905     ATTRS: {
1906         /**
1907          * The list of colours this colour picker supports.
1908          *
1909          * @attribute colours
1910          * @type {String: String} (The keys of the array are the colour names and the values are localized strings)
1911          * @default {}
1912          */
1913         colours: {
1914             value: {}
1915         },
1917         /**
1918          * The function called when a new colour is chosen.
1919          *
1920          * @attribute callback
1921          * @type function
1922          * @default null
1923          */
1924         callback: {
1925             value: null
1926         },
1928         /**
1929          * The context passed to the callback when a colour is chosen.
1930          *
1931          * @attribute context
1932          * @type Y.Node
1933          * @default null
1934          */
1935         context: {
1936             value: null
1937         },
1939         /**
1940          * The prefix for the icon image names.
1941          *
1942          * @attribute iconprefix
1943          * @type String
1944          * @default 'colour_'
1945          */
1946         iconprefix: {
1947             value: 'colour_'
1948         }
1949     }
1950 });
1952 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1953 M.assignfeedback_editpdf.colourpicker = COLOURPICKER;
1954 var STAMPPICKER_NAME = "Colourpicker",
1955     STAMPPICKER;
1957 /**
1958  * Provides an in browser PDF editor.
1959  *
1960  * @module moodle-assignfeedback_editpdf-editor
1961  */
1963 /**
1964  * This is a drop down list of stamps.
1965  *
1966  * @namespace M.assignfeedback_editpdf
1967  * @class stamppicker
1968  * @constructor
1969  * @extends M.assignfeedback_editpdf.dropdown
1970  */
1971 STAMPPICKER = function(config) {
1972     STAMPPICKER.superclass.constructor.apply(this, [config]);
1973 };
1975 Y.extend(STAMPPICKER, M.assignfeedback_editpdf.dropdown, {
1977     /**
1978      * Initialise the menu.
1979      *
1980      * @method initializer
1981      * @return void
1982      */
1983     initializer: function(config) {
1984         var stamplist = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_menu"/>');
1986         // Build a list of stamped buttons.
1987         Y.each(this.get('stamps'), function(stamp) {
1988             var button, listitem, title;
1990             title = M.util.get_string('stamp', 'assignfeedback_editpdf');
1991             button = Y.Node.create('<button><img height="16" width="16" alt="' + title + '" src="' + stamp + '"/></button>');
1992             button.setAttribute('data-stamp', stamp);
1993             button.setAttribute('role', 'menuitem');
1994             button.setStyle('backgroundImage', 'none');
1995             listitem = Y.Node.create('<li/>');
1996             listitem.append(button);
1997             listitem.setAttribute('role', 'none');
1998             stamplist.append(listitem);
1999         }, this);
2002         // Set the call back.
2003         stamplist.delegate('click', this.callback_handler, 'button', this);
2004         stamplist.delegate('key', this.callback_handler, 'down:13', 'button', this);
2006         // Set the accessible header text.
2007         this.set('headerText', M.util.get_string('stamppicker', 'assignfeedback_editpdf'));
2009         // Set the body content.
2010         this.set('bodyContent', stamplist);
2012         STAMPPICKER.superclass.initializer.call(this, config);
2013     },
2014     callback_handler: function(e) {
2015         e.preventDefault();
2016         var callback = this.get('callback'),
2017             callbackcontext = this.get('context'),
2018             bind;
2020         this.hide();
2022         // Call the callback with the specified context.
2023         bind = Y.bind(callback, callbackcontext, e);
2025         bind();
2026     }
2027 }, {
2028     NAME: STAMPPICKER_NAME,
2029     ATTRS: {
2030         /**
2031          * The list of stamps this stamp picker supports.
2032          *
2033          * @attribute stamps
2034          * @type String[] - the stamp filenames.
2035          * @default {}
2036          */
2037         stamps: {
2038             value: []
2039         },
2041         /**
2042          * The function called when a new stamp is chosen.
2043          *
2044          * @attribute callback
2045          * @type function
2046          * @default null
2047          */
2048         callback: {
2049             value: null
2050         },
2052         /**
2053          * The context passed to the callback when a stamp is chosen.
2054          *
2055          * @attribute context
2056          * @type Y.Node
2057          * @default null
2058          */
2059         context: {
2060             value: null
2061         }
2062     }
2063 });
2065 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
2066 M.assignfeedback_editpdf.stamppicker = STAMPPICKER;
2067 var COMMENTMENUNAME = "Commentmenu",
2068     COMMENTMENU;
2070 /**
2071  * Provides an in browser PDF editor.
2072  *
2073  * @module moodle-assignfeedback_editpdf-editor
2074  */
2076 /**
2077  * COMMENTMENU
2078  * This is a drop down list of comment context functions.
2079  *
2080  * @namespace M.assignfeedback_editpdf
2081  * @class commentmenu
2082  * @constructor
2083  * @extends M.assignfeedback_editpdf.dropdown
2084  */
2085 COMMENTMENU = function(config) {
2086     COMMENTMENU.superclass.constructor.apply(this, [config]);
2087 };
2089 Y.extend(COMMENTMENU, M.assignfeedback_editpdf.dropdown, {
2091     /**
2092      * Initialise the menu.
2093      *
2094      * @method initializer
2095      * @return void
2096      */
2097     initializer: function(config) {
2098         var commentlinks,
2099             link,
2100             body,
2101             comment;
2103         comment = this.get('comment');
2104         // Build the list of menu items.
2105         commentlinks = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_menu"/>');
2107         link = Y.Node.create('<li><a tabindex="-1" href="#">' +
2108                M.util.get_string('addtoquicklist', 'assignfeedback_editpdf') +
2109                '</a></li>');
2110         link.on('click', comment.add_to_quicklist, comment);
2111         link.on('key', comment.add_to_quicklist, 'enter,space', comment);
2113         commentlinks.append(link);
2115         link = Y.Node.create('<li><a tabindex="-1" href="#">' +
2116                M.util.get_string('deletecomment', 'assignfeedback_editpdf') +
2117                '</a></li>');
2118         link.on('click', function(e) {
2119             e.preventDefault();
2120             this.menu.hide();
2121             this.remove();
2122         }, comment);
2124         link.on('key', function() {
2125             comment.menu.hide();
2126             comment.remove();
2127         }, 'enter,space', comment);
2129         commentlinks.append(link);
2131         link = Y.Node.create('<li><hr/></li>');
2132         commentlinks.append(link);
2134         // Set the accessible header text.
2135         this.set('headerText', M.util.get_string('commentcontextmenu', 'assignfeedback_editpdf'));
2137         body = Y.Node.create('<div/>');
2139         // Set the body content.
2140         body.append(commentlinks);
2141         this.set('bodyContent', body);
2143         COMMENTMENU.superclass.initializer.call(this, config);
2144     },
2146     /**
2147      * Show the menu.
2148      *
2149      * @method show
2150      * @return void
2151      */
2152     show: function() {
2153         var commentlinks = this.get('boundingBox').one('ul');
2154             commentlinks.all('.quicklist_comment').remove(true);
2155         var comment = this.get('comment');
2157         comment.deleteme = false; // Cancel the deleting of blank comments.
2159         // Now build the list of quicklist comments.
2160         Y.each(comment.editor.quicklist.comments, function(quickcomment) {
2161             var listitem = Y.Node.create('<li class="quicklist_comment"></li>'),
2162                 linkitem = Y.Node.create('<a href="#" tabindex="-1">' + quickcomment.rawtext + '</a>'),
2163                 deletelinkitem = Y.Node.create('<a href="#" tabindex="-1" class="delete_quicklist_comment">' +
2164                                                '<img src="' + M.util.image_url('t/delete', 'core') + '" ' +
2165                                                'alt="' + M.util.get_string('deletecomment', 'assignfeedback_editpdf') + '"/>' +
2166                                                '</a>');
2167             linkitem.setAttribute('title', quickcomment.rawtext);
2168             listitem.append(linkitem);
2169             listitem.append(deletelinkitem);
2171             commentlinks.append(listitem);
2173             listitem.on('click', comment.set_from_quick_comment, comment, quickcomment);
2174             listitem.on('key', comment.set_from_quick_comment, 'space,enter', comment, quickcomment);
2176             deletelinkitem.on('click', comment.remove_from_quicklist, comment, quickcomment);
2177             deletelinkitem.on('key', comment.remove_from_quicklist, 'space,enter', comment, quickcomment);
2178         }, this);
2180         COMMENTMENU.superclass.show.call(this);
2181     }
2182 }, {
2183     NAME: COMMENTMENUNAME,
2184     ATTRS: {
2185         /**
2186          * The comment this menu is attached to.
2187          *
2188          * @attribute comment
2189          * @type M.assignfeedback_editpdf.comment
2190          * @default null
2191          */
2192         comment: {
2193             value: null
2194         }
2196     }
2197 });
2199 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
2200 M.assignfeedback_editpdf.commentmenu = COMMENTMENU;
2201 /* eslint-disable no-unused-vars */
2202 var COMMENTSEARCHNAME = "commentsearch",
2203     COMMENTSEARCH;
2205 /**
2206  * Provides an in browser PDF editor.
2207  *
2208  * @module moodle-assignfeedback_editpdf-editor
2209  */
2211 /**
2212  * This is a searchable dialogue of comments.
2213  *
2214  * @namespace M.assignfeedback_editpdf
2215  * @class commentsearch
2216  * @constructor
2217  * @extends M.core.dialogue
2218  */
2219 COMMENTSEARCH = function(config) {
2220     config.draggable = false;
2221     config.centered = true;
2222     config.width = '400px';
2223     config.visible = false;
2224     config.headerContent = M.util.get_string('searchcomments', 'assignfeedback_editpdf');
2225     config.footerContent = '';
2226     COMMENTSEARCH.superclass.constructor.apply(this, [config]);
2227 };
2229 Y.extend(COMMENTSEARCH, M.core.dialogue, {
2230     /**
2231      * Initialise the menu.
2232      *
2233      * @method initializer
2234      * @return void
2235      */
2236     initializer: function(config) {
2237         var editor,
2238             container,
2239             placeholder,
2240             commentfilter,
2241             commentlist,
2242             bb;
2244         bb = this.get('boundingBox');
2245         bb.addClass('assignfeedback_editpdf_commentsearch');
2247         editor = this.get('editor');
2248         container = Y.Node.create('<div/>');
2250         placeholder = M.util.get_string('filter', 'assignfeedback_editpdf');
2251         commentfilter = Y.Node.create('<input type="text" size="20" placeholder="' + placeholder + '"/>');
2252         container.append(commentfilter);
2253         commentlist = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_search"/>');
2254         container.append(commentlist);
2256         commentfilter.on('keyup', this.filter_search_comments, this);
2257         commentlist.delegate('click', this.focus_on_comment, 'a', this);
2258         commentlist.delegate('key', this.focus_on_comment, 'enter,space', 'a', this);
2260         // Set the body content.
2261         this.set('bodyContent', container);
2263         COMMENTSEARCH.superclass.initializer.call(this, config);
2264     },
2266     /**
2267      * Event handler to filter the list of comments.
2268      *
2269      * @protected
2270      * @method filter_search_comments
2271      */
2272     filter_search_comments: function() {
2273         var filternode,
2274             commentslist,
2275             filtertext,
2276             dialogueid;
2278         dialogueid = this.get('id');
2279         filternode = Y.one('#' + dialogueid + SELECTOR.SEARCHFILTER);
2280         commentslist = Y.one('#' + dialogueid + SELECTOR.SEARCHCOMMENTSLIST);
2282         filtertext = filternode.get('value');
2284         commentslist.all('li').each(function(node) {
2285             if (node.get('text').indexOf(filtertext) !== -1) {
2286                 node.show();
2287             } else {
2288                 node.hide();
2289             }
2290         });
2291     },
2293     /**
2294      * Event handler to focus on a selected comment.
2295      *
2296      * @param Event e
2297      * @protected
2298      * @method focus_on_comment
2299      */
2300     focus_on_comment: function(e) {
2301         e.preventDefault();
2302         var target = e.target.ancestor('li'),
2303             comment = target.getData('comment'),
2304             editor = this.get('editor');
2306         this.hide();
2308         comment.pageno = comment.clean().pageno;
2309         if (comment.pageno !== editor.currentpage) {
2310             // Comment is on a different page.
2311             editor.currentpage = comment.pageno;
2312             editor.change_page();
2313         }
2315         comment.node = comment.drawable.nodes[0].one('textarea');
2316         comment.node.ancestor('div').removeClass('commentcollapsed');
2317         comment.node.focus();
2318     },
2320     /**
2321      * Show the menu.
2322      *
2323      * @method show
2324      * @return void
2325      */
2326     show: function() {
2327         var commentlist = this.get('boundingBox').one('ul'),
2328             editor = this.get('editor');
2330         commentlist.all('li').remove(true);
2332         // Rebuild the latest list of comments.
2333         Y.each(editor.pages, function(page) {
2334             Y.each(page.comments, function(comment) {
2335                 var commentnode = Y.Node.create('<li><a href="#" tabindex="-1"><pre>' + comment.rawtext + '</pre></a></li>');
2336                 commentlist.append(commentnode);
2337                 commentnode.setData('comment', comment);
2338             }, this);
2339         }, this);
2341         this.centerDialogue();
2342         COMMENTSEARCH.superclass.show.call(this);
2343     }
2344 }, {
2345     NAME: COMMENTSEARCHNAME,
2346     ATTRS: {
2347         /**
2348          * The editor this search window is attached to.
2349          *
2350          * @attribute editor
2351          * @type M.assignfeedback_editpdf.editor
2352          * @default null
2353          */
2354         editor: {
2355             value: null
2356         }
2358     }
2359 });
2361 Y.Base.modifyAttrs(COMMENTSEARCH, {
2362     /**
2363      * Whether the widget should be modal or not.
2364      *
2365      * Moodle override: We override this for commentsearch to force it always true.
2366      *
2367      * @attribute Modal
2368      * @type Boolean
2369      * @default true
2370      */
2371     modal: {
2372         getter: function() {
2373             return true;
2374         }
2375     }
2376 });
2378 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
2379 M.assignfeedback_editpdf.commentsearch = COMMENTSEARCH;
2380 // This file is part of Moodle - http://moodle.org/
2381 //
2382 // Moodle is free software: you can redistribute it and/or modify
2383 // it under the terms of the GNU General Public License as published by
2384 // the Free Software Foundation, either version 3 of the License, or
2385 // (at your option) any later version.
2386 //
2387 // Moodle is distributed in the hope that it will be useful,
2388 // but WITHOUT ANY WARRANTY; without even the implied warranty of
2389 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
2390 // GNU General Public License for more details.
2391 //
2392 // You should have received a copy of the GNU General Public License
2393 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
2395 /**
2396  * Provides an in browser PDF editor.
2397  *
2398  * @module moodle-assignfeedback_editpdf-editor
2399  */
2401 /**
2402  * Class representing a list of comments.
2403  *
2404  * @namespace M.assignfeedback_editpdf
2405  * @class comment
2406  * @param M.assignfeedback_editpdf.editor editor
2407  * @param Int gradeid
2408  * @param Int pageno
2409  * @param Int x
2410  * @param Int y
2411  * @param Int width
2412  * @param String colour
2413  * @param String rawtext
2414  */
2415 var COMMENT = function(editor, gradeid, pageno, x, y, width, colour, rawtext) {
2417     /**
2418      * Reference to M.assignfeedback_editpdf.editor.
2419      * @property editor
2420      * @type M.assignfeedback_editpdf.editor
2421      * @public
2422      */
2423     this.editor = editor;
2425     /**
2426      * Grade id
2427      * @property gradeid
2428      * @type Int
2429      * @public
2430      */
2431     this.gradeid = gradeid || 0;
2433     /**
2434      * X position
2435      * @property x
2436      * @type Int
2437      * @public
2438      */
2439     this.x = parseInt(x, 10) || 0;
2441     /**
2442      * Y position
2443      * @property y
2444      * @type Int
2445      * @public
2446      */
2447     this.y = parseInt(y, 10) || 0;
2449     /**
2450      * Comment width
2451      * @property width
2452      * @type Int
2453      * @public
2454      */
2455     this.width = parseInt(width, 10) || 0;
2457     /**
2458      * Comment rawtext
2459      * @property rawtext
2460      * @type String
2461      * @public
2462      */
2463     this.rawtext = rawtext || '';
2465     /**
2466      * Comment page number
2467      * @property pageno
2468      * @type Int
2469      * @public
2470      */
2471     this.pageno = pageno || 0;
2473     /**
2474      * Comment background colour.
2475      * @property colour
2476      * @type String
2477      * @public
2478      */
2479     this.colour = colour || 'yellow';
2481     /**
2482      * Reference to M.assignfeedback_editpdf.drawable
2483      * @property drawable
2484      * @type M.assignfeedback_editpdf.drawable
2485      * @public
2486      */
2487     this.drawable = false;
2489     /**
2490      * Boolean used by a timeout to delete empty comments after a short delay.
2491      * @property deleteme
2492      * @type Boolean
2493      * @public
2494      */
2495     this.deleteme = false;
2497     /**
2498      * Reference to the link that opens the menu.
2499      * @property menulink
2500      * @type Y.Node
2501      * @public
2502      */
2503     this.menulink = null;
2505     /**
2506      * Reference to the dialogue that is the context menu.
2507      * @property menu
2508      * @type M.assignfeedback_editpdf.dropdown
2509      * @public
2510      */
2511     this.menu = null;
2513     /**
2514      * Clean a comment record, returning an oject with only fields that are valid.
2515      * @public
2516      * @method clean
2517      * @return {}
2518      */
2519     this.clean = function() {
2520         return {
2521             gradeid: this.gradeid,
2522             x: parseInt(this.x, 10),
2523             y: parseInt(this.y, 10),
2524             width: parseInt(this.width, 10),
2525             rawtext: this.rawtext,
2526             pageno: parseInt(this.pageno, 10),
2527             colour: this.colour
2528         };
2529     };
2531     /**
2532      * Draw a comment.
2533      * @public
2534      * @method draw_comment
2535      * @param boolean focus - Set the keyboard focus to the new comment if true
2536      * @return M.assignfeedback_editpdf.drawable
2537      */
2538     this.draw = function(focus) {
2539         var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
2540             node,
2541             drawingcanvas = this.editor.get_dialogue_element(SELECTOR.DRAWINGCANVAS),
2542             container,
2543             label,
2544             marker,
2545             menu,
2546             position,
2547             scrollheight;
2549         // Lets add a contenteditable div.
2550         node = Y.Node.create('<textarea/>');
2551         container = Y.Node.create('<div class="commentdrawable"/>');
2552         label = Y.Node.create('<label/>');
2553         marker = Y.Node.create('<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.5 -0.5 13 13" ' +
2554                 'preserveAspectRatio="xMinYMin meet">' +
2555                 '<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" ' +
2556                 'fill="currentColor" opacity="0.9" stroke="rgb(153, 153, 153)" stroke-width="0.5"/></svg>');
2557         menu = Y.Node.create('<a href="#"><img src="' + M.util.image_url('t/contextmenu', 'core') + '"/></a>');
2559         this.menulink = menu;
2560         container.append(label);
2561         label.append(node);
2562         container.append(marker);
2563         container.setAttribute('tabindex', '-1');
2564         label.setAttribute('tabindex', '0');
2565         node.setAttribute('tabindex', '-1');
2566         menu.setAttribute('tabindex', '0');
2568         if (!this.editor.get('readonly')) {
2569             container.append(menu);
2570         } else {
2571             node.setAttribute('readonly', 'readonly');
2572         }
2573         if (this.width < 100) {
2574             this.width = 100;
2575         }
2577         position = this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(this.x, this.y));
2578         node.setStyles({
2579             width: this.width + 'px',
2580             backgroundColor: COMMENTCOLOUR[this.colour],
2581             color: COMMENTTEXTCOLOUR
2582         });
2584         drawingcanvas.append(container);
2585         container.setStyle('position', 'absolute');
2586         container.setX(position.x);
2587         container.setY(position.y);
2588         drawable.store_position(container, position.x, position.y);
2589         drawable.nodes.push(container);
2590         node.set('value', this.rawtext);
2591         scrollheight = node.get('scrollHeight');
2592         node.setStyles({
2593             'height': scrollheight + 'px',
2594             'overflow': 'hidden'
2595         });
2596         marker.setStyle('color', COMMENTCOLOUR[this.colour]);
2597         this.attach_events(node, menu);
2598         if (focus) {
2599             node.focus();
2600         } else if (editor.collapsecomments) {
2601             container.addClass('commentcollapsed');
2602         }
2603         this.drawable = drawable;
2606         return drawable;
2607     };
2609     /**
2610      * Delete an empty comment if it's menu hasn't been opened in time.
2611      * @method delete_comment_later
2612      */
2613     this.delete_comment_later = function() {
2614         if (this.deleteme && !this.is_menu_active()) {
2615             this.remove();
2616         }
2617     };
2619     /**
2620      * Returns true if the menu is active, false otherwise.
2621      *
2622      * @return bool true if menu is active, else false.
2623      */
2624     this.is_menu_active = function() {
2625         return this.menu.get('visible');
2626     };
2628     /**
2629      * Comment nodes have a bunch of event handlers attached to them directly.
2630      * This is all done here for neatness.
2631      *
2632      * @protected
2633      * @method attach_comment_events
2634      * @param node - The Y.Node representing the comment.
2635      * @param menu - The Y.Node representing the menu.
2636      */
2637     this.attach_events = function(node, menu) {
2638         var container = node.ancestor('div'),
2639             label = node.ancestor('label'),
2640             marker = label.next('svg');
2642         // Function to collapse a comment to a marker icon.
2643         node.collapse = function(delay) {
2644             node.collapse.delay = Y.later(delay, node, function() {
2645                 if (editor.collapsecomments && !this.is_menu_active()) {
2646                     container.addClass('commentcollapsed');
2647                 }
2648             }.bind(this));
2649         }.bind(this);
2651         // Function to expand a comment.
2652         node.expand = function() {
2653             if (node.getData('dragging') !== true) {
2654                 if (node.collapse.delay) {
2655                     node.collapse.delay.cancel();
2656                 }
2657                 container.removeClass('commentcollapsed');
2658             }
2659         };
2661         // Expand comment on mouse over (under certain conditions) or click/tap.
2662         container.on('mouseenter', function() {
2663             if (editor.currentedit.tool === 'comment' || editor.currentedit.tool === 'select' || this.editor.get('readonly')) {
2664                 node.expand();
2665             }
2666         }, this);
2667         container.on('click|tap', function() {
2668             node.expand();
2669             node.focus();
2670         }, this);
2672         // Functions to capture reverse tabbing events.
2673         node.on('keyup', function(e) {
2674             if (e.keyCode === 9 && e.shiftKey && menu.getAttribute('tabindex') === '0') {
2675                 // User landed here via Shift+Tab (but not from this comment's menu).
2676                 menu.focus();
2677             }
2678             menu.setAttribute('tabindex', '0');
2679         }, this);
2680         menu.on('keydown', function(e) {
2681             if (e.keyCode === 9 && e.shiftKey) {
2682                 // User is tabbing back to the comment node from its own menu.
2683                 menu.setAttribute('tabindex', '-1');
2684             }
2685         }, this);
2687         // Comment becomes "active" on label or menu focus.
2688         label.on('focus', function() {
2689             node.active = true;
2690             if (node.collapse.delay) {
2691                 node.collapse.delay.cancel();
2692             }
2693             // Give comment a tabindex to prevent focus outline being suppressed.
2694             node.setAttribute('tabindex', '0');
2695             // Expand comment and pass focus to it.
2696             node.expand();
2697             node.focus();
2698             // Now remove label tabindex so user can reverse tab past it.
2699             label.setAttribute('tabindex', '-1');
2700         }, this);
2701         menu.on('focus', function() {
2702             node.active = true;
2703             if (node.collapse.delay) {
2704                 node.collapse.delay.cancel();
2705             }
2706             this.deleteme = false;
2707             // Restore label tabindex so user can tab back to it from menu.
2708             label.setAttribute('tabindex', '0');
2709         }, this);
2711         // Always restore the default tabindex states when moving away.
2712         node.on('blur', function() {
2713             node.setAttribute('tabindex', '-1');
2714         }, this);
2715         label.on('blur', function() {
2716             label.setAttribute('tabindex', '0');
2717         }, this);
2719         // Collapse comment on mouse out if not currently active.
2720         container.on('mouseleave', function() {
2721             if (editor.collapsecomments && node.active !== true) {
2722                 node.collapse(400);
2723             }
2724         }, this);
2726         // Collapse comment on blur.
2727         container.on('blur', function() {
2728             node.active = false;
2729             node.collapse(800);
2730         }, this);
2732         if (!this.editor.get('readonly')) {
2733             // Save the text on blur.
2734             node.on('blur', function() {
2735                 // Save the changes back to the comment.
2736                 this.rawtext = node.get('value');
2737                 this.width = parseInt(node.getStyle('width'), 10);
2739                 // Trim.
2740                 if (this.rawtext.replace(/^\s+|\s+$/g, "") === '') {
2741                     // Delete empty comments.
2742                     this.deleteme = true;
2743                     Y.later(400, this, this.delete_comment_later);
2744                 }
2745                 this.editor.save_current_page();
2746                 this.editor.editingcomment = false;
2747             }, this);
2749             // For delegated event handler.
2750             menu.setData('comment', this);
2752             node.on('keyup', function() {
2753                 node.setStyle('height', 'auto');
2754                 var scrollheight = node.get('scrollHeight'),
2755                     height = parseInt(node.getStyle('height'), 10);
2757                 // Webkit scrollheight fix.
2758                 if (scrollheight === height + 8) {
2759                     scrollheight -= 8;
2760                 }
2761                 node.setStyle('height', scrollheight + 'px');
2762             });
2764             node.on('gesturemovestart', function(e) {
2765                 if (editor.currentedit.tool === 'select') {
2766                     e.preventDefault();
2767                     if (editor.collapsecomments) {
2768                         node.setData('offsetx', 8);
2769                         node.setData('offsety', 8);
2770                     } else {
2771                         node.setData('offsetx', e.clientX - container.getX());
2772                         node.setData('offsety', e.clientY - container.getY());
2773                     }
2774                 }
2775             });
2776             node.on('gesturemove', function(e) {
2777                 if (editor.currentedit.tool === 'select') {
2778                     var x = e.clientX - node.getData('offsetx'),
2779                         y = e.clientY - node.getData('offsety'),
2780                         newlocation,
2781                         windowlocation,
2782                         bounds;
2784                     if (node.getData('dragging') !== true) {
2785                         // Collapse comment during move.
2786                         node.collapse(0);
2787                         node.setData('dragging', true);
2788                     }
2790                     newlocation = this.editor.get_canvas_coordinates(new M.assignfeedback_editpdf.point(x, y));
2791                     bounds = this.editor.get_canvas_bounds(true);
2792                     bounds.x = 0;
2793                     bounds.y = 0;
2795                     bounds.width -= 24;
2796                     bounds.height -= 24;
2797                     // Clip to the window size - the comment icon size.
2798                     newlocation.clip(bounds);
2800                     this.x = newlocation.x;
2801                     this.y = newlocation.y;
2803                     windowlocation = this.editor.get_window_coordinates(newlocation);
2804                     container.setX(windowlocation.x);
2805                     container.setY(windowlocation.y);
2806                     this.drawable.store_position(container, windowlocation.x, windowlocation.y);
2807                 }
2808             }, null, this);
2809             node.on('gesturemoveend', function() {
2810                 if (editor.currentedit.tool === 'select') {
2811                     if (node.getData('dragging') === true) {
2812                         node.setData('dragging', false);
2813                     }
2814                     this.editor.save_current_page();
2815                 }
2816             }, null, this);
2817             marker.on('gesturemovestart', function(e) {
2818                 if (editor.currentedit.tool === 'select') {
2819                     e.preventDefault();
2820                     node.setData('offsetx', e.clientX - container.getX());
2821                     node.setData('offsety', e.clientY - container.getY());
2822                     node.expand();
2823                 }
2824             });
2825             marker.on('gesturemove', function(e) {
2826                 if (editor.currentedit.tool === 'select') {
2827                     var x = e.clientX - node.getData('offsetx'),
2828                         y = e.clientY - node.getData('offsety'),
2829                         newlocation,
2830                         windowlocation,
2831                         bounds;
2833                     if (node.getData('dragging') !== true) {
2834                         // Collapse comment during move.
2835                         node.collapse(100);
2836                         node.setData('dragging', true);
2837                     }
2839                     newlocation = this.editor.get_canvas_coordinates(new M.assignfeedback_editpdf.point(x, y));
2840                     bounds = this.editor.get_canvas_bounds(true);
2841                     bounds.x = 0;
2842                     bounds.y = 0;
2844                     bounds.width -= 24;
2845                     bounds.height -= 24;
2846                     // Clip to the window size - the comment icon size.
2847                     newlocation.clip(bounds);
2849                     this.x = newlocation.x;
2850                     this.y = newlocation.y;
2852                     windowlocation = this.editor.get_window_coordinates(newlocation);
2853                     container.setX(windowlocation.x);
2854                     container.setY(windowlocation.y);
2855                     this.drawable.store_position(container, windowlocation.x, windowlocation.y);
2856                 }
2857             }, null, this);
2858             marker.on('gesturemoveend', function() {
2859                 if (editor.currentedit.tool === 'select') {
2860                     if (node.getData('dragging') === true) {
2861                         node.setData('dragging', false);
2862                     }
2863                     this.editor.save_current_page();
2864                 }
2865             }, null, this);
2867             this.menu = new M.assignfeedback_editpdf.commentmenu({
2868                 buttonNode: this.menulink,
2869                 comment: this
2870             });
2871         }
2872     };
2874     /**
2875      * Delete a comment.
2876      * @method remove
2877      */
2878     this.remove = function() {
2879         var i = 0;
2880         var comments;
2882         comments = this.editor.pages[this.editor.currentpage].comments;
2883         for (i = 0; i < comments.length; i++) {
2884             if (comments[i] === this) {
2885                 comments.splice(i, 1);
2886                 this.drawable.erase();
2887                 this.editor.save_current_page();
2888                 return;
2889             }
2890         }
2891     };
2893     /**
2894      * Event handler to remove a comment from the users quicklist.
2895      *
2896      * @protected
2897      * @method remove_from_quicklist
2898      */
2899     this.remove_from_quicklist = function(e, quickcomment) {
2900         e.preventDefault();
2901         e.stopPropagation();
2903         this.menu.hide();
2905         this.editor.quicklist.remove(quickcomment);
2906     };
2908     /**
2909      * A quick comment was selected in the list, update the active comment and redraw the page.
2910      *
2911      * @param Event e
2912      * @protected
2913      * @method set_from_quick_comment
2914      */
2915     this.set_from_quick_comment = function(e, quickcomment) {
2916         e.preventDefault();
2918         this.menu.hide();
2919         this.deleteme = false;
2921         this.rawtext = quickcomment.rawtext;
2922         this.width = quickcomment.width;
2923         this.colour = quickcomment.colour;
2925         this.editor.save_current_page();
2927         this.editor.redraw();
2929         this.node = this.drawable.nodes[0].one('textarea');
2930         this.node.ancestor('div').removeClass('commentcollapsed');
2931         this.node.focus();
2932     };
2934     /**
2935      * Event handler to add a comment to the users quicklist.
2936      *
2937      * @protected
2938      * @method add_to_quicklist
2939      */
2940     this.add_to_quicklist = function(e) {
2941         e.preventDefault();
2942         this.menu.hide();
2943         this.editor.quicklist.add(this);
2944     };
2946     /**
2947      * Draw the in progress edit.
2948      *
2949      * @public
2950      * @method draw_current_edit
2951      * @param M.assignfeedback_editpdf.edit edit
2952      */
2953     this.draw_current_edit = function(edit) {
2954         var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
2955             shape,
2956             bounds;
2958         bounds = new M.assignfeedback_editpdf.rect();
2959         bounds.bound([edit.start, edit.end]);
2961         // We will draw a box with the current background colour.
2962         shape = this.editor.graphic.addShape({
2963             type: Y.Rect,
2964             width: bounds.width,
2965             height: bounds.height,
2966             fill: {
2967                color: COMMENTCOLOUR[edit.commentcolour]
2968             },
2969             x: bounds.x,
2970             y: bounds.y
2971         });
2973         drawable.shapes.push(shape);
2975         return drawable;
2976     };
2978     /**
2979      * Promote the current edit to a real comment.
2980      *
2981      * @public
2982      * @method init_from_edit
2983      * @param M.assignfeedback_editpdf.edit edit
2984      * @return bool true if comment bound is more than min width/height, else false.
2985      */
2986     this.init_from_edit = function(edit) {
2987         var bounds = new M.assignfeedback_editpdf.rect();
2988         bounds.bound([edit.start, edit.end]);
2990         // Minimum comment width.
2991         if (bounds.width < 100) {
2992             bounds.width = 100;
2993         }
2995         // Save the current edit to the server and the current page list.
2997         this.gradeid = this.editor.get('gradeid');
2998         this.pageno = this.editor.currentpage;
2999         this.x = bounds.x;
3000         this.y = bounds.y;
3001         this.width = bounds.width;
3002         this.colour = edit.commentcolour;
3003         this.rawtext = '';
3005         return (bounds.has_min_width() && bounds.has_min_height());
3006     };
3008     /**
3009      * Update comment position when rotating page.
3010      * @public
3011      * @method updatePosition
3012      */
3013     this.updatePosition = function() {
3014         var node = this.drawable.nodes[0].one('textarea');
3015         var container = node.ancestor('div');
3017         var newlocation = new M.assignfeedback_editpdf.point(this.x, this.y);
3018         var windowlocation = this.editor.get_window_coordinates(newlocation);
3020         container.setX(windowlocation.x);
3021         container.setY(windowlocation.y);
3022         this.drawable.store_position(container, windowlocation.x, windowlocation.y);
3023     };
3025 };
3027 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
3028 M.assignfeedback_editpdf.comment = COMMENT;
3029 // This file is part of Moodle - http://moodle.org/
3030 //
3031 // Moodle is free software: you can redistribute it and/or modify
3032 // it under the terms of the GNU General Public License as published by
3033 // the Free Software Foundation, either version 3 of the License, or
3034 // (at your option) any later version.
3035 //
3036 // Moodle is distributed in the hope that it will be useful,
3037 // but WITHOUT ANY WARRANTY; without even the implied warranty of
3038 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
3039 // GNU General Public License for more details.
3040 //
3041 // You should have received a copy of the GNU General Public License
3042 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
3044 /**
3045  * Provides an in browser PDF editor.
3046  *
3047  * @module moodle-assignfeedback_editpdf-editor
3048  */
3050 /**
3051  * Class representing a users quick comment.
3052  *
3053  * @namespace M.assignfeedback_editpdf
3054  * @class quickcomment
3055  */
3056 var QUICKCOMMENT = function(id, rawtext, width, colour) {
3058     /**
3059      * Quick comment text.
3060      * @property rawtext
3061      * @type String
3062      * @public
3063      */
3064     this.rawtext = rawtext || '';
3066     /**
3067      * ID of the comment
3068      * @property id
3069      * @type Int
3070      * @public
3071      */
3072     this.id = id || 0;
3074     /**
3075      * Width of the comment
3076      * @property width
3077      * @type Int
3078      * @public
3079      */
3080     this.width = width || 100;
3082     /**
3083      * Colour of the comment.
3084      * @property colour
3085      * @type String
3086      * @public
3087      */
3088     this.colour = colour || "yellow";
3089 };
3091 M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
3092 M.assignfeedback_editpdf.quickcomment = QUICKCOMMENT;
3093 // This file is part of Moodle - http://moodle.org/
3094 //
3095 // Moodle is free software: you can redistribute it and/or modify
3096 // it under the terms of the GNU General Public License as published by
3097 // the Free Software Foundation, either version 3 of the License, or
3098 // (at your option) any later version.
3099 //
3100 // Moodle is distributed in the hope that it will be useful,
3101 // but WITHOUT ANY WARRANTY; without even the implied warranty of