1
YUI.add('moodle-assignfeedback_editpdf-editor', function (Y, NAME) {
3
// This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
19
* A list of globals used by this module.
21
* @module moodle-assignfeedback_editpdf-editor
23
var AJAXBASE = M.cfg.wwwroot + '/mod/assign/feedback/editpdf/ajax.php',
24
AJAXBASEPROGRESS = M.cfg.wwwroot + '/mod/assign/feedback/editpdf/ajax_progress.php',
26
DIALOGUE : 'assignfeedback_editpdf_widget'
29
PREVIOUSBUTTON : '.' + CSS.DIALOGUE + ' .navigate-previous-button',
30
NEXTBUTTON : '.' + CSS.DIALOGUE + ' .navigate-next-button',
31
SEARCHCOMMENTSBUTTON : '.' + CSS.DIALOGUE + ' .searchcommentsbutton',
32
SEARCHFILTER : '.assignfeedback_editpdf_commentsearch input',
33
SEARCHCOMMENTSLIST : '.assignfeedback_editpdf_commentsearch ul',
34
PAGESELECT : '.' + CSS.DIALOGUE + ' .navigate-page-select',
35
LOADINGICON : '.' + CSS.DIALOGUE + ' .loading',
36
PROGRESSBARCONTAINER : '.' + CSS.DIALOGUE + ' .progress-info.progress-striped',
37
DRAWINGREGION : '.' + CSS.DIALOGUE + ' .drawingregion',
38
DRAWINGCANVAS : '.' + CSS.DIALOGUE + ' .drawingcanvas',
39
SAVE : '.' + CSS.DIALOGUE + ' .savebutton',
40
COMMENTCOLOURBUTTON : '.' + CSS.DIALOGUE + ' .commentcolourbutton',
41
COMMENTMENU : ' .commentdrawable a',
42
ANNOTATIONCOLOURBUTTON : '.' + CSS.DIALOGUE + ' .annotationcolourbutton',
43
DELETEANNOTATIONBUTTON : '.' + CSS.DIALOGUE + ' .deleteannotationbutton',
44
UNSAVEDCHANGESDIV : '.assignfeedback_editpdf_unsavedchanges',
45
STAMPSBUTTON : '.' + CSS.DIALOGUE + ' .currentstampbutton',
46
DIALOGUE : '.' + CSS.DIALOGUE
48
SELECTEDBORDERCOLOUR = 'rgba(200, 200, 255, 0.9)',
49
SELECTEDFILLCOLOUR = 'rgba(200, 200, 255, 0.5)',
50
COMMENTTEXTCOLOUR = 'rgb(51, 51, 51)',
52
'white' : 'rgb(255,255,255)',
53
'yellow' : 'rgb(255,236,174)',
54
'red' : 'rgb(249,181,179)',
55
'green' : 'rgb(214,234,178)',
56
'blue' : 'rgb(203,217,237)',
57
'clear' : 'rgba(255,255,255, 0)'
60
'white' : 'rgb(255,255,255)',
61
'yellow' : 'rgb(255,207,53)',
62
'red' : 'rgb(239,69,64)',
63
'green' : 'rgb(152,202,62)',
64
'blue' : 'rgb(125,159,211)',
65
'black' : 'rgb(51,51,51)'
69
'comment': '.' + CSS.DIALOGUE + ' .commentbutton',
70
'pen': '.' + CSS.DIALOGUE + ' .penbutton',
71
'line': '.' + CSS.DIALOGUE + ' .linebutton',
72
'rectangle': '.' + CSS.DIALOGUE + ' .rectanglebutton',
73
'oval': '.' + CSS.DIALOGUE + ' .ovalbutton',
74
'stamp': '.' + CSS.DIALOGUE + ' .stampbutton',
75
'select': '.' + CSS.DIALOGUE + ' .selectbutton',
76
'highlight': '.' + CSS.DIALOGUE + ' .highlightbutton'
79
// This file is part of Moodle - http://moodle.org/
81
// Moodle is free software: you can redistribute it and/or modify
82
// it under the terms of the GNU General Public License as published by
83
// the Free Software Foundation, either version 3 of the License, or
84
// (at your option) any later version.
86
// Moodle is distributed in the hope that it will be useful,
87
// but WITHOUT ANY WARRANTY; without even the implied warranty of
88
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
89
// GNU General Public License for more details.
91
// You should have received a copy of the GNU General Public License
92
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
95
* Class representing a 2d point.
97
* @module moodle-assignfeedback_editpdf-editor
103
* @namespace M.assignfeedback_editpdf
108
POINT = function(x, y) {
116
this.x = parseInt(x, 10);
124
this.y = parseInt(y, 10);
127
* Clip this point to the rect
129
* @param M.assignfeedback_editpdf.point
132
this.clip = function(bounds) {
133
if (this.x < bounds.x) {
136
if (this.x > (bounds.x + bounds.width)) {
137
this.x = bounds.x + bounds.width;
139
if (this.y < bounds.y) {
142
if (this.y > (bounds.y + bounds.height)) {
143
this.y = bounds.y + bounds.height;
150
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
151
M.assignfeedback_editpdf.point = POINT;
152
// This file is part of Moodle - http://moodle.org/
154
// Moodle is free software: you can redistribute it and/or modify
155
// it under the terms of the GNU General Public License as published by
156
// the Free Software Foundation, either version 3 of the License, or
157
// (at your option) any later version.
159
// Moodle is distributed in the hope that it will be useful,
160
// but WITHOUT ANY WARRANTY; without even the implied warranty of
161
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
162
// GNU General Public License for more details.
164
// You should have received a copy of the GNU General Public License
165
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
168
* Class representing a 2d rect.
170
* @module moodle-assignfeedback_editpdf-editor
176
* @namespace M.assignfeedback_editpdf
183
RECT = function(x, y, width, height) {
215
this.height = height;
218
* Set this rect to represent the smallest possible rectangle containing this list of points.
220
* @param M.assignfeedback_editpdf.point[]
223
this.bound = function(points) {
231
for (i = 0; i < points.length; i++) {
233
if (point.x < minx || i === 0) {
236
if (point.x > maxx || i === 0) {
239
if (point.y < miny || i === 0) {
242
if (point.y > maxy || i === 0) {
248
this.width = maxx - minx;
249
this.height = maxy - miny;
255
* Checks if rect has min width.
256
* @method has_min_width
257
* @return bool true if width is more than 5px.
260
this.has_min_width = function() {
261
return (this.width >= 5);
265
* Checks if rect has min height.
266
* @method has_min_height
267
* @return bool true if height is more than 5px.
270
this.has_min_height = function() {
271
return (this.height >= 5);
275
* Set min. width of annotation bound.
276
* @method set_min_width
279
this.set_min_width = function() {
284
* Set min. height of annotation bound.
285
* @method set_min_height
288
this.set_min_height = function() {
293
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
294
M.assignfeedback_editpdf.rect = RECT;
295
// This file is part of Moodle - http://moodle.org/
297
// Moodle is free software: you can redistribute it and/or modify
298
// it under the terms of the GNU General Public License as published by
299
// the Free Software Foundation, either version 3 of the License, or
300
// (at your option) any later version.
302
// Moodle is distributed in the hope that it will be useful,
303
// but WITHOUT ANY WARRANTY; without even the implied warranty of
304
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
305
// GNU General Public License for more details.
307
// You should have received a copy of the GNU General Public License
308
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
311
* Class representing a partially completed edit operation.
313
* @module moodle-assignfeedback_editpdf-editor
319
* @namespace M.assignfeedback_editpdf
325
* Starting point for the edit.
327
* @type M.assignfeedback_editpdf.point|false
333
* Finishing point for the edit.
335
* @type M.assignfeedback_editpdf.point|false
341
* Starting time for the edit.
342
* @property starttime
349
* Starting point for the currently selected annotation.
350
* @property annotationstart
351
* @type M.assignfeedback_editpdf.point|false
354
this.annotationstart = false;
357
* The currently selected tool
362
this.tool = "comment";
365
* The currently comment colour
366
* @property commentcolour
370
this.commentcolour = 'yellow';
373
* The currently annotation colour
374
* @property annotationcolour
378
this.annotationcolour = 'red';
381
* The current stamp image.
389
* List of points the the current drawing path.
391
* @type M.assignfeedback_editpdf.point[]
397
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
398
M.assignfeedback_editpdf.edit = EDIT;
399
// This file is part of Moodle - http://moodle.org/
401
// Moodle is free software: you can redistribute it and/or modify
402
// it under the terms of the GNU General Public License as published by
403
// the Free Software Foundation, either version 3 of the License, or
404
// (at your option) any later version.
406
// Moodle is distributed in the hope that it will be useful,
407
// but WITHOUT ANY WARRANTY; without even the implied warranty of
408
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
409
// GNU General Public License for more details.
411
// You should have received a copy of the GNU General Public License
412
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
415
* Class representing a drawable thing which contains both
416
* Y.Nodes, and Y.Shapes.
418
* @module moodle-assignfeedback_editpdf-editor
424
* @namespace M.assignfeedback_editpdf
425
* @param M.assignfeedback_editpdf.editor editor
428
DRAWABLE = function(editor) {
431
* Reference to M.assignfeedback_editpdf.editor.
433
* @type M.assignfeedback_editpdf.editor
436
this.editor = editor;
455
* Delete the shapes from the drawable.
457
* @method erase_drawable
459
this.erase = function() {
461
while (this.shapes.length > 0) {
462
this.editor.graphic.removeShape(this.shapes.pop());
466
while (this.nodes.length > 0) {
467
this.nodes.pop().remove();
474
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
475
M.assignfeedback_editpdf.drawable = DRAWABLE;
476
// This file is part of Moodle - http://moodle.org/
478
// Moodle is free software: you can redistribute it and/or modify
479
// it under the terms of the GNU General Public License as published by
480
// the Free Software Foundation, either version 3 of the License, or
481
// (at your option) any later version.
483
// Moodle is distributed in the hope that it will be useful,
484
// but WITHOUT ANY WARRANTY; without even the implied warranty of
485
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
486
// GNU General Public License for more details.
488
// You should have received a copy of the GNU General Public License
489
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
492
* Class representing a list of annotations.
494
* @module moodle-assignfeedback_editpdf-editor
496
ANNOTATION = function(config) {
497
ANNOTATION.superclass.constructor.apply(this, [config]);
500
ANNOTATION.NAME = "annotation";
501
ANNOTATION.ATTRS = {};
503
Y.extend(ANNOTATION, Y.Base, {
505
* Reference to M.assignfeedback_editpdf.editor.
507
* @type M.assignfeedback_editpdf.editor
521
* Comment page number
563
* @type String - list of points like x1,y1:x2,y2
585
* Reference to M.assignfeedback_editpdf.drawable
587
* @type M.assignfeedback_editpdf.drawable
593
* Initialise the annotation.
595
* @method initializer
598
initializer : function(config) {
599
this.editor = config.editor || null;
600
this.gradeid = parseInt(config.gradeid, 10) || 0;
601
this.pageno = parseInt(config.pageno, 10) || 0;
602
this.x = parseInt(config.x, 10) || 0;
603
this.y = parseInt(config.y, 10) || 0;
604
this.endx = parseInt(config.endx, 10) || 0;
605
this.endy = parseInt(config.endy, 10) || 0;
606
this.path = config.path || '';
607
this.type = config.type || 'rect';
608
this.colour = config.colour || 'red';
609
this.drawable = false;
613
* Clean a comment record, returning an oject with only fields that are valid.
620
gradeid : this.gradeid,
621
x : parseInt(this.x, 10),
622
y : parseInt(this.y, 10),
623
endx : parseInt(this.endx, 10),
624
endy : parseInt(this.endy, 10),
627
pageno : this.pageno,
633
* Draw a selection around this annotation if it is selected.
635
* @method draw_highlight
636
* @return M.assignfeedback_editpdf.drawable
638
draw_highlight : function() {
640
drawingregion = Y.one(SELECTOR.DRAWINGREGION),
641
offsetcanvas = Y.one(SELECTOR.DRAWINGCANVAS).getXY(),
644
if (this.editor.currentannotation === this) {
645
// Draw a highlight around the annotation.
646
bounds = new M.assignfeedback_editpdf.rect();
647
bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y),
648
new M.assignfeedback_editpdf.point(this.endx, this.endy)]);
650
shape = this.editor.graphic.addShape({
653
height: bounds.height,
655
weight: STROKEWEIGHT,
656
color: SELECTEDBORDERCOLOUR
659
color: SELECTEDFILLCOLOUR
664
this.drawable.shapes.push(shape);
666
// Add a delete X to the annotation.
667
var deleteicon = Y.Node.create('<img src="' + M.util.image_url('trash', 'assignfeedback_editpdf') + '"/>'),
668
deletelink = Y.Node.create('<a href="#" role="button"></a>');
670
deleteicon.setAttrs({
671
'alt': M.util.get_string('deleteannotation', 'assignfeedback_editpdf')
673
deleteicon.setStyles({
674
'backgroundColor' : 'white'
676
deletelink.addClass('deleteannotationbutton');
677
deletelink.append(deleteicon);
679
drawingregion.append(deletelink);
680
deletelink.setData('annotation', this);
681
deletelink.setStyle('zIndex', '200');
683
deletelink.on('click', this.remove, this);
684
deletelink.on('key', this.remove, 'space,enter', this);
686
deletelink.setX(offsetcanvas[0] + bounds.x + bounds.width - 18);
687
deletelink.setY(offsetcanvas[1] + bounds.y + 6);
688
this.drawable.nodes.push(deletelink);
690
return this.drawable;
697
* @return M.assignfeedback_editpdf.drawable|false
700
// Should be overridden by the subclass.
701
this.draw_highlight();
702
return this.drawable;
706
* Delete an annotation
711
remove : function(e) {
716
annotations = this.editor.pages[this.editor.currentpage].annotations;
717
for (i = 0; i < annotations.length; i++) {
718
if (annotations[i] === this) {
719
annotations.splice(i, 1);
721
this.drawable.erase();
723
this.editor.currentannotation = false;
724
this.editor.save_current_page();
731
* Move an annotation to a new location.
735
* @method move_annotation
737
move : function(newx, newy) {
738
var diffx = newx - this.x,
739
diffy = newy - this.y,
740
newpath, oldpath, xy,
750
oldpath = this.path.split(':');
751
Y.each(oldpath, function(position) {
752
xy = position.split(',');
753
x = parseInt(xy[0], 10);
754
y = parseInt(xy[1], 10);
755
newpath.push((x + diffx) + ',' + (y + diffy));
758
this.path = newpath.join(':');
762
this.drawable.erase();
764
this.editor.drawables.push(this.draw());
768
* Draw the in progress edit.
771
* @method draw_current_edit
772
* @param M.assignfeedback_editpdf.edit edit
774
draw_current_edit : function(edit) {
775
var noop = edit && false;
776
// Override me please.
781
* Promote the current edit to a real annotation.
784
* @method init_from_edit
785
* @param M.assignfeedback_editpdf.edit edit
786
* @return bool if width/height is more than min. required.
788
init_from_edit : function(edit) {
789
var bounds = new M.assignfeedback_editpdf.rect();
790
bounds.bound([edit.start, edit.end]);
792
this.gradeid = this.editor.get('gradeid');
793
this.pageno = this.editor.currentpage;
796
this.endx = bounds.x + bounds.width;
797
this.endy = bounds.y + bounds.height;
798
this.colour = edit.annotationcolour;
800
return (bounds.has_min_width() && bounds.has_min_height());
805
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
806
M.assignfeedback_editpdf.annotation = ANNOTATION;
807
// This file is part of Moodle - http://moodle.org/
809
// Moodle is free software: you can redistribute it and/or modify
810
// it under the terms of the GNU General Public License as published by
811
// the Free Software Foundation, either version 3 of the License, or
812
// (at your option) any later version.
814
// Moodle is distributed in the hope that it will be useful,
815
// but WITHOUT ANY WARRANTY; without even the implied warranty of
816
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
817
// GNU General Public License for more details.
819
// You should have received a copy of the GNU General Public License
820
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
823
* Class representing a line.
825
* @namespace M.assignfeedback_editpdf
826
* @class annotationline
827
* @extends annotation
828
* @module moodle-assignfeedback_editpdf-editor
830
ANNOTATIONLINE = function(config) {
831
ANNOTATIONLINE.superclass.constructor.apply(this, [config]);
834
ANNOTATIONLINE.NAME = "annotationline";
835
ANNOTATIONLINE.ATTRS = {};
837
Y.extend(ANNOTATIONLINE, M.assignfeedback_editpdf.annotation, {
839
* Draw a line annotation
842
* @return M.assignfeedback_editpdf.drawable
848
drawable = new M.assignfeedback_editpdf.drawable(this.editor);
850
shape = this.editor.graphic.addShape({
854
weight: STROKEWEIGHT,
855
color: ANNOTATIONCOLOUR[this.colour]
859
shape.moveTo(this.x, this.y);
860
shape.lineTo(this.endx, this.endy);
862
drawable.shapes.push(shape);
863
this.drawable = drawable;
865
return ANNOTATIONLINE.superclass.draw.apply(this);
869
* Draw the in progress edit.
872
* @method draw_current_edit
873
* @param M.assignfeedback_editpdf.edit edit
875
draw_current_edit : function(edit) {
876
var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
879
shape = this.editor.graphic.addShape({
883
weight: STROKEWEIGHT,
884
color: ANNOTATIONCOLOUR[edit.annotationcolour]
888
shape.moveTo(edit.start.x, edit.start.y);
889
shape.lineTo(edit.end.x, edit.end.y);
892
drawable.shapes.push(shape);
898
* Promote the current edit to a real annotation.
901
* @method init_from_edit
902
* @param M.assignfeedback_editpdf.edit edit
903
* @return bool true if line bound is more than min width/height, else false.
905
init_from_edit : function(edit) {
906
this.gradeid = this.editor.get('gradeid');
907
this.pageno = this.editor.currentpage;
908
this.x = edit.start.x;
909
this.y = edit.start.y;
910
this.endx = edit.end.x;
911
this.endy = edit.end.y;
912
this.colour = edit.annotationcolour;
915
return !(((this.endx - this.x) === 0) && ((this.endy - this.y) === 0));
920
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
921
M.assignfeedback_editpdf.annotationline = ANNOTATIONLINE;
922
// This file is part of Moodle - http://moodle.org/
924
// Moodle is free software: you can redistribute it and/or modify
925
// it under the terms of the GNU General Public License as published by
926
// the Free Software Foundation, either version 3 of the License, or
927
// (at your option) any later version.
929
// Moodle is distributed in the hope that it will be useful,
930
// but WITHOUT ANY WARRANTY; without even the implied warranty of
931
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
932
// GNU General Public License for more details.
934
// You should have received a copy of the GNU General Public License
935
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
938
* Class representing a rectangle.
940
* @namespace M.assignfeedback_editpdf
941
* @class annotationrectangle
942
* @extends annotation
943
* @module moodle-assignfeedback_editpdf-editor
945
ANNOTATIONRECTANGLE = function(config) {
946
ANNOTATIONRECTANGLE.superclass.constructor.apply(this, [config]);
949
ANNOTATIONRECTANGLE.NAME = "annotationrectangle";
950
ANNOTATIONRECTANGLE.ATTRS = {};
952
Y.extend(ANNOTATIONRECTANGLE, M.assignfeedback_editpdf.annotation, {
954
* Draw a rectangle annotation
957
* @return M.assignfeedback_editpdf.drawable
963
drawable = new M.assignfeedback_editpdf.drawable(this.editor);
965
bounds = new M.assignfeedback_editpdf.rect();
966
bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y),
967
new M.assignfeedback_editpdf.point(this.endx, this.endy)]);
969
shape = this.editor.graphic.addShape({
972
height: bounds.height,
974
weight: STROKEWEIGHT,
975
color: ANNOTATIONCOLOUR[this.colour]
980
drawable.shapes.push(shape);
981
this.drawable = drawable;
983
return ANNOTATIONRECTANGLE.superclass.draw.apply(this);
987
* Draw the in progress edit.
990
* @method draw_current_edit
991
* @param M.assignfeedback_editpdf.edit edit
993
draw_current_edit : function(edit) {
994
var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
998
bounds = new M.assignfeedback_editpdf.rect();
999
bounds.bound([new M.assignfeedback_editpdf.point(edit.start.x, edit.start.y),
1000
new M.assignfeedback_editpdf.point(edit.end.x, edit.end.y)]);
1002
// Set min. width and height of rectangle.
1003
if (!bounds.has_min_width()) {
1004
bounds.set_min_width();
1006
if (!bounds.has_min_height()) {
1007
bounds.set_min_height();
1010
shape = this.editor.graphic.addShape({
1012
width: bounds.width,
1013
height: bounds.height,
1015
weight: STROKEWEIGHT,
1016
color: ANNOTATIONCOLOUR[edit.annotationcolour]
1022
drawable.shapes.push(shape);
1028
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1029
M.assignfeedback_editpdf.annotationrectangle = ANNOTATIONRECTANGLE;
1030
// This file is part of Moodle - http://moodle.org/
1032
// Moodle is free software: you can redistribute it and/or modify
1033
// it under the terms of the GNU General Public License as published by
1034
// the Free Software Foundation, either version 3 of the License, or
1035
// (at your option) any later version.
1037
// Moodle is distributed in the hope that it will be useful,
1038
// but WITHOUT ANY WARRANTY; without even the implied warranty of
1039
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1040
// GNU General Public License for more details.
1042
// You should have received a copy of the GNU General Public License
1043
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
1046
* Class representing a oval.
1048
* @namespace M.assignfeedback_editpdf
1049
* @class annotationoval
1050
* @extends annotation
1051
* @module moodle-assignfeedback_editpdf-editor
1053
ANNOTATIONOVAL = function(config) {
1054
ANNOTATIONOVAL.superclass.constructor.apply(this, [config]);
1057
ANNOTATIONOVAL.NAME = "annotationoval";
1058
ANNOTATIONOVAL.ATTRS = {};
1060
Y.extend(ANNOTATIONOVAL, M.assignfeedback_editpdf.annotation, {
1062
* Draw a oval annotation
1065
* @return M.assignfeedback_editpdf.drawable
1071
drawable = new M.assignfeedback_editpdf.drawable(this.editor);
1073
bounds = new M.assignfeedback_editpdf.rect();
1074
bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y),
1075
new M.assignfeedback_editpdf.point(this.endx, this.endy)]);
1077
shape = this.editor.graphic.addShape({
1079
width: bounds.width,
1080
height: bounds.height,
1082
weight: STROKEWEIGHT,
1083
color: ANNOTATIONCOLOUR[this.colour]
1088
drawable.shapes.push(shape);
1089
this.drawable = drawable;
1091
return ANNOTATIONOVAL.superclass.draw.apply(this);
1095
* Draw the in progress edit.
1098
* @method draw_current_edit
1099
* @param M.assignfeedback_editpdf.edit edit
1101
draw_current_edit : function(edit) {
1102
var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
1106
bounds = new M.assignfeedback_editpdf.rect();
1107
bounds.bound([new M.assignfeedback_editpdf.point(edit.start.x, edit.start.y),
1108
new M.assignfeedback_editpdf.point(edit.end.x, edit.end.y)]);
1110
// Set min. width and height of oval.
1111
if (!bounds.has_min_width()) {
1112
bounds.set_min_width();
1114
if (!bounds.has_min_height()) {
1115
bounds.set_min_height();
1118
shape = this.editor.graphic.addShape({
1120
width: bounds.width,
1121
height: bounds.height,
1123
weight: STROKEWEIGHT,
1124
color: ANNOTATIONCOLOUR[edit.annotationcolour]
1130
drawable.shapes.push(shape);
1136
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1137
M.assignfeedback_editpdf.annotationoval = ANNOTATIONOVAL;
1138
// This file is part of Moodle - http://moodle.org/
1140
// Moodle is free software: you can redistribute it and/or modify
1141
// it under the terms of the GNU General Public License as published by
1142
// the Free Software Foundation, either version 3 of the License, or
1143
// (at your option) any later version.
1145
// Moodle is distributed in the hope that it will be useful,
1146
// but WITHOUT ANY WARRANTY; without even the implied warranty of
1147
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1148
// GNU General Public License for more details.
1150
// You should have received a copy of the GNU General Public License
1151
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
1154
* Class representing a pen.
1156
* @namespace M.assignfeedback_editpdf
1157
* @class annotationpen
1158
* @extends annotation
1159
* @module moodle-assignfeedback_editpdf-editor
1161
ANNOTATIONPEN = function(config) {
1162
ANNOTATIONPEN.superclass.constructor.apply(this, [config]);
1165
ANNOTATIONPEN.NAME = "annotationpen";
1166
ANNOTATIONPEN.ATTRS = {};
1168
Y.extend(ANNOTATIONPEN, M.assignfeedback_editpdf.annotation, {
1170
* Draw a pen annotation
1173
* @return M.assignfeedback_editpdf.drawable
1182
drawable = new M.assignfeedback_editpdf.drawable(this.editor);
1184
shape = this.editor.graphic.addShape({
1188
weight: STROKEWEIGHT,
1189
color: ANNOTATIONCOLOUR[this.colour]
1194
// Recreate the pen path array.
1195
positions = this.path.split(':');
1196
// Redraw all the lines.
1197
Y.each(positions, function(position) {
1198
xy = position.split(',');
1200
shape.moveTo(xy[0], xy[1]);
1203
shape.lineTo(xy[0], xy[1]);
1209
drawable.shapes.push(shape);
1210
this.drawable = drawable;
1212
return ANNOTATIONPEN.superclass.draw.apply(this);
1216
* Draw the in progress edit.
1219
* @method draw_current_edit
1220
* @param M.assignfeedback_editpdf.edit edit
1222
draw_current_edit : function(edit) {
1223
var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
1227
shape = this.editor.graphic.addShape({
1231
weight: STROKEWEIGHT,
1232
color: ANNOTATIONCOLOUR[edit.annotationcolour]
1237
// Recreate the pen path array.
1238
// Redraw all the lines.
1239
Y.each(edit.path, function(position) {
1241
shape.moveTo(position.x, position.y);
1244
shape.lineTo(position.x, position.y);
1250
drawable.shapes.push(shape);
1257
* Promote the current edit to a real annotation.
1260
* @method init_from_edit
1261
* @param M.assignfeedback_editpdf.edit edit
1262
* @return bool true if pen bound is more than min width/height, else false.
1264
init_from_edit : function(edit) {
1265
var bounds = new M.assignfeedback_editpdf.rect(),
1269
// This will get the boundaries of all points in the path.
1270
bounds.bound(edit.path);
1272
for (i = 0; i < edit.path.length; i++) {
1273
pathlist.push(parseInt(edit.path[i].x, 10) + ',' + parseInt(edit.path[i].y, 10));
1276
this.gradeid = this.editor.get('gradeid');
1277
this.pageno = this.editor.currentpage;
1280
this.endx = bounds.x + bounds.width;
1281
this.endy = bounds.y + bounds.height;
1282
this.colour = edit.annotationcolour;
1283
this.path = pathlist.join(':');
1285
return (bounds.has_min_width() || bounds.has_min_height());
1291
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1292
M.assignfeedback_editpdf.annotationpen = ANNOTATIONPEN;
1293
// This file is part of Moodle - http://moodle.org/
1295
// Moodle is free software: you can redistribute it and/or modify
1296
// it under the terms of the GNU General Public License as published by
1297
// the Free Software Foundation, either version 3 of the License, or
1298
// (at your option) any later version.
1300
// Moodle is distributed in the hope that it will be useful,
1301
// but WITHOUT ANY WARRANTY; without even the implied warranty of
1302
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1303
// GNU General Public License for more details.
1305
// You should have received a copy of the GNU General Public License
1306
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
1309
* Class representing a highlight.
1311
* @namespace M.assignfeedback_editpdf
1312
* @class annotationhighlight
1313
* @extends annotation
1314
* @module moodle-assignfeedback_editpdf-editor
1316
ANNOTATIONHIGHLIGHT = function(config) {
1317
ANNOTATIONHIGHLIGHT.superclass.constructor.apply(this, [config]);
1320
ANNOTATIONHIGHLIGHT.NAME = "annotationhighlight";
1321
ANNOTATIONHIGHLIGHT.ATTRS = {};
1323
Y.extend(ANNOTATIONHIGHLIGHT, M.assignfeedback_editpdf.annotation, {
1325
* Draw a highlight annotation
1328
* @return M.assignfeedback_editpdf.drawable
1336
drawable = new M.assignfeedback_editpdf.drawable(this.editor);
1337
bounds = new M.assignfeedback_editpdf.rect();
1338
bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y),
1339
new M.assignfeedback_editpdf.point(this.endx, this.endy)]);
1341
highlightcolour = ANNOTATIONCOLOUR[this.colour];
1343
// Add an alpha channel to the rgb colour.
1345
highlightcolour = highlightcolour.replace('rgb', 'rgba');
1346
highlightcolour = highlightcolour.replace(')', ',0.5)');
1348
shape = this.editor.graphic.addShape({
1350
width: bounds.width,
1351
height: bounds.height,
1354
color: highlightcolour
1360
drawable.shapes.push(shape);
1361
this.drawable = drawable;
1363
return ANNOTATIONHIGHLIGHT.superclass.draw.apply(this);
1367
* Draw the in progress edit.
1370
* @method draw_current_edit
1371
* @param M.assignfeedback_editpdf.edit edit
1373
draw_current_edit : function(edit) {
1374
var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
1379
bounds = new M.assignfeedback_editpdf.rect();
1380
bounds.bound([new M.assignfeedback_editpdf.point(edit.start.x, edit.start.y),
1381
new M.assignfeedback_editpdf.point(edit.end.x, edit.end.y)]);
1383
// Set min. width of highlight.
1384
if (!bounds.has_min_width()) {
1385
bounds.set_min_width();
1388
highlightcolour = ANNOTATIONCOLOUR[edit.annotationcolour];
1389
// Add an alpha channel to the rgb colour.
1391
highlightcolour = highlightcolour.replace('rgb', 'rgba');
1392
highlightcolour = highlightcolour.replace(')', ',0.5)');
1394
// We will draw a box with the current background colour.
1395
shape = this.editor.graphic.addShape({
1397
width: bounds.width,
1401
color: highlightcolour
1407
drawable.shapes.push(shape);
1413
* Promote the current edit to a real annotation.
1416
* @method init_from_edit
1417
* @param M.assignfeedback_editpdf.edit edit
1418
* @return bool true if highlight bound is more than min width/height, else false.
1420
init_from_edit : function(edit) {
1421
var bounds = new M.assignfeedback_editpdf.rect();
1422
bounds.bound([edit.start, edit.end]);
1424
this.gradeid = this.editor.get('gradeid');
1425
this.pageno = this.editor.currentpage;
1427
this.y = edit.start.y;
1428
this.endx = bounds.x + bounds.width;
1429
this.endy = edit.start.y + 16;
1430
this.colour = edit.annotationcolour;
1433
return (bounds.has_min_width());
1438
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1439
M.assignfeedback_editpdf.annotationhighlight = ANNOTATIONHIGHLIGHT;
1440
// This file is part of Moodle - http://moodle.org/
1442
// Moodle is free software: you can redistribute it and/or modify
1443
// it under the terms of the GNU General Public License as published by
1444
// the Free Software Foundation, either version 3 of the License, or
1445
// (at your option) any later version.
1447
// Moodle is distributed in the hope that it will be useful,
1448
// but WITHOUT ANY WARRANTY; without even the implied warranty of
1449
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1450
// GNU General Public License for more details.
1452
// You should have received a copy of the GNU General Public License
1453
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
1456
* Class representing a stamp.
1458
* @namespace M.assignfeedback_editpdf
1459
* @class annotationstamp
1460
* @extends annotation
1461
* @module moodle-assignfeedback_editpdf-editor
1463
ANNOTATIONSTAMP = function(config) {
1464
ANNOTATIONSTAMP.superclass.constructor.apply(this, [config]);
1467
ANNOTATIONSTAMP.NAME = "annotationstamp";
1468
ANNOTATIONSTAMP.ATTRS = {};
1470
Y.extend(ANNOTATIONSTAMP, M.assignfeedback_editpdf.annotation, {
1472
* Draw a stamp annotation
1475
* @return M.assignfeedback_editpdf.drawable
1478
var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
1479
drawingregion = Y.one(SELECTOR.DRAWINGREGION),
1483
position = this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(this.x, this.y));
1484
node = Y.Node.create('<div/>');
1486
'position': 'absolute',
1487
'display': 'inline-block',
1488
'backgroundImage': 'url(' + this.editor.get_stamp_image_url(this.path) + ')',
1489
'width': (this.endx - this.x),
1490
'height': (this.endy - this.y),
1491
'backgroundSize': '100% 100%',
1495
drawingregion.append(node);
1496
node.setX(position.x);
1497
node.setY(position.y);
1499
// Pass throught the event handlers on the div.
1500
node.on('gesturemovestart', this.editor.edit_start, null, this.editor);
1501
node.on('gesturemove', this.editor.edit_move, null, this.editor);
1502
node.on('gesturemoveend', this.editor.edit_end, null, this.editor);
1504
drawable.nodes.push(node);
1506
this.drawable = drawable;
1507
return ANNOTATIONSTAMP.superclass.draw.apply(this);
1511
* Draw the in progress edit.
1514
* @method draw_current_edit
1515
* @param M.assignfeedback_editpdf.edit edit
1517
draw_current_edit : function(edit) {
1518
var bounds = new M.assignfeedback_editpdf.rect(),
1519
drawable = new M.assignfeedback_editpdf.drawable(this.editor),
1520
drawingregion = Y.one(SELECTOR.DRAWINGREGION),
1524
bounds.bound([edit.start, edit.end]);
1525
position = this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(bounds.x, bounds.y));
1527
node = Y.Node.create('<div/>');
1529
'position': 'absolute',
1530
'display': 'inline-block',
1531
'backgroundImage': 'url(' + this.editor.get_stamp_image_url(edit.stamp) + ')',
1532
'width': bounds.width,
1533
'height': bounds.height,
1534
'backgroundSize': '100% 100%',
1538
drawingregion.append(node);
1539
node.setX(position.x);
1540
node.setY(position.y);
1542
drawable.nodes.push(node);
1548
* Promote the current edit to a real annotation.
1551
* @method init_from_edit
1552
* @param M.assignfeedback_editpdf.edit edit
1553
* @return bool if width/height is more than min. required.
1555
init_from_edit : function(edit) {
1556
var bounds = new M.assignfeedback_editpdf.rect();
1557
bounds.bound([edit.start, edit.end]);
1559
if (bounds.width < 40) {
1562
if (bounds.height < 40) {
1565
this.gradeid = this.editor.get('gradeid');
1566
this.pageno = this.editor.currentpage;
1569
this.endx = bounds.x + bounds.width;
1570
this.endy = bounds.y + bounds.height;
1571
this.colour = edit.annotationcolour;
1572
this.path = edit.stamp;
1574
// Min width and height is always more than 40px.
1579
* Move an annotation to a new location.
1583
* @method move_annotation
1585
move : function(newx, newy) {
1586
var diffx = newx - this.x,
1587
diffy = newy - this.y;
1594
if (this.drawable) {
1595
this.drawable.erase();
1597
this.editor.drawables.push(this.draw());
1602
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1603
M.assignfeedback_editpdf.annotationstamp = ANNOTATIONSTAMP;
1604
var DROPDOWN_NAME = "Dropdown menu",
1609
* This is a drop down list of buttons triggered (and aligned to) a button.
1611
* @namespace M.assignfeedback_editpdf.widget.dropdown
1616
DROPDOWN = function(config) {
1617
config.draggable = false;
1618
config.centered = false;
1619
config.width = 'auto';
1620
config.lightbox = false;
1621
config.visible = false;
1622
config.footerContent = '';
1623
DROPDOWN.superclass.constructor.apply(this, [config]);
1626
Y.extend(DROPDOWN, M.core.dialogue, {
1628
* Initialise the menu.
1630
* @method initializer
1633
initializer : function(config) {
1634
var button, body, headertext, bb;
1635
DROPDOWN.superclass.initializer.call(this, config);
1637
bb = this.get('boundingBox');
1638
bb.addClass('assignfeedback_editpdf_dropdown');
1640
// Align the menu to the button that opens it.
1641
button = this.get('buttonNode');
1643
// Close the menu when clicked outside (excluding the button that opened the menu).
1644
body = this.bodyNode;
1646
headertext = Y.Node.create('<h3/>');
1647
headertext.addClass('accesshide');
1648
headertext.setHTML(this.get('headerText'));
1649
body.prepend(headertext);
1651
body.on('clickoutside', function(e) {
1652
if (this.get('visible')) {
1653
// Note: we need to compare ids because for some reason - sometimes button is an Object, not a Y.Node.
1654
if (e.target.get('id') !== button.get('id') && e.target.ancestor().get('id') !== button.get('id')) {
1661
button.on('click', function(e) {e.preventDefault(); this.show();}, this);
1662
button.on('key', this.show, 'enter,space', this);
1666
* Override the show method to align to the button.
1672
var button = this.get('buttonNode');
1674
result = DROPDOWN.superclass.show.call(this);
1675
this.align(button, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
1678
NAME : DROPDOWN_NAME,
1681
* The header for the drop down (only accessible to screen readers).
1683
* @attribute headerText
1692
* The button used to show/hide this drop down menu.
1694
* @attribute buttonNode
1704
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1705
M.assignfeedback_editpdf.dropdown = DROPDOWN;
1706
var COLOURPICKER_NAME = "Colourpicker",
1711
* This is a drop down list of colours.
1713
* @namespace M.assignfeedback_editpdf.colourpicker
1718
COLOURPICKER = function(config) {
1719
COLOURPICKER.superclass.constructor.apply(this, [config]);
1722
Y.extend(COLOURPICKER, M.assignfeedback_editpdf.dropdown, {
1725
* Initialise the menu.
1727
* @method initializer
1730
initializer : function(config) {
1731
var colourlist = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_menu"/>'),
1734
// Build a list of coloured buttons.
1735
Y.each(this.get('colours'), function(rgb, colour) {
1736
var button, listitem, title, img, iconname;
1738
title = M.util.get_string(colour, 'assignfeedback_editpdf');
1739
iconname = this.get('iconprefix') + colour;
1740
img = M.util.image_url(iconname, 'assignfeedback_editpdf');
1741
button = Y.Node.create('<button><img alt="' + title + '" src="' + img + '"/></button>');
1742
button.setAttribute('data-colour', colour);
1743
button.setAttribute('data-rgb', rgb);
1744
button.setStyle('backgroundImage', 'none');
1745
listitem = Y.Node.create('<li/>');
1746
listitem.append(button);
1747
colourlist.append(listitem);
1750
body = Y.Node.create('<div/>');
1752
// Set the call back.
1753
colourlist.delegate('click', this.callback_handler, 'button', this);
1754
colourlist.delegate('key', this.callback_handler, 'down:13', 'button', this);
1756
// Set the accessible header text.
1757
this.set('headerText', M.util.get_string('colourpicker', 'assignfeedback_editpdf'));
1759
// Set the body content.
1760
body.append(colourlist);
1761
this.set('bodyContent', body);
1763
COLOURPICKER.superclass.initializer.call(this, config);
1765
callback_handler : function(e) {
1768
var callback = this.get('callback'),
1769
callbackcontext = this.get('context'),
1774
// Call the callback with the specified context.
1775
bind = Y.bind(callback, callbackcontext, e);
1780
NAME : COLOURPICKER_NAME,
1783
* The list of colours this colour picker supports.
1785
* @attribute colours
1786
* @type {String: String} (The keys of the array are the colour names and the values are localized strings)
1794
* The function called when a new colour is chosen.
1796
* @attribute callback
1805
* The context passed to the callback when a colour is chosen.
1807
* @attribute context
1816
* The prefix for the icon image names.
1818
* @attribute iconprefix
1820
* @default 'colour_'
1828
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1829
M.assignfeedback_editpdf.colourpicker = COLOURPICKER;
1830
var STAMPPICKER_NAME = "Colourpicker",
1835
* This is a drop down list of stamps.
1837
* @namespace M.assignfeedback_editpdf.stamppicker
1842
STAMPPICKER = function(config) {
1843
STAMPPICKER.superclass.constructor.apply(this, [config]);
1846
Y.extend(STAMPPICKER, M.assignfeedback_editpdf.dropdown, {
1849
* Initialise the menu.
1851
* @method initializer
1854
initializer : function(config) {
1855
var stamplist = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_menu"/>');
1857
// Build a list of stamped buttons.
1858
Y.each(this.get('stamps'), function(stamp) {
1859
var button, listitem, title;
1861
title = M.util.get_string('stamp', 'assignfeedback_editpdf');
1862
button = Y.Node.create('<button><img height="16" width="16" alt="' + title + '" src="' + stamp + '"/></button>');
1863
button.setAttribute('data-stamp', stamp);
1864
button.setStyle('backgroundImage', 'none');
1865
listitem = Y.Node.create('<li/>');
1866
listitem.append(button);
1867
stamplist.append(listitem);
1871
// Set the call back.
1872
stamplist.delegate('click', this.callback_handler, 'button', this);
1873
stamplist.delegate('key', this.callback_handler, 'down:13', 'button', this);
1875
// Set the accessible header text.
1876
this.set('headerText', M.util.get_string('stamppicker', 'assignfeedback_editpdf'));
1878
// Set the body content.
1879
this.set('bodyContent', stamplist);
1881
STAMPPICKER.superclass.initializer.call(this, config);
1883
callback_handler : function(e) {
1885
var callback = this.get('callback'),
1886
callbackcontext = this.get('context'),
1891
// Call the callback with the specified context.
1892
bind = Y.bind(callback, callbackcontext, e);
1897
NAME : STAMPPICKER_NAME,
1900
* The list of stamps this stamp picker supports.
1903
* @type String[] - the stamp filenames.
1911
* The function called when a new stamp is chosen.
1913
* @attribute callback
1922
* The context passed to the callback when a stamp is chosen.
1924
* @attribute context
1934
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1935
M.assignfeedback_editpdf.stamppicker = STAMPPICKER;
1936
var COMMENTMENUNAME = "Commentmenu",
1941
* This is a drop down list of comment context functions.
1943
* @namespace M.assignfeedback_editpdf.editor
1944
* @class commentmenu
1948
COMMENTMENU = function(config) {
1949
COMMENTMENU.superclass.constructor.apply(this, [config]);
1952
Y.extend(COMMENTMENU, M.assignfeedback_editpdf.dropdown, {
1955
* Initialise the menu.
1957
* @method initializer
1960
initializer : function(config) {
1966
comment = this.get('comment');
1967
// Build the list of menu items.
1968
commentlinks = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_menu"/>');
1970
link = Y.Node.create('<li><a tabindex="-1" href="#">' + M.util.get_string('addtoquicklist', 'assignfeedback_editpdf') + '</a></li>');
1971
link.on('click', comment.add_to_quicklist, comment);
1972
link.on('key', comment.add_to_quicklist, 'enter,space', comment);
1974
commentlinks.append(link);
1976
link = Y.Node.create('<li><a tabindex="-1" href="#">' + M.util.get_string('deletecomment', 'assignfeedback_editpdf') + '</a></li>');
1977
link.on('click', function(e) { e.preventDefault(); this.menu.hide(); this.remove(); }, comment);
1978
link.on('key', function() { comment.menu.hide(); comment.remove(); }, 'enter,space', comment);
1980
commentlinks.append(link);
1982
link = Y.Node.create('<li><hr/></li>');
1983
commentlinks.append(link);
1985
// Set the accessible header text.
1986
this.set('headerText', M.util.get_string('commentcontextmenu', 'assignfeedback_editpdf'));
1988
body = Y.Node.create('<div/>');
1990
// Set the body content.
1991
body.append(commentlinks);
1992
this.set('bodyContent', body);
1994
COMMENTMENU.superclass.initializer.call(this, config);
2004
var commentlinks = this.get('boundingBox').one('ul');
2005
commentlinks.all('.quicklist_comment').remove(true),
2006
comment = this.get('comment');
2008
comment.deleteme = false; // Cancel the deleting of blank comments.
2010
// Now build the list of quicklist comments.
2011
Y.each(comment.editor.quicklist.comments, function(quickcomment) {
2012
var listitem = Y.Node.create('<li class="quicklist_comment"></li>'),
2013
linkitem = Y.Node.create('<a href="#" tabindex="-1">' + quickcomment.rawtext + '</a>'),
2014
deletelinkitem = Y.Node.create('<a href="#" tabindex="-1" class="delete_quicklist_comment">' +
2015
'<img src="' + M.util.image_url('t/delete', 'core') + '" ' +
2016
'alt="' + M.util.get_string('deletecomment', 'assignfeedback_editpdf') + '"/>' +
2018
listitem.append(linkitem);
2019
listitem.append(deletelinkitem);
2021
commentlinks.append(listitem);
2023
linkitem.on('click', comment.set_from_quick_comment, comment, quickcomment);
2024
linkitem.on('key', comment.set_from_quick_comment, 'space,enter', comment, quickcomment);
2026
deletelinkitem.on('click', comment.remove_from_quicklist, comment, quickcomment);
2027
deletelinkitem.on('key', comment.remove_from_quicklist, 'space,enter', comment, quickcomment);
2030
COMMENTMENU.superclass.show.call(this);
2033
NAME : COMMENTMENUNAME,
2036
* The comment this menu is attached to.
2038
* @attribute comment
2039
* @type M.assignfeedback_editpdf.comment
2049
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
2050
M.assignfeedback_editpdf.commentmenu = COMMENTMENU;
2051
var COMMENTSEARCHNAME = "commentsearch",
2056
* This is a searchable dialogue of comments.
2058
* @namespace M.assignfeedback_editpdf.editor
2059
* @class commentsearch
2063
COMMENTSEARCH = function(config) {
2064
config.draggable = false;
2065
config.centered = true;
2066
config.width = '400px';
2067
config.lightbox = true;
2068
config.visible = false;
2069
config.headerContent = M.util.get_string('searchcomments', 'assignfeedback_editpdf');
2070
config.footerContent = '';
2071
COMMENTSEARCH.superclass.constructor.apply(this, [config]);
2074
Y.extend(COMMENTSEARCH, M.core.dialogue, {
2076
* Initialise the menu.
2078
* @method initializer
2081
initializer : function(config) {
2089
bb = this.get('boundingBox');
2090
bb.addClass('assignfeedback_editpdf_commentsearch');
2092
editor = this.get('editor');
2093
container = Y.Node.create('<div/>');
2095
placeholder = M.util.get_string('filter', 'assignfeedback_editpdf');
2096
commentfilter = Y.Node.create('<input type="text" size="20" placeholder="' + placeholder + '"/>');
2097
container.append(commentfilter);
2098
commentlist = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_menu"/>');
2099
container.append(commentlist);
2101
commentfilter.on('keyup', this.filter_search_comments, null, this);
2102
commentlist.delegate('click', this.focus_on_comment, 'a', this);
2103
commentlist.delegate('key', this.focus_on_comment, 'enter,space', 'a', this);
2105
// Set the body content.
2106
this.set('bodyContent', container);
2108
COMMENTSEARCH.superclass.initializer.call(this, config);
2112
* Event handler to filter the list of comments.
2115
* @method filter_search_comments
2117
filter_search_comments : function() {
2122
filternode = Y.one(SELECTOR.SEARCHFILTER);
2123
commentslist = Y.one(SELECTOR.SEARCHCOMMENTSLIST);
2125
filtertext = filternode.get('value');
2127
commentslist.all('li').each(function (node) {
2128
if (node.get('text').indexOf(filtertext) !== -1) {
2137
* Event handler to focus on a selected comment.
2141
* @method focus_on_comment
2143
focus_on_comment : function(e) {
2145
var target = e.target.ancestor('li'),
2146
comment = target.getData('comment'),
2147
editor = this.get('editor');
2151
if (comment.pageno === editor.currentpage) {
2152
comment.drawable.nodes[0].one('textarea').focus();
2154
// Comment is on a different page.
2155
editor.currentpage = comment.pageno;
2156
editor.change_page();
2157
comment.drawable.nodes[0].one('textarea').focus();
2168
var commentlist = this.get('boundingBox').one('ul'),
2169
editor = this.get('editor');
2171
commentlist.all('li').remove(true);
2173
// Rebuild the latest list of comments.
2174
Y.each(editor.pages, function(page) {
2175
Y.each(page.comments, function(comment) {
2176
var commentnode = Y.Node.create('<li><a href="#" tabindex="-1"><pre>' + comment.rawtext + '</pre></a></li>');
2177
commentlist.append(commentnode);
2178
commentnode.setData('comment', comment);
2182
this.centerDialogue();
2183
COMMENTSEARCH.superclass.show.call(this);
2186
NAME : COMMENTSEARCHNAME,
2189
* The editor this search window is attached to.
2192
* @type M.assignfeedback_editpdf.editor
2202
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
2203
M.assignfeedback_editpdf.commentsearch = COMMENTSEARCH;
2204
// This file is part of Moodle - http://moodle.org/
2206
// Moodle is free software: you can redistribute it and/or modify
2207
// it under the terms of the GNU General Public License as published by
2208
// the Free Software Foundation, either version 3 of the License, or
2209
// (at your option) any later version.
2211
// Moodle is distributed in the hope that it will be useful,
2212
// but WITHOUT ANY WARRANTY; without even the implied warranty of
2213
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2214
// GNU General Public License for more details.
2216
// You should have received a copy of the GNU General Public License
2217
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
2220
* Class representing a list of comments.
2222
* @module moodle-assignfeedback_editpdf-editor
2228
* @namespace M.assignfeedback_editpdf
2230
* @param M.assignfeedback_editpdf.editor editor
2231
* @param Int gradeid
2236
* @param String colour
2237
* @param String rawtext
2239
COMMENT = function(editor, gradeid, pageno, x, y, width, colour, rawtext) {
2242
* Reference to M.assignfeedback_editpdf.editor.
2244
* @type M.assignfeedback_editpdf.editor
2247
this.editor = editor;
2255
this.gradeid = gradeid || 0;
2263
this.x = parseInt(x, 10) || 0;
2271
this.y = parseInt(y, 10) || 0;
2279
this.width = parseInt(width, 10) || 0;
2287
this.rawtext = rawtext || '';
2290
* Comment page number
2295
this.pageno = pageno || 0;
2298
* Comment background colour.
2303
this.colour = colour || 'yellow';
2306
* Reference to M.assignfeedback_editpdf.drawable
2307
* @property drawable
2308
* @type M.assignfeedback_editpdf.drawable
2311
this.drawable = false;
2314
* Boolean used by a timeout to delete empty comments after a short delay.
2315
* @property deleteme
2319
this.deleteme = false;
2322
* Reference to the link that opens the menu.
2323
* @property menulink
2327
this.menulink = null;
2330
* Reference to the dialogue that is the context menu.
2332
* @type M.assignfeedback_editpdf.dropdown
2338
* Clean a comment record, returning an oject with only fields that are valid.
2343
this.clean = function() {
2345
gradeid : this.gradeid,
2346
x : parseInt(this.x, 10),
2347
y : parseInt(this.y, 10),
2348
width : parseInt(this.width, 10),
2349
rawtext : this.rawtext,
2350
pageno : this.currentpage,
2351
colour : this.colour
2358
* @method draw_comment
2359
* @param boolean focus - Set the keyboard focus to the new comment if true
2360
* @return M.assignfeedback_editpdf.drawable
2362
this.draw = function(focus) {
2363
var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
2365
drawingregion = Y.one(SELECTOR.DRAWINGREGION),
2371
// Lets add a contenteditable div.
2372
node = Y.Node.create('<textarea/>');
2373
container = Y.Node.create('<div class="commentdrawable"/>');
2374
menu = Y.Node.create('<a href="#"><img src="' + M.util.image_url('t/contextmenu', 'core') + '"/></a>');
2376
this.menulink = menu;
2377
container.append(node);
2379
if (!this.editor.get('readonly')) {
2380
container.append(menu);
2382
node.setAttribute('readonly', 'readonly');
2384
if (this.width < 100) {
2388
position = this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(this.x, this.y));
2390
width: this.width + 'px',
2391
backgroundColor: COMMENTCOLOUR[this.colour],
2392
color: COMMENTTEXTCOLOUR
2395
drawingregion.append(container);
2396
container.setStyle('position', 'absolute');
2397
container.setX(position.x);
2398
container.setY(position.y);
2399
drawable.nodes.push(container);
2400
node.set('value', this.rawtext);
2401
scrollheight = node.get('scrollHeight'),
2403
'height' : scrollheight + 'px',
2404
'overflow': 'hidden'
2406
if (!this.editor.get('readonly')) {
2407
this.attach_events(node, menu);
2412
this.drawable = drawable;
2419
* Delete an empty comment if it's menu hasn't been opened in time.
2420
* @method delete_comment_later
2422
this.delete_comment_later = function() {
2423
if (this.deleteme) {
2429
* Comment nodes have a bunch of event handlers attached to them directly.
2430
* This is all done here for neatness.
2433
* @method attach_comment_events
2434
* @param node - The Y.Node representing the comment.
2435
* @param menu - The Y.Node representing the menu.
2437
this.attach_events = function(node, menu) {
2438
// Save the text on blur.
2439
node.on('blur', function() {
2440
// Save the changes back to the comment.
2441
this.rawtext = node.get('value');
2442
this.width = parseInt(node.getStyle('width'), 10);
2445
if (this.rawtext.replace(/^\s+|\s+$/g, "") === '') {
2446
// Delete empty comments.
2447
this.deleteme = true;
2448
Y.later(400, this, this.delete_comment_later);
2450
this.editor.save_current_page();
2451
this.editor.editingcomment = false;
2454
// For delegated event handler.
2455
menu.setData('comment', this);
2457
node.on('keyup', function() {
2458
var scrollheight = node.get('scrollHeight'),
2459
height = parseInt(node.getStyle('height'), 10);
2461
// Webkit scrollheight fix.
2462
if (scrollheight === height + 8) {
2465
node.setStyle('height', scrollheight + 'px');
2469
node.on('gesturemovestart', function(e) {
2470
node.setData('dragging', true);
2471
node.setData('offsetx', e.clientX - node.getX());
2472
node.setData('offsety', e.clientY - node.getY());
2474
node.on('gesturemoveend', function() {
2475
node.setData('dragging', false);
2476
this.editor.save_current_page();
2478
node.on('gesturemove', function(e) {
2479
var x = e.clientX - node.getData('offsetx'),
2480
y = e.clientY - node.getData('offsety'),
2487
nodewidth = parseInt(node.getStyle('width'), 10);
2488
nodeheight = parseInt(node.getStyle('height'), 10);
2490
newlocation = this.editor.get_canvas_coordinates(new M.assignfeedback_editpdf.point(x, y));
2491
bounds = this.editor.get_canvas_bounds(true);
2495
bounds.width -= nodewidth + 42;
2496
bounds.height -= nodeheight + 8;
2497
// Clip to the window size - the comment size.
2498
newlocation.clip(bounds);
2500
this.x = newlocation.x;
2501
this.y = newlocation.y;
2503
windowlocation = this.editor.get_window_coordinates(newlocation);
2504
node.ancestor().setX(windowlocation.x);
2505
node.ancestor().setY(windowlocation.y);
2508
this.menu = new M.assignfeedback_editpdf.commentmenu({
2509
buttonNode: this.menulink,
2518
this.remove = function() {
2519
var i = 0, comments;
2521
comments = this.editor.pages[this.editor.currentpage].comments;
2522
for (i = 0; i < comments.length; i++) {
2523
if (comments[i] === this) {
2524
comments.splice(i, 1);
2525
this.drawable.erase();
2526
this.editor.save_current_page();
2533
* Event handler to remove a comment from the users quicklist.
2536
* @method remove_from_quicklist
2538
this.remove_from_quicklist = function(e, quickcomment) {
2543
this.editor.quicklist.remove(quickcomment);
2547
* A quick comment was selected in the list, update the active comment and redraw the page.
2551
* @method set_from_quick_comment
2553
this.set_from_quick_comment = function(e, quickcomment) {
2558
this.rawtext = quickcomment.rawtext;
2559
this.width = quickcomment.width;
2560
this.colour = quickcomment.colour;
2562
this.editor.save_current_page();
2564
this.editor.redraw();
2568
* Event handler to add a comment to the users quicklist.
2571
* @method add_to_quicklist
2573
this.add_to_quicklist = function(e) {
2576
this.editor.quicklist.add(this);
2580
* Draw the in progress edit.
2583
* @method draw_current_edit
2584
* @param M.assignfeedback_editpdf.edit edit
2586
this.draw_current_edit = function(edit) {
2587
var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
2591
bounds = new M.assignfeedback_editpdf.rect();
2592
bounds.bound([edit.start, edit.end]);
2594
// We will draw a box with the current background colour.
2595
shape = this.editor.graphic.addShape({
2597
width: bounds.width,
2598
height: bounds.height,
2600
color: COMMENTCOLOUR[edit.commentcolour]
2606
drawable.shapes.push(shape);
2612
* Promote the current edit to a real comment.
2615
* @method init_from_edit
2616
* @param M.assignfeedback_editpdf.edit edit
2617
* @return bool true if comment bound is more than min width/height, else false.
2619
this.init_from_edit = function(edit) {
2620
var bounds = new M.assignfeedback_editpdf.rect();
2621
bounds.bound([edit.start, edit.end]);
2623
// Minimum comment width.
2624
if (bounds.width < 100) {
2628
// Save the current edit to the server and the current page list.
2630
this.gradeid = this.editor.get('gradeid');
2631
this.pageno = this.editor.currentpage;
2634
this.width = bounds.width;
2635
this.colour = edit.commentcolour;
2638
return (bounds.has_min_width() && bounds.has_min_height());
2643
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
2644
M.assignfeedback_editpdf.comment = COMMENT;
2645
// This file is part of Moodle - http://moodle.org/
2647
// Moodle is free software: you can redistribute it and/or modify
2648
// it under the terms of the GNU General Public License as published by
2649
// the Free Software Foundation, either version 3 of the License, or
2650
// (at your option) any later version.
2652
// Moodle is distributed in the hope that it will be useful,
2653
// but WITHOUT ANY WARRANTY; without even the implied warranty of
2654
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2655
// GNU General Public License for more details.
2657
// You should have received a copy of the GNU General Public License
2658
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
2661
* Class representing a users quick comment.
2663
* @module moodle-assignfeedback_editpdf-editor
2669
* @namespace M.assignfeedback_editpdf
2670
* @class quickcomment
2672
QUICKCOMMENT = function(id, rawtext, width, colour) {
2675
* Quick comment text.
2680
this.rawtext = rawtext || '';
2691
* Width of the comment
2696
this.width = width || 100;
2699
* Colour of the comment.
2704
this.colour = colour || "yellow";
2707
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
2708
M.assignfeedback_editpdf.quickcomment = QUICKCOMMENT;
2709
// This file is part of Moodle - http://moodle.org/
2711
// Moodle is free software: you can redistribute it and/or modify
2712
// it under the terms of the GNU General Public License as published by
2713
// the Free Software Foundation, either version 3 of the License, or
2714
// (at your option) any later version.
2716
// Moodle is distributed in the hope that it will be useful,
2717
// but WITHOUT ANY WARRANTY; without even the implied warranty of
2718
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2719
// GNU General Public License for more details.
2721
// You should have received a copy of the GNU General Public License
2722
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
2725
* Class representing a users list of quick comments.
2727
* @module moodle-assignfeedback_editpdf-editor
2733
* @namespace M.assignfeedback_editpdf
2734
* @class quickcommentlist
2736
QUICKCOMMENTLIST = function(editor) {
2739
* Reference to M.assignfeedback_editpdf.editor.
2741
* @type M.assignfeedback_editpdf.editor
2744
this.editor = editor;
2749
* @type M.assignfeedback_editpdf.quickcomment[]
2755
* Add a comment to the users quicklist.
2760
this.add = function(comment) {
2761
var ajaxurl = AJAXBASE,
2764
// Do not save empty comments.
2765
if (comment.rawtext === '') {
2774
'sesskey' : M.cfg.sesskey,
2775
'action' : 'addtoquicklist',
2776
'userid' : this.editor.get('userid'),
2777
'commenttext' : comment.rawtext,
2778
'width' : comment.width,
2779
'colour' : comment.colour,
2780
'attemptnumber' : this.editor.get('attemptnumber'),
2781
'assignmentid' : this.editor.get('assignmentid')
2784
success: function(tid, response) {
2785
var jsondata, quickcomment;
2787
jsondata = Y.JSON.parse(response.responseText);
2788
if (jsondata.error) {
2789
return new M.core.ajaxException(jsondata);
2791
quickcomment = new M.assignfeedback_editpdf.quickcomment(jsondata.id,
2795
this.comments.push(quickcomment);
2798
return new M.core.exception(e);
2801
failure: function(tid, response) {
2802
return M.core.exception(response.responseText);
2807
Y.io(ajaxurl, config);
2811
* Remove a comment from the users quicklist.
2816
this.remove = function(comment) {
2817
var ajaxurl = AJAXBASE,
2820
// Should not happen.
2830
'sesskey' : M.cfg.sesskey,
2831
'action' : 'removefromquicklist',
2832
'userid' : this.editor.get('userid'),
2833
'commentid' : comment.id,
2834
'attemptnumber' : this.editor.get('attemptnumber'),
2835
'assignmentid' : this.editor.get('assignmentid')
2838
success: function() {
2841
// Find and remove the comment from the quicklist.
2842
i = this.comments.indexOf(comment);
2844
this.comments.splice(i, 1);
2847
failure: function(tid, response) {
2848
return M.core.exception(response.responseText);
2853
Y.io(ajaxurl, config);
2857
* Load the users quick comments list.
2860
* @method load_quicklist
2862
this.load = function() {
2863
var ajaxurl = AJAXBASE,
2871
'sesskey' : M.cfg.sesskey,
2872
'action' : 'loadquicklist',
2873
'userid' : this.editor.get('userid'),
2874
'attemptnumber' : this.editor.get('attemptnumber'),
2875
'assignmentid' : this.editor.get('assignmentid')
2878
success: function(tid, response) {
2881
jsondata = Y.JSON.parse(response.responseText);
2882
if (jsondata.error) {
2883
return new M.core.ajaxException(jsondata);
2885
Y.each(jsondata, function(comment) {
2886
var quickcomment = new M.assignfeedback_editpdf.quickcomment(comment.id,
2890
this.comments.push(quickcomment);
2894
return new M.core.exception(e);
2897
failure: function(tid, response) {
2898
return M.core.exception(response.responseText);
2903
Y.io(ajaxurl, config);
2907
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
2908
M.assignfeedback_editpdf.quickcommentlist = QUICKCOMMENTLIST;
2909
// This file is part of Moodle - http://moodle.org/
2911
// Moodle is free software: you can redistribute it and/or modify
2912
// it under the terms of the GNU General Public License as published by
2913
// the Free Software Foundation, either version 3 of the License, or
2914
// (at your option) any later version.
2916
// Moodle is distributed in the hope that it will be useful,
2917
// but WITHOUT ANY WARRANTY; without even the implied warranty of
2918
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2919
// GNU General Public License for more details.
2921
// You should have received a copy of the GNU General Public License
2922
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
2925
* Provides an in browser PDF editor.
2927
* @module moodle-assignfeedback_editpdf-editor
2932
* This is an in browser PDF editor.
2934
* @namespace M.assignfeedback_editpdf.editor
2939
EDITOR = function() {
2940
EDITOR.superclass.constructor.apply(this, arguments);
2942
EDITOR.prototype = {
2944
// Instance variables.
2946
* The dialogue used for all action menu displays.
2948
* @type M.core.dialogue
2954
* The number of pages in the pdf.
2962
* The active page in the editor.
2970
* A list of page objects. Each page has a list of comments and annotations.
2978
* The yui node for the loading icon.
2986
* Image object of the current page image.
2994
* YUI Graphic class for drawing shapes.
3002
* Info about the current edit operation.
3003
* @property currentedit
3004
* @type M.assignfeedback_editpdf.edit
3007
currentedit : new M.assignfeedback_editpdf.edit(),
3011
* @property currentdrawable
3012
* @type M.assignfeedback_editpdf.drawable (or false)
3015
currentdrawable : false,
3018
* Current drawables.
3019
* @property drawables
3020
* @type array(M.assignfeedback_editpdf.drawable)
3026
* Current comment when the comment menu is open.
3027
* @property currentcomment
3028
* @type M.assignfeedback_editpdf.comment
3031
currentcomment : null,
3034
* Current annotation when the select tool is used.
3035
* @property currentannotation
3036
* @type M.assignfeedback_editpdf.annotation
3039
currentannotation : null,
3042
* Last selected annotation tool
3043
* @property lastannotationtool
3047
lastanntationtool : "pen",
3050
* The users comments quick list
3051
* @property quicklist
3052
* @type M.assignfeedback_editpdf.quickcommentlist
3058
* The search comments window.
3059
* @property searchcommentswindow
3060
* @type M.core.dialogue
3063
searchcommentswindow : null,
3067
* The selected stamp picture.
3068
* @property currentstamp
3072
currentstamp : null,
3083
* Prevent new comments from appearing
3084
* immediately after clicking off a current
3086
* @property editingcomment
3090
editingcomment : false,
3093
* Called during the initialisation process of the object.
3094
* @method initializer
3096
initializer : function() {
3099
link = Y.one('#' + this.get('linkid'));
3102
link.on('click', this.link_handler, this);
3103
link.on('key', this.link_handler, 'down:13', this);
3105
this.currentedit.start = false;
3106
this.currentedit.end = false;
3107
if (!this.get('readonly')) {
3108
this.quicklist = new M.assignfeedback_editpdf.quickcommentlist(this);
3114
* Called to show/hide buttons and set the current colours/stamps.
3115
* @method refresh_button_state
3117
refresh_button_state : function() {
3118
var button, currenttoolnode, imgurl;
3120
// Initalise the colour buttons.
3121
button = Y.one(SELECTOR.COMMENTCOLOURBUTTON);
3123
imgurl = M.util.image_url('background_colour_' + this.currentedit.commentcolour, 'assignfeedback_editpdf');
3124
button.one('img').setAttribute('src', imgurl);
3126
if (this.currentedit.commentcolour === 'clear') {
3127
button.one('img').setStyle('borderStyle', 'dashed');
3129
button.one('img').setStyle('borderStyle', 'solid');
3132
button = Y.one(SELECTOR.ANNOTATIONCOLOURBUTTON);
3133
imgurl = M.util.image_url('colour_' + this.currentedit.annotationcolour, 'assignfeedback_editpdf');
3134
button.one('img').setAttribute('src', imgurl);
3136
currenttoolnode = Y.one(TOOLSELECTOR[this.currentedit.tool]);
3137
currenttoolnode.addClass('assignfeedback_editpdf_selectedbutton');
3138
currenttoolnode.setAttribute('aria-pressed', 'true');
3140
button = Y.one(SELECTOR.STAMPSBUTTON);
3141
button.one('img').setAttrs({'src': this.get_stamp_image_url(this.currentedit.stamp),
3147
* Called to get the bounds of the drawing region.
3148
* @method get_canvas_bounds
3150
get_canvas_bounds : function() {
3151
var canvas = Y.one(SELECTOR.DRAWINGCANVAS),
3152
offsetcanvas = canvas.getXY(),
3153
offsetleft = offsetcanvas[0],
3154
offsettop = offsetcanvas[1],
3155
width = parseInt(canvas.getStyle('width'), 10),
3156
height = parseInt(canvas.getStyle('height'), 10);
3158
return new M.assignfeedback_editpdf.rect(offsetleft, offsettop, width, height);
3162
* Called to translate from window coordinates to canvas coordinates.
3163
* @method get_canvas_coordinates
3164
* @param M.assignfeedback_editpdf.point point in window coordinats.
3166
get_canvas_coordinates : function(point) {
3167
var bounds = this.get_canvas_bounds(),
3168
newpoint = new M.assignfeedback_editpdf.point(point.x - bounds.x, point.y - bounds.y);
3170
bounds.x = bounds.y = 0;
3172
newpoint.clip(bounds);
3177
* Called to translate from canvas coordinates to window coordinates.
3178
* @method get_window_coordinates
3179
* @param M.assignfeedback_editpdf.point point in window coordinats.
3181
get_window_coordinates : function(point) {
3182
var bounds = this.get_canvas_bounds(),
3183
newpoint = new M.assignfeedback_editpdf.point(point.x + bounds.x, point.y + bounds.y);
3189
* Called to open the pdf editing dialogue.
3190
* @method link_handler
3192
link_handler : function(e) {
3196
if (!this.dialogue) {
3197
this.dialogue = new M.core.dialogue({
3198
headerContent: this.get('header'),
3199
bodyContent: this.get('body'),
3200
footerContent: this.get('footer'),
3207
// Add custom class for styling.
3208
this.dialogue.get('boundingBox').addClass(CSS.DIALOGUE);
3210
this.loadingicon = Y.one(SELECTOR.LOADINGICON);
3212
drawingcanvas = Y.one(SELECTOR.DRAWINGCANVAS);
3213
this.graphic = new Y.Graphic({render : SELECTOR.DRAWINGCANVAS});
3215
if (!this.get('readonly')) {
3216
drawingcanvas.on('gesturemovestart', this.edit_start, null, this);
3217
drawingcanvas.on('gesturemove', this.edit_move, null, this);
3218
drawingcanvas.on('gesturemoveend', this.edit_end, null, this);
3220
this.refresh_button_state();
3223
this.load_all_pages();
3225
this.dialogue.centerDialogue();
3226
this.dialogue.show();
3230
* Called to load the information and annotations for all pages.
3231
* @method load_all_pages
3233
load_all_pages : function() {
3234
var ajaxurl = AJAXBASE,
3236
checkconversionstatus,
3244
sesskey : M.cfg.sesskey,
3245
action : 'loadallpages',
3246
userid : this.get('userid'),
3247
attemptnumber : this.get('attemptnumber'),
3248
assignmentid : this.get('assignmentid')
3251
success: function(tid, response) {
3252
this.all_pages_loaded(response.responseText);
3254
failure: function(tid, response) {
3255
return new M.core.exception(response.responseText);
3260
Y.io(ajaxurl, config);
3262
// If pages are not loaded, check PDF conversion status for the progress bar.
3263
if (this.pagecount <= 0) {
3264
checkconversionstatus = {
3269
sesskey : M.cfg.sesskey,
3270
action : 'conversionstatus',
3271
userid : this.get('userid'),
3272
attemptnumber : this.get('attemptnumber'),
3273
assignmentid : this.get('assignmentid')
3276
success: function(tid, response) {
3277
ajax_error_total = 0;
3278
if (this.pagecount === 0) {
3279
var pagetotal = this.get('pagetotal');
3281
// Update the progress bar.
3282
var progressbarcontainer = Y.one(SELECTOR.PROGRESSBARCONTAINER);
3283
var progressbar = progressbarcontainer.one('.bar');
3285
// Calculate progress.
3286
var progress = (response.response / pagetotal) * 100;
3287
progressbar.setStyle('width', progress + '%');
3288
progressbarcontainer.setAttribute('aria-valuenow', progress);
3291
// New ajax request delayed of a second.
3292
Y.later(1000, this, function () {
3293
Y.io(AJAXBASEPROGRESS, checkconversionstatus);
3297
failure: function(tid, response) {
3298
ajax_error_total = ajax_error_total + 1;
3299
// We only continue on error if the all pages were not generated,
3300
// and if the ajax call did not produce 5 errors in the row.
3301
if (this.pagecount === 0 && ajax_error_total < 5) {
3302
Y.later(1000, this, function () {
3303
Y.io(AJAXBASEPROGRESS, checkconversionstatus);
3306
return new M.core.exception(response.responseText);
3310
// We start the AJAX "generated page total number" call a second later to give a chance to
3311
// the AJAX "combined pdf generation" call to clean the previous submission images.
3312
Y.later(1000, this, function () {
3313
ajax_error_total = 0;
3314
Y.io(AJAXBASEPROGRESS, checkconversionstatus);
3320
* The info about all pages in the pdf has been returned.
3321
* @param string The ajax response as text.
3323
* @method all_pages_loaded
3325
all_pages_loaded : function(responsetext) {
3326
var data, i, j, comment, error;
3328
data = Y.JSON.parse(responsetext);
3329
if (data.error || !data.pagecount) {
3330
this.dialogue.hide();
3331
// Display alert dialogue.
3332
error = new M.core.alert({ message: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf') });
3337
this.dialogue.hide();
3338
// Display alert dialogue.
3339
error = new M.core.alert({ title: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf')});
3344
this.pagecount = data.pagecount;
3345
this.pages = data.pages;
3347
for (i = 0; i < this.pages.length; i++) {
3348
for (j = 0; j < this.pages[i].comments.length; j++) {
3349
comment = this.pages[i].comments[j];
3350
this.pages[i].comments[j] = new M.assignfeedback_editpdf.comment(this,
3359
for (j = 0; j < this.pages[i].annotations.length; j++) {
3360
data = this.pages[i].annotations[j];
3361
this.pages[i].annotations[j] = this.create_annotation(data.type, data);
3366
if (this.quicklist) {
3367
this.quicklist.load();
3369
this.setup_navigation();
3370
this.setup_toolbar();
3375
* Get the full pluginfile url for an image file - just given the filename.
3378
* @method get_stamp_image_url
3379
* @param string filename
3381
get_stamp_image_url : function(filename) {
3382
var urls = this.get('stampfiles'),
3385
Y.Array.each(urls, function(url) {
3386
if (url.indexOf(filename) > 0) {
3395
* Attach listeners and enable the color picker buttons.
3397
* @method setup_toolbar
3399
setup_toolbar : function() {
3401
commentcolourbutton,
3402
annotationcolourbutton,
3403
searchcommentsbutton,
3409
searchcommentsbutton = Y.one(SELECTOR.SEARCHCOMMENTSBUTTON);
3410
searchcommentsbutton.on('click', this.open_search_comments, this);
3411
searchcommentsbutton.on('key', this.open_search_comments, 'down:13', this);
3413
if (this.get('readonly')) {
3416
// Setup the tool buttons.
3417
Y.each(TOOLSELECTOR, function(selector, tool) {
3418
toolnode = Y.one(selector);
3419
toolnode.on('click', this.handle_tool_button, this, tool);
3420
toolnode.on('key', this.handle_tool_button, 'down:13', this, tool);
3421
toolnode.setAttribute('aria-pressed', 'false');
3424
// Set the default tool.
3426
commentcolourbutton = Y.one(SELECTOR.COMMENTCOLOURBUTTON);
3427
picker = new M.assignfeedback_editpdf.colourpicker({
3428
buttonNode: commentcolourbutton,
3429
colours: COMMENTCOLOUR,
3430
iconprefix: 'background_colour_',
3431
callback: function (e) {
3432
var colour = e.target.getAttribute('data-colour');
3434
colour = e.target.ancestor().getAttribute('data-colour');
3436
this.currentedit.commentcolour = colour;
3437
this.handle_tool_button(e, "comment");
3442
annotationcolourbutton = Y.one(SELECTOR.ANNOTATIONCOLOURBUTTON);
3443
picker = new M.assignfeedback_editpdf.colourpicker({
3444
buttonNode: annotationcolourbutton,
3445
iconprefix: 'colour_',
3446
colours: ANNOTATIONCOLOUR,
3447
callback: function (e) {
3448
var colour = e.target.getAttribute('data-colour');
3450
colour = e.target.ancestor().getAttribute('data-colour');
3452
this.currentedit.annotationcolour = colour;
3453
if (this.lastannotationtool) {
3454
this.handle_tool_button(e, this.lastannotationtool);
3456
this.handle_tool_button(e, "pen");
3462
stampfiles = this.get('stampfiles');
3463
if (stampfiles.length <= 0) {
3464
Y.one(TOOLSELECTOR.stamp).ancestor().hide();
3466
filename = stampfiles[0].substr(stampfiles[0].lastIndexOf('/') + 1);
3467
this.currentedit.stamp = filename;
3468
currentstampbutton = Y.one(SELECTOR.STAMPSBUTTON);
3470
picker = new M.assignfeedback_editpdf.stamppicker({
3471
buttonNode: currentstampbutton,
3473
callback: function(e) {
3474
var stamp = e.target.getAttribute('data-stamp'),
3478
stamp = e.target.ancestor().getAttribute('data-stamp');
3480
filename = stamp.substr(stamp.lastIndexOf('/'));
3481
this.currentedit.stamp = filename;
3482
this.handle_tool_button(e, "stamp");
3486
this.refresh_button_state();
3491
* Change the current tool.
3493
* @method handle_tool_button
3495
handle_tool_button : function(e, tool) {
3496
var currenttoolnode;
3500
// Change style of the pressed button.
3501
currenttoolnode = Y.one(TOOLSELECTOR[this.currentedit.tool]);
3502
currenttoolnode.removeClass('assignfeedback_editpdf_selectedbutton');
3503
currenttoolnode.setAttribute('aria-pressed', 'false');
3504
this.currentedit.tool = tool;
3505
if (tool !== "comment" && tool !== "select" && tool !== "stamp") {
3506
this.lastannotationtool = tool;
3508
this.refresh_button_state();
3512
* JSON encode the current page data - stripping out drawable references which cannot be encoded.
3514
* @method stringify_current_page
3517
stringify_current_page : function() {
3523
for (i = 0; i < this.pages[this.currentpage].comments.length; i++) {
3524
comments[i] = this.pages[this.currentpage].comments[i].clean();
3526
for (i = 0; i < this.pages[this.currentpage].annotations.length; i++) {
3527
annotations[i] = this.pages[this.currentpage].annotations[i].clean();
3530
page = { comments : comments, annotations : annotations };
3532
return Y.JSON.stringify(page);
3536
* Generate a drawable from the current in progress edit.
3538
* @method get_current_drawable
3540
get_current_drawable : function() {
3545
if (!this.currentedit.start || !this.currentedit.end) {
3549
if (this.currentedit.tool === 'comment') {
3550
comment = new M.assignfeedback_editpdf.comment(this);
3551
drawable = comment.draw_current_edit(this.currentedit);
3553
annotation = this.create_annotation(this.currentedit.tool, {});
3555
drawable = annotation.draw_current_edit(this.currentedit);
3563
* Redraw the active edit.
3565
* @method redraw_active_edit
3567
redraw_current_edit : function() {
3568
if (this.currentdrawable) {
3569
this.currentdrawable.erase();
3571
this.currentdrawable = this.get_current_drawable();
3575
* Event handler for mousedown or touchstart.
3578
* @method edit_start
3580
edit_start : function(e) {
3581
var canvas = Y.one(SELECTOR.DRAWINGCANVAS),
3582
offset = canvas.getXY(),
3583
scrolltop = canvas.get('docScrollY'),
3584
scrollleft = canvas.get('docScrollX'),
3585
point = {x : e.clientX - offset[0] + scrollleft,
3586
y : e.clientY - offset[1] + scrolltop},
3590
// Ignore right mouse click.
3591
if (e.button === 3) {
3595
if (this.currentedit.starttime) {
3599
if (this.editingcomment) {
3603
this.currentedit.starttime = new Date().getTime();
3604
this.currentedit.start = point;
3605
this.currentedit.end = {x : point.x, y : point.y};
3607
if (this.currentedit.tool === 'select') {
3608
x = this.currentedit.end.x;
3609
y = this.currentedit.end.y;
3610
annotations = this.pages[this.currentpage].annotations;
3611
// Find the first annotation whose bounds encompass the click.
3612
Y.each(annotations, function(annotation) {
3613
if (((x - annotation.x) * (x - annotation.endx)) <= 0 &&
3614
((y - annotation.y) * (y - annotation.endy)) <= 0) {
3615
selected = annotation;
3620
lastannotation = this.currentannotation;
3621
this.currentannotation = selected;
3622
if (lastannotation && lastannotation !== selected) {
3623
// Redraw the last selected annotation to remove the highlight.
3624
if (lastannotation.drawable) {
3625
lastannotation.drawable.erase();
3626
this.drawables.push(lastannotation.draw());
3629
// Redraw the newly selected annotation to show the highlight.
3630
if (this.currentannotation.drawable) {
3631
this.currentannotation.drawable.erase();
3633
this.drawables.push(this.currentannotation.draw());
3636
if (this.currentannotation) {
3637
// Used to calculate drag offset.
3638
this.currentedit.annotationstart = { x : this.currentannotation.x,
3639
y : this.currentannotation.y };
3644
* Event handler for mousemove.
3649
edit_move : function(e) {
3650
var bounds = this.get_canvas_bounds(),
3651
canvas = Y.one(SELECTOR.DRAWINGCANVAS),
3652
clientpoint = new M.assignfeedback_editpdf.point(e.clientX + canvas.get('docScrollX'),
3653
e.clientY + canvas.get('docScrollY')),
3654
point = this.get_canvas_coordinates(clientpoint);
3656
// Ignore events out of the canvas area.
3657
if (point.x < 0 || point.x > bounds.width || point.y < 0 || point.y > bounds.height) {
3661
if (this.currentedit.tool === 'pen') {
3662
this.currentedit.path.push(point);
3665
if (this.currentedit.tool === 'select') {
3666
if (this.currentannotation && this.currentedit) {
3667
this.currentannotation.move( this.currentedit.annotationstart.x + point.x - this.currentedit.start.x,
3668
this.currentedit.annotationstart.y + point.y - this.currentedit.start.y);
3671
if (this.currentedit.start) {
3672
this.currentedit.end = point;
3673
this.redraw_current_edit();
3679
* Event handler for mouseup or touchend.
3684
edit_end : function() {
3689
duration = new Date().getTime() - this.currentedit.start;
3691
if (duration < CLICKTIMEOUT || this.currentedit.start === false) {
3695
if (this.currentedit.tool === 'comment') {
3696
if (this.currentdrawable) {
3697
this.currentdrawable.erase();
3699
this.currentdrawable = false;
3700
comment = new M.assignfeedback_editpdf.comment(this);
3701
if (comment.init_from_edit(this.currentedit)) {
3702
this.pages[this.currentpage].comments.push(comment);
3703
this.drawables.push(comment.draw(true));
3704
this.editingcomment = true;
3707
annotation = this.create_annotation(this.currentedit.tool, {});
3709
if (this.currentdrawable) {
3710
this.currentdrawable.erase();
3712
this.currentdrawable = false;
3713
if (annotation.init_from_edit(this.currentedit)) {
3714
this.pages[this.currentpage].annotations.push(annotation);
3715
this.drawables.push(annotation.draw());
3720
// Save the changes.
3721
this.save_current_page();
3723
// Reset the current edit.
3724
this.currentedit.starttime = 0;
3725
this.currentedit.start = false;
3726
this.currentedit.end = false;
3727
this.currentedit.path = [];
3731
* Factory method for creating annotations of the correct subclass.
3733
* @method create_annotation
3735
create_annotation : function(type, data) {
3738
if (type === "line") {
3739
return new M.assignfeedback_editpdf.annotationline(data);
3740
} else if (type === "rectangle") {
3741
return new M.assignfeedback_editpdf.annotationrectangle(data);
3742
} else if (type === "oval") {
3743
return new M.assignfeedback_editpdf.annotationoval(data);
3744
} else if (type === "pen") {
3745
return new M.assignfeedback_editpdf.annotationpen(data);
3746
} else if (type === "highlight") {
3747
return new M.assignfeedback_editpdf.annotationhighlight(data);
3748
} else if (type === "stamp") {
3749
return new M.assignfeedback_editpdf.annotationstamp(data);
3755
* Save all the annotations and comments for the current page.
3757
* @method save_current_page
3759
save_current_page : function() {
3760
var ajaxurl = AJAXBASE,
3768
'sesskey' : M.cfg.sesskey,
3769
'action' : 'savepage',
3770
'index' : this.currentpage,
3771
'userid' : this.get('userid'),
3772
'attemptnumber' : this.get('attemptnumber'),
3773
'assignmentid' : this.get('assignmentid'),
3774
'page' : this.stringify_current_page()
3777
success: function(tid, response) {
3780
jsondata = Y.JSON.parse(response.responseText);
3781
if (jsondata.error) {
3782
return new M.core.ajaxException(jsondata);
3784
Y.one(SELECTOR.UNSAVEDCHANGESDIV).addClass('haschanges');
3786
return new M.core.exception(e);
3789
failure: function(tid, response) {
3790
return new M.core.exception(response.responseText);
3795
Y.io(ajaxurl, config);
3800
* Event handler to open the comment search interface.
3804
* @method open_search_comments
3806
open_search_comments : function(e) {
3807
if (!this.searchcommentswindow) {
3808
this.searchcommentswindow = new M.assignfeedback_editpdf.commentsearch({
3813
this.searchcommentswindow.show();
3818
* Redraw all the comments and annotations.
3822
redraw : function() {
3826
page = this.pages[this.currentpage];
3827
while (this.drawables.length > 0) {
3828
this.drawables.pop().erase();
3831
for (i = 0; i < page.annotations.length; i++) {
3832
this.drawables.push(page.annotations[i].draw());
3834
for (i = 0; i < page.comments.length; i++) {
3835
this.drawables.push(page.comments[i].draw(false));
3840
* Load the image for this pdf page and remove the loading icon (if there).
3842
* @method change_page
3844
change_page : function() {
3845
var drawingcanvas = Y.one(SELECTOR.DRAWINGCANVAS),
3850
previousbutton = Y.one(SELECTOR.PREVIOUSBUTTON);
3851
nextbutton = Y.one(SELECTOR.NEXTBUTTON);
3853
if (this.currentpage > 0) {
3854
previousbutton.removeAttribute('disabled');
3856
previousbutton.setAttribute('disabled', 'true');
3858
if (this.currentpage < (this.pagecount - 1)) {
3859
nextbutton.removeAttribute('disabled');
3861
nextbutton.setAttribute('disabled', 'true');
3864
page = this.pages[this.currentpage];
3865
this.loadingicon.hide();
3866
drawingcanvas.setStyle('backgroundImage', 'url("' + page.url + '")');
3868
// Update page select.
3869
Y.one(SELECTOR.PAGESELECT).set('value', this.currentpage);
3875
* Now we know how many pages there are,
3876
* we can enable the navigation controls.
3878
* @method setup_navigation
3880
setup_navigation : function() {
3887
pageselect = Y.one(SELECTOR.PAGESELECT);
3889
options = pageselect.all('option');
3890
if (options.size() <= 1) {
3891
for (i = 0; i < this.pages.length; i++) {
3892
option = Y.Node.create('<option/>');
3893
option.setAttribute('value', i);
3894
option.setHTML(M.util.get_string('pagenumber', 'assignfeedback_editpdf', i+1));
3895
pageselect.append(option);
3898
pageselect.removeAttribute('disabled');
3899
pageselect.on('change', function() {
3900
this.currentpage = pageselect.get('value');
3904
previousbutton = Y.one(SELECTOR.PREVIOUSBUTTON);
3905
nextbutton = Y.one(SELECTOR.NEXTBUTTON);
3907
previousbutton.on('click', this.previous_page, this);
3908
previousbutton.on('key', this.previous_page, 'down:13', this);
3909
nextbutton.on('click', this.next_page, this);
3910
nextbutton.on('key', this.next_page, 'down:13', this);
3914
* Navigate to the previous page.
3916
* @method previous_page
3918
previous_page : function(e) {
3921
if (this.currentpage < 0) {
3922
this.currentpage = 0;
3928
* Navigate to the next page.
3932
next_page : function(e) {
3935
if (this.currentpage >= this.pages.length) {
3936
this.currentpage = this.pages.length - 1;
3945
Y.extend(EDITOR, Y.Base, EDITOR.prototype, {
3946
NAME : 'moodle-assignfeedback_editpdf-editor',
3949
validator : Y.Lang.isInteger,
3953
validator : Y.Lang.isInteger,
3957
validator : Y.Lang.isInteger,
3961
validator : Y.Lang.isString,
3965
validator : Y.Lang.isString,
3969
validator : Y.Lang.isString,
3973
validator : Y.Lang.isString,
3977
validator : Y.Lang.isString,
3981
validator : Y.Lang.isBoolean,
3985
validator : Y.Lang.isArray,
3989
validator : Y.Lang.isInteger,
3996
* Assignfeedback edit pdf namespace.
3998
* @class assignfeedback_editpdf
4000
M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
4004
* @namespace M.assignfeedback_editpdf.editor
4008
M.assignfeedback_editpdf.editor = M.assignfeedback_editpdf.editor || {};
4011
* Init function - will create a new instance every time.
4014
* @param {Object} params
4016
M.assignfeedback_editpdf.editor.init = M.assignfeedback_editpdf.editor.init || function(params) {
4017
return new EDITOR(params);
4030
"querystring-stringify-simple",
4031
"moodle-core-notification-dialog",
4032
"moodle-core-notification-exception",
4033
"moodle-core-notification-ajaxexception"