| 1 | efrain | 1 | // This file is part of Moodle - http://moodle.org/
 | 
        
           |  |  | 2 | //
 | 
        
           |  |  | 3 | // Moodle is free software: you can redistribute it and/or modify
 | 
        
           |  |  | 4 | // it under the terms of the GNU General Public License as published by
 | 
        
           |  |  | 5 | // the Free Software Foundation, either version 3 of the License, or
 | 
        
           |  |  | 6 | // (at your option) any later version.
 | 
        
           |  |  | 7 | //
 | 
        
           |  |  | 8 | // Moodle is distributed in the hope that it will be useful,
 | 
        
           |  |  | 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
        
           |  |  | 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
        
           |  |  | 11 | // GNU General Public License for more details.
 | 
        
           |  |  | 12 | //
 | 
        
           |  |  | 13 | // You should have received a copy of the GNU General Public License
 | 
        
           |  |  | 14 | // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 | 
        
           |  |  | 15 | /* eslint-disable no-unused-vars */
 | 
        
           |  |  | 16 |   | 
        
           |  |  | 17 | /**
 | 
        
           |  |  | 18 |  * Provides an in browser PDF editor.
 | 
        
           |  |  | 19 |  *
 | 
        
           |  |  | 20 |  * @module moodle-assignfeedback_editpdf-editor
 | 
        
           |  |  | 21 |  */
 | 
        
           |  |  | 22 |   | 
        
           |  |  | 23 | /**
 | 
        
           |  |  | 24 |  * EDITOR
 | 
        
           |  |  | 25 |  * This is an in browser PDF editor.
 | 
        
           |  |  | 26 |  *
 | 
        
           |  |  | 27 |  * @namespace M.assignfeedback_editpdf
 | 
        
           |  |  | 28 |  * @class editor
 | 
        
           |  |  | 29 |  * @constructor
 | 
        
           |  |  | 30 |  * @extends Y.Base
 | 
        
           |  |  | 31 |  */
 | 
        
           |  |  | 32 | var EDITOR = function() {
 | 
        
           |  |  | 33 |     EDITOR.superclass.constructor.apply(this, arguments);
 | 
        
           |  |  | 34 | };
 | 
        
           |  |  | 35 | EDITOR.prototype = {
 | 
        
           |  |  | 36 |   | 
        
           |  |  | 37 |     /**
 | 
        
           |  |  | 38 |      * Store old coordinates of the annotations before rotation happens.
 | 
        
           |  |  | 39 |      */
 | 
        
           |  |  | 40 |     oldannotationcoordinates: null,
 | 
        
           |  |  | 41 |   | 
        
           |  |  | 42 |     /**
 | 
        
           |  |  | 43 |      * The dialogue used for all action menu displays.
 | 
        
           |  |  | 44 |      *
 | 
        
           |  |  | 45 |      * @property type
 | 
        
           |  |  | 46 |      * @type M.core.dialogue
 | 
        
           |  |  | 47 |      * @protected
 | 
        
           |  |  | 48 |      */
 | 
        
           |  |  | 49 |     dialogue: null,
 | 
        
           |  |  | 50 |   | 
        
           |  |  | 51 |     /**
 | 
        
           |  |  | 52 |      * The panel used for all action menu displays.
 | 
        
           |  |  | 53 |      *
 | 
        
           |  |  | 54 |      * @property type
 | 
        
           |  |  | 55 |      * @type Y.Node
 | 
        
           |  |  | 56 |      * @protected
 | 
        
           |  |  | 57 |      */
 | 
        
           |  |  | 58 |     panel: null,
 | 
        
           |  |  | 59 |   | 
        
           |  |  | 60 |     /**
 | 
        
           |  |  | 61 |      * The number of pages in the pdf.
 | 
        
           |  |  | 62 |      *
 | 
        
           |  |  | 63 |      * @property pagecount
 | 
        
           |  |  | 64 |      * @type Number
 | 
        
           |  |  | 65 |      * @protected
 | 
        
           |  |  | 66 |      */
 | 
        
           |  |  | 67 |     pagecount: 0,
 | 
        
           |  |  | 68 |   | 
        
           |  |  | 69 |     /**
 | 
        
           |  |  | 70 |      * The active page in the editor.
 | 
        
           |  |  | 71 |      *
 | 
        
           |  |  | 72 |      * @property currentpage
 | 
        
           |  |  | 73 |      * @type Number
 | 
        
           |  |  | 74 |      * @protected
 | 
        
           |  |  | 75 |      */
 | 
        
           |  |  | 76 |     currentpage: 0,
 | 
        
           |  |  | 77 |   | 
        
           |  |  | 78 |     /**
 | 
        
           |  |  | 79 |      * A list of page objects. Each page has a list of comments and annotations.
 | 
        
           |  |  | 80 |      *
 | 
        
           |  |  | 81 |      * @property pages
 | 
        
           |  |  | 82 |      * @type array
 | 
        
           |  |  | 83 |      * @protected
 | 
        
           |  |  | 84 |      */
 | 
        
           |  |  | 85 |     pages: [],
 | 
        
           |  |  | 86 |   | 
        
           |  |  | 87 |     /**
 | 
        
           |  |  | 88 |      * The reported status of the document.
 | 
        
           |  |  | 89 |      *
 | 
        
           |  |  | 90 |      * @property documentstatus
 | 
        
           |  |  | 91 |      * @type int
 | 
        
           |  |  | 92 |      * @protected
 | 
        
           |  |  | 93 |      */
 | 
        
           |  |  | 94 |     documentstatus: 0,
 | 
        
           |  |  | 95 |   | 
        
           |  |  | 96 |     /**
 | 
        
           |  |  | 97 |      * The yui node for the loading icon.
 | 
        
           |  |  | 98 |      *
 | 
        
           |  |  | 99 |      * @property loadingicon
 | 
        
           |  |  | 100 |      * @type Node
 | 
        
           |  |  | 101 |      * @protected
 | 
        
           |  |  | 102 |      */
 | 
        
           |  |  | 103 |     loadingicon: null,
 | 
        
           |  |  | 104 |   | 
        
           |  |  | 105 |     /**
 | 
        
           |  |  | 106 |      * Image object of the current page image.
 | 
        
           |  |  | 107 |      *
 | 
        
           |  |  | 108 |      * @property pageimage
 | 
        
           |  |  | 109 |      * @type Image
 | 
        
           |  |  | 110 |      * @protected
 | 
        
           |  |  | 111 |      */
 | 
        
           |  |  | 112 |     pageimage: null,
 | 
        
           |  |  | 113 |   | 
        
           |  |  | 114 |     /**
 | 
        
           |  |  | 115 |      * YUI Graphic class for drawing shapes.
 | 
        
           |  |  | 116 |      *
 | 
        
           |  |  | 117 |      * @property graphic
 | 
        
           |  |  | 118 |      * @type Graphic
 | 
        
           |  |  | 119 |      * @protected
 | 
        
           |  |  | 120 |      */
 | 
        
           |  |  | 121 |     graphic: null,
 | 
        
           |  |  | 122 |   | 
        
           |  |  | 123 |     /**
 | 
        
           |  |  | 124 |      * Info about the current edit operation.
 | 
        
           |  |  | 125 |      *
 | 
        
           |  |  | 126 |      * @property currentedit
 | 
        
           |  |  | 127 |      * @type M.assignfeedback_editpdf.edit
 | 
        
           |  |  | 128 |      * @protected
 | 
        
           |  |  | 129 |      */
 | 
        
           |  |  | 130 |     currentedit: new M.assignfeedback_editpdf.edit(),
 | 
        
           |  |  | 131 |   | 
        
           |  |  | 132 |     /**
 | 
        
           |  |  | 133 |      * Current drawable.
 | 
        
           |  |  | 134 |      *
 | 
        
           |  |  | 135 |      * @property currentdrawable
 | 
        
           |  |  | 136 |      * @type M.assignfeedback_editpdf.drawable|false
 | 
        
           |  |  | 137 |      * @protected
 | 
        
           |  |  | 138 |      */
 | 
        
           |  |  | 139 |     currentdrawable: false,
 | 
        
           |  |  | 140 |   | 
        
           |  |  | 141 |     /**
 | 
        
           |  |  | 142 |      * Current drawables.
 | 
        
           |  |  | 143 |      *
 | 
        
           |  |  | 144 |      * @property drawables
 | 
        
           |  |  | 145 |      * @type array(M.assignfeedback_editpdf.drawable)
 | 
        
           |  |  | 146 |      * @protected
 | 
        
           |  |  | 147 |      */
 | 
        
           |  |  | 148 |     drawables: [],
 | 
        
           |  |  | 149 |   | 
        
           |  |  | 150 |     /**
 | 
        
           |  |  | 151 |      * Current comment when the comment menu is open.
 | 
        
           |  |  | 152 |      * @property currentcomment
 | 
        
           |  |  | 153 |      * @type M.assignfeedback_editpdf.comment
 | 
        
           |  |  | 154 |      * @protected
 | 
        
           |  |  | 155 |      */
 | 
        
           |  |  | 156 |     currentcomment: null,
 | 
        
           |  |  | 157 |   | 
        
           |  |  | 158 |     /**
 | 
        
           |  |  | 159 |      * Current annotation when the select tool is used.
 | 
        
           |  |  | 160 |      * @property currentannotation
 | 
        
           |  |  | 161 |      * @type M.assignfeedback_editpdf.annotation
 | 
        
           |  |  | 162 |      * @protected
 | 
        
           |  |  | 163 |      */
 | 
        
           |  |  | 164 |     currentannotation: null,
 | 
        
           |  |  | 165 |   | 
        
           |  |  | 166 |     /**
 | 
        
           |  |  | 167 |      * Track the previous annotation so we can remove selection highlights.
 | 
        
           |  |  | 168 |      * @property lastannotation
 | 
        
           |  |  | 169 |      * @type M.assignfeedback_editpdf.annotation
 | 
        
           |  |  | 170 |      * @protected
 | 
        
           |  |  | 171 |      */
 | 
        
           |  |  | 172 |     lastannotation: null,
 | 
        
           |  |  | 173 |   | 
        
           |  |  | 174 |     /**
 | 
        
           |  |  | 175 |      * Last selected annotation tool
 | 
        
           |  |  | 176 |      * @property lastannotationtool
 | 
        
           |  |  | 177 |      * @type String
 | 
        
           |  |  | 178 |      * @protected
 | 
        
           |  |  | 179 |      */
 | 
        
           |  |  | 180 |     lastannotationtool: "pen",
 | 
        
           |  |  | 181 |   | 
        
           |  |  | 182 |     /**
 | 
        
           |  |  | 183 |      * The users comments quick list
 | 
        
           |  |  | 184 |      * @property quicklist
 | 
        
           |  |  | 185 |      * @type M.assignfeedback_editpdf.quickcommentlist
 | 
        
           |  |  | 186 |      * @protected
 | 
        
           |  |  | 187 |      */
 | 
        
           |  |  | 188 |     quicklist: null,
 | 
        
           |  |  | 189 |   | 
        
           |  |  | 190 |     /**
 | 
        
           |  |  | 191 |      * The search comments window.
 | 
        
           |  |  | 192 |      * @property searchcommentswindow
 | 
        
           |  |  | 193 |      * @type M.core.dialogue
 | 
        
           |  |  | 194 |      * @protected
 | 
        
           |  |  | 195 |      */
 | 
        
           |  |  | 196 |     searchcommentswindow: null,
 | 
        
           |  |  | 197 |   | 
        
           |  |  | 198 |   | 
        
           |  |  | 199 |     /**
 | 
        
           |  |  | 200 |      * The selected stamp picture.
 | 
        
           |  |  | 201 |      * @property currentstamp
 | 
        
           |  |  | 202 |      * @type String
 | 
        
           |  |  | 203 |      * @protected
 | 
        
           |  |  | 204 |      */
 | 
        
           |  |  | 205 |     currentstamp: null,
 | 
        
           |  |  | 206 |   | 
        
           |  |  | 207 |     /**
 | 
        
           |  |  | 208 |      * The stamps.
 | 
        
           |  |  | 209 |      * @property stamps
 | 
        
           |  |  | 210 |      * @type Array
 | 
        
           |  |  | 211 |      * @protected
 | 
        
           |  |  | 212 |      */
 | 
        
           |  |  | 213 |     stamps: [],
 | 
        
           |  |  | 214 |   | 
        
           |  |  | 215 |     /**
 | 
        
           |  |  | 216 |      * Prevent new comments from appearing
 | 
        
           |  |  | 217 |      * immediately after clicking off a current
 | 
        
           |  |  | 218 |      * comment
 | 
        
           |  |  | 219 |      * @property editingcomment
 | 
        
           |  |  | 220 |      * @type Boolean
 | 
        
           |  |  | 221 |      * @public
 | 
        
           |  |  | 222 |      */
 | 
        
           |  |  | 223 |     editingcomment: false,
 | 
        
           |  |  | 224 |   | 
        
           |  |  | 225 |     /**
 | 
        
           |  |  | 226 |      * Should inactive comments be collapsed?
 | 
        
           |  |  | 227 |      *
 | 
        
           |  |  | 228 |      * @property collapsecomments
 | 
        
           |  |  | 229 |      * @type Boolean
 | 
        
           |  |  | 230 |      * @public
 | 
        
           |  |  | 231 |      */
 | 
        
           |  |  | 232 |     collapsecomments: true,
 | 
        
           |  |  | 233 |   | 
        
           |  |  | 234 |     /**
 | 
        
           |  |  | 235 |      * Called during the initialisation process of the object.
 | 
        
           |  |  | 236 |      * @method initializer
 | 
        
           |  |  | 237 |      */
 | 
        
           |  |  | 238 |     initializer: function() {
 | 
        
           |  |  | 239 |         var link;
 | 
        
           |  |  | 240 |   | 
        
           |  |  | 241 |         link = Y.one('#' + this.get('linkid'));
 | 
        
           |  |  | 242 |   | 
        
           |  |  | 243 |         if (link) {
 | 
        
           |  |  | 244 |             link.on('click', this.link_handler, this);
 | 
        
           |  |  | 245 |             link.on('key', this.link_handler, 'down:13', this);
 | 
        
           |  |  | 246 |   | 
        
           |  |  | 247 |             // We call the amd module to see if we can take control of the review panel.
 | 
        
           |  |  | 248 |             require(['mod_assign/grading_review_panel'], function(ReviewPanelManager) {
 | 
        
           |  |  | 249 |                 var panelManager = new ReviewPanelManager();
 | 
        
           |  |  | 250 |   | 
        
           |  |  | 251 |                 var panel = panelManager.getReviewPanel('assignfeedback_editpdf');
 | 
        
           |  |  | 252 |                 if (panel) {
 | 
        
           |  |  | 253 |                     panel = Y.one(panel);
 | 
        
           |  |  | 254 |                     panel.empty();
 | 
        
           |  |  | 255 |                     link.ancestor('.fitem').hide();
 | 
        
           |  |  | 256 |                     this.open_in_panel(panel);
 | 
        
           |  |  | 257 |                 }
 | 
        
           |  |  | 258 |                 this.currentedit.start = false;
 | 
        
           |  |  | 259 |                 this.currentedit.end = false;
 | 
        
           |  |  | 260 |                 if (!this.get('readonly')) {
 | 
        
           |  |  | 261 |                     this.quicklist = new M.assignfeedback_editpdf.quickcommentlist(this);
 | 
        
           |  |  | 262 |                 }
 | 
        
           |  |  | 263 |             }.bind(this));
 | 
        
           |  |  | 264 |   | 
        
           |  |  | 265 |         }
 | 
        
           |  |  | 266 |     },
 | 
        
           |  |  | 267 |   | 
        
           |  |  | 268 |     /**
 | 
        
           |  |  | 269 |      * Called to show/hide buttons and set the current colours/stamps.
 | 
        
           |  |  | 270 |      * @method refresh_button_state
 | 
        
           |  |  | 271 |      */
 | 
        
           |  |  | 272 |     refresh_button_state: function() {
 | 
        
           |  |  | 273 |         var button, currenttoolnode, imgurl, drawingregion, stampimgurl, drawingcanvas;
 | 
        
           |  |  | 274 |   | 
        
           |  |  | 275 |         // Initalise the colour buttons.
 | 
        
           |  |  | 276 |         button = this.get_dialogue_element(SELECTOR.COMMENTCOLOURBUTTON);
 | 
        
           |  |  | 277 |   | 
        
           |  |  | 278 |         imgurl = M.util.image_url('background_colour_' + this.currentedit.commentcolour, 'assignfeedback_editpdf');
 | 
        
           |  |  | 279 |         button.one('img').setAttribute('src', imgurl);
 | 
        
           |  |  | 280 |   | 
        
           |  |  | 281 |         if (this.currentedit.commentcolour === 'clear') {
 | 
        
           |  |  | 282 |             button.one('img').setStyle('borderStyle', 'dashed');
 | 
        
           |  |  | 283 |         } else {
 | 
        
           |  |  | 284 |             button.one('img').setStyle('borderStyle', 'solid');
 | 
        
           |  |  | 285 |         }
 | 
        
           |  |  | 286 |   | 
        
           |  |  | 287 |         button = this.get_dialogue_element(SELECTOR.ANNOTATIONCOLOURBUTTON);
 | 
        
           |  |  | 288 |         imgurl = M.util.image_url('colour_' + this.currentedit.annotationcolour, 'assignfeedback_editpdf');
 | 
        
           |  |  | 289 |         button.one('img').setAttribute('src', imgurl);
 | 
        
           |  |  | 290 |   | 
        
           |  |  | 291 |         currenttoolnode = this.get_dialogue_element(TOOLSELECTOR[this.currentedit.tool]);
 | 
        
           |  |  | 292 |         currenttoolnode.addClass('assignfeedback_editpdf_selectedbutton');
 | 
        
           |  |  | 293 |         currenttoolnode.setAttribute('aria-pressed', 'true');
 | 
        
           |  |  | 294 |         drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION);
 | 
        
           |  |  | 295 |         drawingregion.setAttribute('data-currenttool', this.currentedit.tool);
 | 
        
           |  |  | 296 |   | 
        
           |  |  | 297 |         button = this.get_dialogue_element(SELECTOR.STAMPSBUTTON);
 | 
        
           |  |  | 298 |         stampimgurl = this.get_stamp_image_url(this.currentedit.stamp);
 | 
        
           |  |  | 299 |         button.one('img').setAttrs({'src': stampimgurl,
 | 
        
           |  |  | 300 |                                     'height': '16',
 | 
        
           |  |  | 301 |                                     'width': '16'});
 | 
        
           |  |  | 302 |   | 
        
           |  |  | 303 |         drawingcanvas = this.get_dialogue_element(SELECTOR.DRAWINGCANVAS);
 | 
        
           |  |  | 304 |         switch (this.currentedit.tool) {
 | 
        
           |  |  | 305 |             case 'drag':
 | 
        
           |  |  | 306 |                 drawingcanvas.setStyle('cursor', 'move');
 | 
        
           |  |  | 307 |                 break;
 | 
        
           |  |  | 308 |             case 'highlight':
 | 
        
           |  |  | 309 |                 drawingcanvas.setStyle('cursor', 'text');
 | 
        
           |  |  | 310 |                 break;
 | 
        
           |  |  | 311 |             case 'select':
 | 
        
           |  |  | 312 |                 drawingcanvas.setStyle('cursor', 'default');
 | 
        
           |  |  | 313 |                 break;
 | 
        
           |  |  | 314 |             case 'stamp':
 | 
        
           |  |  | 315 |                 drawingcanvas.setStyle('cursor', 'url(' + stampimgurl + '), crosshair');
 | 
        
           |  |  | 316 |                 break;
 | 
        
           |  |  | 317 |             default:
 | 
        
           |  |  | 318 |                 drawingcanvas.setStyle('cursor', 'crosshair');
 | 
        
           |  |  | 319 |         }
 | 
        
           |  |  | 320 |     },
 | 
        
           |  |  | 321 |   | 
        
           |  |  | 322 |     /**
 | 
        
           |  |  | 323 |      * Called to get the bounds of the drawing region.
 | 
        
           |  |  | 324 |      * @method get_canvas_bounds
 | 
        
           |  |  | 325 |      */
 | 
        
           |  |  | 326 |     get_canvas_bounds: function() {
 | 
        
           |  |  | 327 |         var canvas = this.get_dialogue_element(SELECTOR.DRAWINGCANVAS),
 | 
        
           |  |  | 328 |             offsetcanvas = canvas.getXY(),
 | 
        
           |  |  | 329 |             offsetleft = offsetcanvas[0],
 | 
        
           |  |  | 330 |             offsettop = offsetcanvas[1],
 | 
        
           |  |  | 331 |             width = parseInt(canvas.getStyle('width'), 10),
 | 
        
           |  |  | 332 |             height = parseInt(canvas.getStyle('height'), 10);
 | 
        
           |  |  | 333 |   | 
        
           |  |  | 334 |         return new M.assignfeedback_editpdf.rect(offsetleft, offsettop, width, height);
 | 
        
           |  |  | 335 |     },
 | 
        
           |  |  | 336 |   | 
        
           |  |  | 337 |     /**
 | 
        
           |  |  | 338 |      * Called to translate from window coordinates to canvas coordinates.
 | 
        
           |  |  | 339 |      * @method get_canvas_coordinates
 | 
        
           |  |  | 340 |      * @param M.assignfeedback_editpdf.point point in window coordinats.
 | 
        
           |  |  | 341 |      */
 | 
        
           |  |  | 342 |     get_canvas_coordinates: function(point) {
 | 
        
           |  |  | 343 |         var bounds = this.get_canvas_bounds(),
 | 
        
           |  |  | 344 |             newpoint = new M.assignfeedback_editpdf.point(point.x - bounds.x, point.y - bounds.y);
 | 
        
           |  |  | 345 |   | 
        
           |  |  | 346 |         bounds.x = bounds.y = 0;
 | 
        
           |  |  | 347 |   | 
        
           |  |  | 348 |         newpoint.clip(bounds);
 | 
        
           |  |  | 349 |         return newpoint;
 | 
        
           |  |  | 350 |     },
 | 
        
           |  |  | 351 |   | 
        
           |  |  | 352 |     /**
 | 
        
           |  |  | 353 |      * Called to translate from canvas coordinates to window coordinates.
 | 
        
           |  |  | 354 |      * @method get_window_coordinates
 | 
        
           |  |  | 355 |      * @param M.assignfeedback_editpdf.point point in window coordinats.
 | 
        
           |  |  | 356 |      */
 | 
        
           |  |  | 357 |     get_window_coordinates: function(point) {
 | 
        
           |  |  | 358 |         var bounds = this.get_canvas_bounds(),
 | 
        
           |  |  | 359 |             newpoint = new M.assignfeedback_editpdf.point(point.x + bounds.x, point.y + bounds.y);
 | 
        
           |  |  | 360 |   | 
        
           |  |  | 361 |         return newpoint;
 | 
        
           |  |  | 362 |     },
 | 
        
           |  |  | 363 |   | 
        
           |  |  | 364 |     /**
 | 
        
           |  |  | 365 |      * Open the edit-pdf editor in the panel in the page instead of a popup.
 | 
        
           |  |  | 366 |      * @method open_in_panel
 | 
        
           |  |  | 367 |      */
 | 
        
           |  |  | 368 |     open_in_panel: function(panel) {
 | 
        
           |  |  | 369 |         var drawingcanvas;
 | 
        
           |  |  | 370 |   | 
        
           |  |  | 371 |         this.panel = panel;
 | 
        
           |  |  | 372 |         panel.append(this.get('body'));
 | 
        
           |  |  | 373 |         panel.addClass(CSS.DIALOGUE);
 | 
        
           |  |  | 374 |   | 
        
           |  |  | 375 |         this.loadingicon = this.get_dialogue_element(SELECTOR.LOADINGICON);
 | 
        
           |  |  | 376 |   | 
        
           |  |  | 377 |         drawingcanvas = this.get_dialogue_element(SELECTOR.DRAWINGCANVAS);
 | 
        
           |  |  | 378 |         this.graphic = new Y.Graphic({render: drawingcanvas});
 | 
        
           |  |  | 379 |   | 
        
           |  |  | 380 |         if (!this.get('readonly')) {
 | 
        
           |  |  | 381 |             drawingcanvas.on('gesturemovestart', this.edit_start, null, this);
 | 
        
           |  |  | 382 |             drawingcanvas.on('gesturemove', this.edit_move, null, this);
 | 
        
           |  |  | 383 |             drawingcanvas.on('gesturemoveend', this.edit_end, null, this);
 | 
        
           |  |  | 384 |   | 
        
           |  |  | 385 |             this.refresh_button_state();
 | 
        
           |  |  | 386 |         }
 | 
        
           |  |  | 387 |   | 
        
           |  |  | 388 |         this.start_generation();
 | 
        
           |  |  | 389 |     },
 | 
        
           |  |  | 390 |   | 
        
           |  |  | 391 |     /**
 | 
        
           |  |  | 392 |      * Called to open the pdf editing dialogue.
 | 
        
           |  |  | 393 |      * @method link_handler
 | 
        
           |  |  | 394 |      */
 | 
        
           |  |  | 395 |     link_handler: function(e) {
 | 
        
           |  |  | 396 |         var drawingcanvas;
 | 
        
           |  |  | 397 |         var resize = true;
 | 
        
           |  |  | 398 |         e.preventDefault();
 | 
        
           |  |  | 399 |   | 
        
           |  |  | 400 |         if (!this.dialogue) {
 | 
        
           |  |  | 401 |             this.dialogue = new M.core.dialogue({
 | 
        
           |  |  | 402 |                 headerContent: this.get('header'),
 | 
        
           |  |  | 403 |                 bodyContent: this.get('body'),
 | 
        
           |  |  | 404 |                 footerContent: this.get('footer'),
 | 
        
           |  |  | 405 |                 modal: true,
 | 
        
           |  |  | 406 |                 width: '840px',
 | 
        
           |  |  | 407 |                 visible: false,
 | 
        
           |  |  | 408 |                 draggable: true
 | 
        
           |  |  | 409 |             });
 | 
        
           |  |  | 410 |   | 
        
           |  |  | 411 |             // Add custom class for styling.
 | 
        
           |  |  | 412 |             this.dialogue.get('boundingBox').addClass(CSS.DIALOGUE);
 | 
        
           |  |  | 413 |   | 
        
           |  |  | 414 |             this.loadingicon = this.get_dialogue_element(SELECTOR.LOADINGICON);
 | 
        
           |  |  | 415 |   | 
        
           |  |  | 416 |             drawingcanvas = this.get_dialogue_element(SELECTOR.DRAWINGCANVAS);
 | 
        
           |  |  | 417 |             this.graphic = new Y.Graphic({render: drawingcanvas});
 | 
        
           |  |  | 418 |   | 
        
           |  |  | 419 |             if (!this.get('readonly')) {
 | 
        
           |  |  | 420 |                 drawingcanvas.on('gesturemovestart', this.edit_start, null, this);
 | 
        
           |  |  | 421 |                 drawingcanvas.on('gesturemove', this.edit_move, null, this);
 | 
        
           |  |  | 422 |                 drawingcanvas.on('gesturemoveend', this.edit_end, null, this);
 | 
        
           |  |  | 423 |   | 
        
           |  |  | 424 |                 this.refresh_button_state();
 | 
        
           |  |  | 425 |             }
 | 
        
           |  |  | 426 |   | 
        
           |  |  | 427 |             this.start_generation();
 | 
        
           |  |  | 428 |             drawingcanvas.on('windowresize', this.resize, this);
 | 
        
           |  |  | 429 |   | 
        
           |  |  | 430 |             resize = false;
 | 
        
           |  |  | 431 |         }
 | 
        
           |  |  | 432 |         this.dialogue.centerDialogue();
 | 
        
           |  |  | 433 |         this.dialogue.show();
 | 
        
           |  |  | 434 |   | 
        
           |  |  | 435 |         // Redraw when the dialogue is moved, to ensure the absolute elements are all positioned correctly.
 | 
        
           |  |  | 436 |         this.dialogue.dd.on('drag:end', this.redraw, this);
 | 
        
           |  |  | 437 |         if (resize) {
 | 
        
           |  |  | 438 |             this.resize(); // When re-opening the dialog call redraw, to make sure the size + layout is correct.
 | 
        
           |  |  | 439 |         }
 | 
        
           |  |  | 440 |     },
 | 
        
           |  |  | 441 |   | 
        
           |  |  | 442 |     /**
 | 
        
           |  |  | 443 |      * Called to load the information and annotations for all pages.
 | 
        
           |  |  | 444 |      *
 | 
        
           |  |  | 445 |      * @method start_generation
 | 
        
           |  |  | 446 |      */
 | 
        
           |  |  | 447 |     start_generation: function() {
 | 
        
           |  |  | 448 |         this.poll_document_conversion_status();
 | 
        
           |  |  | 449 |     },
 | 
        
           |  |  | 450 |   | 
        
           |  |  | 451 |     /**
 | 
        
           |  |  | 452 |      * Poll the current document conversion status and start the next step
 | 
        
           |  |  | 453 |      * in the process.
 | 
        
           |  |  | 454 |      *
 | 
        
           |  |  | 455 |      * @method poll_document_conversion_status
 | 
        
           |  |  | 456 |      */
 | 
        
           |  |  | 457 |     poll_document_conversion_status: function() {
 | 
        
           |  |  | 458 |         var requestUserId = this.get('userid');
 | 
        
           |  |  | 459 |   | 
        
           |  |  | 460 |         Y.io(AJAXBASE, {
 | 
        
           |  |  | 461 |             method: 'get',
 | 
        
           |  |  | 462 |             context: this,
 | 
        
           |  |  | 463 |             sync: false,
 | 
        
           |  |  | 464 |             data: {
 | 
        
           |  |  | 465 |                 sesskey: M.cfg.sesskey,
 | 
        
           |  |  | 466 |                 action: 'pollconversions',
 | 
        
           |  |  | 467 |                 userid: this.get('userid'),
 | 
        
           |  |  | 468 |                 attemptnumber: this.get('attemptnumber'),
 | 
        
           |  |  | 469 |                 assignmentid: this.get('assignmentid'),
 | 
        
           |  |  | 470 |                 readonly: this.get('readonly') ? 1 : 0
 | 
        
           |  |  | 471 |             },
 | 
        
           |  |  | 472 |             on: {
 | 
        
           |  |  | 473 |                 success: function(tid, response) {
 | 
        
           |  |  | 474 |                     var currentUserRegion = Y.one(SELECTOR.USERINFOREGION);
 | 
        
           |  |  | 475 |                     if (currentUserRegion) {
 | 
        
           |  |  | 476 |                         var currentUserId = currentUserRegion.getAttribute('data-userid');
 | 
        
           |  |  | 477 |                         if (currentUserId && (currentUserId != requestUserId)) {
 | 
        
           |  |  | 478 |                             // Polling conversion status needs to abort because
 | 
        
           |  |  | 479 |                             // the current user changed.
 | 
        
           |  |  | 480 |                             return;
 | 
        
           |  |  | 481 |                         }
 | 
        
           |  |  | 482 |                     }
 | 
        
           |  |  | 483 |                     var data = this.handle_response_data(response),
 | 
        
           |  |  | 484 |                         poll = false;
 | 
        
           |  |  | 485 |                     if (data) {
 | 
        
           |  |  | 486 |                         // When we are requesting the readonly version of the pages, they should
 | 
        
           |  |  | 487 |                         // always be available (see document_services::get_page_images_for_attempt)
 | 
        
           |  |  | 488 |                         // so we can just serve them immediately without triggering any document
 | 
        
           |  |  | 489 |                         // conversion or polling.
 | 
        
           |  |  | 490 |                         //
 | 
        
           |  |  | 491 |                         // This is necessary to prevent situations where the student has updated
 | 
        
           |  |  | 492 |                         // their submission and the teacher has annotated a previous version of
 | 
        
           |  |  | 493 |                         // the submission in the assignment grader. In this situation if a student
 | 
        
           |  |  | 494 |                         // views the online version of the annotated PDF ("View annotated PDF" link)
 | 
        
           |  |  | 495 |                         // the readonly pages here and the updated pages (awaiting conversion) will
 | 
        
           |  |  | 496 |                         // never match, and the code endlessly polls.
 | 
        
           |  |  | 497 |                         //
 | 
        
           |  |  | 498 |                         // See also: MDL-45580, MDL-66626, MDL-75898.
 | 
        
           |  |  | 499 |                         if (this.get('readonly') === true) {
 | 
        
           |  |  | 500 |                             this.prepare_pages_for_display(data);
 | 
        
           |  |  | 501 |                             return;
 | 
        
           |  |  | 502 |                         }
 | 
        
           |  |  | 503 |   | 
        
           |  |  | 504 |                         this.documentstatus = data.status;
 | 
        
           |  |  | 505 |                         if (data.status === 0) {
 | 
        
           |  |  | 506 |                             // The combined document is still waiting for input to be ready.
 | 
        
           |  |  | 507 |                             poll = true;
 | 
        
           |  |  | 508 |   | 
        
           |  |  | 509 |                         } else if (data.status === 1 || data.status === 3) {
 | 
        
           |  |  | 510 |                             // The combine document is ready for conversion into a single PDF.
 | 
        
           |  |  | 511 |                             poll = true;
 | 
        
           |  |  | 512 |   | 
        
           |  |  | 513 |                         } else if (data.status === 2 || data.status === -1) {
 | 
        
           |  |  | 514 |                             // The combined PDF is ready.
 | 
        
           |  |  | 515 |                             // We now know the page count and can convert it to a set of images.
 | 
        
           |  |  | 516 |   | 
        
           |  |  | 517 |                             if (data.pageready == data.pagecount) {
 | 
        
           |  |  | 518 |                                 this.prepare_pages_for_display(data);
 | 
        
           |  |  | 519 |                             } else {
 | 
        
           |  |  | 520 |                                 // Some pages are not ready yet.
 | 
        
           |  |  | 521 |                                 // Note: We use a different polling process here which does not block.
 | 
        
           |  |  | 522 |                                 this.update_page_load_progress();
 | 
        
           |  |  | 523 |   | 
        
           |  |  | 524 |                                 // Fetch the images for the combined document.
 | 
        
           |  |  | 525 |                                 this.start_document_to_image_conversion();
 | 
        
           |  |  | 526 |                             }
 | 
        
           |  |  | 527 |                         }
 | 
        
           |  |  | 528 |   | 
        
           |  |  | 529 |                         if (poll) {
 | 
        
           |  |  | 530 |                             // Check again in 1 second.
 | 
        
           |  |  | 531 |                             Y.later(1000, this, this.poll_document_conversion_status);
 | 
        
           |  |  | 532 |                         }
 | 
        
           |  |  | 533 |                     }
 | 
        
           |  |  | 534 |                 },
 | 
        
           |  |  | 535 |                 failure: function(tid, response) {
 | 
        
           |  |  | 536 |                     return new M.core.exception(response.responseText);
 | 
        
           |  |  | 537 |                 }
 | 
        
           |  |  | 538 |             }
 | 
        
           |  |  | 539 |         });
 | 
        
           |  |  | 540 |     },
 | 
        
           |  |  | 541 |   | 
        
           |  |  | 542 |     /**
 | 
        
           |  |  | 543 |      * Spwan the PDF to Image conversion on the server.
 | 
        
           |  |  | 544 |      *
 | 
        
           |  |  | 545 |      * @method get_images_for_documents
 | 
        
           |  |  | 546 |      */
 | 
        
           |  |  | 547 |     start_document_to_image_conversion: function() {
 | 
        
           |  |  | 548 |         Y.io(AJAXBASE, {
 | 
        
           |  |  | 549 |             method: 'get',
 | 
        
           |  |  | 550 |             context: this,
 | 
        
           |  |  | 551 |             sync: false,
 | 
        
           |  |  | 552 |             data: {
 | 
        
           |  |  | 553 |                 sesskey: M.cfg.sesskey,
 | 
        
           |  |  | 554 |                 action: 'pollconversions',
 | 
        
           |  |  | 555 |                 userid: this.get('userid'),
 | 
        
           |  |  | 556 |                 attemptnumber: this.get('attemptnumber'),
 | 
        
           |  |  | 557 |                 assignmentid: this.get('assignmentid'),
 | 
        
           |  |  | 558 |                 readonly: this.get('readonly') ? 1 : 0
 | 
        
           |  |  | 559 |             },
 | 
        
           |  |  | 560 |             on: {
 | 
        
           |  |  | 561 |                 success: function(tid, response) {
 | 
        
           |  |  | 562 |                     var data = this.handle_response_data(response);
 | 
        
           |  |  | 563 |                     if (data) {
 | 
        
           |  |  | 564 |                         this.documentstatus = data.status;
 | 
        
           |  |  | 565 |                         if (data.status === 2) {
 | 
        
           |  |  | 566 |                             // The pages are ready. Add all of the annotations to them.
 | 
        
           |  |  | 567 |                             this.prepare_pages_for_display(data);
 | 
        
           |  |  | 568 |                         }
 | 
        
           |  |  | 569 |                     }
 | 
        
           |  |  | 570 |                 },
 | 
        
           |  |  | 571 |                 failure: function(tid, response) {
 | 
        
           |  |  | 572 |                     return new M.core.exception(response.responseText);
 | 
        
           |  |  | 573 |                 }
 | 
        
           |  |  | 574 |             }
 | 
        
           |  |  | 575 |         });
 | 
        
           |  |  | 576 |     },
 | 
        
           |  |  | 577 |   | 
        
           |  |  | 578 |     /**
 | 
        
           |  |  | 579 |      * Display an error in a small part of the page (don't block everything).
 | 
        
           |  |  | 580 |      *
 | 
        
           |  |  | 581 |      * @param string The error text.
 | 
        
           |  |  | 582 |      * @param boolean dismissable Not critical messages can be removed after a short display.
 | 
        
           |  |  | 583 |      * @protected
 | 
        
           |  |  | 584 |      * @method warning
 | 
        
           |  |  | 585 |      */
 | 
        
           |  |  | 586 |     warning: function(message, dismissable) {
 | 
        
           |  |  | 587 |         var icontemplate = this.get_dialogue_element(SELECTOR.ICONMESSAGECONTAINER);
 | 
        
           |  |  | 588 |         var warningregion = this.get_dialogue_element(SELECTOR.WARNINGMESSAGECONTAINER);
 | 
        
           |  |  | 589 |         var delay = 15, duration = 1;
 | 
        
           |  |  | 590 |         var messageclasses = 'assignfeedback_editpdf_warningmessages alert alert-warning';
 | 
        
           |  |  | 591 |         if (dismissable) {
 | 
        
           |  |  | 592 |             delay = 4;
 | 
        
           |  |  | 593 |             messageclasses = 'assignfeedback_editpdf_warningmessages alert alert-info';
 | 
        
           |  |  | 594 |         }
 | 
        
           |  |  | 595 |         var warningelement = Y.Node.create('<div class="' + messageclasses + '" role="alert"></div>');
 | 
        
           |  |  | 596 |   | 
        
           |  |  | 597 |         // Copy info icon template.
 | 
        
           |  |  | 598 |         warningelement.append(icontemplate.one('*').cloneNode());
 | 
        
           |  |  | 599 |   | 
        
           |  |  | 600 |         // Append the message.
 | 
        
           |  |  | 601 |         warningelement.append(message);
 | 
        
           |  |  | 602 |   | 
        
           |  |  | 603 |         // Add the entire warning to the container.
 | 
        
           |  |  | 604 |         warningregion.prepend(warningelement);
 | 
        
           |  |  | 605 |   | 
        
           |  |  | 606 |         // Remove the message after a short delay.
 | 
        
           |  |  | 607 |         warningelement.transition(
 | 
        
           |  |  | 608 |             {
 | 
        
           |  |  | 609 |                 duration: duration,
 | 
        
           |  |  | 610 |                 delay: delay,
 | 
        
           |  |  | 611 |                 opacity: 0
 | 
        
           |  |  | 612 |             },
 | 
        
           |  |  | 613 |             function() {
 | 
        
           |  |  | 614 |                 warningelement.remove();
 | 
        
           |  |  | 615 |             }
 | 
        
           |  |  | 616 |         );
 | 
        
           |  |  | 617 |     },
 | 
        
           |  |  | 618 |   | 
        
           |  |  | 619 |     /**
 | 
        
           |  |  | 620 |      * The info about all pages in the pdf has been returned.
 | 
        
           |  |  | 621 |      *
 | 
        
           |  |  | 622 |      * @param string The ajax response as text.
 | 
        
           |  |  | 623 |      * @protected
 | 
        
           |  |  | 624 |      * @method prepare_pages_for_display
 | 
        
           |  |  | 625 |      */
 | 
        
           |  |  | 626 |     prepare_pages_for_display: function(data) {
 | 
        
           |  |  | 627 |         var i, j, comment, error, annotation, readonly;
 | 
        
           |  |  | 628 |   | 
        
           |  |  | 629 |         if (!data.pagecount) {
 | 
        
           |  |  | 630 |             if (this.dialogue) {
 | 
        
           |  |  | 631 |                 this.dialogue.hide();
 | 
        
           |  |  | 632 |             }
 | 
        
           |  |  | 633 |             // Display alert dialogue.
 | 
        
           |  |  | 634 |             error = new M.core.alert({message: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf')});
 | 
        
           |  |  | 635 |             error.show();
 | 
        
           |  |  | 636 |             return;
 | 
        
           |  |  | 637 |         }
 | 
        
           |  |  | 638 |   | 
        
           | 1441 | ariadna | 639 |         this.pagecount = data.pagecount;
 | 
        
           | 1 | efrain | 640 |         this.pages = data.pages;
 | 
        
           |  |  | 641 |   | 
        
           |  |  | 642 |         for (i = 0; i < this.pages.length; i++) {
 | 
        
           |  |  | 643 |             for (j = 0; j < this.pages[i].comments.length; j++) {
 | 
        
           |  |  | 644 |                 comment = this.pages[i].comments[j];
 | 
        
           |  |  | 645 |                 this.pages[i].comments[j] = new M.assignfeedback_editpdf.comment(this,
 | 
        
           |  |  | 646 |                                                                                  comment.gradeid,
 | 
        
           |  |  | 647 |                                                                                  comment.pageno,
 | 
        
           |  |  | 648 |                                                                                  comment.x,
 | 
        
           |  |  | 649 |                                                                                  comment.y,
 | 
        
           |  |  | 650 |                                                                                  comment.width,
 | 
        
           |  |  | 651 |                                                                                  comment.colour,
 | 
        
           |  |  | 652 |                                                                                  comment.rawtext);
 | 
        
           |  |  | 653 |             }
 | 
        
           |  |  | 654 |             for (j = 0; j < this.pages[i].annotations.length; j++) {
 | 
        
           |  |  | 655 |                 annotation = this.pages[i].annotations[j];
 | 
        
           |  |  | 656 |                 this.pages[i].annotations[j] = this.create_annotation(annotation.type, annotation);
 | 
        
           |  |  | 657 |             }
 | 
        
           |  |  | 658 |         }
 | 
        
           |  |  | 659 |   | 
        
           |  |  | 660 |         readonly = this.get('readonly');
 | 
        
           |  |  | 661 |         if (!readonly && data.partial) {
 | 
        
           |  |  | 662 |             // Warn about non converted files, but only for teachers.
 | 
        
           |  |  | 663 |             this.warning(M.util.get_string('partialwarning', 'assignfeedback_editpdf'), false);
 | 
        
           |  |  | 664 |         }
 | 
        
           |  |  | 665 |   | 
        
           |  |  | 666 |         // Update the ui.
 | 
        
           |  |  | 667 |         if (this.quicklist) {
 | 
        
           |  |  | 668 |             this.quicklist.load();
 | 
        
           |  |  | 669 |         }
 | 
        
           |  |  | 670 |         this.setup_navigation();
 | 
        
           |  |  | 671 |         this.setup_toolbar();
 | 
        
           |  |  | 672 |         this.change_page();
 | 
        
           |  |  | 673 |     },
 | 
        
           |  |  | 674 |   | 
        
           |  |  | 675 |     /**
 | 
        
           |  |  | 676 |      * Fetch the page images.
 | 
        
           |  |  | 677 |      *
 | 
        
           |  |  | 678 |      * @method update_page_load_progress
 | 
        
           |  |  | 679 |      */
 | 
        
           |  |  | 680 |     update_page_load_progress: function() {
 | 
        
           |  |  | 681 |         var checkconversionstatus,
 | 
        
           |  |  | 682 |             ajax_error_total = 0,
 | 
        
           |  |  | 683 |             progressbar = this.get_dialogue_element(SELECTOR.PROGRESSBARCONTAINER + ' .bar');
 | 
        
           |  |  | 684 |   | 
        
           |  |  | 685 |         if (!progressbar) {
 | 
        
           |  |  | 686 |             return;
 | 
        
           |  |  | 687 |         }
 | 
        
           |  |  | 688 |   | 
        
           |  |  | 689 |         // If pages are not loaded, check PDF conversion status for the progress bar.
 | 
        
           |  |  | 690 |         checkconversionstatus = {
 | 
        
           |  |  | 691 |             method: 'get',
 | 
        
           |  |  | 692 |             context: this,
 | 
        
           |  |  | 693 |             sync: false,
 | 
        
           |  |  | 694 |             data: {
 | 
        
           |  |  | 695 |                 sesskey: M.cfg.sesskey,
 | 
        
           |  |  | 696 |                 action: 'conversionstatus',
 | 
        
           |  |  | 697 |                 userid: this.get('userid'),
 | 
        
           |  |  | 698 |                 attemptnumber: this.get('attemptnumber'),
 | 
        
           |  |  | 699 |                 assignmentid: this.get('assignmentid')
 | 
        
           |  |  | 700 |             },
 | 
        
           |  |  | 701 |             on: {
 | 
        
           |  |  | 702 |                 success: function(tid, response) {
 | 
        
           |  |  | 703 |                     ajax_error_total = 0;
 | 
        
           |  |  | 704 |   | 
        
           |  |  | 705 |                     var progress = 0;
 | 
        
           |  |  | 706 |                     var progressbar = this.get_dialogue_element(SELECTOR.PROGRESSBARCONTAINER + ' .bar');
 | 
        
           |  |  | 707 |                     if (progressbar) {
 | 
        
           |  |  | 708 |                         // Calculate progress.
 | 
        
           |  |  | 709 |                         progress = (response.response / this.pagecount) * 100;
 | 
        
           |  |  | 710 |                         progressbar.setStyle('width', progress + '%');
 | 
        
           |  |  | 711 |                         progressbar.ancestor(SELECTOR.PROGRESSBARCONTAINER).setAttribute('aria-valuenow', progress);
 | 
        
           |  |  | 712 |   | 
        
           |  |  | 713 |                         if (progress < 100) {
 | 
        
           |  |  | 714 |                             // Keep polling until all pages are generated.
 | 
        
           |  |  | 715 |                             M.util.js_pending('checkconversionstatus');
 | 
        
           |  |  | 716 |                             Y.later(1000, this, function() {
 | 
        
           |  |  | 717 |                                 M.util.js_complete('checkconversionstatus');
 | 
        
           |  |  | 718 |                                 Y.io(AJAXBASEPROGRESS, checkconversionstatus);
 | 
        
           |  |  | 719 |                             });
 | 
        
           |  |  | 720 |                         }
 | 
        
           |  |  | 721 |                     }
 | 
        
           |  |  | 722 |                 },
 | 
        
           |  |  | 723 |                 failure: function(tid, response) {
 | 
        
           |  |  | 724 |                     ajax_error_total = ajax_error_total + 1;
 | 
        
           |  |  | 725 |                     // We only continue on error if the all pages were not generated,
 | 
        
           |  |  | 726 |                     // and if the ajax call did not produce 5 errors in the row.
 | 
        
           |  |  | 727 |                     if (this.pagecount === 0 && ajax_error_total < 5) {
 | 
        
           |  |  | 728 |                         M.util.js_pending('checkconversionstatus');
 | 
        
           |  |  | 729 |                         Y.later(1000, this, function() {
 | 
        
           |  |  | 730 |                             M.util.js_complete('checkconversionstatus');
 | 
        
           |  |  | 731 |                             Y.io(AJAXBASEPROGRESS, checkconversionstatus);
 | 
        
           |  |  | 732 |                         });
 | 
        
           |  |  | 733 |                     }
 | 
        
           |  |  | 734 |                     return new M.core.exception(response.responseText);
 | 
        
           |  |  | 735 |                 }
 | 
        
           |  |  | 736 |             }
 | 
        
           |  |  | 737 |         };
 | 
        
           |  |  | 738 |         // We start the AJAX "generated page total number" call a second later to give a chance to
 | 
        
           |  |  | 739 |         // the AJAX "combined pdf generation" call to clean the previous submission images.
 | 
        
           |  |  | 740 |         M.util.js_pending('checkconversionstatus');
 | 
        
           |  |  | 741 |         Y.later(1000, this, function() {
 | 
        
           |  |  | 742 |             ajax_error_total = 0;
 | 
        
           |  |  | 743 |             M.util.js_complete('checkconversionstatus');
 | 
        
           |  |  | 744 |             Y.io(AJAXBASEPROGRESS, checkconversionstatus);
 | 
        
           |  |  | 745 |         });
 | 
        
           |  |  | 746 |     },
 | 
        
           |  |  | 747 |   | 
        
           |  |  | 748 |     /**
 | 
        
           |  |  | 749 |      * Handle response data.
 | 
        
           |  |  | 750 |      *
 | 
        
           |  |  | 751 |      * @method  handle_response_data
 | 
        
           |  |  | 752 |      * @param   {object} response
 | 
        
           |  |  | 753 |      * @return  {object}
 | 
        
           |  |  | 754 |      */
 | 
        
           |  |  | 755 |     handle_response_data: function(response) {
 | 
        
           |  |  | 756 |         var data;
 | 
        
           |  |  | 757 |         try {
 | 
        
           |  |  | 758 |             data = Y.JSON.parse(response.responseText);
 | 
        
           |  |  | 759 |             if (data.error) {
 | 
        
           |  |  | 760 |                 if (this.dialogue) {
 | 
        
           |  |  | 761 |                     this.dialogue.hide();
 | 
        
           |  |  | 762 |                 }
 | 
        
           |  |  | 763 |   | 
        
           |  |  | 764 |                 new M.core.alert({
 | 
        
           |  |  | 765 |                     message: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf'),
 | 
        
           |  |  | 766 |                     visible: true
 | 
        
           |  |  | 767 |                 });
 | 
        
           |  |  | 768 |             } else {
 | 
        
           |  |  | 769 |                 return data;
 | 
        
           |  |  | 770 |             }
 | 
        
           |  |  | 771 |         } catch (e) {
 | 
        
           |  |  | 772 |             if (this.dialogue) {
 | 
        
           |  |  | 773 |                 this.dialogue.hide();
 | 
        
           |  |  | 774 |             }
 | 
        
           |  |  | 775 |   | 
        
           |  |  | 776 |             new M.core.alert({
 | 
        
           |  |  | 777 |                 title: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf'),
 | 
        
           |  |  | 778 |                 visible: true
 | 
        
           |  |  | 779 |             });
 | 
        
           |  |  | 780 |         }
 | 
        
           |  |  | 781 |   | 
        
           |  |  | 782 |         return;
 | 
        
           |  |  | 783 |     },
 | 
        
           |  |  | 784 |   | 
        
           |  |  | 785 |     /**
 | 
        
           |  |  | 786 |      * Get the full pluginfile url for an image file - just given the filename.
 | 
        
           |  |  | 787 |      *
 | 
        
           |  |  | 788 |      * @public
 | 
        
           |  |  | 789 |      * @method get_stamp_image_url
 | 
        
           |  |  | 790 |      * @param string filename
 | 
        
           |  |  | 791 |      */
 | 
        
           |  |  | 792 |     get_stamp_image_url: function(filename) {
 | 
        
           |  |  | 793 |         var urls = this.get('stampfiles'),
 | 
        
           |  |  | 794 |             fullurl = '';
 | 
        
           |  |  | 795 |   | 
        
           |  |  | 796 |         Y.Array.each(urls, function(url) {
 | 
        
           |  |  | 797 |             if (url.indexOf(filename) > 0) {
 | 
        
           |  |  | 798 |                 fullurl = url;
 | 
        
           |  |  | 799 |             }
 | 
        
           |  |  | 800 |         }, this);
 | 
        
           |  |  | 801 |   | 
        
           |  |  | 802 |         return fullurl;
 | 
        
           |  |  | 803 |     },
 | 
        
           |  |  | 804 |   | 
        
           |  |  | 805 |     /**
 | 
        
           |  |  | 806 |      * Attach listeners and enable the color picker buttons.
 | 
        
           |  |  | 807 |      * @protected
 | 
        
           |  |  | 808 |      * @method setup_toolbar
 | 
        
           |  |  | 809 |      */
 | 
        
           |  |  | 810 |     setup_toolbar: function() {
 | 
        
           |  |  | 811 |         var toolnode,
 | 
        
           |  |  | 812 |             commentcolourbutton,
 | 
        
           |  |  | 813 |             annotationcolourbutton,
 | 
        
           |  |  | 814 |             searchcommentsbutton,
 | 
        
           |  |  | 815 |             expcolcommentsbutton,
 | 
        
           |  |  | 816 |             rotateleftbutton,
 | 
        
           |  |  | 817 |             rotaterightbutton,
 | 
        
           |  |  | 818 |             currentstampbutton,
 | 
        
           |  |  | 819 |             stampfiles,
 | 
        
           |  |  | 820 |             picker,
 | 
        
           |  |  | 821 |             filename;
 | 
        
           |  |  | 822 |   | 
        
           |  |  | 823 |         searchcommentsbutton = this.get_dialogue_element(SELECTOR.SEARCHCOMMENTSBUTTON);
 | 
        
           |  |  | 824 |         searchcommentsbutton.on('click', this.open_search_comments, this);
 | 
        
           |  |  | 825 |         searchcommentsbutton.on('key', this.open_search_comments, 'down:13', this);
 | 
        
           |  |  | 826 |   | 
        
           |  |  | 827 |         expcolcommentsbutton = this.get_dialogue_element(SELECTOR.EXPCOLCOMMENTSBUTTON);
 | 
        
           |  |  | 828 |         expcolcommentsbutton.on('click', this.expandCollapseComments, this);
 | 
        
           |  |  | 829 |         expcolcommentsbutton.on('key', this.expandCollapseComments, 'down:13', this);
 | 
        
           |  |  | 830 |   | 
        
           |  |  | 831 |         if (this.get('readonly')) {
 | 
        
           |  |  | 832 |             return;
 | 
        
           |  |  | 833 |         }
 | 
        
           |  |  | 834 |   | 
        
           |  |  | 835 |         // Rotate Left.
 | 
        
           |  |  | 836 |         rotateleftbutton = this.get_dialogue_element(SELECTOR.ROTATELEFTBUTTON);
 | 
        
           |  |  | 837 |         rotateleftbutton.on('click', this.rotatePDF, this, true);
 | 
        
           |  |  | 838 |         rotateleftbutton.on('key', this.rotatePDF, 'down:13', this, true);
 | 
        
           |  |  | 839 |   | 
        
           |  |  | 840 |         // Rotate Right.
 | 
        
           |  |  | 841 |         rotaterightbutton = this.get_dialogue_element(SELECTOR.ROTATERIGHTBUTTON);
 | 
        
           |  |  | 842 |         rotaterightbutton.on('click', this.rotatePDF, this, false);
 | 
        
           |  |  | 843 |         rotaterightbutton.on('key', this.rotatePDF, 'down:13', this, false);
 | 
        
           |  |  | 844 |   | 
        
           |  |  | 845 |         this.disable_touch_scroll();
 | 
        
           |  |  | 846 |   | 
        
           |  |  | 847 |         // Setup the tool buttons.
 | 
        
           |  |  | 848 |         Y.each(TOOLSELECTOR, function(selector, tool) {
 | 
        
           |  |  | 849 |             toolnode = this.get_dialogue_element(selector);
 | 
        
           |  |  | 850 |             toolnode.on('click', this.handle_tool_button, this, tool);
 | 
        
           |  |  | 851 |             toolnode.on('key', this.handle_tool_button, 'down:13', this, tool);
 | 
        
           |  |  | 852 |             toolnode.setAttribute('aria-pressed', 'false');
 | 
        
           |  |  | 853 |         }, this);
 | 
        
           |  |  | 854 |   | 
        
           |  |  | 855 |         // Set the default tool.
 | 
        
           |  |  | 856 |   | 
        
           |  |  | 857 |         commentcolourbutton = this.get_dialogue_element(SELECTOR.COMMENTCOLOURBUTTON);
 | 
        
           |  |  | 858 |         picker = new M.assignfeedback_editpdf.colourpicker({
 | 
        
           |  |  | 859 |             buttonNode: commentcolourbutton,
 | 
        
           |  |  | 860 |             colours: COMMENTCOLOUR,
 | 
        
           |  |  | 861 |             iconprefix: 'background_colour_',
 | 
        
           |  |  | 862 |             callback: function(e) {
 | 
        
           |  |  | 863 |                 var colour = e.target.getAttribute('data-colour');
 | 
        
           |  |  | 864 |                 if (!colour) {
 | 
        
           |  |  | 865 |                     colour = e.target.ancestor().getAttribute('data-colour');
 | 
        
           |  |  | 866 |                 }
 | 
        
           |  |  | 867 |                 this.currentedit.commentcolour = colour;
 | 
        
           |  |  | 868 |                 this.handle_tool_button(e, "comment");
 | 
        
           |  |  | 869 |             },
 | 
        
           |  |  | 870 |             context: this
 | 
        
           |  |  | 871 |         });
 | 
        
           |  |  | 872 |   | 
        
           |  |  | 873 |         annotationcolourbutton = this.get_dialogue_element(SELECTOR.ANNOTATIONCOLOURBUTTON);
 | 
        
           |  |  | 874 |         picker = new M.assignfeedback_editpdf.colourpicker({
 | 
        
           |  |  | 875 |             buttonNode: annotationcolourbutton,
 | 
        
           |  |  | 876 |             iconprefix: 'colour_',
 | 
        
           |  |  | 877 |             colours: ANNOTATIONCOLOUR,
 | 
        
           |  |  | 878 |             callback: function(e) {
 | 
        
           |  |  | 879 |                 var colour = e.target.getAttribute('data-colour');
 | 
        
           |  |  | 880 |                 if (!colour) {
 | 
        
           |  |  | 881 |                     colour = e.target.ancestor().getAttribute('data-colour');
 | 
        
           |  |  | 882 |                 }
 | 
        
           |  |  | 883 |                 this.currentedit.annotationcolour = colour;
 | 
        
           |  |  | 884 |                 if (this.lastannotationtool) {
 | 
        
           |  |  | 885 |                     this.handle_tool_button(e, this.lastannotationtool);
 | 
        
           |  |  | 886 |                 } else {
 | 
        
           |  |  | 887 |                     this.handle_tool_button(e, "pen");
 | 
        
           |  |  | 888 |                 }
 | 
        
           |  |  | 889 |             },
 | 
        
           |  |  | 890 |             context: this
 | 
        
           |  |  | 891 |         });
 | 
        
           |  |  | 892 |   | 
        
           |  |  | 893 |         stampfiles = this.get('stampfiles');
 | 
        
           |  |  | 894 |         if (stampfiles.length <= 0) {
 | 
        
           |  |  | 895 |             this.get_dialogue_element(TOOLSELECTOR.stamp).ancestor().hide();
 | 
        
           |  |  | 896 |         } else {
 | 
        
           |  |  | 897 |             filename = stampfiles[0].substr(stampfiles[0].lastIndexOf('/') + 1);
 | 
        
           |  |  | 898 |             this.currentedit.stamp = filename;
 | 
        
           |  |  | 899 |             currentstampbutton = this.get_dialogue_element(SELECTOR.STAMPSBUTTON);
 | 
        
           |  |  | 900 |   | 
        
           |  |  | 901 |             picker = new M.assignfeedback_editpdf.stamppicker({
 | 
        
           |  |  | 902 |                 buttonNode: currentstampbutton,
 | 
        
           |  |  | 903 |                 stamps: stampfiles,
 | 
        
           |  |  | 904 |                 callback: function(e) {
 | 
        
           |  |  | 905 |                     var stamp = e.target.getAttribute('data-stamp'),
 | 
        
           |  |  | 906 |                         filename;
 | 
        
           |  |  | 907 |   | 
        
           |  |  | 908 |                     if (!stamp) {
 | 
        
           |  |  | 909 |                         stamp = e.target.ancestor().getAttribute('data-stamp');
 | 
        
           |  |  | 910 |                     }
 | 
        
           |  |  | 911 |                     filename = stamp.substr(stamp.lastIndexOf('/'));
 | 
        
           |  |  | 912 |                     this.currentedit.stamp = filename;
 | 
        
           |  |  | 913 |                     this.handle_tool_button(e, "stamp");
 | 
        
           |  |  | 914 |                 },
 | 
        
           |  |  | 915 |                 context: this
 | 
        
           |  |  | 916 |             });
 | 
        
           |  |  | 917 |             this.refresh_button_state();
 | 
        
           |  |  | 918 |         }
 | 
        
           |  |  | 919 |     },
 | 
        
           |  |  | 920 |   | 
        
           |  |  | 921 |     /**
 | 
        
           |  |  | 922 |      * Change the current tool.
 | 
        
           |  |  | 923 |      * @protected
 | 
        
           |  |  | 924 |      * @method handle_tool_button
 | 
        
           |  |  | 925 |      */
 | 
        
           |  |  | 926 |     handle_tool_button: function(e, tool) {
 | 
        
           |  |  | 927 |         var currenttoolnode;
 | 
        
           |  |  | 928 |   | 
        
           |  |  | 929 |         e.preventDefault();
 | 
        
           |  |  | 930 |   | 
        
           |  |  | 931 |         // Change style of the pressed button.
 | 
        
           |  |  | 932 |         currenttoolnode = this.get_dialogue_element(TOOLSELECTOR[this.currentedit.tool]);
 | 
        
           |  |  | 933 |         currenttoolnode.removeClass('assignfeedback_editpdf_selectedbutton');
 | 
        
           |  |  | 934 |         currenttoolnode.setAttribute('aria-pressed', 'false');
 | 
        
           |  |  | 935 |         this.currentedit.tool = tool;
 | 
        
           |  |  | 936 |   | 
        
           |  |  | 937 |         if (tool !== "comment" && tool !== "select" && tool !== "drag" && tool !== "stamp") {
 | 
        
           |  |  | 938 |             this.lastannotationtool = tool;
 | 
        
           |  |  | 939 |         }
 | 
        
           |  |  | 940 |   | 
        
           |  |  | 941 |         this.refresh_button_state();
 | 
        
           |  |  | 942 |     },
 | 
        
           |  |  | 943 |   | 
        
           |  |  | 944 |     /**
 | 
        
           |  |  | 945 |      * JSON encode the current page data - stripping out drawable references which cannot be encoded.
 | 
        
           |  |  | 946 |      * @protected
 | 
        
           |  |  | 947 |      * @method stringify_current_page
 | 
        
           |  |  | 948 |      * @return string
 | 
        
           |  |  | 949 |      */
 | 
        
           |  |  | 950 |     stringify_current_page: function() {
 | 
        
           |  |  | 951 |         var comments = [],
 | 
        
           |  |  | 952 |             annotations = [],
 | 
        
           |  |  | 953 |             page,
 | 
        
           |  |  | 954 |             i = 0;
 | 
        
           |  |  | 955 |   | 
        
           |  |  | 956 |         for (i = 0; i < this.pages[this.currentpage].comments.length; i++) {
 | 
        
           |  |  | 957 |             comments[i] = this.pages[this.currentpage].comments[i].clean();
 | 
        
           |  |  | 958 |         }
 | 
        
           |  |  | 959 |         for (i = 0; i < this.pages[this.currentpage].annotations.length; i++) {
 | 
        
           |  |  | 960 |             annotations[i] = this.pages[this.currentpage].annotations[i].clean();
 | 
        
           |  |  | 961 |         }
 | 
        
           |  |  | 962 |   | 
        
           |  |  | 963 |         page = {comments: comments, annotations: annotations};
 | 
        
           |  |  | 964 |   | 
        
           |  |  | 965 |         return Y.JSON.stringify(page);
 | 
        
           |  |  | 966 |     },
 | 
        
           |  |  | 967 |   | 
        
           |  |  | 968 |     /**
 | 
        
           |  |  | 969 |      * Generate a drawable from the current in progress edit.
 | 
        
           |  |  | 970 |      * @protected
 | 
        
           |  |  | 971 |      * @method get_current_drawable
 | 
        
           |  |  | 972 |      */
 | 
        
           |  |  | 973 |     get_current_drawable: function() {
 | 
        
           |  |  | 974 |         var comment,
 | 
        
           |  |  | 975 |             annotation,
 | 
        
           |  |  | 976 |             drawable = false;
 | 
        
           |  |  | 977 |   | 
        
           |  |  | 978 |         if (!this.currentedit.start || !this.currentedit.end) {
 | 
        
           |  |  | 979 |             return false;
 | 
        
           |  |  | 980 |         }
 | 
        
           |  |  | 981 |   | 
        
           |  |  | 982 |         if (this.currentedit.tool === 'comment') {
 | 
        
           |  |  | 983 |             comment = new M.assignfeedback_editpdf.comment(this);
 | 
        
           |  |  | 984 |             drawable = comment.draw_current_edit(this.currentedit);
 | 
        
           |  |  | 985 |         } else {
 | 
        
           |  |  | 986 |             annotation = this.create_annotation(this.currentedit.tool, {});
 | 
        
           |  |  | 987 |             if (annotation) {
 | 
        
           |  |  | 988 |                 drawable = annotation.draw_current_edit(this.currentedit);
 | 
        
           |  |  | 989 |             }
 | 
        
           |  |  | 990 |         }
 | 
        
           |  |  | 991 |   | 
        
           |  |  | 992 |         return drawable;
 | 
        
           |  |  | 993 |     },
 | 
        
           |  |  | 994 |   | 
        
           |  |  | 995 |     /**
 | 
        
           |  |  | 996 |      * Find an element within the dialogue.
 | 
        
           |  |  | 997 |      * @protected
 | 
        
           |  |  | 998 |      * @method get_dialogue_element
 | 
        
           |  |  | 999 |      */
 | 
        
           |  |  | 1000 |     get_dialogue_element: function(selector) {
 | 
        
           |  |  | 1001 |         if (this.panel) {
 | 
        
           |  |  | 1002 |             return this.panel.one(selector);
 | 
        
           |  |  | 1003 |         } else {
 | 
        
           |  |  | 1004 |             return this.dialogue.get('boundingBox').one(selector);
 | 
        
           |  |  | 1005 |         }
 | 
        
           |  |  | 1006 |     },
 | 
        
           |  |  | 1007 |   | 
        
           |  |  | 1008 |     /**
 | 
        
           |  |  | 1009 |      * Redraw the active edit.
 | 
        
           |  |  | 1010 |      * @protected
 | 
        
           |  |  | 1011 |      * @method redraw_active_edit
 | 
        
           |  |  | 1012 |      */
 | 
        
           |  |  | 1013 |     redraw_current_edit: function() {
 | 
        
           |  |  | 1014 |         if (this.currentdrawable) {
 | 
        
           |  |  | 1015 |             this.currentdrawable.erase();
 | 
        
           |  |  | 1016 |         }
 | 
        
           |  |  | 1017 |         this.currentdrawable = this.get_current_drawable();
 | 
        
           |  |  | 1018 |     },
 | 
        
           |  |  | 1019 |   | 
        
           |  |  | 1020 |     /**
 | 
        
           |  |  | 1021 |      * Event handler for mousedown or touchstart.
 | 
        
           |  |  | 1022 |      * @protected
 | 
        
           |  |  | 1023 |      * @param Event
 | 
        
           |  |  | 1024 |      * @method edit_start
 | 
        
           |  |  | 1025 |      */
 | 
        
           |  |  | 1026 |     edit_start: function(e) {
 | 
        
           |  |  | 1027 |         var canvas = this.get_dialogue_element(SELECTOR.DRAWINGCANVAS),
 | 
        
           |  |  | 1028 |             offset = canvas.getXY(),
 | 
        
           |  |  | 1029 |             scrolltop = canvas.get('docScrollY'),
 | 
        
           |  |  | 1030 |             scrollleft = canvas.get('docScrollX'),
 | 
        
           |  |  | 1031 |             point = {x: e.clientX - offset[0] + scrollleft,
 | 
        
           |  |  | 1032 |                      y: e.clientY - offset[1] + scrolltop},
 | 
        
           |  |  | 1033 |             selected = false;
 | 
        
           |  |  | 1034 |   | 
        
           |  |  | 1035 |         // Ignore right mouse click.
 | 
        
           |  |  | 1036 |         if (e.button === 3) {
 | 
        
           |  |  | 1037 |             return;
 | 
        
           |  |  | 1038 |         }
 | 
        
           |  |  | 1039 |   | 
        
           |  |  | 1040 |         if (this.currentedit.starttime) {
 | 
        
           |  |  | 1041 |             return;
 | 
        
           |  |  | 1042 |         }
 | 
        
           |  |  | 1043 |   | 
        
           |  |  | 1044 |         if (this.editingcomment) {
 | 
        
           |  |  | 1045 |             return;
 | 
        
           |  |  | 1046 |         }
 | 
        
           |  |  | 1047 |   | 
        
           |  |  | 1048 |         this.currentedit.starttime = new Date().getTime();
 | 
        
           |  |  | 1049 |         this.currentedit.start = point;
 | 
        
           |  |  | 1050 |         this.currentedit.end = {x: point.x, y: point.y};
 | 
        
           |  |  | 1051 |   | 
        
           |  |  | 1052 |         if (this.currentedit.tool === 'select') {
 | 
        
           |  |  | 1053 |             var x = this.currentedit.end.x,
 | 
        
           |  |  | 1054 |                 y = this.currentedit.end.y,
 | 
        
           |  |  | 1055 |                 annotations = this.pages[this.currentpage].annotations;
 | 
        
           |  |  | 1056 |             // Find the first annotation whose bounds encompass the click.
 | 
        
           |  |  | 1057 |             Y.each(annotations, function(annotation) {
 | 
        
           |  |  | 1058 |                 if (((x - annotation.x) * (x - annotation.endx)) <= 0 &&
 | 
        
           |  |  | 1059 |                     ((y - annotation.y) * (y - annotation.endy)) <= 0) {
 | 
        
           |  |  | 1060 |                     selected = annotation;
 | 
        
           |  |  | 1061 |                 }
 | 
        
           |  |  | 1062 |             });
 | 
        
           |  |  | 1063 |   | 
        
           |  |  | 1064 |             if (selected) {
 | 
        
           |  |  | 1065 |                 this.lastannotation = this.currentannotation;
 | 
        
           |  |  | 1066 |                 this.currentannotation = selected;
 | 
        
           |  |  | 1067 |                 if (this.lastannotation && this.lastannotation !== selected) {
 | 
        
           |  |  | 1068 |                     // Redraw the last selected annotation to remove the highlight.
 | 
        
           |  |  | 1069 |                     if (this.lastannotation.drawable) {
 | 
        
           |  |  | 1070 |                         this.lastannotation.drawable.erase();
 | 
        
           |  |  | 1071 |                         this.drawables.push(this.lastannotation.draw());
 | 
        
           |  |  | 1072 |                     }
 | 
        
           |  |  | 1073 |                 }
 | 
        
           |  |  | 1074 |                 // Redraw the newly selected annotation to show the highlight.
 | 
        
           |  |  | 1075 |                 if (this.currentannotation.drawable) {
 | 
        
           |  |  | 1076 |                     this.currentannotation.drawable.erase();
 | 
        
           |  |  | 1077 |                 }
 | 
        
           |  |  | 1078 |                 this.drawables.push(this.currentannotation.draw());
 | 
        
           |  |  | 1079 |             } else {
 | 
        
           |  |  | 1080 |                 this.lastannotation = this.currentannotation;
 | 
        
           |  |  | 1081 |                 this.currentannotation = null;
 | 
        
           |  |  | 1082 |   | 
        
           |  |  | 1083 |                 // Redraw the last selected annotation to remove the highlight.
 | 
        
           |  |  | 1084 |                 if (this.lastannotation && this.lastannotation.drawable) {
 | 
        
           |  |  | 1085 |                     this.lastannotation.drawable.erase();
 | 
        
           |  |  | 1086 |                     this.drawables.push(this.lastannotation.draw());
 | 
        
           |  |  | 1087 |                 }
 | 
        
           |  |  | 1088 |             }
 | 
        
           |  |  | 1089 |         }
 | 
        
           |  |  | 1090 |         if (this.currentannotation) {
 | 
        
           |  |  | 1091 |             // Used to calculate drag offset.
 | 
        
           |  |  | 1092 |             this.currentedit.annotationstart = {x: this.currentannotation.x,
 | 
        
           |  |  | 1093 |                                                  y: this.currentannotation.y};
 | 
        
           |  |  | 1094 |         }
 | 
        
           |  |  | 1095 |     },
 | 
        
           |  |  | 1096 |   | 
        
           |  |  | 1097 |     /**
 | 
        
           |  |  | 1098 |      * Event handler for mousemove.
 | 
        
           |  |  | 1099 |      * @protected
 | 
        
           |  |  | 1100 |      * @param Event
 | 
        
           |  |  | 1101 |      * @method edit_move
 | 
        
           |  |  | 1102 |      */
 | 
        
           |  |  | 1103 |     edit_move: function(e) {
 | 
        
           |  |  | 1104 |         var bounds = this.get_canvas_bounds(),
 | 
        
           |  |  | 1105 |             canvas = this.get_dialogue_element(SELECTOR.DRAWINGCANVAS),
 | 
        
           |  |  | 1106 |             drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION),
 | 
        
           |  |  | 1107 |             clientpoint = new M.assignfeedback_editpdf.point(e.clientX + canvas.get('docScrollX'),
 | 
        
           |  |  | 1108 |                                                              e.clientY + canvas.get('docScrollY')),
 | 
        
           |  |  | 1109 |             point = this.get_canvas_coordinates(clientpoint),
 | 
        
           |  |  | 1110 |             activeelement = document.activeElement,
 | 
        
           |  |  | 1111 |             diffX,
 | 
        
           |  |  | 1112 |             diffY;
 | 
        
           |  |  | 1113 |   | 
        
           |  |  | 1114 |         if (activeelement.type === 'textarea') {
 | 
        
           |  |  | 1115 |             return;
 | 
        
           |  |  | 1116 |         }
 | 
        
           |  |  | 1117 |   | 
        
           |  |  | 1118 |         e.preventDefault();
 | 
        
           |  |  | 1119 |   | 
        
           |  |  | 1120 |         // Ignore events out of the canvas area.
 | 
        
           |  |  | 1121 |         if (point.x < 0 || point.x > bounds.width || point.y < 0 || point.y > bounds.height) {
 | 
        
           |  |  | 1122 |             return;
 | 
        
           |  |  | 1123 |         }
 | 
        
           |  |  | 1124 |   | 
        
           |  |  | 1125 |         if (this.currentedit.tool === 'pen') {
 | 
        
           |  |  | 1126 |             this.currentedit.path.push(point);
 | 
        
           |  |  | 1127 |         }
 | 
        
           |  |  | 1128 |   | 
        
           |  |  | 1129 |         if (this.currentedit.tool === 'select') {
 | 
        
           |  |  | 1130 |             if (this.currentannotation && this.currentedit) {
 | 
        
           |  |  | 1131 |                 this.currentannotation.move(this.currentedit.annotationstart.x + point.x - this.currentedit.start.x,
 | 
        
           |  |  | 1132 |                                              this.currentedit.annotationstart.y + point.y - this.currentedit.start.y);
 | 
        
           |  |  | 1133 |             }
 | 
        
           |  |  | 1134 |         } else if (this.currentedit.tool === 'drag') {
 | 
        
           |  |  | 1135 |             diffX = point.x - this.currentedit.start.x;
 | 
        
           |  |  | 1136 |             diffY = point.y - this.currentedit.start.y;
 | 
        
           |  |  | 1137 |   | 
        
           |  |  | 1138 |             drawingregion.getDOMNode().scrollLeft -= diffX;
 | 
        
           |  |  | 1139 |             drawingregion.getDOMNode().scrollTop -= diffY;
 | 
        
           |  |  | 1140 |   | 
        
           |  |  | 1141 |         } else {
 | 
        
           |  |  | 1142 |             if (this.currentedit.start) {
 | 
        
           |  |  | 1143 |                 this.currentedit.end = point;
 | 
        
           |  |  | 1144 |                 this.redraw_current_edit();
 | 
        
           |  |  | 1145 |             }
 | 
        
           |  |  | 1146 |         }
 | 
        
           |  |  | 1147 |     },
 | 
        
           |  |  | 1148 |   | 
        
           |  |  | 1149 |     /**
 | 
        
           |  |  | 1150 |      * Event handler for mouseup or touchend.
 | 
        
           |  |  | 1151 |      * @protected
 | 
        
           |  |  | 1152 |      * @param Event
 | 
        
           |  |  | 1153 |      * @method edit_end
 | 
        
           |  |  | 1154 |      */
 | 
        
           |  |  | 1155 |     edit_end: function() {
 | 
        
           |  |  | 1156 |         var duration,
 | 
        
           |  |  | 1157 |             comment,
 | 
        
           |  |  | 1158 |             annotation;
 | 
        
           |  |  | 1159 |   | 
        
           |  |  | 1160 |         duration = new Date().getTime() - this.currentedit.start;
 | 
        
           |  |  | 1161 |   | 
        
           |  |  | 1162 |         if (duration < CLICKTIMEOUT || this.currentedit.start === false) {
 | 
        
           |  |  | 1163 |             return;
 | 
        
           |  |  | 1164 |         }
 | 
        
           |  |  | 1165 |   | 
        
           |  |  | 1166 |         if (this.currentedit.tool === 'comment') {
 | 
        
           |  |  | 1167 |             if (this.currentdrawable) {
 | 
        
           |  |  | 1168 |                 this.currentdrawable.erase();
 | 
        
           |  |  | 1169 |             }
 | 
        
           |  |  | 1170 |             this.currentdrawable = false;
 | 
        
           |  |  | 1171 |             comment = new M.assignfeedback_editpdf.comment(this);
 | 
        
           |  |  | 1172 |             if (comment.init_from_edit(this.currentedit)) {
 | 
        
           |  |  | 1173 |                 this.pages[this.currentpage].comments.push(comment);
 | 
        
           |  |  | 1174 |                 this.drawables.push(comment.draw(true));
 | 
        
           |  |  | 1175 |                 this.editingcomment = true;
 | 
        
           |  |  | 1176 |             }
 | 
        
           |  |  | 1177 |         } else {
 | 
        
           |  |  | 1178 |             annotation = this.create_annotation(this.currentedit.tool, {});
 | 
        
           |  |  | 1179 |             if (annotation) {
 | 
        
           |  |  | 1180 |                 if (this.currentdrawable) {
 | 
        
           |  |  | 1181 |                     this.currentdrawable.erase();
 | 
        
           |  |  | 1182 |                 }
 | 
        
           |  |  | 1183 |                 this.currentdrawable = false;
 | 
        
           |  |  | 1184 |                 if (annotation.init_from_edit(this.currentedit)) {
 | 
        
           |  |  | 1185 |                     this.pages[this.currentpage].annotations.push(annotation);
 | 
        
           |  |  | 1186 |                     this.drawables.push(annotation.draw());
 | 
        
           |  |  | 1187 |                 }
 | 
        
           |  |  | 1188 |             }
 | 
        
           |  |  | 1189 |         }
 | 
        
           |  |  | 1190 |   | 
        
           |  |  | 1191 |         // Save the changes.
 | 
        
           |  |  | 1192 |         this.save_current_page();
 | 
        
           |  |  | 1193 |   | 
        
           |  |  | 1194 |         // Reset the current edit.
 | 
        
           |  |  | 1195 |         this.currentedit.starttime = 0;
 | 
        
           |  |  | 1196 |         this.currentedit.start = false;
 | 
        
           |  |  | 1197 |         this.currentedit.end = false;
 | 
        
           |  |  | 1198 |         this.currentedit.path = [];
 | 
        
           |  |  | 1199 |     },
 | 
        
           |  |  | 1200 |   | 
        
           |  |  | 1201 |     /**
 | 
        
           |  |  | 1202 |      * Resize the dialogue window when the browser is resized.
 | 
        
           |  |  | 1203 |      * @public
 | 
        
           |  |  | 1204 |      * @method resize
 | 
        
           |  |  | 1205 |      */
 | 
        
           |  |  | 1206 |     resize: function() {
 | 
        
           |  |  | 1207 |         var drawingregion, drawregionheight;
 | 
        
           |  |  | 1208 |   | 
        
           |  |  | 1209 |         if (this.dialogue) {
 | 
        
           |  |  | 1210 |             if (!this.dialogue.get('visible')) {
 | 
        
           |  |  | 1211 |                 return;
 | 
        
           |  |  | 1212 |             }
 | 
        
           |  |  | 1213 |             this.dialogue.centerDialogue();
 | 
        
           |  |  | 1214 |         }
 | 
        
           |  |  | 1215 |   | 
        
           |  |  | 1216 |         // Make sure the dialogue box is not bigger than the max height of the viewport.
 | 
        
           |  |  | 1217 |         drawregionheight = Y.one('body').get('winHeight') - 120; // Space for toolbar + titlebar.
 | 
        
           |  |  | 1218 |         if (drawregionheight < 100) {
 | 
        
           |  |  | 1219 |             drawregionheight = 100;
 | 
        
           |  |  | 1220 |         }
 | 
        
           |  |  | 1221 |         drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION);
 | 
        
           |  |  | 1222 |         if (this.dialogue) {
 | 
        
           |  |  | 1223 |             drawingregion.setStyle('maxHeight', drawregionheight + 'px');
 | 
        
           |  |  | 1224 |         }
 | 
        
           |  |  | 1225 |         this.redraw();
 | 
        
           |  |  | 1226 |         return true;
 | 
        
           |  |  | 1227 |     },
 | 
        
           |  |  | 1228 |   | 
        
           |  |  | 1229 |     /**
 | 
        
           |  |  | 1230 |      * Factory method for creating annotations of the correct subclass.
 | 
        
           |  |  | 1231 |      * @public
 | 
        
           |  |  | 1232 |      * @method create_annotation
 | 
        
           |  |  | 1233 |      */
 | 
        
           |  |  | 1234 |     create_annotation: function(type, data) {
 | 
        
           |  |  | 1235 |         data.type = type;
 | 
        
           |  |  | 1236 |         data.editor = this;
 | 
        
           |  |  | 1237 |         if (type === "line") {
 | 
        
           |  |  | 1238 |             return new M.assignfeedback_editpdf.annotationline(data);
 | 
        
           |  |  | 1239 |         } else if (type === "rectangle") {
 | 
        
           |  |  | 1240 |             return new M.assignfeedback_editpdf.annotationrectangle(data);
 | 
        
           |  |  | 1241 |         } else if (type === "oval") {
 | 
        
           |  |  | 1242 |             return new M.assignfeedback_editpdf.annotationoval(data);
 | 
        
           |  |  | 1243 |         } else if (type === "pen") {
 | 
        
           |  |  | 1244 |             return new M.assignfeedback_editpdf.annotationpen(data);
 | 
        
           |  |  | 1245 |         } else if (type === "highlight") {
 | 
        
           |  |  | 1246 |             return new M.assignfeedback_editpdf.annotationhighlight(data);
 | 
        
           |  |  | 1247 |         } else if (type === "stamp") {
 | 
        
           |  |  | 1248 |             return new M.assignfeedback_editpdf.annotationstamp(data);
 | 
        
           |  |  | 1249 |         }
 | 
        
           |  |  | 1250 |         return false;
 | 
        
           |  |  | 1251 |     },
 | 
        
           |  |  | 1252 |   | 
        
           |  |  | 1253 |     /**
 | 
        
           |  |  | 1254 |      * Save all the annotations and comments for the current page.
 | 
        
           |  |  | 1255 |      * @protected
 | 
        
           |  |  | 1256 |      * @method save_current_page
 | 
        
           |  |  | 1257 |      */
 | 
        
           |  |  | 1258 |     save_current_page: function() {
 | 
        
           |  |  | 1259 |         this.clear_warnings(false);
 | 
        
           |  |  | 1260 |         var ajaxurl = AJAXBASE,
 | 
        
           |  |  | 1261 |             config;
 | 
        
           |  |  | 1262 |   | 
        
           |  |  | 1263 |         config = {
 | 
        
           |  |  | 1264 |             method: 'post',
 | 
        
           |  |  | 1265 |             context: this,
 | 
        
           |  |  | 1266 |             sync: false,
 | 
        
           |  |  | 1267 |             data: {
 | 
        
           |  |  | 1268 |                 'sesskey': M.cfg.sesskey,
 | 
        
           |  |  | 1269 |                 'action': 'savepage',
 | 
        
           |  |  | 1270 |                 'index': this.currentpage,
 | 
        
           |  |  | 1271 |                 'userid': this.get('userid'),
 | 
        
           |  |  | 1272 |                 'attemptnumber': this.get('attemptnumber'),
 | 
        
           |  |  | 1273 |                 'assignmentid': this.get('assignmentid'),
 | 
        
           |  |  | 1274 |                 'page': this.stringify_current_page()
 | 
        
           |  |  | 1275 |             },
 | 
        
           |  |  | 1276 |             on: {
 | 
        
           |  |  | 1277 |                 success: function(tid, response) {
 | 
        
           |  |  | 1278 |                     var jsondata;
 | 
        
           |  |  | 1279 |                     try {
 | 
        
           |  |  | 1280 |                         jsondata = Y.JSON.parse(response.responseText);
 | 
        
           |  |  | 1281 |                         if (jsondata.error) {
 | 
        
           |  |  | 1282 |                             return new M.core.ajaxException(jsondata);
 | 
        
           |  |  | 1283 |                         }
 | 
        
           |  |  | 1284 |                         // Show warning that we have not saved the feedback.
 | 
        
           |  |  | 1285 |                         Y.one(SELECTOR.UNSAVEDCHANGESINPUT).set('value', 'true');
 | 
        
           |  |  | 1286 |                         this.warning(M.util.get_string('draftchangessaved', 'assignfeedback_editpdf'), true);
 | 
        
           |  |  | 1287 |                     } catch (e) {
 | 
        
           |  |  | 1288 |                         return new M.core.exception(e);
 | 
        
           |  |  | 1289 |                     }
 | 
        
           |  |  | 1290 |                 },
 | 
        
           |  |  | 1291 |                 failure: function(tid, response) {
 | 
        
           |  |  | 1292 |                     return new M.core.exception(response.responseText);
 | 
        
           |  |  | 1293 |                 }
 | 
        
           |  |  | 1294 |             }
 | 
        
           |  |  | 1295 |         };
 | 
        
           |  |  | 1296 |   | 
        
           |  |  | 1297 |         Y.io(ajaxurl, config);
 | 
        
           |  |  | 1298 |     },
 | 
        
           |  |  | 1299 |   | 
        
           |  |  | 1300 |     /**
 | 
        
           |  |  | 1301 |      * Event handler to open the comment search interface.
 | 
        
           |  |  | 1302 |      *
 | 
        
           |  |  | 1303 |      * @param Event e
 | 
        
           |  |  | 1304 |      * @protected
 | 
        
           |  |  | 1305 |      * @method open_search_comments
 | 
        
           |  |  | 1306 |      */
 | 
        
           |  |  | 1307 |     open_search_comments: function(e) {
 | 
        
           |  |  | 1308 |         if (!this.searchcommentswindow) {
 | 
        
           |  |  | 1309 |             this.searchcommentswindow = new M.assignfeedback_editpdf.commentsearch({
 | 
        
           |  |  | 1310 |                 editor: this
 | 
        
           |  |  | 1311 |             });
 | 
        
           |  |  | 1312 |         }
 | 
        
           |  |  | 1313 |   | 
        
           |  |  | 1314 |         this.searchcommentswindow.show();
 | 
        
           |  |  | 1315 |         e.preventDefault();
 | 
        
           |  |  | 1316 |     },
 | 
        
           |  |  | 1317 |   | 
        
           |  |  | 1318 |     /**
 | 
        
           |  |  | 1319 |      * Toggle function to expand/collapse all comments on page.
 | 
        
           |  |  | 1320 |      *
 | 
        
           |  |  | 1321 |      * @protected
 | 
        
           |  |  | 1322 |      * @method expandCollapseComments
 | 
        
           |  |  | 1323 |      */
 | 
        
           |  |  | 1324 |     expandCollapseComments: function() {
 | 
        
           |  |  | 1325 |         var comments = Y.all('.commentdrawable');
 | 
        
           |  |  | 1326 |   | 
        
           |  |  | 1327 |         if (this.collapsecomments) {
 | 
        
           |  |  | 1328 |             this.collapsecomments = false;
 | 
        
           |  |  | 1329 |             comments.removeClass('commentcollapsed');
 | 
        
           |  |  | 1330 |         } else {
 | 
        
           |  |  | 1331 |             this.collapsecomments = true;
 | 
        
           |  |  | 1332 |             comments.addClass('commentcollapsed');
 | 
        
           |  |  | 1333 |         }
 | 
        
           |  |  | 1334 |     },
 | 
        
           |  |  | 1335 |   | 
        
           |  |  | 1336 |     /**
 | 
        
           |  |  | 1337 |      * Redraw all the comments and annotations.
 | 
        
           |  |  | 1338 |      * @protected
 | 
        
           |  |  | 1339 |      * @method redraw
 | 
        
           |  |  | 1340 |      */
 | 
        
           |  |  | 1341 |     redraw: function() {
 | 
        
           |  |  | 1342 |         var i,
 | 
        
           |  |  | 1343 |             page;
 | 
        
           |  |  | 1344 |   | 
        
           |  |  | 1345 |         page = this.pages[this.currentpage];
 | 
        
           |  |  | 1346 |         if (page === undefined) {
 | 
        
           |  |  | 1347 |             return; // Can happen if a redraw is triggered by an event, before the page has been selected.
 | 
        
           |  |  | 1348 |         }
 | 
        
           |  |  | 1349 |         while (this.drawables.length > 0) {
 | 
        
           |  |  | 1350 |             this.drawables.pop().erase();
 | 
        
           |  |  | 1351 |         }
 | 
        
           |  |  | 1352 |   | 
        
           |  |  | 1353 |         for (i = 0; i < page.annotations.length; i++) {
 | 
        
           |  |  | 1354 |             this.drawables.push(page.annotations[i].draw());
 | 
        
           |  |  | 1355 |         }
 | 
        
           |  |  | 1356 |         for (i = 0; i < page.comments.length; i++) {
 | 
        
           |  |  | 1357 |             this.drawables.push(page.comments[i].draw(false));
 | 
        
           |  |  | 1358 |         }
 | 
        
           |  |  | 1359 |     },
 | 
        
           |  |  | 1360 |   | 
        
           |  |  | 1361 |     /**
 | 
        
           |  |  | 1362 |      * Clear all current warning messages from display.
 | 
        
           |  |  | 1363 |      * @protected
 | 
        
           |  |  | 1364 |      * @method clear_warnings
 | 
        
           |  |  | 1365 |      * @param {Boolean} allwarnings If true, all previous warnings are removed.
 | 
        
           |  |  | 1366 |      */
 | 
        
           |  |  | 1367 |     clear_warnings: function(allwarnings) {
 | 
        
           |  |  | 1368 |         // Remove all warning messages, they may not relate to the current document or page anymore.
 | 
        
           |  |  | 1369 |         var warningregion = this.get_dialogue_element(SELECTOR.WARNINGMESSAGECONTAINER);
 | 
        
           |  |  | 1370 |         if (allwarnings) {
 | 
        
           |  |  | 1371 |             warningregion.empty();
 | 
        
           |  |  | 1372 |         } else {
 | 
        
           |  |  | 1373 |             warningregion.all('.alert-info').remove(true);
 | 
        
           |  |  | 1374 |         }
 | 
        
           |  |  | 1375 |     },
 | 
        
           |  |  | 1376 |   | 
        
           |  |  | 1377 |     /**
 | 
        
           |  |  | 1378 |      * Load the image for this pdf page and remove the loading icon (if there).
 | 
        
           |  |  | 1379 |      * @protected
 | 
        
           |  |  | 1380 |      * @method change_page
 | 
        
           |  |  | 1381 |      */
 | 
        
           |  |  | 1382 |     change_page: function() {
 | 
        
           |  |  | 1383 |         var drawingcanvas = this.get_dialogue_element(SELECTOR.DRAWINGCANVAS),
 | 
        
           |  |  | 1384 |             page,
 | 
        
           |  |  | 1385 |             previousbutton,
 | 
        
           |  |  | 1386 |             nextbutton;
 | 
        
           |  |  | 1387 |   | 
        
           |  |  | 1388 |         previousbutton = this.get_dialogue_element(SELECTOR.PREVIOUSBUTTON);
 | 
        
           |  |  | 1389 |         nextbutton = this.get_dialogue_element(SELECTOR.NEXTBUTTON);
 | 
        
           |  |  | 1390 |   | 
        
           |  |  | 1391 |         if (this.currentpage > 0) {
 | 
        
           |  |  | 1392 |             previousbutton.removeAttribute('disabled');
 | 
        
           |  |  | 1393 |         } else {
 | 
        
           |  |  | 1394 |             previousbutton.setAttribute('disabled', 'true');
 | 
        
           |  |  | 1395 |         }
 | 
        
           |  |  | 1396 |         if (this.currentpage < (this.pagecount - 1)) {
 | 
        
           |  |  | 1397 |             nextbutton.removeAttribute('disabled');
 | 
        
           |  |  | 1398 |         } else {
 | 
        
           |  |  | 1399 |             nextbutton.setAttribute('disabled', 'true');
 | 
        
           |  |  | 1400 |         }
 | 
        
           |  |  | 1401 |   | 
        
           |  |  | 1402 |         page = this.pages[this.currentpage];
 | 
        
           |  |  | 1403 |         if (this.loadingicon) {
 | 
        
           |  |  | 1404 |             this.loadingicon.hide();
 | 
        
           |  |  | 1405 |         }
 | 
        
           |  |  | 1406 |         drawingcanvas.setStyle('backgroundImage', 'url("' + page.url + '")');
 | 
        
           |  |  | 1407 |         drawingcanvas.setStyle('width', page.width + 'px');
 | 
        
           |  |  | 1408 |         drawingcanvas.setStyle('height', page.height + 'px');
 | 
        
           |  |  | 1409 |         drawingcanvas.scrollIntoView();
 | 
        
           |  |  | 1410 |   | 
        
           |  |  | 1411 |         // Update page select.
 | 
        
           |  |  | 1412 |         this.get_dialogue_element(SELECTOR.PAGESELECT).set('selectedIndex', this.currentpage);
 | 
        
           |  |  | 1413 |   | 
        
           |  |  | 1414 |         this.resize(); // Internally will call 'redraw', after checking the dialogue size.
 | 
        
           |  |  | 1415 |     },
 | 
        
           |  |  | 1416 |   | 
        
           |  |  | 1417 |     /**
 | 
        
           |  |  | 1418 |      * Now we know how many pages there are,
 | 
        
           |  |  | 1419 |      * we can enable the navigation controls.
 | 
        
           |  |  | 1420 |      * @protected
 | 
        
           |  |  | 1421 |      * @method setup_navigation
 | 
        
           |  |  | 1422 |      */
 | 
        
           |  |  | 1423 |     setup_navigation: function() {
 | 
        
           |  |  | 1424 |         var pageselect,
 | 
        
           |  |  | 1425 |             i,
 | 
        
           |  |  | 1426 |             strinfo,
 | 
        
           |  |  | 1427 |             option,
 | 
        
           |  |  | 1428 |             previousbutton,
 | 
        
           |  |  | 1429 |             nextbutton;
 | 
        
           |  |  | 1430 |   | 
        
           |  |  | 1431 |         pageselect = this.get_dialogue_element(SELECTOR.PAGESELECT);
 | 
        
           |  |  | 1432 |   | 
        
           |  |  | 1433 |         var options = pageselect.all('option');
 | 
        
           |  |  | 1434 |         if (options.size() <= 1) {
 | 
        
           |  |  | 1435 |             for (i = 0; i < this.pages.length; i++) {
 | 
        
           |  |  | 1436 |                 option = Y.Node.create('<option/>');
 | 
        
           |  |  | 1437 |                 option.setAttribute('value', i);
 | 
        
           |  |  | 1438 |                 strinfo = {page: i + 1, total: this.pages.length};
 | 
        
           |  |  | 1439 |                 option.setHTML(M.util.get_string('pagexofy', 'assignfeedback_editpdf', strinfo));
 | 
        
           |  |  | 1440 |                 pageselect.append(option);
 | 
        
           |  |  | 1441 |             }
 | 
        
           |  |  | 1442 |         }
 | 
        
           |  |  | 1443 |         pageselect.removeAttribute('disabled');
 | 
        
           |  |  | 1444 |         pageselect.on('change', function() {
 | 
        
           |  |  | 1445 |             this.currentpage = pageselect.get('value');
 | 
        
           |  |  | 1446 |             this.clear_warnings(false);
 | 
        
           |  |  | 1447 |             this.change_page();
 | 
        
           |  |  | 1448 |         }, this);
 | 
        
           |  |  | 1449 |   | 
        
           |  |  | 1450 |         previousbutton = this.get_dialogue_element(SELECTOR.PREVIOUSBUTTON);
 | 
        
           |  |  | 1451 |         nextbutton = this.get_dialogue_element(SELECTOR.NEXTBUTTON);
 | 
        
           |  |  | 1452 |   | 
        
           |  |  | 1453 |         previousbutton.on('click', this.previous_page, this);
 | 
        
           |  |  | 1454 |         previousbutton.on('key', this.previous_page, 'down:13', this);
 | 
        
           |  |  | 1455 |         nextbutton.on('click', this.next_page, this);
 | 
        
           |  |  | 1456 |         nextbutton.on('key', this.next_page, 'down:13', this);
 | 
        
           |  |  | 1457 |     },
 | 
        
           |  |  | 1458 |   | 
        
           |  |  | 1459 |     /**
 | 
        
           |  |  | 1460 |      * Navigate to the previous page.
 | 
        
           |  |  | 1461 |      * @protected
 | 
        
           |  |  | 1462 |      * @method previous_page
 | 
        
           |  |  | 1463 |      */
 | 
        
           |  |  | 1464 |     previous_page: function(e) {
 | 
        
           |  |  | 1465 |         e.preventDefault();
 | 
        
           |  |  | 1466 |         this.currentpage--;
 | 
        
           |  |  | 1467 |         if (this.currentpage < 0) {
 | 
        
           |  |  | 1468 |             this.currentpage = 0;
 | 
        
           |  |  | 1469 |         }
 | 
        
           |  |  | 1470 |         this.clear_warnings(false);
 | 
        
           |  |  | 1471 |         this.change_page();
 | 
        
           |  |  | 1472 |     },
 | 
        
           |  |  | 1473 |   | 
        
           |  |  | 1474 |     /**
 | 
        
           |  |  | 1475 |      * Navigate to the next page.
 | 
        
           |  |  | 1476 |      * @protected
 | 
        
           |  |  | 1477 |      * @method next_page
 | 
        
           |  |  | 1478 |      */
 | 
        
           |  |  | 1479 |     next_page: function(e) {
 | 
        
           |  |  | 1480 |         e.preventDefault();
 | 
        
           |  |  | 1481 |         this.currentpage++;
 | 
        
           |  |  | 1482 |         if (this.currentpage >= this.pages.length) {
 | 
        
           |  |  | 1483 |             this.currentpage = this.pages.length - 1;
 | 
        
           |  |  | 1484 |         }
 | 
        
           |  |  | 1485 |         this.clear_warnings(false);
 | 
        
           |  |  | 1486 |         this.change_page();
 | 
        
           |  |  | 1487 |     },
 | 
        
           |  |  | 1488 |   | 
        
           |  |  | 1489 |     /**
 | 
        
           |  |  | 1490 |      * Update any absolutely positioned nodes, within each drawable, when the drawing canvas is scrolled
 | 
        
           |  |  | 1491 |      * @protected
 | 
        
           |  |  | 1492 |      * @method move_canvas
 | 
        
           |  |  | 1493 |      */
 | 
        
           |  |  | 1494 |     move_canvas: function() {
 | 
        
           |  |  | 1495 |         var drawingregion, x, y, i;
 | 
        
           |  |  | 1496 |   | 
        
           |  |  | 1497 |         drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION);
 | 
        
           |  |  | 1498 |         x = parseInt(drawingregion.get('scrollLeft'), 10);
 | 
        
           |  |  | 1499 |         y = parseInt(drawingregion.get('scrollTop'), 10);
 | 
        
           |  |  | 1500 |   | 
        
           |  |  | 1501 |         for (i = 0; i < this.drawables.length; i++) {
 | 
        
           |  |  | 1502 |             this.drawables[i].scroll_update(x, y);
 | 
        
           |  |  | 1503 |         }
 | 
        
           |  |  | 1504 |     },
 | 
        
           |  |  | 1505 |   | 
        
           |  |  | 1506 |     /**
 | 
        
           |  |  | 1507 |      * Calculate degree to rotate.
 | 
        
           |  |  | 1508 |      * @protected
 | 
        
           |  |  | 1509 |      * @param {Object} e javascript event
 | 
        
           |  |  | 1510 |      * @param {boolean} left  true if rotating left, false if rotating right
 | 
        
           |  |  | 1511 |      * @method rotatepdf
 | 
        
           |  |  | 1512 |      */
 | 
        
           |  |  | 1513 |     rotatePDF: function(e, left) {
 | 
        
           |  |  | 1514 |         e.preventDefault();
 | 
        
           |  |  | 1515 |   | 
        
           |  |  | 1516 |         if (this.get('destroyed')) {
 | 
        
           |  |  | 1517 |             return;
 | 
        
           |  |  | 1518 |         }
 | 
        
           |  |  | 1519 |         var self = this;
 | 
        
           |  |  | 1520 |         // Save old coordinates.
 | 
        
           |  |  | 1521 |         var i;
 | 
        
           |  |  | 1522 |         this.oldannotationcoordinates = [];
 | 
        
           |  |  | 1523 |         var annotations = this.pages[this.currentpage].annotations;
 | 
        
           |  |  | 1524 |         for (i = 0; i < annotations.length; i++) {
 | 
        
           |  |  | 1525 |             var oldannotation = annotations[i];
 | 
        
           |  |  | 1526 |             this.oldannotationcoordinates.push([oldannotation.x, oldannotation.y]);
 | 
        
           |  |  | 1527 |         }
 | 
        
           |  |  | 1528 |   | 
        
           |  |  | 1529 |         var ajaxurl = AJAXBASE;
 | 
        
           |  |  | 1530 |         var config = {
 | 
        
           |  |  | 1531 |             method: 'post',
 | 
        
           |  |  | 1532 |             context: this,
 | 
        
           |  |  | 1533 |             sync: false,
 | 
        
           |  |  | 1534 |             data: {
 | 
        
           |  |  | 1535 |                 'sesskey': M.cfg.sesskey,
 | 
        
           |  |  | 1536 |                 'action': 'rotatepage',
 | 
        
           |  |  | 1537 |                 'index': this.currentpage,
 | 
        
           |  |  | 1538 |                 'userid': this.get('userid'),
 | 
        
           |  |  | 1539 |                 'attemptnumber': this.get('attemptnumber'),
 | 
        
           |  |  | 1540 |                 'assignmentid': this.get('assignmentid'),
 | 
        
           |  |  | 1541 |                 'rotateleft': left
 | 
        
           |  |  | 1542 |             },
 | 
        
           |  |  | 1543 |             on: {
 | 
        
           |  |  | 1544 |                 success: function(tid, response) {
 | 
        
           |  |  | 1545 |                     var jsondata;
 | 
        
           |  |  | 1546 |                     try {
 | 
        
           |  |  | 1547 |                         jsondata = Y.JSON.parse(response.responseText);
 | 
        
           |  |  | 1548 |                         var page = self.pages[self.currentpage];
 | 
        
           |  |  | 1549 |                         page.url = jsondata.page.url;
 | 
        
           |  |  | 1550 |                         page.width = jsondata.page.width;
 | 
        
           |  |  | 1551 |                         page.height = jsondata.page.height;
 | 
        
           |  |  | 1552 |                         self.loadingicon.hide();
 | 
        
           |  |  | 1553 |   | 
        
           |  |  | 1554 |                         // Change canvas size to fix the new page.
 | 
        
           |  |  | 1555 |                         var drawingcanvas = self.get_dialogue_element(SELECTOR.DRAWINGCANVAS);
 | 
        
           |  |  | 1556 |                         drawingcanvas.setStyle('backgroundImage', 'url("' + page.url + '")');
 | 
        
           |  |  | 1557 |                         drawingcanvas.setStyle('width', page.width + 'px');
 | 
        
           |  |  | 1558 |                         drawingcanvas.setStyle('height', page.height + 'px');
 | 
        
           |  |  | 1559 |   | 
        
           |  |  | 1560 |                         /**
 | 
        
           |  |  | 1561 |                          * Move annotation to old position.
 | 
        
           |  |  | 1562 |                          * Reason: When canvas size change
 | 
        
           |  |  | 1563 |                          * > Shape annotations move with relation to canvas coordinates
 | 
        
           |  |  | 1564 |                          * > Nodes of stamp annotations move with relation to canvas coordinates
 | 
        
           |  |  | 1565 |                          * > Presentation (picture) of stamp annotations  stay to document coordinates (stick to its own position)
 | 
        
           |  |  | 1566 |                          * > Without relocating the node and presentation of a stamp annotation to the same x,y position,
 | 
        
           |  |  | 1567 |                          * the stamp annotation cannot be chosen when using "drag" tool.
 | 
        
           |  |  | 1568 |                          * The following code brings all annotations to their old positions with relation to the canvas coordinates.
 | 
        
           |  |  | 1569 |                          */
 | 
        
           |  |  | 1570 |                         var i;
 | 
        
           |  |  | 1571 |                         // Annotations.
 | 
        
           |  |  | 1572 |                         var annotations = page.annotations;
 | 
        
           |  |  | 1573 |                         for (i = 0; i < annotations.length; i++) {
 | 
        
           |  |  | 1574 |                             if (self.oldannotationcoordinates && self.oldannotationcoordinates[i]) {
 | 
        
           |  |  | 1575 |                                 var oldX = self.oldannotationcoordinates[i][0];
 | 
        
           |  |  | 1576 |                                 var oldY = self.oldannotationcoordinates[i][1];
 | 
        
           |  |  | 1577 |                                 var annotation = annotations[i];
 | 
        
           |  |  | 1578 |                                 annotation.move(oldX, oldY);
 | 
        
           |  |  | 1579 |                             }
 | 
        
           |  |  | 1580 |                         }
 | 
        
           |  |  | 1581 |                         /**
 | 
        
           |  |  | 1582 |                          * Update Position of comments with relation to canvas coordinates.
 | 
        
           |  |  | 1583 |                          * Without this code, the comments will stay at their positions in windows/document coordinates.
 | 
        
           |  |  | 1584 |                          */
 | 
        
           |  |  | 1585 |                         var oldcomments = page.comments;
 | 
        
           |  |  | 1586 |                         for (i = 0; i < oldcomments.length; i++) {
 | 
        
           |  |  | 1587 |                             oldcomments[i].updatePosition();
 | 
        
           |  |  | 1588 |                         }
 | 
        
           |  |  | 1589 |                         // Save Annotations.
 | 
        
           |  |  | 1590 |                         return self.save_current_page();
 | 
        
           |  |  | 1591 |                     } catch (e) {
 | 
        
           |  |  | 1592 |                         return new M.core.exception(e);
 | 
        
           |  |  | 1593 |                     }
 | 
        
           |  |  | 1594 |                 },
 | 
        
           |  |  | 1595 |                 failure: function(tid, response) {
 | 
        
           |  |  | 1596 |                     return new M.core.exception(response.responseText);
 | 
        
           |  |  | 1597 |                 }
 | 
        
           |  |  | 1598 |             }
 | 
        
           |  |  | 1599 |         };
 | 
        
           |  |  | 1600 |         Y.io(ajaxurl, config);
 | 
        
           |  |  | 1601 |     },
 | 
        
           |  |  | 1602 |   | 
        
           |  |  | 1603 |     /**
 | 
        
           |  |  | 1604 |      * Test the browser support for options objects on event listeners.
 | 
        
           |  |  | 1605 |      * @return Boolean
 | 
        
           |  |  | 1606 |      */
 | 
        
           |  |  | 1607 |     event_listener_options_supported: function() {
 | 
        
           |  |  | 1608 |         var passivesupported = false,
 | 
        
           |  |  | 1609 |             options,
 | 
        
           |  |  | 1610 |             testeventname = "testpassiveeventoptions";
 | 
        
           |  |  | 1611 |   | 
        
           |  |  | 1612 |         // Options support testing example from:
 | 
        
           |  |  | 1613 |         // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
 | 
        
           |  |  | 1614 |   | 
        
           |  |  | 1615 |         try {
 | 
        
           |  |  | 1616 |             options = Object.defineProperty({}, "passive", {
 | 
        
           |  |  | 1617 |                 // eslint-disable-next-line getter-return
 | 
        
           |  |  | 1618 |                 get: function() {
 | 
        
           |  |  | 1619 |                     passivesupported = true;
 | 
        
           |  |  | 1620 |                 }
 | 
        
           |  |  | 1621 |             });
 | 
        
           |  |  | 1622 |   | 
        
           |  |  | 1623 |             // We use an event name that is not likely to conflict with any real event.
 | 
        
           |  |  | 1624 |             document.addEventListener(testeventname, options, options);
 | 
        
           |  |  | 1625 |             // We remove the event listener as we have tested the options already.
 | 
        
           |  |  | 1626 |             document.removeEventListener(testeventname, options, options);
 | 
        
           |  |  | 1627 |         } catch(err) {
 | 
        
           |  |  | 1628 |             // It's already false.
 | 
        
           |  |  | 1629 |             passivesupported = false;
 | 
        
           |  |  | 1630 |         }
 | 
        
           |  |  | 1631 |         return passivesupported;
 | 
        
           |  |  | 1632 |     },
 | 
        
           |  |  | 1633 |   | 
        
           |  |  | 1634 |     /**
 | 
        
           |  |  | 1635 |      * Disable Touch Move scrolling
 | 
        
           |  |  | 1636 |      */
 | 
        
           |  |  | 1637 |     disable_touch_scroll: function() {
 | 
        
           |  |  | 1638 |         if (this.event_listener_options_supported()) {
 | 
        
           |  |  | 1639 |             document.addEventListener('touchmove', this.stop_touch_scroll.bind(this), {passive: false});
 | 
        
           |  |  | 1640 |         }
 | 
        
           |  |  | 1641 |     },
 | 
        
           |  |  | 1642 |   | 
        
           |  |  | 1643 |     /**
 | 
        
           |  |  | 1644 |      * Stop Touch Scrolling
 | 
        
           |  |  | 1645 |      * @param {Object} e
 | 
        
           |  |  | 1646 |      */
 | 
        
           |  |  | 1647 |     stop_touch_scroll: function(e) {
 | 
        
           |  |  | 1648 |         var drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION);
 | 
        
           |  |  | 1649 |   | 
        
           |  |  | 1650 |         if (drawingregion.contains(e.target)) {
 | 
        
           |  |  | 1651 |             e.stopPropagation();
 | 
        
           |  |  | 1652 |             e.preventDefault();
 | 
        
           |  |  | 1653 |         }
 | 
        
           |  |  | 1654 |     }
 | 
        
           |  |  | 1655 |   | 
        
           |  |  | 1656 | };
 | 
        
           |  |  | 1657 |   | 
        
           |  |  | 1658 | Y.extend(EDITOR, Y.Base, EDITOR.prototype, {
 | 
        
           |  |  | 1659 |     NAME: 'moodle-assignfeedback_editpdf-editor',
 | 
        
           |  |  | 1660 |     ATTRS: {
 | 
        
           |  |  | 1661 |         userid: {
 | 
        
           |  |  | 1662 |             validator: Y.Lang.isInteger,
 | 
        
           |  |  | 1663 |             value: 0
 | 
        
           |  |  | 1664 |         },
 | 
        
           |  |  | 1665 |         assignmentid: {
 | 
        
           |  |  | 1666 |             validator: Y.Lang.isInteger,
 | 
        
           |  |  | 1667 |             value: 0
 | 
        
           |  |  | 1668 |         },
 | 
        
           |  |  | 1669 |         attemptnumber: {
 | 
        
           |  |  | 1670 |             validator: Y.Lang.isInteger,
 | 
        
           |  |  | 1671 |             value: 0
 | 
        
           |  |  | 1672 |         },
 | 
        
           |  |  | 1673 |         header: {
 | 
        
           |  |  | 1674 |             validator: Y.Lang.isString,
 | 
        
           |  |  | 1675 |             value: ''
 | 
        
           |  |  | 1676 |         },
 | 
        
           |  |  | 1677 |         body: {
 | 
        
           |  |  | 1678 |             validator: Y.Lang.isString,
 | 
        
           |  |  | 1679 |             value: ''
 | 
        
           |  |  | 1680 |         },
 | 
        
           |  |  | 1681 |         footer: {
 | 
        
           |  |  | 1682 |             validator: Y.Lang.isString,
 | 
        
           |  |  | 1683 |             value: ''
 | 
        
           |  |  | 1684 |         },
 | 
        
           |  |  | 1685 |         linkid: {
 | 
        
           |  |  | 1686 |             validator: Y.Lang.isString,
 | 
        
           |  |  | 1687 |             value: ''
 | 
        
           |  |  | 1688 |         },
 | 
        
           |  |  | 1689 |         deletelinkid: {
 | 
        
           |  |  | 1690 |             validator: Y.Lang.isString,
 | 
        
           |  |  | 1691 |             value: ''
 | 
        
           |  |  | 1692 |         },
 | 
        
           |  |  | 1693 |         readonly: {
 | 
        
           |  |  | 1694 |             validator: Y.Lang.isBoolean,
 | 
        
           |  |  | 1695 |             value: true
 | 
        
           |  |  | 1696 |         },
 | 
        
           |  |  | 1697 |         stampfiles: {
 | 
        
           |  |  | 1698 |             validator: Y.Lang.isArray,
 | 
        
           |  |  | 1699 |             value: ''
 | 
        
           |  |  | 1700 |         }
 | 
        
           |  |  | 1701 |     }
 | 
        
           |  |  | 1702 | });
 | 
        
           |  |  | 1703 |   | 
        
           |  |  | 1704 | M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
 | 
        
           |  |  | 1705 | M.assignfeedback_editpdf.editor = M.assignfeedback_editpdf.editor || {};
 | 
        
           |  |  | 1706 |   | 
        
           |  |  | 1707 | /**
 | 
        
           |  |  | 1708 |  * Init function - will create a new instance every time.
 | 
        
           |  |  | 1709 |  * @method editor.init
 | 
        
           |  |  | 1710 |  * @static
 | 
        
           |  |  | 1711 |  * @param {Object} params
 | 
        
           |  |  | 1712 |  */
 | 
        
           |  |  | 1713 | M.assignfeedback_editpdf.editor.init = M.assignfeedback_editpdf.editor.init || function(params) {
 | 
        
           |  |  | 1714 |     M.assignfeedback_editpdf.instance = new EDITOR(params);
 | 
        
           |  |  | 1715 |     return M.assignfeedback_editpdf.instance;
 | 
        
           |  |  | 1716 | };
 |