[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/ -> moodle-assignfeedback_editpdf-editor-debug.js (source)

   1  YUI.add('moodle-assignfeedback_editpdf-editor', function (Y, NAME) {
   2  
   3  // This file is part of Moodle - http://moodle.org/
   4  //
   5  // Moodle is free software: you can redistribute it and/or modify
   6  // it under the terms of the GNU General Public License as published by
   7  // the Free Software Foundation, either version 3 of the License, or
   8  // (at your option) any later version.
   9  //
  10  // Moodle is distributed in the hope that it will be useful,
  11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13  // GNU General Public License for more details.
  14  //
  15  // You should have received a copy of the GNU General Public License
  16  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  17  /* eslint-disable no-unused-vars */
  18  
  19  /**
  20   * A list of globals used by this module.
  21   *
  22   * @module moodle-assignfeedback_editpdf-editor
  23   */
  24  var AJAXBASE = M.cfg.wwwroot + '/mod/assign/feedback/editpdf/ajax.php',
  25      AJAXBASEPROGRESS = M.cfg.wwwroot + '/mod/assign/feedback/editpdf/ajax_progress.php',
  26      CSS = {
  27          DIALOGUE: 'assignfeedback_editpdf_widget'
  28      },
  29      SELECTOR = {
  30          PREVIOUSBUTTON:  '.navigate-previous-button',
  31          NEXTBUTTON:  ' .navigate-next-button',
  32          SEARCHCOMMENTSBUTTON: '.searchcommentsbutton',
  33          SEARCHFILTER: '.assignfeedback_editpdf_commentsearch input',
  34          SEARCHCOMMENTSLIST: '.assignfeedback_editpdf_commentsearch ul',
  35          PAGESELECT: '.navigate-page-select',
  36          LOADINGICON: '.loading',
  37          PROGRESSBARCONTAINER: '.progress-info.progress-striped',
  38          DRAWINGREGION: '.drawingregion',
  39          DRAWINGCANVAS: '.drawingcanvas',
  40          SAVE: '.savebutton',
  41          COMMENTCOLOURBUTTON: '.commentcolourbutton',
  42          COMMENTMENU: '.commentdrawable a',
  43          ANNOTATIONCOLOURBUTTON:  '.annotationcolourbutton',
  44          DELETEANNOTATIONBUTTON: '.deleteannotationbutton',
  45          UNSAVEDCHANGESDIV: '.assignfeedback_editpdf_unsavedchanges',
  46          UNSAVEDCHANGESINPUT: 'input[name="assignfeedback_editpdf_haschanges"]',
  47          STAMPSBUTTON: '.currentstampbutton',
  48          DIALOGUE: '.' + CSS.DIALOGUE
  49      },
  50      SELECTEDBORDERCOLOUR = 'rgba(200, 200, 255, 0.9)',
  51      SELECTEDFILLCOLOUR = 'rgba(200, 200, 255, 0.5)',
  52      COMMENTTEXTCOLOUR = 'rgb(51, 51, 51)',
  53      COMMENTCOLOUR = {
  54          'white': 'rgb(255,255,255)',
  55          'yellow': 'rgb(255,236,174)',
  56          'red': 'rgb(249,181,179)',
  57          'green': 'rgb(214,234,178)',
  58          'blue': 'rgb(203,217,237)',
  59          'clear': 'rgba(255,255,255, 0)'
  60      },
  61      ANNOTATIONCOLOUR = {
  62          'white': 'rgb(255,255,255)',
  63          'yellow': 'rgb(255,207,53)',
  64          'red': 'rgb(239,69,64)',
  65          'green': 'rgb(152,202,62)',
  66          'blue': 'rgb(125,159,211)',
  67          'black': 'rgb(51,51,51)'
  68      },
  69      CLICKTIMEOUT = 300,
  70      TOOLSELECTOR = {
  71          'comment': '.commentbutton',
  72          'pen': '.penbutton',
  73          'line': '.linebutton',
  74          'rectangle': '.rectanglebutton',
  75          'oval': '.ovalbutton',
  76          'stamp': '.stampbutton',
  77          'select': '.selectbutton',
  78          'drag': '.dragbutton',
  79          'highlight': '.highlightbutton'
  80      },
  81      STROKEWEIGHT = 4;
  82  // This file is part of Moodle - http://moodle.org/
  83  //
  84  // Moodle is free software: you can redistribute it and/or modify
  85  // it under the terms of the GNU General Public License as published by
  86  // the Free Software Foundation, either version 3 of the License, or
  87  // (at your option) any later version.
  88  //
  89  // Moodle is distributed in the hope that it will be useful,
  90  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  91  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  92  // GNU General Public License for more details.
  93  //
  94  // You should have received a copy of the GNU General Public License
  95  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  96  
  97  /**
  98   * Provides an in browser PDF editor.
  99   *
 100   * @module moodle-assignfeedback_editpdf-editor
 101   */
 102  
 103  /**
 104   * Class representing a 2d point.
 105   *
 106   * @namespace M.assignfeedback_editpdf
 107   * @param Number x
 108   * @param Number y
 109   * @class point
 110   */
 111  var POINT = function(x, y) {
 112  
 113      /**
 114       * X coordinate.
 115       * @property x
 116       * @type int
 117       * @public
 118       */
 119      this.x = parseInt(x, 10);
 120  
 121      /**
 122       * Y coordinate.
 123       * @property y
 124       * @type int
 125       * @public
 126       */
 127      this.y = parseInt(y, 10);
 128  
 129      /**
 130       * Clip this point to the rect
 131       * @method clip
 132       * @param M.assignfeedback_editpdf.point
 133       * @public
 134       */
 135      this.clip = function(bounds) {
 136          if (this.x < bounds.x) {
 137              this.x = bounds.x;
 138          }
 139          if (this.x > (bounds.x + bounds.width)) {
 140              this.x = bounds.x + bounds.width;
 141          }
 142          if (this.y < bounds.y) {
 143              this.y = bounds.y;
 144          }
 145          if (this.y > (bounds.y + bounds.height)) {
 146              this.y = bounds.y + bounds.height;
 147          }
 148          // For chaining.
 149          return this;
 150      };
 151  };
 152  
 153  M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
 154  M.assignfeedback_editpdf.point = POINT;
 155  // This file is part of Moodle - http://moodle.org/
 156  //
 157  // Moodle is free software: you can redistribute it and/or modify
 158  // it under the terms of the GNU General Public License as published by
 159  // the Free Software Foundation, either version 3 of the License, or
 160  // (at your option) any later version.
 161  //
 162  // Moodle is distributed in the hope that it will be useful,
 163  // but WITHOUT ANY WARRANTY; without even the implied warranty of
 164  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 165  // GNU General Public License for more details.
 166  //
 167  // You should have received a copy of the GNU General Public License
 168  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 169  
 170  /**
 171   * Provides an in browser PDF editor.
 172   *
 173   * @module moodle-assignfeedback_editpdf-editor
 174   */
 175  
 176  /**
 177   * Class representing a 2d rect.
 178   *
 179   * @namespace M.assignfeedback_editpdf
 180   * @param int x
 181   * @param int y
 182   * @param int width
 183   * @param int height
 184   * @class rect
 185   */
 186  var RECT = function(x, y, width, height) {
 187  
 188      /**
 189       * X coordinate.
 190       * @property x
 191       * @type int
 192       * @public
 193       */
 194      this.x = x;
 195  
 196      /**
 197       * Y coordinate.
 198       * @property y
 199       * @type int
 200       * @public
 201       */
 202      this.y = y;
 203  
 204      /**
 205       * Width
 206       * @property width
 207       * @type int
 208       * @public
 209       */
 210      this.width = width;
 211  
 212      /**
 213       * Height
 214       * @property height
 215       * @type int
 216       * @public
 217       */
 218      this.height = height;
 219  
 220      /**
 221       * Set this rect to represent the smallest possible rectangle containing this list of points.
 222       * @method bounds
 223       * @param M.assignfeedback_editpdf.point[]
 224       * @public
 225       */
 226      this.bound = function(points) {
 227          var minx = 0,
 228              maxx = 0,
 229              miny = 0,
 230              maxy = 0,
 231              i = 0,
 232              point;
 233  
 234          for (i = 0; i < points.length; i++) {
 235              point = points[i];
 236              if (point.x < minx || i === 0) {
 237                  minx = point.x;
 238              }
 239              if (point.x > maxx || i === 0) {
 240                  maxx = point.x;
 241              }
 242              if (point.y < miny || i === 0) {
 243                  miny = point.y;
 244              }
 245              if (point.y > maxy || i === 0) {
 246                  maxy = point.y;
 247              }
 248          }
 249          this.x = minx;
 250          this.y = miny;
 251          this.width = maxx - minx;
 252          this.height = maxy - miny;
 253          // Allow chaining.
 254          return this;
 255      };
 256  
 257      /**
 258       * Checks if rect has min width.
 259       * @method has_min_width
 260       * @return bool true if width is more than 5px.
 261       * @public
 262       */
 263      this.has_min_width = function() {
 264          return (this.width >= 5);
 265      };
 266  
 267      /**
 268       * Checks if rect has min height.
 269       * @method has_min_height
 270       * @return bool true if height is more than 5px.
 271       * @public
 272       */
 273      this.has_min_height = function() {
 274          return (this.height >= 5);
 275      };
 276  
 277      /**
 278       * Set min. width of annotation bound.
 279       * @method set_min_width
 280       * @public
 281       */
 282      this.set_min_width = function() {
 283          this.width = 5;
 284      };
 285  
 286      /**
 287       * Set min. height of annotation bound.
 288       * @method set_min_height
 289       * @public
 290       */
 291      this.set_min_height = function() {
 292          this.height = 5;
 293      };
 294  };
 295  
 296  M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
 297  M.assignfeedback_editpdf.rect = RECT;
 298  // This file is part of Moodle - http://moodle.org/
 299  //
 300  // Moodle is free software: you can redistribute it and/or modify
 301  // it under the terms of the GNU General Public License as published by
 302  // the Free Software Foundation, either version 3 of the License, or
 303  // (at your option) any later version.
 304  //
 305  // Moodle is distributed in the hope that it will be useful,
 306  // but WITHOUT ANY WARRANTY; without even the implied warranty of
 307  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 308  // GNU General Public License for more details.
 309  //
 310  // You should have received a copy of the GNU General Public License
 311  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 312  
 313  /**
 314   * Provides an in browser PDF editor.
 315   *
 316   * @module moodle-assignfeedback_editpdf-editor
 317   */
 318  
 319  /**
 320   * EDIT
 321   *
 322   * @namespace M.assignfeedback_editpdf
 323   * @class edit
 324   */
 325  var EDIT = function() {
 326  
 327      /**
 328       * Starting point for the edit.
 329       * @property start
 330       * @type M.assignfeedback_editpdf.point|false
 331       * @public
 332       */
 333      this.start = false;
 334  
 335      /**
 336       * Finishing point for the edit.
 337       * @property end
 338       * @type M.assignfeedback_editpdf.point|false
 339       * @public
 340       */
 341      this.end = false;
 342  
 343      /**
 344       * Starting time for the edit.
 345       * @property starttime
 346       * @type int
 347       * @public
 348       */
 349      this.starttime = 0;
 350  
 351      /**
 352       * Starting point for the currently selected annotation.
 353       * @property annotationstart
 354       * @type M.assignfeedback_editpdf.point|false
 355       * @public
 356       */
 357      this.annotationstart = false;
 358  
 359      /**
 360       * The currently selected tool
 361       * @property tool
 362       * @type String
 363       * @public
 364       */
 365      this.tool = "drag";
 366  
 367      /**
 368       * The currently comment colour
 369       * @property commentcolour
 370       * @type String
 371       * @public
 372       */
 373      this.commentcolour = 'yellow';
 374  
 375      /**
 376       * The currently annotation colour
 377       * @property annotationcolour
 378       * @type String
 379       * @public
 380       */
 381      this.annotationcolour = 'red';
 382  
 383      /**
 384       * The current stamp image.
 385       * @property stamp
 386       * @type String
 387       * @public
 388       */
 389      this.stamp = '';
 390  
 391      /**
 392       * List of points the the current drawing path.
 393       * @property path
 394       * @type M.assignfeedback_editpdf.point[]
 395       * @public
 396       */
 397      this.path = [];
 398  };
 399  
 400  M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
 401  M.assignfeedback_editpdf.edit = EDIT;
 402  // This file is part of Moodle - http://moodle.org/
 403  //
 404  // Moodle is free software: you can redistribute it and/or modify
 405  // it under the terms of the GNU General Public License as published by
 406  // the Free Software Foundation, either version 3 of the License, or
 407  // (at your option) any later version.
 408  //
 409  // Moodle is distributed in the hope that it will be useful,
 410  // but WITHOUT ANY WARRANTY; without even the implied warranty of
 411  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 412  // GNU General Public License for more details.
 413  //
 414  // You should have received a copy of the GNU General Public License
 415  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 416  /* global SELECTOR */
 417  
 418  /**
 419   * Provides an in browser PDF editor.
 420   *
 421   * @module moodle-assignfeedback_editpdf-editor
 422   */
 423  
 424  /**
 425   * Class representing a drawable thing which contains both Y.Nodes, and Y.Shapes.
 426   *
 427   * @namespace M.assignfeedback_editpdf
 428   * @param M.assignfeedback_editpdf.editor editor
 429   * @class drawable
 430   */
 431  var DRAWABLE = function(editor) {
 432  
 433      /**
 434       * Reference to M.assignfeedback_editpdf.editor.
 435       * @property editor
 436       * @type M.assignfeedback_editpdf.editor
 437       * @public
 438       */
 439      this.editor = editor;
 440  
 441      /**
 442       * Array of Y.Shape
 443       * @property shapes
 444       * @type Y.Shape[]
 445       * @public
 446       */
 447      this.shapes = [];
 448  
 449      /**
 450       * Array of Y.Node
 451       * @property nodes
 452       * @type Y.Node[]
 453       * @public
 454       */
 455      this.nodes = [];
 456  
 457      /**
 458       * Delete the shapes from the drawable.
 459       * @protected
 460       * @method erase_drawable
 461       */
 462      this.erase = function() {
 463          if (this.shapes) {
 464              while (this.shapes.length > 0) {
 465                  this.editor.graphic.removeShape(this.shapes.pop());
 466              }
 467          }
 468          if (this.nodes) {
 469              while (this.nodes.length > 0) {
 470                  this.nodes.pop().remove();
 471              }
 472          }
 473      };
 474  
 475      /**
 476       * Update the positions of all absolutely positioned nodes, when the drawing canvas is scrolled
 477       * @public
 478       * @method scroll_update
 479       * @param scrollx int
 480       * @param scrolly int
 481       */
 482      this.scroll_update = function(scrollx, scrolly) {
 483          var i, x, y;
 484          for (i = 0; i < this.nodes.length; i++) {
 485              x = this.nodes[i].getData('x');
 486              y = this.nodes[i].getData('y');
 487              if (x !== undefined && y !== undefined) {
 488                  this.nodes[i].setX(parseInt(x, 10) - scrollx);
 489                  this.nodes[i].setY(parseInt(y, 10) - scrolly);
 490              }
 491          }
 492      };
 493  
 494      /**
 495       * Store the initial position of the node, so it can be updated when the drawing canvas is scrolled
 496       * @public
 497       * @method store_position
 498       * @param container
 499       * @param x
 500       * @param y
 501       */
 502      this.store_position = function(container, x, y) {
 503          var drawingregion, scrollx, scrolly;
 504  
 505          drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION);
 506          scrollx = parseInt(drawingregion.get('scrollLeft'), 10);
 507          scrolly = parseInt(drawingregion.get('scrollTop'), 10);
 508          container.setData('x', x + scrollx);
 509          container.setData('y', y + scrolly);
 510      };
 511  };
 512  
 513  M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
 514  M.assignfeedback_editpdf.drawable = DRAWABLE;
 515  // This file is part of Moodle - http://moodle.org/
 516  //
 517  // Moodle is free software: you can redistribute it and/or modify
 518  // it under the terms of the GNU General Public License as published by
 519  // the Free Software Foundation, either version 3 of the License, or
 520  // (at your option) any later version.
 521  //
 522  // Moodle is distributed in the hope that it will be useful,
 523  // but WITHOUT ANY WARRANTY; without even the implied warranty of
 524  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 525  // GNU General Public License for more details.
 526  //
 527  // You should have received a copy of the GNU General Public License
 528  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 529  /* global STROKEWEIGHT, SELECTOR, SELECTEDBORDERCOLOUR, SELECTEDFILLCOLOUR */
 530  
 531  /**
 532   * Provides an in browser PDF editor.
 533   *
 534   * @module moodle-assignfeedback_editpdf-editor
 535   */
 536  
 537  /**
 538   * Class representing a highlight.
 539   *
 540   * @namespace M.assignfeedback_editpdf
 541   * @class annotation
 542   * @constructor
 543   */
 544  var ANNOTATION = function(config) {
 545      ANNOTATION.superclass.constructor.apply(this, [config]);
 546  };
 547  
 548  ANNOTATION.NAME = "annotation";
 549  ANNOTATION.ATTRS = {};
 550  
 551  Y.extend(ANNOTATION, Y.Base, {
 552      /**
 553       * Reference to M.assignfeedback_editpdf.editor.
 554       * @property editor
 555       * @type M.assignfeedback_editpdf.editor
 556       * @public
 557       */
 558      editor: null,
 559  
 560      /**
 561       * Grade id
 562       * @property gradeid
 563       * @type Int
 564       * @public
 565       */
 566      gradeid: 0,
 567  
 568      /**
 569       * Comment page number
 570       * @property pageno
 571       * @type Int
 572       * @public
 573       */
 574      pageno: 0,
 575  
 576      /**
 577       * X position
 578       * @property x
 579       * @type Int
 580       * @public
 581       */
 582      x: 0,
 583  
 584      /**
 585       * Y position
 586       * @property y
 587       * @type Int
 588       * @public
 589       */
 590      y: 0,
 591  
 592      /**
 593       * Ending x position
 594       * @property endx
 595       * @type Int
 596       * @public
 597       */
 598      endx: 0,
 599  
 600      /**
 601       * Ending y position
 602       * @property endy
 603       * @type Int
 604       * @public
 605       */
 606      endy: 0,
 607  
 608      /**
 609       * Path
 610       * @property path
 611       * @type String - list of points like x1,y1:x2,y2
 612       * @public
 613       */
 614      path: '',
 615  
 616      /**
 617       * Tool.
 618       * @property type
 619       * @type String
 620       * @public
 621       */
 622      type: 'rect',
 623  
 624      /**
 625       * Annotation colour.
 626       * @property colour
 627       * @type String
 628       * @public
 629       */
 630      colour: 'red',
 631  
 632      /**
 633       * Reference to M.assignfeedback_editpdf.drawable
 634       * @property drawable
 635       * @type M.assignfeedback_editpdf.drawable
 636       * @public
 637       */
 638      drawable: false,
 639  
 640      /**
 641       * Initialise the annotation.
 642       *
 643       * @method initializer
 644       * @return void
 645       */
 646      initializer: function(config) {
 647          this.editor = config.editor || null;
 648          this.gradeid = parseInt(config.gradeid, 10) || 0;
 649          this.pageno = parseInt(config.pageno, 10) || 0;
 650          this.x = parseInt(config.x, 10) || 0;
 651          this.y = parseInt(config.y, 10) || 0;
 652          this.endx = parseInt(config.endx, 10) || 0;
 653          this.endy = parseInt(config.endy, 10) || 0;
 654          this.path = config.path || '';
 655          this.type = config.type || 'rect';
 656          this.colour = config.colour || 'red';
 657          this.drawable = false;
 658      },
 659  
 660      /**
 661       * Clean a comment record, returning an oject with only fields that are valid.
 662       * @public
 663       * @method clean
 664       * @return {}
 665       */
 666      clean: function() {
 667          return {
 668              gradeid: this.gradeid,
 669              x: parseInt(this.x, 10),
 670              y: parseInt(this.y, 10),
 671              endx: parseInt(this.endx, 10),
 672              endy: parseInt(this.endy, 10),
 673              type: this.type,
 674              path: this.path,
 675              pageno: this.pageno,
 676              colour: this.colour
 677          };
 678      },
 679  
 680      /**
 681       * Draw a selection around this annotation if it is selected.
 682       * @public
 683       * @method draw_highlight
 684       * @return M.assignfeedback_editpdf.drawable
 685       */
 686      draw_highlight: function() {
 687          var bounds,
 688              drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION),
 689              offsetcanvas = this.editor.get_dialogue_element(SELECTOR.DRAWINGCANVAS).getXY(),
 690              shape;
 691  
 692          if (this.editor.currentannotation === this) {
 693              // Draw a highlight around the annotation.
 694              bounds = new M.assignfeedback_editpdf.rect();
 695              bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y),
 696                            new M.assignfeedback_editpdf.point(this.endx, this.endy)]);
 697  
 698              shape = this.editor.graphic.addShape({
 699                  type: Y.Rect,
 700                  width: bounds.width,
 701                  height: bounds.height,
 702                  stroke: {
 703                     weight: STROKEWEIGHT,
 704                     color: SELECTEDBORDERCOLOUR
 705                  },
 706                  fill: {
 707                     color: SELECTEDFILLCOLOUR
 708                  },
 709                  x: bounds.x,
 710                  y: bounds.y
 711              });
 712              this.drawable.shapes.push(shape);
 713  
 714              // Add a delete X to the annotation.
 715              var deleteicon = Y.Node.create('<img src="' + M.util.image_url('trash', 'assignfeedback_editpdf') + '"/>'),
 716                  deletelink = Y.Node.create('<a href="#" role="button"></a>');
 717  
 718              deleteicon.setAttrs({
 719                  'alt': M.util.get_string('deleteannotation', 'assignfeedback_editpdf')
 720              });
 721              deleteicon.setStyles({
 722                  'backgroundColor': 'white'
 723              });
 724              deletelink.addClass('deleteannotationbutton');
 725              deletelink.append(deleteicon);
 726  
 727              drawingregion.append(deletelink);
 728              deletelink.setData('annotation', this);
 729              deletelink.setStyle('zIndex', '200');
 730  
 731              deletelink.on('click', this.remove, this);
 732              deletelink.on('key', this.remove, 'space,enter', this);
 733  
 734              deletelink.setX(offsetcanvas[0] + bounds.x + bounds.width - 18);
 735              deletelink.setY(offsetcanvas[1] + bounds.y + 6);
 736              this.drawable.nodes.push(deletelink);
 737          }
 738          return this.drawable;
 739      },
 740  
 741      /**
 742       * Draw an annotation
 743       * @public
 744       * @method draw
 745       * @return M.assignfeedback_editpdf.drawable|false
 746       */
 747      draw: function() {
 748          // Should be overridden by the subclass.
 749          this.draw_highlight();
 750          return this.drawable;
 751      },
 752  
 753      /**
 754       * Delete an annotation
 755       * @protected
 756       * @method remove
 757       * @param event
 758       */
 759      remove: function(e) {
 760          var annotations,
 761              i;
 762  
 763          e.preventDefault();
 764  
 765          annotations = this.editor.pages[this.editor.currentpage].annotations;
 766          for (i = 0; i < annotations.length; i++) {
 767              if (annotations[i] === this) {
 768                  annotations.splice(i, 1);
 769                  if (this.drawable) {
 770                      this.drawable.erase();
 771                  }
 772                  this.editor.currentannotation = false;
 773                  this.editor.save_current_page();
 774                  return;
 775              }
 776          }
 777      },
 778  
 779      /**
 780       * Move an annotation to a new location.
 781       * @public
 782       * @param int newx
 783       * @param int newy
 784       * @method move_annotation
 785       */
 786      move: function(newx, newy) {
 787          var diffx = newx - this.x,
 788              diffy = newy - this.y,
 789              newpath, oldpath, xy,
 790              x, y;
 791  
 792          this.x += diffx;
 793          this.y += diffy;
 794          this.endx += diffx;
 795          this.endy += diffy;
 796  
 797          if (this.path) {
 798              newpath = [];
 799              oldpath = this.path.split(':');
 800              Y.each(oldpath, function(position) {
 801                  xy = position.split(',');
 802                  x = parseInt(xy[0], 10);
 803                  y = parseInt(xy[1], 10);
 804                  newpath.push((x + diffx) + ',' + (y + diffy));
 805              });
 806  
 807              this.path = newpath.join(':');
 808  
 809          }
 810          if (this.drawable) {
 811              this.drawable.erase();
 812          }
 813          this.editor.drawables.push(this.draw());
 814      },
 815  
 816      /**
 817       * Draw the in progress edit.
 818       *
 819       * @public
 820       * @method draw_current_edit
 821       * @param M.assignfeedback_editpdf.edit edit
 822       */
 823      draw_current_edit: function(edit) {
 824          var noop = edit && false;
 825          // Override me please.
 826          return noop;
 827      },
 828  
 829      /**
 830       * Promote the current edit to a real annotation.
 831       *
 832       * @public
 833       * @method init_from_edit
 834       * @param M.assignfeedback_editpdf.edit edit
 835       * @return bool if width/height is more than min. required.
 836       */
 837      init_from_edit: function(edit) {
 838          var bounds = new M.assignfeedback_editpdf.rect();
 839          bounds.bound([edit.start, edit.end]);
 840  
 841          this.gradeid = this.editor.get('gradeid');
 842          this.pageno = this.editor.currentpage;
 843          this.x = bounds.x;
 844          this.y = bounds.y;
 845          this.endx = bounds.x + bounds.width;
 846          this.endy = bounds.y + bounds.height;
 847          this.colour = edit.annotationcolour;
 848          this.path = '';
 849          return (bounds.has_min_width() && bounds.has_min_height());
 850      }
 851  
 852  });
 853  
 854  M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
 855  M.assignfeedback_editpdf.annotation = ANNOTATION;
 856  // This file is part of Moodle - http://moodle.org/
 857  //
 858  // Moodle is free software: you can redistribute it and/or modify
 859  // it under the terms of the GNU General Public License as published by
 860  // the Free Software Foundation, either version 3 of the License, or
 861  // (at your option) any later version.
 862  //
 863  // Moodle is distributed in the hope that it will be useful,
 864  // but WITHOUT ANY WARRANTY; without even the implied warranty of
 865  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 866  // GNU General Public License for more details.
 867  //
 868  // You should have received a copy of the GNU General Public License
 869  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 870  /* global STROKEWEIGHT, ANNOTATIONCOLOUR */
 871  
 872  /**
 873   * Provides an in browser PDF editor.
 874   *
 875   * @module moodle-assignfeedback_editpdf-editor
 876   */
 877  
 878  /**
 879   * Class representing a line.
 880   *
 881   * @namespace M.assignfeedback_editpdf
 882   * @class annotationline
 883   * @extends M.assignfeedback_editpdf.annotation
 884   */
 885  var ANNOTATIONLINE = function(config) {
 886      ANNOTATIONLINE.superclass.constructor.apply(this, [config]);
 887  };
 888  
 889  ANNOTATIONLINE.NAME = "annotationline";
 890  ANNOTATIONLINE.ATTRS = {};
 891  
 892  Y.extend(ANNOTATIONLINE, M.assignfeedback_editpdf.annotation, {
 893      /**
 894       * Draw a line annotation
 895       * @protected
 896       * @method draw
 897       * @return M.assignfeedback_editpdf.drawable
 898       */
 899      draw: function() {
 900          var drawable,
 901              shape;
 902  
 903          drawable = new M.assignfeedback_editpdf.drawable(this.editor);
 904  
 905          shape = this.editor.graphic.addShape({
 906          type: Y.Path,
 907              fill: false,
 908              stroke: {
 909                  weight: STROKEWEIGHT,
 910                  color: ANNOTATIONCOLOUR[this.colour]
 911              }
 912          });
 913  
 914          shape.moveTo(this.x, this.y);
 915          shape.lineTo(this.endx, this.endy);
 916          shape.end();
 917          drawable.shapes.push(shape);
 918          this.drawable = drawable;
 919  
 920          return ANNOTATIONLINE.superclass.draw.apply(this);
 921      },
 922  
 923      /**
 924       * Draw the in progress edit.
 925       *
 926       * @public
 927       * @method draw_current_edit
 928       * @param M.assignfeedback_editpdf.edit edit
 929       */
 930      draw_current_edit: function(edit) {
 931          var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
 932              shape;
 933  
 934          shape = this.editor.graphic.addShape({
 935             type: Y.Path,
 936              fill: false,
 937              stroke: {
 938                  weight: STROKEWEIGHT,
 939                  color: ANNOTATIONCOLOUR[edit.annotationcolour]
 940              }
 941          });
 942  
 943          shape.moveTo(edit.start.x, edit.start.y);
 944          shape.lineTo(edit.end.x, edit.end.y);
 945          shape.end();
 946  
 947          drawable.shapes.push(shape);
 948  
 949          return drawable;
 950      },
 951  
 952      /**
 953       * Promote the current edit to a real annotation.
 954       *
 955       * @public
 956       * @method init_from_edit
 957       * @param M.assignfeedback_editpdf.edit edit
 958       * @return bool true if line bound is more than min width/height, else false.
 959       */
 960      init_from_edit: function(edit) {
 961          this.gradeid = this.editor.get('gradeid');
 962          this.pageno = this.editor.currentpage;
 963          this.x = edit.start.x;
 964          this.y = edit.start.y;
 965          this.endx = edit.end.x;
 966          this.endy = edit.end.y;
 967          this.colour = edit.annotationcolour;
 968          this.path = '';
 969  
 970          return !(((this.endx - this.x) === 0) && ((this.endy - this.y) === 0));
 971      }
 972  
 973  });
 974  
 975  M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
 976  M.assignfeedback_editpdf.annotationline = ANNOTATIONLINE;
 977  // This file is part of Moodle - http://moodle.org/
 978  //
 979  // Moodle is free software: you can redistribute it and/or modify
 980  // it under the terms of the GNU General Public License as published by
 981  // the Free Software Foundation, either version 3 of the License, or
 982  // (at your option) any later version.
 983  //
 984  // Moodle is distributed in the hope that it will be useful,
 985  // but WITHOUT ANY WARRANTY; without even the implied warranty of
 986  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 987  // GNU General Public License for more details.
 988  //
 989  // You should have received a copy of the GNU General Public License
 990  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 991  /* global STROKEWEIGHT, ANNOTATIONCOLOUR */
 992  
 993  /**
 994   * Provides an in browser PDF editor.
 995   *
 996   * @module moodle-assignfeedback_editpdf-editor
 997   */
 998  
 999  /**
1000   * Class representing a rectangle.
1001   *
1002   * @namespace M.assignfeedback_editpdf
1003   * @class annotationrectangle
1004   * @extends M.assignfeedback_editpdf.annotation
1005   */
1006  var ANNOTATIONRECTANGLE = function(config) {
1007      ANNOTATIONRECTANGLE.superclass.constructor.apply(this, [config]);
1008  };
1009  
1010  ANNOTATIONRECTANGLE.NAME = "annotationrectangle";
1011  ANNOTATIONRECTANGLE.ATTRS = {};
1012  
1013  Y.extend(ANNOTATIONRECTANGLE, M.assignfeedback_editpdf.annotation, {
1014      /**
1015       * Draw a rectangle annotation
1016       * @protected
1017       * @method draw
1018       * @return M.assignfeedback_editpdf.drawable
1019       */
1020      draw: function() {
1021          var drawable,
1022              bounds,
1023              shape;
1024  
1025          drawable = new M.assignfeedback_editpdf.drawable(this.editor);
1026  
1027          bounds = new M.assignfeedback_editpdf.rect();
1028          bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y),
1029                        new M.assignfeedback_editpdf.point(this.endx, this.endy)]);
1030  
1031          shape = this.editor.graphic.addShape({
1032              type: Y.Rect,
1033              width: bounds.width,
1034              height: bounds.height,
1035              stroke: {
1036                 weight: STROKEWEIGHT,
1037                 color: ANNOTATIONCOLOUR[this.colour]
1038              },
1039              x: bounds.x,
1040              y: bounds.y
1041          });
1042          drawable.shapes.push(shape);
1043          this.drawable = drawable;
1044  
1045          return ANNOTATIONRECTANGLE.superclass.draw.apply(this);
1046      },
1047  
1048      /**
1049       * Draw the in progress edit.
1050       *
1051       * @public
1052       * @method draw_current_edit
1053       * @param M.assignfeedback_editpdf.edit edit
1054       */
1055      draw_current_edit: function(edit) {
1056          var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
1057              shape,
1058              bounds;
1059  
1060          bounds = new M.assignfeedback_editpdf.rect();
1061          bounds.bound([new M.assignfeedback_editpdf.point(edit.start.x, edit.start.y),
1062                        new M.assignfeedback_editpdf.point(edit.end.x, edit.end.y)]);
1063  
1064          // Set min. width and height of rectangle.
1065          if (!bounds.has_min_width()) {
1066              bounds.set_min_width();
1067          }
1068          if (!bounds.has_min_height()) {
1069              bounds.set_min_height();
1070          }
1071  
1072          shape = this.editor.graphic.addShape({
1073              type: Y.Rect,
1074              width: bounds.width,
1075              height: bounds.height,
1076              stroke: {
1077                 weight: STROKEWEIGHT,
1078                 color: ANNOTATIONCOLOUR[edit.annotationcolour]
1079              },
1080              x: bounds.x,
1081              y: bounds.y
1082          });
1083  
1084          drawable.shapes.push(shape);
1085  
1086          return drawable;
1087      }
1088  });
1089  
1090  M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1091  M.assignfeedback_editpdf.annotationrectangle = ANNOTATIONRECTANGLE;
1092  // This file is part of Moodle - http://moodle.org/
1093  //
1094  // Moodle is free software: you can redistribute it and/or modify
1095  // it under the terms of the GNU General Public License as published by
1096  // the Free Software Foundation, either version 3 of the License, or
1097  // (at your option) any later version.
1098  //
1099  // Moodle is distributed in the hope that it will be useful,
1100  // but WITHOUT ANY WARRANTY; without even the implied warranty of
1101  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1102  // GNU General Public License for more details.
1103  //
1104  // You should have received a copy of the GNU General Public License
1105  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
1106  /* global STROKEWEIGHT, ANNOTATIONCOLOUR */
1107  
1108  /**
1109   * Provides an in browser PDF editor.
1110   *
1111   * @module moodle-assignfeedback_editpdf-editor
1112   */
1113  
1114  /**
1115   * Class representing a oval.
1116   *
1117   * @namespace M.assignfeedback_editpdf
1118   * @class annotationoval
1119   * @extends M.assignfeedback_editpdf.annotation
1120   */
1121  var ANNOTATIONOVAL = function(config) {
1122      ANNOTATIONOVAL.superclass.constructor.apply(this, [config]);
1123  };
1124  
1125  ANNOTATIONOVAL.NAME = "annotationoval";
1126  ANNOTATIONOVAL.ATTRS = {};
1127  
1128  Y.extend(ANNOTATIONOVAL, M.assignfeedback_editpdf.annotation, {
1129      /**
1130       * Draw a oval annotation
1131       * @protected
1132       * @method draw
1133       * @return M.assignfeedback_editpdf.drawable
1134       */
1135      draw: function() {
1136          var drawable,
1137              bounds,
1138              shape;
1139  
1140          drawable = new M.assignfeedback_editpdf.drawable(this.editor);
1141  
1142          bounds = new M.assignfeedback_editpdf.rect();
1143          bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y),
1144                        new M.assignfeedback_editpdf.point(this.endx, this.endy)]);
1145  
1146          shape = this.editor.graphic.addShape({
1147              type: Y.Ellipse,
1148              width: bounds.width,
1149              height: bounds.height,
1150              stroke: {
1151                 weight: STROKEWEIGHT,
1152                 color: ANNOTATIONCOLOUR[this.colour]
1153              },
1154              x: bounds.x,
1155              y: bounds.y
1156          });
1157          drawable.shapes.push(shape);
1158          this.drawable = drawable;
1159  
1160          return ANNOTATIONOVAL.superclass.draw.apply(this);
1161      },
1162  
1163      /**
1164       * Draw the in progress edit.
1165       *
1166       * @public
1167       * @method draw_current_edit
1168       * @param M.assignfeedback_editpdf.edit edit
1169       */
1170      draw_current_edit: function(edit) {
1171          var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
1172              shape,
1173              bounds;
1174  
1175          bounds = new M.assignfeedback_editpdf.rect();
1176          bounds.bound([new M.assignfeedback_editpdf.point(edit.start.x, edit.start.y),
1177                        new M.assignfeedback_editpdf.point(edit.end.x, edit.end.y)]);
1178  
1179          // Set min. width and height of oval.
1180          if (!bounds.has_min_width()) {
1181              bounds.set_min_width();
1182          }
1183          if (!bounds.has_min_height()) {
1184              bounds.set_min_height();
1185          }
1186  
1187          shape = this.editor.graphic.addShape({
1188              type: Y.Ellipse,
1189              width: bounds.width,
1190              height: bounds.height,
1191              stroke: {
1192                 weight: STROKEWEIGHT,
1193                 color: ANNOTATIONCOLOUR[edit.annotationcolour]
1194              },
1195              x: bounds.x,
1196              y: bounds.y
1197          });
1198  
1199          drawable.shapes.push(shape);
1200  
1201          return drawable;
1202      }
1203  });
1204  
1205  M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1206  M.assignfeedback_editpdf.annotationoval = ANNOTATIONOVAL;
1207  // This file is part of Moodle - http://moodle.org/
1208  //
1209  // Moodle is free software: you can redistribute it and/or modify
1210  // it under the terms of the GNU General Public License as published by
1211  // the Free Software Foundation, either version 3 of the License, or
1212  // (at your option) any later version.
1213  //
1214  // Moodle is distributed in the hope that it will be useful,
1215  // but WITHOUT ANY WARRANTY; without even the implied warranty of
1216  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1217  // GNU General Public License for more details.
1218  //
1219  // You should have received a copy of the GNU General Public License
1220  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
1221  /* global STROKEWEIGHT, ANNOTATIONCOLOUR */
1222  
1223  /**
1224   * Provides an in browser PDF editor.
1225   *
1226   * @module moodle-assignfeedback_editpdf-editor
1227   */
1228  
1229  /**
1230   * Class representing a pen.
1231   *
1232   * @namespace M.assignfeedback_editpdf
1233   * @class annotationpen
1234   * @extends M.assignfeedback_editpdf.annotation
1235   */
1236  var ANNOTATIONPEN = function(config) {
1237      ANNOTATIONPEN.superclass.constructor.apply(this, [config]);
1238  };
1239  
1240  ANNOTATIONPEN.NAME = "annotationpen";
1241  ANNOTATIONPEN.ATTRS = {};
1242  
1243  Y.extend(ANNOTATIONPEN, M.assignfeedback_editpdf.annotation, {
1244      /**
1245       * Draw a pen annotation
1246       * @protected
1247       * @method draw
1248       * @return M.assignfeedback_editpdf.drawable
1249       */
1250      draw: function() {
1251          var drawable,
1252              shape,
1253              first,
1254              positions,
1255              xy;
1256  
1257          drawable = new M.assignfeedback_editpdf.drawable(this.editor);
1258  
1259          shape = this.editor.graphic.addShape({
1260             type: Y.Path,
1261              fill: false,
1262              stroke: {
1263                  weight: STROKEWEIGHT,
1264                  color: ANNOTATIONCOLOUR[this.colour]
1265              }
1266          });
1267  
1268          first = true;
1269          // Recreate the pen path array.
1270          positions = this.path.split(':');
1271          // Redraw all the lines.
1272          Y.each(positions, function(position) {
1273              xy = position.split(',');
1274              if (first) {
1275                  shape.moveTo(xy[0], xy[1]);
1276                  first = false;
1277              } else {
1278                  shape.lineTo(xy[0], xy[1]);
1279              }
1280          }, this);
1281  
1282          shape.end();
1283  
1284          drawable.shapes.push(shape);
1285          this.drawable = drawable;
1286  
1287          return ANNOTATIONPEN.superclass.draw.apply(this);
1288      },
1289  
1290      /**
1291       * Draw the in progress edit.
1292       *
1293       * @public
1294       * @method draw_current_edit
1295       * @param M.assignfeedback_editpdf.edit edit
1296       */
1297      draw_current_edit: function(edit) {
1298          var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
1299              shape,
1300              first;
1301  
1302          shape = this.editor.graphic.addShape({
1303             type: Y.Path,
1304              fill: false,
1305              stroke: {
1306                  weight: STROKEWEIGHT,
1307                  color: ANNOTATIONCOLOUR[edit.annotationcolour]
1308              }
1309          });
1310  
1311          first = true;
1312          // Recreate the pen path array.
1313          // Redraw all the lines.
1314          Y.each(edit.path, function(position) {
1315              if (first) {
1316                  shape.moveTo(position.x, position.y);
1317                  first = false;
1318              } else {
1319                  shape.lineTo(position.x, position.y);
1320              }
1321          }, this);
1322  
1323          shape.end();
1324  
1325          drawable.shapes.push(shape);
1326  
1327          return drawable;
1328      },
1329  
1330  
1331      /**
1332       * Promote the current edit to a real annotation.
1333       *
1334       * @public
1335       * @method init_from_edit
1336       * @param M.assignfeedback_editpdf.edit edit
1337       * @return bool true if pen bound is more than min width/height, else false.
1338       */
1339      init_from_edit: function(edit) {
1340          var bounds = new M.assignfeedback_editpdf.rect(),
1341              pathlist = [],
1342              i = 0;
1343  
1344          // This will get the boundaries of all points in the path.
1345          bounds.bound(edit.path);
1346  
1347          for (i = 0; i < edit.path.length; i++) {
1348              pathlist.push(parseInt(edit.path[i].x, 10) + ',' + parseInt(edit.path[i].y, 10));
1349          }
1350  
1351          this.gradeid = this.editor.get('gradeid');
1352          this.pageno = this.editor.currentpage;
1353          this.x = bounds.x;
1354          this.y = bounds.y;
1355          this.endx = bounds.x + bounds.width;
1356          this.endy = bounds.y + bounds.height;
1357          this.colour = edit.annotationcolour;
1358          this.path = pathlist.join(':');
1359  
1360          return (bounds.has_min_width() || bounds.has_min_height());
1361      }
1362  
1363  
1364  });
1365  
1366  M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1367  M.assignfeedback_editpdf.annotationpen = ANNOTATIONPEN;
1368  // This file is part of Moodle - http://moodle.org/
1369  //
1370  // Moodle is free software: you can redistribute it and/or modify
1371  // it under the terms of the GNU General Public License as published by
1372  // the Free Software Foundation, either version 3 of the License, or
1373  // (at your option) any later version.
1374  //
1375  // Moodle is distributed in the hope that it will be useful,
1376  // but WITHOUT ANY WARRANTY; without even the implied warranty of
1377  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1378  // GNU General Public License for more details.
1379  //
1380  // You should have received a copy of the GNU General Public License
1381  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
1382  /* global ANNOTATIONCOLOUR */
1383  
1384  /**
1385   * Provides an in browser PDF editor.
1386   *
1387   * @module moodle-assignfeedback_editpdf-editor
1388   */
1389  
1390  /**
1391   * Class representing a highlight.
1392   *
1393   * @namespace M.assignfeedback_editpdf
1394   * @class annotationhighlight
1395   * @extends M.assignfeedback_editpdf.annotation
1396   * @module moodle-assignfeedback_editpdf-editor
1397   */
1398  var ANNOTATIONHIGHLIGHT = function(config) {
1399      ANNOTATIONHIGHLIGHT.superclass.constructor.apply(this, [config]);
1400  };
1401  
1402  ANNOTATIONHIGHLIGHT.NAME = "annotationhighlight";
1403  ANNOTATIONHIGHLIGHT.ATTRS = {};
1404  
1405  Y.extend(ANNOTATIONHIGHLIGHT, M.assignfeedback_editpdf.annotation, {
1406      /**
1407       * Draw a highlight annotation
1408       * @protected
1409       * @method draw
1410       * @return M.assignfeedback_editpdf.drawable
1411       */
1412      draw: function() {
1413          var drawable,
1414              shape,
1415              bounds,
1416              highlightcolour;
1417  
1418          drawable = new M.assignfeedback_editpdf.drawable(this.editor);
1419          bounds = new M.assignfeedback_editpdf.rect();
1420          bounds.bound([new M.assignfeedback_editpdf.point(this.x, this.y),
1421                        new M.assignfeedback_editpdf.point(this.endx, this.endy)]);
1422  
1423          highlightcolour = ANNOTATIONCOLOUR[this.colour];
1424  
1425          // Add an alpha channel to the rgb colour.
1426  
1427          highlightcolour = highlightcolour.replace('rgb', 'rgba');
1428          highlightcolour = highlightcolour.replace(')', ',0.5)');
1429  
1430          shape = this.editor.graphic.addShape({
1431              type: Y.Rect,
1432              width: bounds.width,
1433              height: bounds.height,
1434              stroke: false,
1435              fill: {
1436                  color: highlightcolour
1437              },
1438              x: bounds.x,
1439              y: bounds.y
1440          });
1441  
1442          drawable.shapes.push(shape);
1443          this.drawable = drawable;
1444  
1445          return ANNOTATIONHIGHLIGHT.superclass.draw.apply(this);
1446      },
1447  
1448      /**
1449       * Draw the in progress edit.
1450       *
1451       * @public
1452       * @method draw_current_edit
1453       * @param M.assignfeedback_editpdf.edit edit
1454       */
1455      draw_current_edit: function(edit) {
1456          var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
1457              shape,
1458              bounds,
1459              highlightcolour;
1460  
1461          bounds = new M.assignfeedback_editpdf.rect();
1462          bounds.bound([new M.assignfeedback_editpdf.point(edit.start.x, edit.start.y),
1463                        new M.assignfeedback_editpdf.point(edit.end.x, edit.end.y)]);
1464  
1465          // Set min. width of highlight.
1466          if (!bounds.has_min_width()) {
1467              bounds.set_min_width();
1468          }
1469  
1470          highlightcolour = ANNOTATIONCOLOUR[edit.annotationcolour];
1471          // Add an alpha channel to the rgb colour.
1472  
1473          highlightcolour = highlightcolour.replace('rgb', 'rgba');
1474          highlightcolour = highlightcolour.replace(')', ',0.5)');
1475  
1476          // We will draw a box with the current background colour.
1477          shape = this.editor.graphic.addShape({
1478              type: Y.Rect,
1479              width: bounds.width,
1480              height: 16,
1481              stroke: false,
1482              fill: {
1483                 color: highlightcolour
1484              },
1485              x: bounds.x,
1486              y: edit.start.y
1487          });
1488  
1489          drawable.shapes.push(shape);
1490  
1491          return drawable;
1492      },
1493  
1494      /**
1495       * Promote the current edit to a real annotation.
1496       *
1497       * @public
1498       * @method init_from_edit
1499       * @param M.assignfeedback_editpdf.edit edit
1500       * @return bool true if highlight bound is more than min width/height, else false.
1501       */
1502      init_from_edit: function(edit) {
1503          var bounds = new M.assignfeedback_editpdf.rect();
1504          bounds.bound([edit.start, edit.end]);
1505  
1506          this.gradeid = this.editor.get('gradeid');
1507          this.pageno = this.editor.currentpage;
1508          this.x = bounds.x;
1509          this.y = edit.start.y;
1510          this.endx = bounds.x + bounds.width;
1511          this.endy = edit.start.y + 16;
1512          this.colour = edit.annotationcolour;
1513          this.page = '';
1514  
1515          return (bounds.has_min_width());
1516      }
1517  
1518  });
1519  
1520  M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1521  M.assignfeedback_editpdf.annotationhighlight = ANNOTATIONHIGHLIGHT;
1522  // This file is part of Moodle - http://moodle.org/
1523  //
1524  // Moodle is free software: you can redistribute it and/or modify
1525  // it under the terms of the GNU General Public License as published by
1526  // the Free Software Foundation, either version 3 of the License, or
1527  // (at your option) any later version.
1528  //
1529  // Moodle is distributed in the hope that it will be useful,
1530  // but WITHOUT ANY WARRANTY; without even the implied warranty of
1531  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1532  // GNU General Public License for more details.
1533  //
1534  // You should have received a copy of the GNU General Public License
1535  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
1536  /* global SELECTOR */
1537  
1538  /**
1539   * Provides an in browser PDF editor.
1540   *
1541   * @module moodle-assignfeedback_editpdf-editor
1542   */
1543  
1544  /**
1545   * Class representing a stamp.
1546   *
1547   * @namespace M.assignfeedback_editpdf
1548   * @class annotationstamp
1549   * @extends M.assignfeedback_editpdf.annotation
1550   */
1551  var ANNOTATIONSTAMP = function(config) {
1552      ANNOTATIONSTAMP.superclass.constructor.apply(this, [config]);
1553  };
1554  
1555  ANNOTATIONSTAMP.NAME = "annotationstamp";
1556  ANNOTATIONSTAMP.ATTRS = {};
1557  
1558  Y.extend(ANNOTATIONSTAMP, M.assignfeedback_editpdf.annotation, {
1559      /**
1560       * Draw a stamp annotation
1561       * @protected
1562       * @method draw
1563       * @return M.assignfeedback_editpdf.drawable
1564       */
1565      draw: function() {
1566          var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
1567              drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION),
1568              node,
1569              position;
1570  
1571          position = this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(this.x, this.y));
1572          node = Y.Node.create('<div/>');
1573          node.setStyles({
1574              'position': 'absolute',
1575              'display': 'inline-block',
1576              'backgroundImage': 'url(' + this.editor.get_stamp_image_url(this.path) + ')',
1577              'width': (this.endx - this.x),
1578              'height': (this.endy - this.y),
1579              'backgroundSize': '100% 100%',
1580              'zIndex': 50
1581          });
1582  
1583          drawingregion.append(node);
1584          node.setX(position.x);
1585          node.setY(position.y);
1586          drawable.store_position(node, position.x, position.y);
1587  
1588          // Bind events only when editing.
1589          if (!this.editor.get('readonly')) {
1590              // Pass through the event handlers on the div.
1591              node.on('gesturemovestart', this.editor.edit_start, null, this.editor);
1592              node.on('gesturemove', this.editor.edit_move, null, this.editor);
1593              node.on('gesturemoveend', this.editor.edit_end, null, this.editor);
1594          }
1595  
1596          drawable.nodes.push(node);
1597  
1598          this.drawable = drawable;
1599          return ANNOTATIONSTAMP.superclass.draw.apply(this);
1600      },
1601  
1602      /**
1603       * Draw the in progress edit.
1604       *
1605       * @public
1606       * @method draw_current_edit
1607       * @param M.assignfeedback_editpdf.edit edit
1608       */
1609      draw_current_edit: function(edit) {
1610          var bounds = new M.assignfeedback_editpdf.rect(),
1611              drawable = new M.assignfeedback_editpdf.drawable(this.editor),
1612              drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION),
1613              node,
1614              position;
1615  
1616          bounds.bound([edit.start, edit.end]);
1617          position = this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(bounds.x, bounds.y));
1618  
1619          node = Y.Node.create('<div/>');
1620          node.setStyles({
1621              'position': 'absolute',
1622              'display': 'inline-block',
1623              'backgroundImage': 'url(' + this.editor.get_stamp_image_url(edit.stamp) + ')',
1624              'width': bounds.width,
1625              'height': bounds.height,
1626              'backgroundSize': '100% 100%',
1627              'zIndex': 50
1628          });
1629  
1630          drawingregion.append(node);
1631          node.setX(position.x);
1632          node.setY(position.y);
1633          drawable.store_position(node, position.x, position.y);
1634  
1635          drawable.nodes.push(node);
1636  
1637          return drawable;
1638      },
1639  
1640      /**
1641       * Promote the current edit to a real annotation.
1642       *
1643       * @public
1644       * @method init_from_edit
1645       * @param M.assignfeedback_editpdf.edit edit
1646       * @return bool if width/height is more than min. required.
1647       */
1648      init_from_edit: function(edit) {
1649          var bounds = new M.assignfeedback_editpdf.rect();
1650          bounds.bound([edit.start, edit.end]);
1651  
1652          if (bounds.width < 40) {
1653              bounds.width = 40;
1654          }
1655          if (bounds.height < 40) {
1656              bounds.height = 40;
1657          }
1658          this.gradeid = this.editor.get('gradeid');
1659          this.pageno = this.editor.currentpage;
1660          this.x = bounds.x;
1661          this.y = bounds.y;
1662          this.endx = bounds.x + bounds.width;
1663          this.endy = bounds.y + bounds.height;
1664          this.colour = edit.annotationcolour;
1665          this.path = edit.stamp;
1666  
1667          // Min width and height is always more than 40px.
1668          return true;
1669      },
1670  
1671      /**
1672       * Move an annotation to a new location.
1673       * @public
1674       * @param int newx
1675       * @param int newy
1676       * @method move_annotation
1677       */
1678      move: function(newx, newy) {
1679          var diffx = newx - this.x,
1680              diffy = newy - this.y;
1681  
1682          this.x += diffx;
1683          this.y += diffy;
1684          this.endx += diffx;
1685          this.endy += diffy;
1686  
1687          if (this.drawable) {
1688              this.drawable.erase();
1689          }
1690          this.editor.drawables.push(this.draw());
1691      }
1692  
1693  });
1694  
1695  M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1696  M.assignfeedback_editpdf.annotationstamp = ANNOTATIONSTAMP;
1697  var DROPDOWN_NAME = "Dropdown menu",
1698      DROPDOWN;
1699  
1700  /**
1701   * Provides an in browser PDF editor.
1702   *
1703   * @module moodle-assignfeedback_editpdf-editor
1704   */
1705  
1706  /**
1707   * This is a drop down list of buttons triggered (and aligned to) a button.
1708   *
1709   * @namespace M.assignfeedback_editpdf
1710   * @class dropdown
1711   * @constructor
1712   * @extends M.core.dialogue
1713   */
1714  DROPDOWN = function(config) {
1715      config.draggable = false;
1716      config.centered = false;
1717      config.width = 'auto';
1718      config.visible = false;
1719      config.footerContent = '';
1720      DROPDOWN.superclass.constructor.apply(this, [config]);
1721  };
1722  
1723  Y.extend(DROPDOWN, M.core.dialogue, {
1724      /**
1725       * Initialise the menu.
1726       *
1727       * @method initializer
1728       * @return void
1729       */
1730      initializer: function(config) {
1731          var button, body, headertext, bb;
1732          DROPDOWN.superclass.initializer.call(this, config);
1733  
1734          bb = this.get('boundingBox');
1735          bb.addClass('assignfeedback_editpdf_dropdown');
1736  
1737          // Align the menu to the button that opens it.
1738          button = this.get('buttonNode');
1739  
1740          // Close the menu when clicked outside (excluding the button that opened the menu).
1741          body = this.bodyNode;
1742  
1743          headertext = Y.Node.create('<h3/>');
1744          headertext.addClass('accesshide');
1745          headertext.setHTML(this.get('headerText'));
1746          body.prepend(headertext);
1747  
1748          body.on('clickoutside', function(e) {
1749              if (this.get('visible')) {
1750                  // Note: we need to compare ids because for some reason - sometimes button is an Object, not a Y.Node.
1751                  if (e.target.get('id') !== button.get('id') && e.target.ancestor().get('id') !== button.get('id')) {
1752                      e.preventDefault();
1753                      this.hide();
1754                  }
1755              }
1756          }, this);
1757  
1758          button.on('click', function(e) {
1759              e.preventDefault(); this.show();
1760          }, this);
1761          button.on('key', this.show, 'enter,space', this);
1762      },
1763  
1764      /**
1765       * Override the show method to align to the button.
1766       *
1767       * @method show
1768       * @return void
1769       */
1770      show: function() {
1771          var button = this.get('buttonNode'),
1772              result = DROPDOWN.superclass.show.call(this);
1773          this.align(button, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
1774  
1775          return result;
1776      }
1777  }, {
1778      NAME: DROPDOWN_NAME,
1779      ATTRS: {
1780          /**
1781           * The header for the drop down (only accessible to screen readers).
1782           *
1783           * @attribute headerText
1784           * @type String
1785           * @default ''
1786           */
1787          headerText: {
1788              value: ''
1789          },
1790  
1791          /**
1792           * The button used to show/hide this drop down menu.
1793           *
1794           * @attribute buttonNode
1795           * @type Y.Node
1796           * @default null
1797           */
1798          buttonNode: {
1799              value: null
1800          }
1801      }
1802  });
1803  
1804  Y.Base.modifyAttrs(DROPDOWN, {
1805      /**
1806       * Whether the widget should be modal or not.
1807       *
1808       * Moodle override: We override this for commentsearch to force it always false.
1809       *
1810       * @attribute Modal
1811       * @type Boolean
1812       * @default false
1813       */
1814      modal: {
1815          getter: function() {
1816              return false;
1817          }
1818      }
1819  });
1820  
1821  M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1822  M.assignfeedback_editpdf.dropdown = DROPDOWN;
1823  var COLOURPICKER_NAME = "Colourpicker",
1824      COLOURPICKER;
1825  
1826  /**
1827   * Provides an in browser PDF editor.
1828   *
1829   * @module moodle-assignfeedback_editpdf-editor
1830   */
1831  
1832  /**
1833   * COLOURPICKER
1834   * This is a drop down list of colours.
1835   *
1836   * @namespace M.assignfeedback_editpdf
1837   * @class colourpicker
1838   * @constructor
1839   * @extends M.assignfeedback_editpdf.dropdown
1840   */
1841  COLOURPICKER = function(config) {
1842      COLOURPICKER.superclass.constructor.apply(this, [config]);
1843  };
1844  
1845  Y.extend(COLOURPICKER, M.assignfeedback_editpdf.dropdown, {
1846  
1847      /**
1848       * Initialise the menu.
1849       *
1850       * @method initializer
1851       * @return void
1852       */
1853      initializer: function(config) {
1854          var colourlist = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_menu"/>'),
1855              body;
1856  
1857          // Build a list of coloured buttons.
1858          Y.each(this.get('colours'), function(rgb, colour) {
1859              var button, listitem, title, img, iconname;
1860  
1861              title = M.util.get_string(colour, 'assignfeedback_editpdf');
1862              iconname = this.get('iconprefix') + colour;
1863              img = M.util.image_url(iconname, 'assignfeedback_editpdf');
1864              button = Y.Node.create('<button><img alt="' + title + '" src="' + img + '"/></button>');
1865              button.setAttribute('data-colour', colour);
1866              button.setAttribute('data-rgb', rgb);
1867              button.setStyle('backgroundImage', 'none');
1868              listitem = Y.Node.create('<li/>');
1869              listitem.append(button);
1870              colourlist.append(listitem);
1871          }, this);
1872  
1873          body = Y.Node.create('<div/>');
1874  
1875          // Set the call back.
1876          colourlist.delegate('click', this.callback_handler, 'button', this);
1877          colourlist.delegate('key', this.callback_handler, 'down:13', 'button', this);
1878  
1879          // Set the accessible header text.
1880          this.set('headerText', M.util.get_string('colourpicker', 'assignfeedback_editpdf'));
1881  
1882          // Set the body content.
1883          body.append(colourlist);
1884          this.set('bodyContent', body);
1885  
1886          COLOURPICKER.superclass.initializer.call(this, config);
1887      },
1888      callback_handler: function(e) {
1889          e.preventDefault();
1890  
1891          var callback = this.get('callback'),
1892              callbackcontext = this.get('context'),
1893              bind;
1894  
1895          this.hide();
1896  
1897          // Call the callback with the specified context.
1898          bind = Y.bind(callback, callbackcontext, e);
1899  
1900          bind();
1901      }
1902  }, {
1903      NAME: COLOURPICKER_NAME,
1904      ATTRS: {
1905          /**
1906           * The list of colours this colour picker supports.
1907           *
1908           * @attribute colours
1909           * @type {String: String} (The keys of the array are the colour names and the values are localized strings)
1910           * @default {}
1911           */
1912          colours: {
1913              value: {}
1914          },
1915  
1916          /**
1917           * The function called when a new colour is chosen.
1918           *
1919           * @attribute callback
1920           * @type function
1921           * @default null
1922           */
1923          callback: {
1924              value: null
1925          },
1926  
1927          /**
1928           * The context passed to the callback when a colour is chosen.
1929           *
1930           * @attribute context
1931           * @type Y.Node
1932           * @default null
1933           */
1934          context: {
1935              value: null
1936          },
1937  
1938          /**
1939           * The prefix for the icon image names.
1940           *
1941           * @attribute iconprefix
1942           * @type String
1943           * @default 'colour_'
1944           */
1945          iconprefix: {
1946              value: 'colour_'
1947          }
1948      }
1949  });
1950  
1951  M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
1952  M.assignfeedback_editpdf.colourpicker = COLOURPICKER;
1953  var STAMPPICKER_NAME = "Colourpicker",
1954      STAMPPICKER;
1955  
1956  /**
1957   * Provides an in browser PDF editor.
1958   *
1959   * @module moodle-assignfeedback_editpdf-editor
1960   */
1961  
1962  /**
1963   * This is a drop down list of stamps.
1964   *
1965   * @namespace M.assignfeedback_editpdf
1966   * @class stamppicker
1967   * @constructor
1968   * @extends M.assignfeedback_editpdf.dropdown
1969   */
1970  STAMPPICKER = function(config) {
1971      STAMPPICKER.superclass.constructor.apply(this, [config]);
1972  };
1973  
1974  Y.extend(STAMPPICKER, M.assignfeedback_editpdf.dropdown, {
1975  
1976      /**
1977       * Initialise the menu.
1978       *
1979       * @method initializer
1980       * @return void
1981       */
1982      initializer: function(config) {
1983          var stamplist = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_menu"/>');
1984  
1985          // Build a list of stamped buttons.
1986          Y.each(this.get('stamps'), function(stamp) {
1987              var button, listitem, title;
1988  
1989              title = M.util.get_string('stamp', 'assignfeedback_editpdf');
1990              button = Y.Node.create('<button><img height="16" width="16" alt="' + title + '" src="' + stamp + '"/></button>');
1991              button.setAttribute('data-stamp', stamp);
1992              button.setStyle('backgroundImage', 'none');
1993              listitem = Y.Node.create('<li/>');
1994              listitem.append(button);
1995              stamplist.append(listitem);
1996          }, this);
1997  
1998  
1999          // Set the call back.
2000          stamplist.delegate('click', this.callback_handler, 'button', this);
2001          stamplist.delegate('key', this.callback_handler, 'down:13', 'button', this);
2002  
2003          // Set the accessible header text.
2004          this.set('headerText', M.util.get_string('stamppicker', 'assignfeedback_editpdf'));
2005  
2006          // Set the body content.
2007          this.set('bodyContent', stamplist);
2008  
2009          STAMPPICKER.superclass.initializer.call(this, config);
2010      },
2011      callback_handler: function(e) {
2012          e.preventDefault();
2013          var callback = this.get('callback'),
2014              callbackcontext = this.get('context'),
2015              bind;
2016  
2017          this.hide();
2018  
2019          // Call the callback with the specified context.
2020          bind = Y.bind(callback, callbackcontext, e);
2021  
2022          bind();
2023      }
2024  }, {
2025      NAME: STAMPPICKER_NAME,
2026      ATTRS: {
2027          /**
2028           * The list of stamps this stamp picker supports.
2029           *
2030           * @attribute stamps
2031           * @type String[] - the stamp filenames.
2032           * @default {}
2033           */
2034          stamps: {
2035              value: []
2036          },
2037  
2038          /**
2039           * The function called when a new stamp is chosen.
2040           *
2041           * @attribute callback
2042           * @type function
2043           * @default null
2044           */
2045          callback: {
2046              value: null
2047          },
2048  
2049          /**
2050           * The context passed to the callback when a stamp is chosen.
2051           *
2052           * @attribute context
2053           * @type Y.Node
2054           * @default null
2055           */
2056          context: {
2057              value: null
2058          }
2059      }
2060  });
2061  
2062  M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
2063  M.assignfeedback_editpdf.stamppicker = STAMPPICKER;
2064  var COMMENTMENUNAME = "Commentmenu",
2065      COMMENTMENU;
2066  
2067  /**
2068   * Provides an in browser PDF editor.
2069   *
2070   * @module moodle-assignfeedback_editpdf-editor
2071   */
2072  
2073  /**
2074   * COMMENTMENU
2075   * This is a drop down list of comment context functions.
2076   *
2077   * @namespace M.assignfeedback_editpdf
2078   * @class commentmenu
2079   * @constructor
2080   * @extends M.assignfeedback_editpdf.dropdown
2081   */
2082  COMMENTMENU = function(config) {
2083      COMMENTMENU.superclass.constructor.apply(this, [config]);
2084  };
2085  
2086  Y.extend(COMMENTMENU, M.assignfeedback_editpdf.dropdown, {
2087  
2088      /**
2089       * Initialise the menu.
2090       *
2091       * @method initializer
2092       * @return void
2093       */
2094      initializer: function(config) {
2095          var commentlinks,
2096              link,
2097              body,
2098              comment;
2099  
2100          comment = this.get('comment');
2101          // Build the list of menu items.
2102          commentlinks = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_menu"/>');
2103  
2104          link = Y.Node.create('<li><a tabindex="-1" href="#">' +
2105                 M.util.get_string('addtoquicklist', 'assignfeedback_editpdf') +
2106                 '</a></li>');
2107          link.on('click', comment.add_to_quicklist, comment);
2108          link.on('key', comment.add_to_quicklist, 'enter,space', comment);
2109  
2110          commentlinks.append(link);
2111  
2112          link = Y.Node.create('<li><a tabindex="-1" href="#">' +
2113                 M.util.get_string('deletecomment', 'assignfeedback_editpdf') +
2114                 '</a></li>');
2115          link.on('click', function(e) {
2116              e.preventDefault();
2117              this.menu.hide();
2118              this.remove();
2119          }, comment);
2120  
2121          link.on('key', function() {
2122              comment.menu.hide();
2123              comment.remove();
2124          }, 'enter,space', comment);
2125  
2126          commentlinks.append(link);
2127  
2128          link = Y.Node.create('<li><hr/></li>');
2129          commentlinks.append(link);
2130  
2131          // Set the accessible header text.
2132          this.set('headerText', M.util.get_string('commentcontextmenu', 'assignfeedback_editpdf'));
2133  
2134          body = Y.Node.create('<div/>');
2135  
2136          // Set the body content.
2137          body.append(commentlinks);
2138          this.set('bodyContent', body);
2139  
2140          COMMENTMENU.superclass.initializer.call(this, config);
2141      },
2142  
2143      /**
2144       * Show the menu.
2145       *
2146       * @method show
2147       * @return void
2148       */
2149      show: function() {
2150          var commentlinks = this.get('boundingBox').one('ul');
2151              commentlinks.all('.quicklist_comment').remove(true);
2152          var comment = this.get('comment');
2153  
2154          comment.deleteme = false; // Cancel the deleting of blank comments.
2155  
2156          // Now build the list of quicklist comments.
2157          Y.each(comment.editor.quicklist.comments, function(quickcomment) {
2158              var listitem = Y.Node.create('<li class="quicklist_comment"></li>'),
2159                  linkitem = Y.Node.create('<a href="#" tabindex="-1">' + quickcomment.rawtext + '</a>'),
2160                  deletelinkitem = Y.Node.create('<a href="#" tabindex="-1" class="delete_quicklist_comment">' +
2161                                                 '<img src="' + M.util.image_url('t/delete', 'core') + '" ' +
2162                                                 'alt="' + M.util.get_string('deletecomment', 'assignfeedback_editpdf') + '"/>' +
2163                                                 '</a>');
2164              listitem.append(linkitem);
2165              listitem.append(deletelinkitem);
2166  
2167              commentlinks.append(listitem);
2168  
2169              linkitem.on('click', comment.set_from_quick_comment, comment, quickcomment);
2170              linkitem.on('key', comment.set_from_quick_comment, 'space,enter', comment, quickcomment);
2171  
2172              deletelinkitem.on('click', comment.remove_from_quicklist, comment, quickcomment);
2173              deletelinkitem.on('key', comment.remove_from_quicklist, 'space,enter', comment, quickcomment);
2174          }, this);
2175  
2176          COMMENTMENU.superclass.show.call(this);
2177      }
2178  }, {
2179      NAME: COMMENTMENUNAME,
2180      ATTRS: {
2181          /**
2182           * The comment this menu is attached to.
2183           *
2184           * @attribute comment
2185           * @type M.assignfeedback_editpdf.comment
2186           * @default null
2187           */
2188          comment: {
2189              value: null
2190          }
2191  
2192      }
2193  });
2194  
2195  M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
2196  M.assignfeedback_editpdf.commentmenu = COMMENTMENU;
2197  /* eslint-disable no-unused-vars */
2198  /* global SELECTOR */
2199  var COMMENTSEARCHNAME = "commentsearch",
2200      COMMENTSEARCH;
2201  
2202  /**
2203   * Provides an in browser PDF editor.
2204   *
2205   * @module moodle-assignfeedback_editpdf-editor
2206   */
2207  
2208  /**
2209   * This is a searchable dialogue of comments.
2210   *
2211   * @namespace M.assignfeedback_editpdf
2212   * @class commentsearch
2213   * @constructor
2214   * @extends M.core.dialogue
2215   */
2216  COMMENTSEARCH = function(config) {
2217      config.draggable = false;
2218      config.centered = true;
2219      config.width = '400px';
2220      config.visible = false;
2221      config.headerContent = M.util.get_string('searchcomments', 'assignfeedback_editpdf');
2222      config.footerContent = '';
2223      COMMENTSEARCH.superclass.constructor.apply(this, [config]);
2224  };
2225  
2226  Y.extend(COMMENTSEARCH, M.core.dialogue, {
2227      /**
2228       * Initialise the menu.
2229       *
2230       * @method initializer
2231       * @return void
2232       */
2233      initializer: function(config) {
2234          var editor,
2235              container,
2236              placeholder,
2237              commentfilter,
2238              commentlist,
2239              bb;
2240  
2241          bb = this.get('boundingBox');
2242          bb.addClass('assignfeedback_editpdf_commentsearch');
2243  
2244          editor = this.get('editor');
2245          container = Y.Node.create('<div/>');
2246  
2247          placeholder = M.util.get_string('filter', 'assignfeedback_editpdf');
2248          commentfilter = Y.Node.create('<input type="text" size="20" placeholder="' + placeholder + '"/>');
2249          container.append(commentfilter);
2250          commentlist = Y.Node.create('<ul role="menu" class="assignfeedback_editpdf_menu"/>');
2251          container.append(commentlist);
2252  
2253          commentfilter.on('keyup', this.filter_search_comments, this);
2254          commentlist.delegate('click', this.focus_on_comment, 'a', this);
2255          commentlist.delegate('key', this.focus_on_comment, 'enter,space', 'a', this);
2256  
2257          // Set the body content.
2258          this.set('bodyContent', container);
2259  
2260          COMMENTSEARCH.superclass.initializer.call(this, config);
2261      },
2262  
2263      /**
2264       * Event handler to filter the list of comments.
2265       *
2266       * @protected
2267       * @method filter_search_comments
2268       */
2269      filter_search_comments: function() {
2270          var filternode,
2271              commentslist,
2272              filtertext,
2273              dialogueid;
2274  
2275          dialogueid = this.get('id');
2276          filternode = Y.one('#' + dialogueid + SELECTOR.SEARCHFILTER);
2277          commentslist = Y.one('#' + dialogueid + SELECTOR.SEARCHCOMMENTSLIST);
2278  
2279          filtertext = filternode.get('value');
2280  
2281          commentslist.all('li').each(function(node) {
2282              if (node.get('text').indexOf(filtertext) !== -1) {
2283                  node.show();
2284              } else {
2285                  node.hide();
2286              }
2287          });
2288      },
2289  
2290      /**
2291       * Event handler to focus on a selected comment.
2292       *
2293       * @param Event e
2294       * @protected
2295       * @method focus_on_comment
2296       */
2297      focus_on_comment: function(e) {
2298          e.preventDefault();
2299          var target = e.target.ancestor('li'),
2300              comment = target.getData('comment'),
2301              editor = this.get('editor');
2302  
2303          this.hide();
2304  
2305          if (comment.pageno === editor.currentpage) {
2306              comment.drawable.nodes[0].one('textarea').focus();
2307          } else {
2308              // Comment is on a different page.
2309              editor.currentpage = comment.pageno;
2310              editor.change_page();
2311              comment.drawable.nodes[0].one('textarea').focus();
2312          }
2313      },
2314  
2315      /**
2316       * Show the menu.
2317       *
2318       * @method show
2319       * @return void
2320       */
2321      show: function() {
2322          var commentlist = this.get('boundingBox').one('ul'),
2323              editor = this.get('editor');
2324  
2325          commentlist.all('li').remove(true);
2326  
2327          // Rebuild the latest list of comments.
2328          Y.each(editor.pages, function(page) {
2329              Y.each(page.comments, function(comment) {
2330                  var commentnode = Y.Node.create('<li><a href="#" tabindex="-1"><pre>' + comment.rawtext + '</pre></a></li>');
2331                  commentlist.append(commentnode);
2332                  commentnode.setData('comment', comment);
2333              }, this);
2334          }, this);
2335  
2336          this.centerDialogue();
2337          COMMENTSEARCH.superclass.show.call(this);
2338      }
2339  }, {
2340      NAME: COMMENTSEARCHNAME,
2341      ATTRS: {
2342          /**
2343           * The editor this search window is attached to.
2344           *
2345           * @attribute editor
2346           * @type M.assignfeedback_editpdf.editor
2347           * @default null
2348           */
2349          editor: {
2350              value: null
2351          }
2352  
2353      }
2354  });
2355  
2356  Y.Base.modifyAttrs(COMMENTSEARCH, {
2357      /**
2358       * Whether the widget should be modal or not.
2359       *
2360       * Moodle override: We override this for commentsearch to force it always true.
2361       *
2362       * @attribute Modal
2363       * @type Boolean
2364       * @default true
2365       */
2366      modal: {
2367          getter: function() {
2368              return true;
2369          }
2370      }
2371  });
2372  
2373  M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
2374  M.assignfeedback_editpdf.commentsearch = COMMENTSEARCH;
2375  // This file is part of Moodle - http://moodle.org/
2376  //
2377  // Moodle is free software: you can redistribute it and/or modify
2378  // it under the terms of the GNU General Public License as published by
2379  // the Free Software Foundation, either version 3 of the License, or
2380  // (at your option) any later version.
2381  //
2382  // Moodle is distributed in the hope that it will be useful,
2383  // but WITHOUT ANY WARRANTY; without even the implied warranty of
2384  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
2385  // GNU General Public License for more details.
2386  //
2387  // You should have received a copy of the GNU General Public License
2388  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
2389  /* global SELECTOR, COMMENTCOLOUR, COMMENTTEXTCOLOUR */
2390  
2391  /**
2392   * Provides an in browser PDF editor.
2393   *
2394   * @module moodle-assignfeedback_editpdf-editor
2395   */
2396  
2397  /**
2398   * Class representing a list of comments.
2399   *
2400   * @namespace M.assignfeedback_editpdf
2401   * @class comment
2402   * @param M.assignfeedback_editpdf.editor editor
2403   * @param Int gradeid
2404   * @param Int pageno
2405   * @param Int x
2406   * @param Int y
2407   * @param Int width
2408   * @param String colour
2409   * @param String rawtext
2410   */
2411  var COMMENT = function(editor, gradeid, pageno, x, y, width, colour, rawtext) {
2412  
2413      /**
2414       * Reference to M.assignfeedback_editpdf.editor.
2415       * @property editor
2416       * @type M.assignfeedback_editpdf.editor
2417       * @public
2418       */
2419      this.editor = editor;
2420  
2421      /**
2422       * Grade id
2423       * @property gradeid
2424       * @type Int
2425       * @public
2426       */
2427      this.gradeid = gradeid || 0;
2428  
2429      /**
2430       * X position
2431       * @property x
2432       * @type Int
2433       * @public
2434       */
2435      this.x = parseInt(x, 10) || 0;
2436  
2437      /**
2438       * Y position
2439       * @property y
2440       * @type Int
2441       * @public
2442       */
2443      this.y = parseInt(y, 10) || 0;
2444  
2445      /**
2446       * Comment width
2447       * @property width
2448       * @type Int
2449       * @public
2450       */
2451      this.width = parseInt(width, 10) || 0;
2452  
2453      /**
2454       * Comment rawtext
2455       * @property rawtext
2456       * @type String
2457       * @public
2458       */
2459      this.rawtext = rawtext || '';
2460  
2461      /**
2462       * Comment page number
2463       * @property pageno
2464       * @type Int
2465       * @public
2466       */
2467      this.pageno = pageno || 0;
2468  
2469      /**
2470       * Comment background colour.
2471       * @property colour
2472       * @type String
2473       * @public
2474       */
2475      this.colour = colour || 'yellow';
2476  
2477      /**
2478       * Reference to M.assignfeedback_editpdf.drawable
2479       * @property drawable
2480       * @type M.assignfeedback_editpdf.drawable
2481       * @public
2482       */
2483      this.drawable = false;
2484  
2485      /**
2486       * Boolean used by a timeout to delete empty comments after a short delay.
2487       * @property deleteme
2488       * @type Boolean
2489       * @public
2490       */
2491      this.deleteme = false;
2492  
2493      /**
2494       * Reference to the link that opens the menu.
2495       * @property menulink
2496       * @type Y.Node
2497       * @public
2498       */
2499      this.menulink = null;
2500  
2501      /**
2502       * Reference to the dialogue that is the context menu.
2503       * @property menu
2504       * @type M.assignfeedback_editpdf.dropdown
2505       * @public
2506       */
2507      this.menu = null;
2508  
2509      /**
2510       * Clean a comment record, returning an oject with only fields that are valid.
2511       * @public
2512       * @method clean
2513       * @return {}
2514       */
2515      this.clean = function() {
2516          return {
2517              gradeid: this.gradeid,
2518              x: parseInt(this.x, 10),
2519              y: parseInt(this.y, 10),
2520              width: parseInt(this.width, 10),
2521              rawtext: this.rawtext,
2522              pageno: this.currentpage,
2523              colour: this.colour
2524          };
2525      };
2526  
2527      /**
2528       * Draw a comment.
2529       * @public
2530       * @method draw_comment
2531       * @param boolean focus - Set the keyboard focus to the new comment if true
2532       * @return M.assignfeedback_editpdf.drawable
2533       */
2534      this.draw = function(focus) {
2535          var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
2536              node,
2537              drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION),
2538              container,
2539              menu,
2540              position,
2541              scrollheight;
2542  
2543          // Lets add a contenteditable div.
2544          node = Y.Node.create('<textarea/>');
2545          container = Y.Node.create('<div class="commentdrawable"/>');
2546          menu = Y.Node.create('<a href="#"><img src="' + M.util.image_url('t/contextmenu', 'core') + '"/></a>');
2547  
2548          this.menulink = menu;
2549          container.append(node);
2550  
2551          if (!this.editor.get('readonly')) {
2552              container.append(menu);
2553          } else {
2554              node.setAttribute('readonly', 'readonly');
2555          }
2556          if (this.width < 100) {
2557              this.width = 100;
2558          }
2559  
2560          position = this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(this.x, this.y));
2561          node.setStyles({
2562              width: this.width + 'px',
2563              backgroundColor: COMMENTCOLOUR[this.colour],
2564              color: COMMENTTEXTCOLOUR
2565          });
2566  
2567          drawingregion.append(container);
2568          container.setStyle('position', 'absolute');
2569          container.setX(position.x);
2570          container.setY(position.y);
2571          drawable.store_position(container, position.x, position.y);
2572          drawable.nodes.push(container);
2573          node.set('value', this.rawtext);
2574          scrollheight = node.get('scrollHeight');
2575          node.setStyles({
2576              'height': scrollheight + 'px',
2577              'overflow': 'hidden'
2578          });
2579          if (!this.editor.get('readonly')) {
2580              this.attach_events(node, menu);
2581          }
2582          if (focus) {
2583              node.focus();
2584          }
2585          this.drawable = drawable;
2586  
2587  
2588          return drawable;
2589      };
2590  
2591      /**
2592       * Delete an empty comment if it's menu hasn't been opened in time.
2593       * @method delete_comment_later
2594       */
2595      this.delete_comment_later = function() {
2596          if (this.deleteme) {
2597              this.remove();
2598          }
2599      };
2600  
2601      /**
2602       * Comment nodes have a bunch of event handlers attached to them directly.
2603       * This is all done here for neatness.
2604       *
2605       * @protected
2606       * @method attach_comment_events
2607       * @param node - The Y.Node representing the comment.
2608       * @param menu - The Y.Node representing the menu.
2609       */
2610      this.attach_events = function(node, menu) {
2611          // Save the text on blur.
2612          node.on('blur', function() {
2613              // Save the changes back to the comment.
2614              this.rawtext = node.get('value');
2615              this.width = parseInt(node.getStyle('width'), 10);
2616  
2617              // Trim.
2618              if (this.rawtext.replace(/^\s+|\s+$/g, "") === '') {
2619                  // Delete empty comments.
2620                  this.deleteme = true;
2621                  Y.later(400, this, this.delete_comment_later);
2622              }
2623              this.editor.save_current_page();
2624              this.editor.editingcomment = false;
2625          }, this);
2626  
2627          // For delegated event handler.
2628          menu.setData('comment', this);
2629  
2630          node.on('keyup', function() {
2631              var scrollheight = node.get('scrollHeight'),
2632                  height = parseInt(node.getStyle('height'), 10);
2633  
2634              // Webkit scrollheight fix.
2635              if (scrollheight === height + 8) {
2636                  scrollheight -= 8;
2637              }
2638              node.setStyle('height', scrollheight + 'px');
2639  
2640          });
2641  
2642          node.on('gesturemovestart', function(e) {
2643              if (editor.currentedit.tool === 'select') {
2644                  e.preventDefault();
2645                  node.setData('dragging', true);
2646                  node.setData('offsetx', e.clientX - node.getX());
2647                  node.setData('offsety', e.clientY - node.getY());
2648              }
2649          });
2650          node.on('gesturemoveend', function() {
2651              if (editor.currentedit.tool === 'select') {
2652                  node.setData('dragging', false);
2653                  this.editor.save_current_page();
2654              }
2655          }, null, this);
2656          node.on('gesturemove', function(e) {
2657              if (editor.currentedit.tool === 'select') {
2658                  var x = e.clientX - node.getData('offsetx'),
2659                      y = e.clientY - node.getData('offsety'),
2660                      nodewidth,
2661                      nodeheight,
2662                      newlocation,
2663                      windowlocation,
2664                      bounds;
2665  
2666                  nodewidth = parseInt(node.getStyle('width'), 10);
2667                  nodeheight = parseInt(node.getStyle('height'), 10);
2668  
2669                  newlocation = this.editor.get_canvas_coordinates(new M.assignfeedback_editpdf.point(x, y));
2670                  bounds = this.editor.get_canvas_bounds(true);
2671                  bounds.x = 0;
2672                  bounds.y = 0;
2673  
2674                  bounds.width -= nodewidth + 42;
2675                  bounds.height -= nodeheight + 8;
2676                  // Clip to the window size - the comment size.
2677                  newlocation.clip(bounds);
2678  
2679                  this.x = newlocation.x;
2680                  this.y = newlocation.y;
2681  
2682                  windowlocation = this.editor.get_window_coordinates(newlocation);
2683                  node.ancestor().setX(windowlocation.x);
2684                  node.ancestor().setY(windowlocation.y);
2685                  this.drawable.store_position(node.ancestor(), windowlocation.x, windowlocation.y);
2686              }
2687          }, null, this);
2688  
2689          this.menu = new M.assignfeedback_editpdf.commentmenu({
2690              buttonNode: this.menulink,
2691              comment: this
2692          });
2693      };
2694  
2695      /**
2696       * Delete a comment.
2697       * @method remove
2698       */
2699      this.remove = function() {
2700          var i = 0;
2701          var comments;
2702  
2703          comments = this.editor.pages[this.editor.currentpage].comments;
2704          for (i = 0; i < comments.length; i++) {
2705              if (comments[i] === this) {
2706                  comments.splice(i, 1);
2707                  this.drawable.erase();
2708                  this.editor.save_current_page();
2709                  return;
2710              }
2711          }
2712      };
2713  
2714      /**
2715       * Event handler to remove a comment from the users quicklist.
2716       *
2717       * @protected
2718       * @method remove_from_quicklist
2719       */
2720      this.remove_from_quicklist = function(e, quickcomment) {
2721          e.preventDefault();
2722  
2723          this.menu.hide();
2724  
2725          this.editor.quicklist.remove(quickcomment);
2726      };
2727  
2728      /**
2729       * A quick comment was selected in the list, update the active comment and redraw the page.
2730       *
2731       * @param Event e
2732       * @protected
2733       * @method set_from_quick_comment
2734       */
2735      this.set_from_quick_comment = function(e, quickcomment) {
2736          e.preventDefault();
2737  
2738          this.menu.hide();
2739  
2740          this.rawtext = quickcomment.rawtext;
2741          this.width = quickcomment.width;
2742          this.colour = quickcomment.colour;
2743  
2744          this.editor.save_current_page();
2745  
2746          this.editor.redraw();
2747      };
2748  
2749      /**
2750       * Event handler to add a comment to the users quicklist.
2751       *
2752       * @protected
2753       * @method add_to_quicklist
2754       */
2755      this.add_to_quicklist = function(e) {
2756          e.preventDefault();
2757          this.menu.hide();
2758          this.editor.quicklist.add(this);
2759      };
2760  
2761      /**
2762       * Draw the in progress edit.
2763       *
2764       * @public
2765       * @method draw_current_edit
2766       * @param M.assignfeedback_editpdf.edit edit
2767       */
2768      this.draw_current_edit = function(edit) {
2769          var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
2770              shape,
2771              bounds;
2772  
2773          bounds = new M.assignfeedback_editpdf.rect();
2774          bounds.bound([edit.start, edit.end]);
2775  
2776          // We will draw a box with the current background colour.
2777          shape = this.editor.graphic.addShape({
2778              type: Y.Rect,
2779              width: bounds.width,
2780              height: bounds.height,
2781              fill: {
2782                 color: COMMENTCOLOUR[edit.commentcolour]
2783              },
2784              x: bounds.x,
2785              y: bounds.y
2786          });
2787  
2788          drawable.shapes.push(shape);
2789  
2790          return drawable;
2791      };
2792  
2793      /**
2794       * Promote the current edit to a real comment.
2795       *
2796       * @public
2797       * @method init_from_edit
2798       * @param M.assignfeedback_editpdf.edit edit
2799       * @return bool true if comment bound is more than min width/height, else false.
2800       */
2801      this.init_from_edit = function(edit) {
2802          var bounds = new M.assignfeedback_editpdf.rect();
2803          bounds.bound([edit.start, edit.end]);
2804  
2805          // Minimum comment width.
2806          if (bounds.width < 100) {
2807              bounds.width = 100;
2808          }
2809  
2810          // Save the current edit to the server and the current page list.
2811  
2812          this.gradeid = this.editor.get('gradeid');
2813          this.pageno = this.editor.currentpage;
2814          this.x = bounds.x;
2815          this.y = bounds.y;
2816          this.width = bounds.width;
2817          this.colour = edit.commentcolour;
2818          this.rawtext = '';
2819  
2820          return (bounds.has_min_width() && bounds.has_min_height());
2821      };
2822  
2823  };
2824  
2825  M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
2826  M.assignfeedback_editpdf.comment = COMMENT;
2827  // This file is part of Moodle - http://moodle.org/
2828  //
2829  // Moodle is free software: you can redistribute it and/or modify
2830  // it under the terms of the GNU General Public License as published by
2831  // the Free Software Foundation, either version 3 of the License, or
2832  // (at your option) any later version.
2833  //
2834  // Moodle is distributed in the hope that it will be useful,
2835  // but WITHOUT ANY WARRANTY; without even the implied warranty of
2836  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
2837  // GNU General Public License for more details.
2838  //
2839  // You should have received a copy of the GNU General Public License
2840  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
2841  
2842  /**
2843   * Provides an in browser PDF editor.
2844   *
2845   * @module moodle-assignfeedback_editpdf-editor
2846   */
2847  
2848  /**
2849   * Class representing a users quick comment.
2850   *
2851   * @namespace M.assignfeedback_editpdf
2852   * @class quickcomment
2853   */
2854  var QUICKCOMMENT = function(id, rawtext, width, colour) {
2855  
2856      /**
2857       * Quick comment text.
2858       * @property rawtext
2859       * @type String
2860       * @public
2861       */
2862      this.rawtext = rawtext || '';
2863  
2864      /**
2865       * ID of the comment
2866       * @property id
2867       * @type Int
2868       * @public
2869       */
2870      this.id = id || 0;
2871  
2872      /**
2873       * Width of the comment
2874       * @property width
2875       * @type Int
2876       * @public
2877       */
2878      this.width = width || 100;
2879  
2880      /**
2881       * Colour of the comment.
2882       * @property colour
2883       * @type String
2884       * @public
2885       */
2886      this.colour = colour || "yellow";
2887  };
2888  
2889  M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
2890  M.assignfeedback_editpdf.quickcomment = QUICKCOMMENT;
2891  // This file is part of Moodle - http://moodle.org/
2892  //
2893  // Moodle is free software: you can redistribute it and/or modify
2894  // it under the terms of the GNU General Public License as published by
2895  // the Free Software Foundation, either version 3 of the License, or
2896  // (at your option) any later version.
2897  //
2898  // Moodle is distributed in the hope that it will be useful,
2899  // but WITHOUT ANY WARRANTY; without even the implied warranty of
2900  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
2901  // GNU General Public License for more details.
2902  //
2903  // You should have received a copy of the GNU General Public License
2904  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
2905  /* global AJAXBASE */
2906  
2907  /**
2908   * Provides an in browser PDF editor.
2909   *
2910   * @module moodle-assignfeedback_editpdf-editor
2911   */
2912  
2913  /**
2914   * Class representing a users list of quick comments.
2915   *
2916   * @namespace M.assignfeedback_editpdf
2917   * @class quickcommentlist
2918   */
2919  var QUICKCOMMENTLIST = function(editor) {
2920  
2921      /**
2922       * Reference to M.assignfeedback_editpdf.editor.
2923       * @property editor
2924       * @type M.assignfeedback_editpdf.editor
2925       * @public
2926       */
2927      this.editor = editor;
2928  
2929      /**
2930       * Array of Comments
2931       * @property shapes
2932       * @type M.assignfeedback_editpdf.quickcomment[]
2933       * @public
2934       */
2935      this.comments = [];
2936  
2937      /**
2938       * Add a comment to the users quicklist.
2939       *
2940       * @protected
2941       * @method add
2942       */
2943      this.add = function(comment) {
2944          var ajaxurl = AJAXBASE,
2945              config;
2946  
2947          // Do not save empty comments.
2948          if (comment.rawtext === '') {
2949              return;
2950          }
2951  
2952          config = {
2953              method: 'post',
2954              context: this,
2955              sync: false,
2956              data: {
2957                  'sesskey': M.cfg.sesskey,
2958                  'action': 'addtoquicklist',
2959                  'userid': this.editor.get('userid'),
2960                  'commenttext': comment.rawtext,
2961                  'width': comment.width,
2962                  'colour': comment.colour,
2963                  'attemptnumber': this.editor.get('attemptnumber'),
2964                  'assignmentid': this.editor.get('assignmentid')
2965              },
2966              on: {
2967                  success: function(tid, response) {
2968                      var jsondata, quickcomment;
2969                      try {
2970                          jsondata = Y.JSON.parse(response.responseText);
2971                          if (jsondata.error) {
2972                              return new M.core.ajaxException(jsondata);
2973                          } else {
2974                              quickcomment = new M.assignfeedback_editpdf.quickcomment(jsondata.id,
2975                                                                                       jsondata.rawtext,
2976                                                                                       jsondata.width,
2977                                                                                       jsondata.colour);
2978                              this.comments.push(quickcomment);
2979                          }
2980                      } catch (e) {
2981                          return new M.core.exception(e);
2982                      }
2983                  },
2984                  failure: function(tid, response) {
2985                      return M.core.exception(response.responseText);
2986                  }
2987              }
2988          };
2989  
2990          Y.io(ajaxurl, config);
2991      };
2992  
2993      /**
2994       * Remove a comment from the users quicklist.
2995       *
2996       * @public
2997       * @method remove
2998       */
2999      this.remove = function(comment) {
3000          var ajaxurl = AJAXBASE,
3001              config;
3002  
3003          // Should not happen.
3004          if (!comment) {
3005              return;
3006          }
3007  
3008          config = {
3009              method: 'post',
3010              context: this,
3011              sync: false,
3012              data: {
3013                  'sesskey': M.cfg.sesskey,
3014                  'action': 'removefromquicklist',
3015                  'userid': this.editor.get('userid'),
3016                  'commentid': comment.id,
3017                  'attemptnumber': this.editor.get('attemptnumber'),
3018                  'assignmentid': this.editor.get('assignmentid')
3019              },
3020              on: {
3021                  success: function() {
3022                      var i;
3023  
3024                      // Find and remove the comment from the quicklist.
3025                      i = this.comments.indexOf(comment);
3026                      if (i >= 0) {
3027                          this.comments.splice(i, 1);
3028                      }
3029                  },
3030                  failure: function(tid, response) {
3031                      return M.core.exception(response.responseText);
3032                  }
3033              }
3034          };
3035  
3036          Y.io(ajaxurl, config);
3037      };
3038  
3039      /**
3040       * Load the users quick comments list.
3041       *
3042       * @protected
3043       * @method load_quicklist
3044       */
3045      this.load = function() {
3046          var ajaxurl = AJAXBASE,
3047              config;
3048  
3049          config = {
3050              method: 'get',
3051              context: this,
3052              sync: false,
3053              data: {
3054                  'sesskey': M.cfg.sesskey,
3055                  'action': 'loadquicklist',
3056                  'userid': this.editor.get('userid'),
3057                  'attemptnumber': this.editor.get('attemptnumber'),
3058                  'assignmentid': this.editor.get('assignmentid')
3059              },
3060              on: {
3061                  success: function(tid, response) {
3062                      var jsondata;
3063                      try {
3064                          jsondata = Y.JSON.parse(response.responseText);
3065                          if (jsondata.error) {
3066                              return new M.core.ajaxException(jsondata);
3067                          } else {
3068                              Y.each(jsondata, function(comment) {
3069                                  var quickcomment = new M.assignfeedback_editpdf.quickcomment(comment.id,
3070                                                                                               comment.rawtext,
3071                                                                                               comment.width,
3072                                                                                               comment.colour);
3073                                  this.comments.push(quickcomment);
3074                              }, this);
3075                          }
3076                      } catch (e) {
3077                          return new M.core.exception(e);
3078                      }
3079                  },
3080                  failure: function(tid, response) {
3081                      return M.core.exception(response.responseText);
3082                  }
3083              }
3084          };
3085  
3086          Y.io(ajaxurl, config);
3087      };
3088  };
3089  
3090  M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
3091  M.assignfeedback_editpdf.quickcommentlist = QUICKCOMMENTLIST;
3092  // This file is part of Moodle - http://moodle.org/
3093  //
3094  // Moodle is free software: you can redistribute it and/or modify
3095  // it under the terms of the GNU General Public License as published by
3096  // the Free Software Foundation, either version 3 of the License, or
3097  // (at your option) any later version.
3098  //
3099  // Moodle is distributed in the hope that it will be useful,
3100  // but WITHOUT ANY WARRANTY; without even the implied warranty of
3101  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
3102  // GNU General Public License for more details.
3103  //
3104  // You should have received a copy of the GNU General Public License
3105  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
3106  /* eslint-disable no-unused-vars */
3107  /* global SELECTOR, TOOLSELECTOR, AJAXBASE, COMMENTCOLOUR, ANNOTATIONCOLOUR, AJAXBASEPROGRESS, CLICKTIMEOUT */
3108  
3109  /**
3110   * Provides an in browser PDF editor.
3111   *
3112   * @module moodle-assignfeedback_editpdf-editor
3113   */
3114  
3115  /**
3116   * EDITOR
3117   * This is an in browser PDF editor.
3118   *
3119   * @namespace M.assignfeedback_editpdf
3120   * @class editor
3121   * @constructor
3122   * @extends Y.Base
3123   */
3124  var EDITOR = function() {
3125      EDITOR.superclass.constructor.apply(this, arguments);
3126  };
3127  EDITOR.prototype = {
3128  
3129      /**
3130       * The dialogue used for all action menu displays.
3131       *
3132       * @property type
3133       * @type M.core.dialogue
3134       * @protected
3135       */
3136      dialogue: null,
3137  
3138      /**
3139       * The panel used for all action menu displays.
3140       *
3141       * @property type
3142       * @type Y.Node
3143       * @protected
3144       */
3145      panel: null,
3146  
3147      /**
3148       * The number of pages in the pdf.
3149       *
3150       * @property pagecount
3151       * @type Number
3152       * @protected
3153       */
3154      pagecount: 0,
3155  
3156      /**
3157       * The active page in the editor.
3158       *
3159       * @property currentpage
3160       * @type Number
3161       * @protected
3162       */
3163      currentpage: 0,
3164  
3165      /**
3166       * A list of page objects. Each page has a list of comments and annotations.
3167       *
3168       * @property pages
3169       * @type array
3170       * @protected
3171       */
3172      pages: [],
3173  
3174      /**
3175       * The yui node for the loading icon.
3176       *
3177       * @property loadingicon
3178       * @type Node
3179       * @protected
3180       */
3181      loadingicon: null,
3182  
3183      /**
3184       * Image object of the current page image.
3185       *
3186       * @property pageimage
3187       * @type Image
3188       * @protected
3189       */
3190      pageimage: null,
3191  
3192      /**
3193       * YUI Graphic class for drawing shapes.
3194       *
3195       * @property graphic
3196       * @type Graphic
3197       * @protected
3198       */
3199      graphic: null,
3200  
3201      /**
3202       * Info about the current edit operation.
3203       *
3204       * @property currentedit
3205       * @type M.assignfeedback_editpdf.edit
3206       * @protected
3207       */
3208      currentedit: new M.assignfeedback_editpdf.edit(),
3209  
3210      /**
3211       * Current drawable.
3212       *
3213       * @property currentdrawable
3214       * @type M.assignfeedback_editpdf.drawable|false
3215       * @protected
3216       */
3217      currentdrawable: false,
3218  
3219      /**
3220       * Current drawables.
3221       *
3222       * @property drawables
3223       * @type array(M.assignfeedback_editpdf.drawable)
3224       * @protected
3225       */
3226      drawables: [],
3227  
3228      /**
3229       * Current comment when the comment menu is open.
3230       * @property currentcomment
3231       * @type M.assignfeedback_editpdf.comment
3232       * @protected
3233       */
3234      currentcomment: null,
3235  
3236      /**
3237       * Current annotation when the select tool is used.
3238       * @property currentannotation
3239       * @type M.assignfeedback_editpdf.annotation
3240       * @protected
3241       */
3242      currentannotation: null,
3243  
3244      /**
3245       * Last selected annotation tool
3246       * @property lastannotationtool
3247       * @type String
3248       * @protected
3249       */
3250      lastanntationtool: "pen",
3251  
3252      /**
3253       * The users comments quick list
3254       * @property quicklist
3255       * @type M.assignfeedback_editpdf.quickcommentlist
3256       * @protected
3257       */
3258      quicklist: null,
3259  
3260      /**
3261       * The search comments window.
3262       * @property searchcommentswindow
3263       * @type M.core.dialogue
3264       * @protected
3265       */
3266      searchcommentswindow: null,
3267  
3268  
3269      /**
3270       * The selected stamp picture.
3271       * @property currentstamp
3272       * @type String
3273       * @protected
3274       */
3275      currentstamp: null,
3276  
3277      /**
3278       * The stamps.
3279       * @property stamps
3280       * @type Array
3281       * @protected
3282       */
3283      stamps: [],
3284  
3285      /**
3286       * Prevent new comments from appearing
3287       * immediately after clicking off a current
3288       * comment
3289       * @property editingcomment
3290       * @type Boolean
3291       * @public
3292       */
3293      editingcomment: false,
3294  
3295      /**
3296       * Called during the initialisation process of the object.
3297       * @method initializer
3298       */
3299      initializer: function() {
3300          var link;
3301  
3302          link = Y.one('#' + this.get('linkid'));
3303  
3304          if (link) {
3305              link.on('click', this.link_handler, this);
3306              link.on('key', this.link_handler, 'down:13', this);
3307  
3308              // We call the amd module to see if we can take control of the review panel.
3309              require(['mod_assign/grading_review_panel'], function(ReviewPanelManager) {
3310                  var panelManager = new ReviewPanelManager();
3311  
3312                  var panel = panelManager.getReviewPanel('assignfeedback_editpdf');
3313                  if (panel) {
3314                      panel = Y.one(panel);
3315                      panel.empty();
3316                      link.ancestor('.fitem').hide();
3317                      this.open_in_panel(panel);
3318                  }
3319                  this.currentedit.start = false;
3320                  this.currentedit.end = false;
3321                  if (!this.get('readonly')) {
3322                      this.quicklist = new M.assignfeedback_editpdf.quickcommentlist(this);
3323                  }
3324              }.bind(this));
3325  
3326          }
3327      },
3328  
3329      /**
3330       * Called to show/hide buttons and set the current colours/stamps.
3331       * @method refresh_button_state
3332       */
3333      refresh_button_state: function() {
3334          var button, currenttoolnode, imgurl, drawingregion;
3335  
3336          // Initalise the colour buttons.
3337          button = this.get_dialogue_element(SELECTOR.COMMENTCOLOURBUTTON);
3338  
3339          imgurl = M.util.image_url('background_colour_' + this.currentedit.commentcolour, 'assignfeedback_editpdf');
3340          button.one('img').setAttribute('src', imgurl);
3341  
3342          if (this.currentedit.commentcolour === 'clear') {
3343              button.one('img').setStyle('borderStyle', 'dashed');
3344          } else {
3345              button.one('img').setStyle('borderStyle', 'solid');
3346          }
3347  
3348          button = this.get_dialogue_element(SELECTOR.ANNOTATIONCOLOURBUTTON);
3349          imgurl = M.util.image_url('colour_' + this.currentedit.annotationcolour, 'assignfeedback_editpdf');
3350          button.one('img').setAttribute('src', imgurl);
3351  
3352          currenttoolnode = this.get_dialogue_element(TOOLSELECTOR[this.currentedit.tool]);
3353          currenttoolnode.addClass('assignfeedback_editpdf_selectedbutton');
3354          currenttoolnode.setAttribute('aria-pressed', 'true');
3355          drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION);
3356          drawingregion.setAttribute('data-currenttool', this.currentedit.tool);
3357  
3358          button = this.get_dialogue_element(SELECTOR.STAMPSBUTTON);
3359          button.one('img').setAttrs({'src': this.get_stamp_image_url(this.currentedit.stamp),
3360                                      'height': '16',
3361                                      'width': '16'});
3362      },
3363  
3364      /**
3365       * Called to get the bounds of the drawing region.
3366       * @method get_canvas_bounds
3367       */
3368      get_canvas_bounds: function() {
3369          var canvas = this.get_dialogue_element(SELECTOR.DRAWINGCANVAS),
3370              offsetcanvas = canvas.getXY(),
3371              offsetleft = offsetcanvas[0],
3372              offsettop = offsetcanvas[1],
3373              width = parseInt(canvas.getStyle('width'), 10),
3374              height = parseInt(canvas.getStyle('height'), 10);
3375  
3376          return new M.assignfeedback_editpdf.rect(offsetleft, offsettop, width, height);
3377      },
3378  
3379      /**
3380       * Called to translate from window coordinates to canvas coordinates.
3381       * @method get_canvas_coordinates
3382       * @param M.assignfeedback_editpdf.point point in window coordinats.
3383       */
3384      get_canvas_coordinates: function(point) {
3385          var bounds = this.get_canvas_bounds(),
3386              newpoint = new M.assignfeedback_editpdf.point(point.x - bounds.x, point.y - bounds.y);
3387  
3388          bounds.x = bounds.y = 0;
3389  
3390          newpoint.clip(bounds);
3391          return newpoint;
3392      },
3393  
3394      /**
3395       * Called to translate from canvas coordinates to window coordinates.
3396       * @method get_window_coordinates
3397       * @param M.assignfeedback_editpdf.point point in window coordinats.
3398       */
3399      get_window_coordinates: function(point) {
3400          var bounds = this.get_canvas_bounds(),
3401              newpoint = new M.assignfeedback_editpdf.point(point.x + bounds.x, point.y + bounds.y);
3402  
3403          return newpoint;
3404      },
3405  
3406      /**
3407       * Open the edit-pdf editor in the panel in the page instead of a popup.
3408       * @method open_in_panel
3409       */
3410      open_in_panel: function(panel) {
3411          var drawingcanvas, drawingregion;
3412  
3413          this.panel = panel;
3414          panel.append(this.get('body'));
3415          panel.addClass(CSS.DIALOGUE);
3416  
3417          this.loadingicon = this.get_dialogue_element(SELECTOR.LOADINGICON);
3418  
3419          drawingcanvas = this.get_dialogue_element(SELECTOR.DRAWINGCANVAS);
3420          this.graphic = new Y.Graphic({render: drawingcanvas});
3421  
3422          drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION);
3423          drawingregion.on('scroll', this.move_canvas, this);
3424  
3425          if (!this.get('readonly')) {
3426              drawingcanvas.on('gesturemovestart', this.edit_start, null, this);
3427              drawingcanvas.on('gesturemove', this.edit_move, null, this);
3428              drawingcanvas.on('gesturemoveend', this.edit_end, null, this);
3429  
3430              this.refresh_button_state();
3431          }
3432  
3433          this.load_all_pages();
3434      },
3435  
3436      /**
3437       * Called to open the pdf editing dialogue.
3438       * @method link_handler
3439       */
3440      link_handler: function(e) {
3441          var drawingcanvas, drawingregion;
3442          var resize = true;
3443          e.preventDefault();
3444  
3445          if (!this.dialogue) {
3446              this.dialogue = new M.core.dialogue({
3447                  headerContent: this.get('header'),
3448                  bodyContent: this.get('body'),
3449                  footerContent: this.get('footer'),
3450                  modal: true,
3451                  width: '840px',
3452                  visible: false,
3453                  draggable: true
3454              });
3455  
3456              // Add custom class for styling.
3457              this.dialogue.get('boundingBox').addClass(CSS.DIALOGUE);
3458  
3459              this.loadingicon = this.get_dialogue_element(SELECTOR.LOADINGICON);
3460  
3461              drawingcanvas = this.get_dialogue_element(SELECTOR.DRAWINGCANVAS);
3462              this.graphic = new Y.Graphic({render: drawingcanvas});
3463  
3464              drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION);
3465              drawingregion.on('scroll', this.move_canvas, this);
3466  
3467              if (!this.get('readonly')) {
3468                  drawingcanvas.on('gesturemovestart', this.edit_start, null, this);
3469                  drawingcanvas.on('gesturemove', this.edit_move, null, this);
3470                  drawingcanvas.on('gesturemoveend', this.edit_end, null, this);
3471  
3472                  this.refresh_button_state();
3473              }
3474  
3475              this.load_all_pages();
3476              drawingcanvas.on('windowresize', this.resize, this);
3477  
3478              resize = false;
3479          }
3480          this.dialogue.centerDialogue();
3481          this.dialogue.show();
3482  
3483          // Redraw when the dialogue is moved, to ensure the absolute elements are all positioned correctly.
3484          this.dialogue.dd.on('drag:end', this.redraw, this);
3485          if (resize) {
3486              this.resize(); // When re-opening the dialog call redraw, to make sure the size + layout is correct.
3487          }
3488      },
3489  
3490      /**
3491       * Called to load the information and annotations for all pages.
3492       * @method load_all_pages
3493       */
3494      load_all_pages: function() {
3495          var ajaxurl = AJAXBASE,
3496              config,
3497              checkconversionstatus,
3498              ajax_error_total;
3499  
3500          config = {
3501              method: 'get',
3502              context: this,
3503              sync: false,
3504              data: {
3505                  sesskey: M.cfg.sesskey,
3506                  action: 'loadallpages',
3507                  userid: this.get('userid'),
3508                  attemptnumber: this.get('attemptnumber'),
3509                  assignmentid: this.get('assignmentid'),
3510                  readonly: this.get('readonly') ? 1 : 0
3511              },
3512              on: {
3513                  success: function(tid, response) {
3514                      this.all_pages_loaded(response.responseText);
3515                  },
3516                  failure: function(tid, response) {
3517                      return new M.core.exception(response.responseText);
3518                  }
3519              }
3520          };
3521  
3522          Y.io(ajaxurl, config);
3523  
3524          // If pages are not loaded, check PDF conversion status for the progress bar.
3525          if (this.pagecount <= 0) {
3526              checkconversionstatus = {
3527                  method: 'get',
3528                  context: this,
3529                  sync: false,
3530                  data: {
3531                      sesskey: M.cfg.sesskey,
3532                      action: 'conversionstatus',
3533                      userid: this.get('userid'),
3534                      attemptnumber: this.get('attemptnumber'),
3535                      assignmentid: this.get('assignmentid')
3536                  },
3537                  on: {
3538                      success: function(tid, response) {
3539                          ajax_error_total = 0;
3540                          if (this.pagecount === 0) {
3541                              var pagetotal = this.get('pagetotal');
3542  
3543                              // Update the progress bar.
3544                              var progressbarcontainer = this.get_dialogue_element(SELECTOR.PROGRESSBARCONTAINER);
3545                              var progressbar = progressbarcontainer.one('.bar');
3546                              if (progressbar) {
3547                                  // Calculate progress.
3548                                  var progress = (response.response / pagetotal) * 100;
3549                                  progressbar.setStyle('width', progress + '%');
3550                                  progressbarcontainer.setAttribute('aria-valuenow', progress);
3551                              }
3552  
3553                              // New ajax request delayed of a second.
3554                              Y.later(1000, this, function() {
3555                                  Y.io(AJAXBASEPROGRESS, checkconversionstatus);
3556                              });
3557                          }
3558                      },
3559                      failure: function(tid, response) {
3560                          ajax_error_total = ajax_error_total + 1;
3561                          // We only continue on error if the all pages were not generated,
3562                          // and if the ajax call did not produce 5 errors in the row.
3563                          if (this.pagecount === 0 && ajax_error_total < 5) {
3564                              Y.later(1000, this, function() {
3565                                  Y.io(AJAXBASEPROGRESS, checkconversionstatus);
3566                              });
3567                          }
3568                          return new M.core.exception(response.responseText);
3569                      }
3570                  }
3571              };
3572              // We start the AJAX "generated page total number" call a second later to give a chance to
3573              // the AJAX "combined pdf generation" call to clean the previous submission images.
3574              Y.later(1000, this, function() {
3575                  ajax_error_total = 0;
3576                  Y.io(AJAXBASEPROGRESS, checkconversionstatus);
3577              });
3578          }
3579      },
3580  
3581      /**
3582       * The info about all pages in the pdf has been returned.
3583       * @param string The ajax response as text.
3584       * @protected
3585       * @method all_pages_loaded
3586       */
3587      all_pages_loaded: function(responsetext) {
3588          var data, i, j, comment, error;
3589          try {
3590              data = Y.JSON.parse(responsetext);
3591              if (data.error || !data.pagecount) {
3592                  if (this.dialogue) {
3593                      this.dialogue.hide();
3594                  }
3595                  // Display alert dialogue.
3596                  error = new M.core.alert({message: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf')});
3597                  error.show();
3598                  return;
3599              }
3600          } catch (e) {
3601              if (this.dialogue) {
3602                  this.dialogue.hide();
3603              }
3604              // Display alert dialogue.
3605              error = new M.core.alert({title: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf')});
3606              error.show();
3607              return;
3608          }
3609  
3610          this.pagecount = data.pagecount;
3611          this.pages = data.pages;
3612  
3613          for (i = 0; i < this.pages.length; i++) {
3614              for (j = 0; j < this.pages[i].comments.length; j++) {
3615                  comment = this.pages[i].comments[j];
3616                  this.pages[i].comments[j] = new M.assignfeedback_editpdf.comment(this,
3617                                                                                   comment.gradeid,
3618                                                                                   comment.pageno,
3619                                                                                   comment.x,
3620                                                                                   comment.y,
3621                                                                                   comment.width,
3622                                                                                   comment.colour,
3623                                                                                   comment.rawtext);
3624              }
3625              for (j = 0; j < this.pages[i].annotations.length; j++) {
3626                  data = this.pages[i].annotations[j];
3627                  this.pages[i].annotations[j] = this.create_annotation(data.type, data);
3628              }
3629          }
3630  
3631          // Update the ui.
3632          if (this.quicklist) {
3633              this.quicklist.load();
3634          }
3635          this.setup_navigation();
3636          this.setup_toolbar();
3637          this.change_page();
3638      },
3639  
3640      /**
3641       * Get the full pluginfile url for an image file - just given the filename.
3642       *
3643       * @public
3644       * @method get_stamp_image_url
3645       * @param string filename
3646       */
3647      get_stamp_image_url: function(filename) {
3648          var urls = this.get('stampfiles'),
3649              fullurl = '';
3650  
3651          Y.Array.each(urls, function(url) {
3652              if (url.indexOf(filename) > 0) {
3653                  fullurl = url;
3654              }
3655          }, this);
3656  
3657          return fullurl;
3658      },
3659  
3660      /**
3661       * Attach listeners and enable the color picker buttons.
3662       * @protected
3663       * @method setup_toolbar
3664       */
3665      setup_toolbar: function() {
3666          var toolnode,
3667              commentcolourbutton,
3668              annotationcolourbutton,
3669              searchcommentsbutton,
3670              currentstampbutton,
3671              stampfiles,
3672              picker,
3673              filename;
3674  
3675          searchcommentsbutton = this.get_dialogue_element(SELECTOR.SEARCHCOMMENTSBUTTON);
3676          searchcommentsbutton.on('click', this.open_search_comments, this);
3677          searchcommentsbutton.on('key', this.open_search_comments, 'down:13', this);
3678  
3679          if (this.get('readonly')) {
3680              return;
3681          }
3682          // Setup the tool buttons.
3683          Y.each(TOOLSELECTOR, function(selector, tool) {
3684              toolnode = this.get_dialogue_element(selector);
3685              toolnode.on('click', this.handle_tool_button, this, tool);
3686              toolnode.on('key', this.handle_tool_button, 'down:13', this, tool);
3687              toolnode.setAttribute('aria-pressed', 'false');
3688          }, this);
3689  
3690          // Set the default tool.
3691  
3692          commentcolourbutton = this.get_dialogue_element(SELECTOR.COMMENTCOLOURBUTTON);
3693          picker = new M.assignfeedback_editpdf.colourpicker({
3694              buttonNode: commentcolourbutton,
3695              colours: COMMENTCOLOUR,
3696              iconprefix: 'background_colour_',
3697              callback: function(e) {
3698                  var colour = e.target.getAttribute('data-colour');
3699                  if (!colour) {
3700                      colour = e.target.ancestor().getAttribute('data-colour');
3701                  }
3702                  this.currentedit.commentcolour = colour;
3703                  this.handle_tool_button(e, "comment");
3704              },
3705              context: this
3706          });
3707  
3708          annotationcolourbutton = this.get_dialogue_element(SELECTOR.ANNOTATIONCOLOURBUTTON);
3709          picker = new M.assignfeedback_editpdf.colourpicker({
3710              buttonNode: annotationcolourbutton,
3711              iconprefix: 'colour_',
3712              colours: ANNOTATIONCOLOUR,
3713              callback: function(e) {
3714                  var colour = e.target.getAttribute('data-colour');
3715                  if (!colour) {
3716                      colour = e.target.ancestor().getAttribute('data-colour');
3717                  }
3718                  this.currentedit.annotationcolour = colour;
3719                  if (this.lastannotationtool) {
3720                      this.handle_tool_button(e, this.lastannotationtool);
3721                  } else {
3722                      this.handle_tool_button(e, "pen");
3723                  }
3724              },
3725              context: this
3726          });
3727  
3728          stampfiles = this.get('stampfiles');
3729          if (stampfiles.length <= 0) {
3730              this.get_dialogue_element(TOOLSELECTOR.stamp).ancestor().hide();
3731          } else {
3732              filename = stampfiles[0].substr(stampfiles[0].lastIndexOf('/') + 1);
3733              this.currentedit.stamp = filename;
3734              currentstampbutton = this.get_dialogue_element(SELECTOR.STAMPSBUTTON);
3735  
3736              picker = new M.assignfeedback_editpdf.stamppicker({
3737                  buttonNode: currentstampbutton,
3738                  stamps: stampfiles,
3739                  callback: function(e) {
3740                      var stamp = e.target.getAttribute('data-stamp'),
3741                          filename;
3742  
3743                      if (!stamp) {
3744                          stamp = e.target.ancestor().getAttribute('data-stamp');
3745                      }
3746                      filename = stamp.substr(stamp.lastIndexOf('/'));
3747                      this.currentedit.stamp = filename;
3748                      this.handle_tool_button(e, "stamp");
3749                  },
3750                  context: this
3751              });
3752              this.refresh_button_state();
3753          }
3754      },
3755  
3756      /**
3757       * Change the current tool.
3758       * @protected
3759       * @method handle_tool_button
3760       */
3761      handle_tool_button: function(e, tool) {
3762          var currenttoolnode;
3763  
3764          e.preventDefault();
3765  
3766          // Change style of the pressed button.
3767          currenttoolnode = this.get_dialogue_element(TOOLSELECTOR[this.currentedit.tool]);
3768          currenttoolnode.removeClass('assignfeedback_editpdf_selectedbutton');
3769          currenttoolnode.setAttribute('aria-pressed', 'false');
3770          this.currentedit.tool = tool;
3771  
3772          if (tool !== "comment" && tool !== "select" && tool !== "drag" && tool !== "stamp") {
3773              this.lastannotationtool = tool;
3774          }
3775          this.refresh_button_state();
3776      },
3777  
3778      /**
3779       * JSON encode the current page data - stripping out drawable references which cannot be encoded.
3780       * @protected
3781       * @method stringify_current_page
3782       * @return string
3783       */
3784      stringify_current_page: function() {
3785          var comments = [],
3786              annotations = [],
3787              page,
3788              i = 0;
3789  
3790          for (i = 0; i < this.pages[this.currentpage].comments.length; i++) {
3791              comments[i] = this.pages[this.currentpage].comments[i].clean();
3792          }
3793          for (i = 0; i < this.pages[this.currentpage].annotations.length; i++) {
3794              annotations[i] = this.pages[this.currentpage].annotations[i].clean();
3795          }
3796  
3797          page = {comments: comments, annotations: annotations};
3798  
3799          return Y.JSON.stringify(page);
3800      },
3801  
3802      /**
3803       * Generate a drawable from the current in progress edit.
3804       * @protected
3805       * @method get_current_drawable
3806       */
3807      get_current_drawable: function() {
3808          var comment,
3809              annotation,
3810              drawable = false;
3811  
3812          if (!this.currentedit.start || !this.currentedit.end) {
3813              return false;
3814          }
3815  
3816          if (this.currentedit.tool === 'comment') {
3817              comment = new M.assignfeedback_editpdf.comment(this);
3818              drawable = comment.draw_current_edit(this.currentedit);
3819          } else {
3820              annotation = this.create_annotation(this.currentedit.tool, {});
3821              if (annotation) {
3822                  drawable = annotation.draw_current_edit(this.currentedit);
3823              }
3824          }
3825  
3826          return drawable;
3827      },
3828  
3829      /**
3830       * Find an element within the dialogue.
3831       * @protected
3832       * @method get_dialogue_element
3833       */
3834      get_dialogue_element: function(selector) {
3835          if (this.panel) {
3836              return this.panel.one(selector);
3837          } else {
3838              return this.dialogue.get('boundingBox').one(selector);
3839          }
3840      },
3841  
3842      /**
3843       * Redraw the active edit.
3844       * @protected
3845       * @method redraw_active_edit
3846       */
3847      redraw_current_edit: function() {
3848          if (this.currentdrawable) {
3849              this.currentdrawable.erase();
3850          }
3851          this.currentdrawable = this.get_current_drawable();
3852      },
3853  
3854      /**
3855       * Event handler for mousedown or touchstart.
3856       * @protected
3857       * @param Event
3858       * @method edit_start
3859       */
3860      edit_start: function(e) {
3861          e.preventDefault();
3862          var canvas = this.get_dialogue_element(SELECTOR.DRAWINGCANVAS),
3863              offset = canvas.getXY(),
3864              scrolltop = canvas.get('docScrollY'),
3865              scrollleft = canvas.get('docScrollX'),
3866              point = {x: e.clientX - offset[0] + scrollleft,
3867                       y: e.clientY - offset[1] + scrolltop},
3868              selected = false,
3869              lastannotation;
3870  
3871          // Ignore right mouse click.
3872          if (e.button === 3) {
3873              return;
3874          }
3875  
3876          if (this.currentedit.starttime) {
3877              return;
3878          }
3879  
3880          if (this.editingcomment) {
3881              return;
3882          }
3883  
3884          this.currentedit.starttime = new Date().getTime();
3885          this.currentedit.start = point;
3886          this.currentedit.end = {x: point.x, y: point.y};
3887  
3888          if (this.currentedit.tool === 'select') {
3889              var x = this.currentedit.end.x,
3890                  y = this.currentedit.end.y,
3891                  annotations = this.pages[this.currentpage].annotations;
3892              // Find the first annotation whose bounds encompass the click.
3893              Y.each(annotations, function(annotation) {
3894                  if (((x - annotation.x) * (x - annotation.endx)) <= 0 &&
3895                      ((y - annotation.y) * (y - annotation.endy)) <= 0) {
3896                      selected = annotation;
3897                  }
3898              });
3899  
3900              if (selected) {
3901                  lastannotation = this.currentannotation;
3902                  this.currentannotation = selected;
3903                  if (lastannotation && lastannotation !== selected) {
3904                      // Redraw the last selected annotation to remove the highlight.
3905                      if (lastannotation.drawable) {
3906                          lastannotation.drawable.erase();
3907                          this.drawables.push(lastannotation.draw());
3908                      }
3909                  }
3910                  // Redraw the newly selected annotation to show the highlight.
3911                  if (this.currentannotation.drawable) {
3912                      this.currentannotation.drawable.erase();
3913                  }
3914                  this.drawables.push(this.currentannotation.draw());
3915              }
3916          }
3917          if (this.currentannotation) {
3918              // Used to calculate drag offset.
3919              this.currentedit.annotationstart = {x: this.currentannotation.x,
3920                                                   y: this.currentannotation.y};
3921          }
3922      },
3923  
3924      /**
3925       * Event handler for mousemove.
3926       * @protected
3927       * @param Event
3928       * @method edit_move
3929       */
3930      edit_move: function(e) {
3931          e.preventDefault();
3932          var bounds = this.get_canvas_bounds(),
3933              canvas = this.get_dialogue_element(SELECTOR.DRAWINGCANVAS),
3934              drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION),
3935              clientpoint = new M.assignfeedback_editpdf.point(e.clientX + canvas.get('docScrollX'),
3936                                                               e.clientY + canvas.get('docScrollY')),
3937              point = this.get_canvas_coordinates(clientpoint),
3938              diffX,
3939              diffY;
3940  
3941          // Ignore events out of the canvas area.
3942          if (point.x < 0 || point.x > bounds.width || point.y < 0 || point.y > bounds.height) {
3943              return;
3944          }
3945  
3946          if (this.currentedit.tool === 'pen') {
3947              this.currentedit.path.push(point);
3948          }
3949  
3950          if (this.currentedit.tool === 'select') {
3951              if (this.currentannotation && this.currentedit) {
3952                  this.currentannotation.move(this.currentedit.annotationstart.x + point.x - this.currentedit.start.x,
3953                                               this.currentedit.annotationstart.y + point.y - this.currentedit.start.y);
3954              }
3955          } else if (this.currentedit.tool === 'drag') {
3956              diffX = point.x - this.currentedit.start.x;
3957              diffY = point.y - this.currentedit.start.y;
3958  
3959              drawingregion.getDOMNode().scrollLeft -= diffX;
3960              drawingregion.getDOMNode().scrollTop -= diffY;
3961  
3962          } else {
3963              if (this.currentedit.start) {
3964                  this.currentedit.end = point;
3965                  this.redraw_current_edit();
3966              }
3967          }
3968      },
3969  
3970      /**
3971       * Event handler for mouseup or touchend.
3972       * @protected
3973       * @param Event
3974       * @method edit_end
3975       */
3976      edit_end: function() {
3977          var duration,
3978              comment,
3979              annotation;
3980  
3981          duration = new Date().getTime() - this.currentedit.start;
3982  
3983          if (duration < CLICKTIMEOUT || this.currentedit.start === false) {
3984              return;
3985          }
3986  
3987          if (this.currentedit.tool === 'comment') {
3988              if (this.currentdrawable) {
3989                  this.currentdrawable.erase();
3990              }
3991              this.currentdrawable = false;
3992              comment = new M.assignfeedback_editpdf.comment(this);
3993              if (comment.init_from_edit(this.currentedit)) {
3994                  this.pages[this.currentpage].comments.push(comment);
3995                  this.drawables.push(comment.draw(true));
3996                  this.editingcomment = true;
3997              }
3998          } else {
3999              annotation = this.create_annotation(this.currentedit.tool, {});
4000              if (annotation) {
4001                  if (this.currentdrawable) {
4002                      this.currentdrawable.erase();
4003                  }
4004                  this.currentdrawable = false;
4005                  if (annotation.init_from_edit(this.currentedit)) {
4006                      this.pages[this.currentpage].annotations.push(annotation);
4007                      this.drawables.push(annotation.draw());
4008                  }
4009              }
4010          }
4011  
4012          // Save the changes.
4013          this.save_current_page();
4014  
4015          // Reset the current edit.
4016          this.currentedit.starttime = 0;
4017          this.currentedit.start = false;
4018          this.currentedit.end = false;
4019          this.currentedit.path = [];
4020      },
4021  
4022      /**
4023       * Resize the dialogue window when the browser is resized.
4024       * @public
4025       * @method resize
4026       */
4027      resize: function() {
4028          var drawingregion, drawregionheight;
4029  
4030          if (this.dialogue) {
4031              if (!this.dialogue.get('visible')) {
4032                  return;
4033              }
4034              this.dialogue.centerDialogue();
4035          }
4036  
4037          // Make sure the dialogue box is not bigger than the max height of the viewport.
4038          drawregionheight = Y.one('body').get('winHeight') - 120; // Space for toolbar + titlebar.
4039          if (drawregionheight < 100) {
4040              drawregionheight = 100;
4041          }
4042          drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION);
4043          if (this.dialogue) {
4044              drawingregion.setStyle('maxHeight', drawregionheight + 'px');
4045          }
4046          this.redraw();
4047          return true;
4048      },
4049  
4050      /**
4051       * Factory method for creating annotations of the correct subclass.
4052       * @public
4053       * @method create_annotation
4054       */
4055      create_annotation: function(type, data) {
4056          data.type = type;
4057          data.editor = this;
4058          if (type === "line") {
4059              return new M.assignfeedback_editpdf.annotationline(data);
4060          } else if (type === "rectangle") {
4061              return new M.assignfeedback_editpdf.annotationrectangle(data);
4062          } else if (type === "oval") {
4063              return new M.assignfeedback_editpdf.annotationoval(data);
4064          } else if (type === "pen") {
4065              return new M.assignfeedback_editpdf.annotationpen(data);
4066          } else if (type === "highlight") {
4067              return new M.assignfeedback_editpdf.annotationhighlight(data);
4068          } else if (type === "stamp") {
4069              return new M.assignfeedback_editpdf.annotationstamp(data);
4070          }
4071          return false;
4072      },
4073  
4074      /**
4075       * Save all the annotations and comments for the current page.
4076       * @protected
4077       * @method save_current_page
4078       */
4079      save_current_page: function() {
4080          var ajaxurl = AJAXBASE,
4081              config;
4082  
4083          config = {
4084              method: 'post',
4085              context: this,
4086              sync: false,
4087              data: {
4088                  'sesskey': M.cfg.sesskey,
4089                  'action': 'savepage',
4090                  'index': this.currentpage,
4091                  'userid': this.get('userid'),
4092                  'attemptnumber': this.get('attemptnumber'),
4093                  'assignmentid': this.get('assignmentid'),
4094                  'page': this.stringify_current_page()
4095              },
4096              on: {
4097                  success: function(tid, response) {
4098                      var jsondata;
4099                      try {
4100                          jsondata = Y.JSON.parse(response.responseText);
4101                          if (jsondata.error) {
4102                              return new M.core.ajaxException(jsondata);
4103                          }
4104                          Y.one(SELECTOR.UNSAVEDCHANGESINPUT).set('value', 'true');
4105                          Y.one(SELECTOR.UNSAVEDCHANGESDIV).setStyle('opacity', 1);
4106                          Y.one(SELECTOR.UNSAVEDCHANGESDIV).setStyle('display', 'inline-block');
4107                          Y.one(SELECTOR.UNSAVEDCHANGESDIV).transition({
4108                              duration: 1,
4109                              delay: 2,
4110                              opacity: 0
4111                          }, function() {
4112                              Y.one(SELECTOR.UNSAVEDCHANGESDIV).setStyle('display', 'none');
4113                          });
4114                      } catch (e) {
4115                          return new M.core.exception(e);
4116                      }
4117                  },
4118                  failure: function(tid, response) {
4119                      return new M.core.exception(response.responseText);
4120                  }
4121              }
4122          };
4123  
4124          Y.io(ajaxurl, config);
4125  
4126      },
4127  
4128      /**
4129       * Event handler to open the comment search interface.
4130       *
4131       * @param Event e
4132       * @protected
4133       * @method open_search_comments
4134       */
4135      open_search_comments: function(e) {
4136          if (!this.searchcommentswindow) {
4137              this.searchcommentswindow = new M.assignfeedback_editpdf.commentsearch({
4138                  editor: this
4139              });
4140          }
4141  
4142          this.searchcommentswindow.show();
4143          e.preventDefault();
4144      },
4145  
4146      /**
4147       * Redraw all the comments and annotations.
4148       * @protected
4149       * @method redraw
4150       */
4151      redraw: function() {
4152          var i,
4153              page;
4154  
4155          page = this.pages[this.currentpage];
4156          if (page === undefined) {
4157              return; // Can happen if a redraw is triggered by an event, before the page has been selected.
4158          }
4159          while (this.drawables.length > 0) {
4160              this.drawables.pop().erase();
4161          }
4162  
4163          for (i = 0; i < page.annotations.length; i++) {
4164              this.drawables.push(page.annotations[i].draw());
4165          }
4166          for (i = 0; i < page.comments.length; i++) {
4167              this.drawables.push(page.comments[i].draw(false));
4168          }
4169      },
4170  
4171      /**
4172       * Load the image for this pdf page and remove the loading icon (if there).
4173       * @protected
4174       * @method change_page
4175       */
4176      change_page: function() {
4177          var drawingcanvas = this.get_dialogue_element(SELECTOR.DRAWINGCANVAS),
4178              page,
4179              previousbutton,
4180              nextbutton;
4181  
4182          previousbutton = this.get_dialogue_element(SELECTOR.PREVIOUSBUTTON);
4183          nextbutton = this.get_dialogue_element(SELECTOR.NEXTBUTTON);
4184  
4185          if (this.currentpage > 0) {
4186              previousbutton.removeAttribute('disabled');
4187          } else {
4188              previousbutton.setAttribute('disabled', 'true');
4189          }
4190          if (this.currentpage < (this.pagecount - 1)) {
4191              nextbutton.removeAttribute('disabled');
4192          } else {
4193              nextbutton.setAttribute('disabled', 'true');
4194          }
4195  
4196          page = this.pages[this.currentpage];
4197          this.loadingicon.hide();
4198          drawingcanvas.setStyle('backgroundImage', 'url("' + page.url + '")');
4199          drawingcanvas.setStyle('width', page.width + 'px');
4200          drawingcanvas.setStyle('height', page.height + 'px');
4201  
4202          // Update page select.
4203          this.get_dialogue_element(SELECTOR.PAGESELECT).set('selectedIndex', this.currentpage);
4204  
4205          this.resize(); // Internally will call 'redraw', after checking the dialogue size.
4206      },
4207  
4208      /**
4209       * Now we know how many pages there are,
4210       * we can enable the navigation controls.
4211       * @protected
4212       * @method setup_navigation
4213       */
4214      setup_navigation: function() {
4215          var pageselect,
4216              i,
4217              strinfo,
4218              option,
4219              previousbutton,
4220              nextbutton;
4221  
4222          pageselect = this.get_dialogue_element(SELECTOR.PAGESELECT);
4223  
4224          var options = pageselect.all('option');
4225          if (options.size() <= 1) {
4226              for (i = 0; i < this.pages.length; i++) {
4227                  option = Y.Node.create('<option/>');
4228                  option.setAttribute('value', i);
4229                  strinfo = {page: i + 1, total: this.pages.length};
4230                  option.setHTML(M.util.get_string('pagexofy', 'assignfeedback_editpdf', strinfo));
4231                  pageselect.append(option);
4232              }
4233          }
4234          pageselect.removeAttribute('disabled');
4235          pageselect.on('change', function() {
4236              this.currentpage = pageselect.get('value');
4237              this.change_page();
4238          }, this);
4239  
4240          previousbutton = this.get_dialogue_element(SELECTOR.PREVIOUSBUTTON);
4241          nextbutton = this.get_dialogue_element(SELECTOR.NEXTBUTTON);
4242  
4243          previousbutton.on('click', this.previous_page, this);
4244          previousbutton.on('key', this.previous_page, 'down:13', this);
4245          nextbutton.on('click', this.next_page, this);
4246          nextbutton.on('key', this.next_page, 'down:13', this);
4247      },
4248  
4249      /**
4250       * Navigate to the previous page.
4251       * @protected
4252       * @method previous_page
4253       */
4254      previous_page: function(e) {
4255          e.preventDefault();
4256          this.currentpage--;
4257          if (this.currentpage < 0) {
4258              this.currentpage = 0;
4259          }
4260          this.change_page();
4261      },
4262  
4263      /**
4264       * Navigate to the next page.
4265       * @protected
4266       * @method next_page
4267       */
4268      next_page: function(e) {
4269          e.preventDefault();
4270          this.currentpage++;
4271          if (this.currentpage >= this.pages.length) {
4272              this.currentpage = this.pages.length - 1;
4273          }
4274          this.change_page();
4275      },
4276  
4277      /**
4278       * Update any absolutely positioned nodes, within each drawable, when the drawing canvas is scrolled
4279       * @protected
4280       * @method move_canvas
4281       */
4282      move_canvas: function() {
4283          var drawingregion, x, y, i;
4284  
4285          drawingregion = this.get_dialogue_element(SELECTOR.DRAWINGREGION);
4286          x = parseInt(drawingregion.get('scrollLeft'), 10);
4287          y = parseInt(drawingregion.get('scrollTop'), 10);
4288  
4289          for (i = 0; i < this.drawables.length; i++) {
4290              this.drawables[i].scroll_update(x, y);
4291          }
4292      }
4293  
4294  };
4295  
4296  Y.extend(EDITOR, Y.Base, EDITOR.prototype, {
4297      NAME: 'moodle-assignfeedback_editpdf-editor',
4298      ATTRS: {
4299          userid: {
4300              validator: Y.Lang.isInteger,
4301              value: 0
4302          },
4303          assignmentid: {
4304              validator: Y.Lang.isInteger,
4305              value: 0
4306          },
4307          attemptnumber: {
4308              validator: Y.Lang.isInteger,
4309              value: 0
4310          },
4311          header: {
4312              validator: Y.Lang.isString,
4313              value: ''
4314          },
4315          body: {
4316              validator: Y.Lang.isString,
4317              value: ''
4318          },
4319          footer: {
4320              validator: Y.Lang.isString,
4321              value: ''
4322          },
4323          linkid: {
4324              validator: Y.Lang.isString,
4325              value: ''
4326          },
4327          deletelinkid: {
4328              validator: Y.Lang.isString,
4329              value: ''
4330          },
4331          readonly: {
4332              validator: Y.Lang.isBoolean,
4333              value: true
4334          },
4335          stampfiles: {
4336              validator: Y.Lang.isArray,
4337              value: ''
4338          },
4339          pagetotal: {
4340              validator: Y.Lang.isInteger,
4341              value: 0
4342          }
4343      }
4344  });
4345  
4346  M.assignfeedback_editpdf = M.assignfeedback_editpdf || {};
4347  M.assignfeedback_editpdf.editor = M.assignfeedback_editpdf.editor || {};
4348  
4349  /**
4350   * Init function - will create a new instance every time.
4351   * @method editor.init
4352   * @static
4353   * @param {Object} params
4354   */
4355  M.assignfeedback_editpdf.editor.init = M.assignfeedback_editpdf.editor.init || function(params) {
4356      M.assignfeedback_editpdf.instance = new EDITOR(params);
4357      return M.assignfeedback_editpdf.instance;
4358  };
4359  
4360  
4361  }, '@VERSION@', {
4362      "requires": [
4363          "base",
4364          "event",
4365          "node",
4366          "io",
4367          "graphics",
4368          "json",
4369          "event-move",
4370          "event-resize",
4371          "transition",
4372          "querystring-stringify-simple",
4373          "moodle-core-notification-dialog",
4374          "moodle-core-notification-exception",
4375          "moodle-core-notification-ajaxexception"
4376      ]
4377  });


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1