[
  {
    "path": ".gitattributes",
    "content": "*.js linguist-language=java\n*.css linguist-language=java\n*.html linguist-language=java"
  },
  {
    "path": ".gitignore",
    "content": "target\r\n*.iml\r\nout/\r\n.idea\r\n.classpath\r\n.project\r\n.settings\r\nbin/\r\n.myeclipse"
  },
  {
    "path": "Dockerfile",
    "content": "FROM java:8\n\nMAINTAINER octopus\n\nRUN mkdir -p /spider-flow\n\nWORKDIR /spider-flow\n\nEXPOSE 8088\n\nADD ./spider-flow-web/target/spider-flow.jar ./\n\nCMD sleep 30;java -Djava.security.egd=file:/dev/./urandom -jar spider-flow.jar"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\r\n\r\nCopyright (c) 2019 小东\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\nSOFTWARE.\r\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\r\n    <img src=\"https://www.spiderflow.org/images/logo.svg\" width=\"600\">\r\n</p>\r\n<p align=\"center\">\r\n    <a target=\"_blank\" href=\"https://www.oracle.com/technetwork/java/javase/downloads/index.html\"><img src=\"https://img.shields.io/badge/JDK-1.8+-green.svg\" /></a>\r\n    <a target=\"_blank\" href=\"https://www.spiderflow.org\"><img src=\"https://img.shields.io/badge/Docs-latest-blue.svg\"/></a>\r\n    <a target=\"_blank\" href=\"https://github.com/ssssssss-team/spider-flow/releases\"><img src=\"https://img.shields.io/github/v/release/ssssssss-team/spider-flow?logo=github\"></a>\r\n    <a target=\"_blank\" href='https://gitee.com/ssssssss-team/spider-flow'><img src=\"https://gitee.com/ssssssss-team/spider-flow/badge/star.svg?theme=white\" /></a>\r\n    <a target=\"_blank\" href='https://github.com/ssssssss-team/spider-flow'><img src=\"https://img.shields.io/github/stars/ssssssss-team/spider-flow.svg?style=social\"/></a>\r\n    <a target=\"_blank\" href=\"LICENSE\"><img src=\"https://img.shields.io/:license-MIT-blue.svg\"></a>\r\n    <a target=\"_blank\" href=\"https://shang.qq.com/wpa/qunwpa?idkey=10faa4cf9743e0aa379a72f2ad12a9e576c81462742143c8f3391b52e8c3ed8d\"><img src=\"https://img.shields.io/badge/Join-QQGroup-blue\"></a>\r\n</p>\r\n\r\n[介绍](#介绍) | [特性](#特性) | [插件](#插件) | <a target=\"_blank\" href=\"http://demo.spiderflow.org\">DEMO站点</a> | <a target=\"_blank\" href=\"https://www.spiderflow.org\">文档</a> | <a target=\"_blank\" href=\"https://www.spiderflow.org/changelog.html\">更新日志</a> | [截图](#项目部分截图) | [其它开源](#其它开源项目) | [免责声明](#免责声明)\r\n\r\n## 介绍\r\n平台以流程图的方式定义爬虫,是一个高度灵活可配置的爬虫平台\r\n\r\n## 特性\r\n- [x] 支持Xpath/JsonPath/css选择器/正则提取/混搭提取\r\n- [x] 支持JSON/XML/二进制格式\r\n- [x] 支持多数据源、SQL select/selectInt/selectOne/insert/update/delete\r\n- [x] 支持爬取JS动态渲染(或ajax)的页面\r\n- [x] 支持代理\r\n- [x] 支持自动保存至数据库/文件\r\n- [x] 常用字符串、日期、文件、加解密等函数\r\n- [x] 支持插件扩展(自定义执行器，自定义方法）\r\n- [x] 任务监控,任务日志\r\n- [x] 支持HTTP接口\r\n- [x] 支持Cookie自动管理\r\n- [x] 支持自定义函数\r\n\r\n## 插件\r\n- [x] [Selenium插件](https://gitee.com/ssssssss-team/spider-flow-selenium)\r\n- [x] [Redis插件](https://gitee.com/ssssssss-team/spider-flow-redis)\r\n- [x] [OSS插件](https://gitee.com/ssssssss-team/spider-flow-oss)\r\n- [x] [Mongodb插件](https://gitee.com/ssssssss-team/spider-flow-mongodb)\r\n- [x] [IP代理池插件](https://gitee.com/ssssssss-team/spider-flow-proxypool)\r\n- [x] [OCR识别插件](https://gitee.com/ssssssss-team/spider-flow-ocr)\r\n- [x] [电子邮箱插件](https://gitee.com/ssssssss-team/spider-flow-mailbox)\r\n\r\n## 项目部分截图\r\n### 爬虫列表\r\n![爬虫列表](https://images.gitee.com/uploads/images/2020/0412/104521_e1eb3fbb_297689.png \"list.png\")\r\n### 爬虫测试\r\n![爬虫测试](https://images.gitee.com/uploads/images/2020/0412/104659_b06dfbf0_297689.gif \"test.gif\")\r\n### Debug\r\n![Debug](https://images.gitee.com/uploads/images/2020/0412/104741_f9e1190e_297689.png \"debug.png\")\r\n### 日志\r\n![日志](https://images.gitee.com/uploads/images/2020/0412/104800_a757f569_297689.png \"logo.png\")\r\n\r\n## 其它开源项目\r\n- [spider-flow-vue，spider-flow的前端](https://gitee.com/ssssssss-team/spider-flow-vue)\r\n- [magic-api，一个以XML为基础自动映射为HTTP接口的框架](https://gitee.com/ssssssss-team/magic-api)\r\n- [magic-api-spring-boot-starter](https://gitee.com/ssssssss-team/magic-api-spring-boot-starter)\r\n\r\n\r\n## 免责声明\r\n请勿将`spider-flow`应用到任何可能会违反法律规定和道德约束的工作中，请友善使用`spider-flow`，遵守蜘蛛协议，不要将`spider-flow`用于任何非法用途。如您选择使用`spider-flow`即代表您遵守此协议，作者不承担任何由于您违反此协议带来任何的法律风险和损失，一切后果由您承担。\r\n"
  },
  {
    "path": "db/spiderflow.sql",
    "content": "SET FOREIGN_KEY_CHECKS=0;\n\nCREATE DATABASE spiderflow;\nUSE spiderflow;\n\nDROP TABLE IF EXISTS `sp_flow`;\nCREATE TABLE `sp_flow` (\n  `id` varchar(32) NOT NULL,\n  `name` varchar(64) DEFAULT NULL COMMENT '任务名字',\n  `xml` longtext DEFAULT NULL COMMENT 'xml表达式',\n  `cron` varchar(255) DEFAULT NULL COMMENT 'corn表达式',\n  `enabled` char(1) DEFAULT '0' COMMENT '任务是否启动,默认未启动',\n  `create_date` datetime DEFAULT CURRENT_TIMESTAMP  COMMENT '创建时间',\n  `last_execute_time` datetime DEFAULT NULL  COMMENT '上一次执行时间',\n  `next_execute_time` datetime DEFAULT NULL   COMMENT '下一次执行时间',\n  `execute_count` int(8) DEFAULT NULL  COMMENT '定时执行的已执行次数',\n  PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '爬虫任务表';\n\nINSERT INTO `sp_flow` VALUES ('b45fb98d2a564c23ba623a377d5e12e9', '爬取码云GVP', '<mxGraphModel>\\n  <root>\\n    <mxCell id=\\\"0\\\">\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;spiderName&quot;:&quot;爬取码云GVP&quot;,&quot;threadCount&quot;:&quot;&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"1\\\" parent=\\\"0\\\"/>\\n    <mxCell id=\\\"2\\\" value=\\\"开始\\\" style=\\\"start\\\" parent=\\\"1\\\" vertex=\\\"1\\\">\\n      <mxGeometry x=\\\"80\\\" y=\\\"80\\\" width=\\\"24\\\" height=\\\"24\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;shape&quot;:&quot;start&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"5\\\" value=\\\"抓取首页\\\" style=\\\"request\\\" parent=\\\"1\\\" vertex=\\\"1\\\">\\n      <mxGeometry x=\\\"180\\\" y=\\\"80\\\" width=\\\"24\\\" height=\\\"24\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;抓取首页&quot;,&quot;loopVariableName&quot;:&quot;&quot;,&quot;sleep&quot;:&quot;&quot;,&quot;timeout&quot;:&quot;&quot;,&quot;response-charset&quot;:&quot;&quot;,&quot;method&quot;:&quot;GET&quot;,&quot;body-type&quot;:&quot;none&quot;,&quot;body-content-type&quot;:&quot;text/plain&quot;,&quot;loopCount&quot;:&quot;&quot;,&quot;url&quot;:&quot;https://gitee.com/gvp/all&quot;,&quot;proxy&quot;:&quot;&quot;,&quot;request-body&quot;:[&quot;&quot;],&quot;follow-redirect&quot;:&quot;1&quot;,&quot;shape&quot;:&quot;request&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"6\\\" value=\\\"\\\" parent=\\\"1\\\" source=\\\"2\\\" target=\\\"5\\\" edge=\\\"1\\\">\\n      <mxGeometry relative=\\\"1\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;&quot;,&quot;condition&quot;:&quot;&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"7\\\" value=\\\"提取项目名、地址\\\" style=\\\"variable\\\" parent=\\\"1\\\" vertex=\\\"1\\\">\\n      <mxGeometry x=\\\"330\\\" y=\\\"80\\\" width=\\\"24\\\" height=\\\"24\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;提取项目名、地址&quot;,&quot;loopVariableName&quot;:&quot;&quot;,&quot;variable-name&quot;:[&quot;projectUrls&quot;,&quot;projectNames&quot;],&quot;loopCount&quot;:&quot;&quot;,&quot;variable-value&quot;:[&quot;${extract.selectors(resp.html,&#39;.categorical-project-card a&#39;,&#39;attr&#39;,&#39;href&#39;)}&quot;,&quot;${extract.selectors(resp.html,&#39;.project-name&#39;)}&quot;],&quot;shape&quot;:&quot;variable&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"8\\\" value=\\\"\\\" parent=\\\"1\\\" source=\\\"5\\\" target=\\\"7\\\" edge=\\\"1\\\">\\n      <mxGeometry relative=\\\"1\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;&quot;,&quot;condition&quot;:&quot;&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"9\\\" value=\\\"抓取详情页\\\" style=\\\"request\\\" parent=\\\"1\\\" vertex=\\\"1\\\">\\n      <mxGeometry x=\\\"450.16668701171875\\\" y=\\\"80\\\" width=\\\"24\\\" height=\\\"24\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;抓取详情页&quot;,&quot;loopVariableName&quot;:&quot;projectIndex&quot;,&quot;sleep&quot;:&quot;&quot;,&quot;timeout&quot;:&quot;&quot;,&quot;response-charset&quot;:&quot;&quot;,&quot;method&quot;:&quot;GET&quot;,&quot;body-type&quot;:&quot;none&quot;,&quot;body-content-type&quot;:&quot;text/plain&quot;,&quot;loopCount&quot;:&quot;10&quot;,&quot;url&quot;:&quot;https://gitee.com/${projectUrls[projectIndex]}&quot;,&quot;proxy&quot;:&quot;&quot;,&quot;request-body&quot;:[&quot;&quot;],&quot;follow-redirect&quot;:&quot;1&quot;,&quot;shape&quot;:&quot;request&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"10\\\" value=\\\"\\\" parent=\\\"1\\\" source=\\\"7\\\" target=\\\"9\\\" edge=\\\"1\\\">\\n      <mxGeometry relative=\\\"1\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;&quot;,&quot;condition&quot;:&quot;&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"12\\\" value=\\\"提取项目描述\\\" style=\\\"variable\\\" parent=\\\"1\\\" vertex=\\\"1\\\">\\n      <mxGeometry x=\\\"550\\\" y=\\\"80\\\" width=\\\"24\\\" height=\\\"24\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;提取项目描述&quot;,&quot;loopVariableName&quot;:&quot;&quot;,&quot;variable-name&quot;:[&quot;projectDesc&quot;],&quot;loopCount&quot;:&quot;&quot;,&quot;variable-value&quot;:[&quot;${extract.selector(resp.html,&#39;.git-project-desc-text&#39;)}&quot;],&quot;shape&quot;:&quot;variable&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"13\\\" value=\\\"\\\" parent=\\\"1\\\" source=\\\"9\\\" target=\\\"12\\\" edge=\\\"1\\\">\\n      <mxGeometry relative=\\\"1\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;&quot;,&quot;condition&quot;:&quot;&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"14\\\" value=\\\"输出\\\" style=\\\"output\\\" parent=\\\"1\\\" vertex=\\\"1\\\">\\n      <mxGeometry x=\\\"660.1666870117188\\\" y=\\\"80\\\" width=\\\"24\\\" height=\\\"24\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;输出&quot;,&quot;output-name&quot;:[&quot;项目名&quot;,&quot;项目地址&quot;,&quot;项目描述&quot;],&quot;output-value&quot;:[&quot;${projectNames[projectIndex]}&quot;,&quot;https://gitee.com${projectUrls[projectIndex]}&quot;,&quot;${projectDesc}&quot;],&quot;shape&quot;:&quot;output&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"15\\\" value=\\\"\\\" parent=\\\"1\\\" source=\\\"12\\\" target=\\\"14\\\" edge=\\\"1\\\">\\n      <mxGeometry relative=\\\"1\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;&quot;,&quot;condition&quot;:&quot;&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n  </root>\\n</mxGraphModel>\\n', null, '0', '2019-08-22 13:46:54', null, null, null);\nINSERT INTO `sp_flow` VALUES ('f0a67f17ee1a498a9b2f4ca30556f3c3', '抓取每日菜价', '<mxGraphModel>\\n  <root>\\n    <mxCell id=\\\"0\\\">\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;spiderName&quot;:&quot;抓取每日菜价&quot;,&quot;threadCount&quot;:&quot;&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"1\\\" parent=\\\"0\\\"/>\\n    <mxCell id=\\\"2\\\" value=\\\"开始\\\" style=\\\"start\\\" parent=\\\"1\\\" vertex=\\\"1\\\">\\n      <mxGeometry x=\\\"80\\\" y=\\\"80\\\" width=\\\"24\\\" height=\\\"24\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;shape&quot;:&quot;start&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"3\\\" value=\\\"开始抓取\\\" style=\\\"request\\\" parent=\\\"1\\\" vertex=\\\"1\\\">\\n      <mxGeometry x=\\\"219.83334350585938\\\" y=\\\"80\\\" width=\\\"24\\\" height=\\\"24\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;开始抓取&quot;,&quot;loopVariableName&quot;:&quot;&quot;,&quot;sleep&quot;:&quot;&quot;,&quot;timeout&quot;:&quot;&quot;,&quot;response-charset&quot;:&quot;&quot;,&quot;method&quot;:&quot;GET&quot;,&quot;body-type&quot;:&quot;none&quot;,&quot;body-content-type&quot;:&quot;text/plain&quot;,&quot;loopCount&quot;:&quot;&quot;,&quot;url&quot;:&quot;http://www.beijingprice.cn:8086/price/priceToday/PageLoad/LoadPrice?jsoncallback=1&quot;,&quot;proxy&quot;:&quot;&quot;,&quot;request-body&quot;:[&quot;&quot;],&quot;follow-redirect&quot;:&quot;1&quot;,&quot;shape&quot;:&quot;request&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"4\\\" value=\\\"\\\" parent=\\\"1\\\" source=\\\"2\\\" target=\\\"3\\\" edge=\\\"1\\\">\\n      <mxGeometry relative=\\\"1\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;&quot;,&quot;condition&quot;:&quot;&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"5\\\" value=\\\"解析JSON\\\" style=\\\"variable\\\" parent=\\\"1\\\" vertex=\\\"1\\\">\\n      <mxGeometry x=\\\"350\\\" y=\\\"80\\\" width=\\\"24\\\" height=\\\"24\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;解析JSON&quot;,&quot;loopVariableName&quot;:&quot;&quot;,&quot;variable-name&quot;:[&quot;jsonstr&quot;,&quot;jsondata&quot;,&quot;data&quot;],&quot;loopCount&quot;:&quot;&quot;,&quot;variable-value&quot;:[&quot;${string.substring(resp.html,2,resp.html.length()-1)}&quot;,&quot;${json.parse(jsonstr)}&quot;,&quot;${extract.jsonpath(jsondata[0],&#39;data&#39;)}&quot;],&quot;shape&quot;:&quot;variable&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"6\\\" value=\\\"\\\" parent=\\\"1\\\" source=\\\"3\\\" target=\\\"5\\\" edge=\\\"1\\\">\\n      <mxGeometry relative=\\\"1\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;&quot;,&quot;condition&quot;:&quot;&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"7\\\" value=\\\"输出\\\" style=\\\"output\\\" parent=\\\"1\\\" vertex=\\\"1\\\">\\n      <mxGeometry x=\\\"480.16668701171875\\\" y=\\\"80\\\" width=\\\"24\\\" height=\\\"24\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;输出&quot;,&quot;loopVariableName&quot;:&quot;i&quot;,&quot;output-name&quot;:[&quot;菜名&quot;,&quot;菜价&quot;,&quot;单位&quot;],&quot;loopCount&quot;:&quot;${list.length(data)}&quot;,&quot;output-value&quot;:[&quot;${data[i].ItemName}&quot;,&quot;${data[i].Price04}&quot;,&quot;${data[i].ItemUnit}&quot;],&quot;shape&quot;:&quot;output&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"8\\\" value=\\\"\\\" parent=\\\"1\\\" source=\\\"5\\\" target=\\\"7\\\" edge=\\\"1\\\">\\n      <mxGeometry relative=\\\"1\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;&quot;,&quot;condition&quot;:&quot;&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n  </root>\\n</mxGraphModel>\\n', null, '0', '2019-08-22 13:48:22', null, null, null);\nINSERT INTO `sp_flow` VALUES ('b4430885ba8349588d1220d37eac831d', '爬取开源中国动弹', '<mxGraphModel>\\n  <root>\\n    <mxCell id=\\\"0\\\">\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;spiderName&quot;:&quot;爬取开源中国动弹&quot;,&quot;threadCount&quot;:&quot;&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"1\\\" parent=\\\"0\\\"/>\\n    <mxCell id=\\\"2\\\" value=\\\"开始\\\" style=\\\"start\\\" vertex=\\\"1\\\" parent=\\\"1\\\">\\n      <mxGeometry x=\\\"80\\\" y=\\\"80\\\" width=\\\"32\\\" height=\\\"32\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;shape&quot;:&quot;start&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"3\\\" value=\\\"爬取动弹\\\" style=\\\"request\\\" vertex=\\\"1\\\" parent=\\\"1\\\">\\n      <mxGeometry x=\\\"220\\\" y=\\\"80\\\" width=\\\"32\\\" height=\\\"32\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;爬取动弹&quot;,&quot;loopVariableName&quot;:&quot;&quot;,&quot;sleep&quot;:&quot;&quot;,&quot;timeout&quot;:&quot;&quot;,&quot;response-charset&quot;:&quot;&quot;,&quot;method&quot;:&quot;GET&quot;,&quot;parameter-name&quot;:[&quot;type&quot;,&quot;lastLogId&quot;],&quot;body-type&quot;:&quot;none&quot;,&quot;body-content-type&quot;:&quot;text/plain&quot;,&quot;loopCount&quot;:&quot;&quot;,&quot;url&quot;:&quot;https://www.oschina.net/tweets/widgets/_tweet_index_list &quot;,&quot;proxy&quot;:&quot;&quot;,&quot;parameter-value&quot;:[&quot;ajax&quot;,&quot;${lastLogId}&quot;],&quot;request-body&quot;:&quot;&quot;,&quot;follow-redirect&quot;:&quot;1&quot;,&quot;tls-validate&quot;:&quot;1&quot;,&quot;shape&quot;:&quot;request&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"4\\\" value=\\\"\\\" edge=\\\"1\\\" parent=\\\"1\\\" source=\\\"2\\\" target=\\\"3\\\">\\n      <mxGeometry relative=\\\"1\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;&quot;,&quot;condition&quot;:&quot;&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"5\\\" value=\\\"提取lastLogId以及tweets\\\" style=\\\"variable\\\" vertex=\\\"1\\\" parent=\\\"1\\\">\\n      <mxGeometry x=\\\"340\\\" y=\\\"80\\\" width=\\\"32\\\" height=\\\"32\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;提取lastLogId以及tweets&quot;,&quot;loopVariableName&quot;:&quot;&quot;,&quot;variable-name&quot;:[&quot;lastLogId&quot;,&quot;tweets&quot;,&quot;fetchCount&quot;],&quot;loopCount&quot;:&quot;&quot;,&quot;variable-value&quot;:[&quot;${resp.selector(&#39;.tweet-item:last-child&#39;).attr(&#39;data-tweet-id&#39;)}&quot;,&quot;${resp.selectors(&#39;.tweet-item[data-tweet-id]&#39;)}&quot;,&quot;${fetchCount == null ? 0 : fetchCount + 1}&quot;],&quot;shape&quot;:&quot;variable&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"6\\\" value=\\\"\\\" edge=\\\"1\\\" parent=\\\"1\\\" source=\\\"3\\\" target=\\\"5\\\">\\n      <mxGeometry relative=\\\"1\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;&quot;,&quot;condition&quot;:&quot;&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"7\\\" value=\\\"循环\\\" style=\\\"loop\\\" vertex=\\\"1\\\" parent=\\\"1\\\">\\n      <mxGeometry x=\\\"340\\\" y=\\\"250\\\" width=\\\"32\\\" height=\\\"32\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;循环&quot;,&quot;loopVariableName&quot;:&quot;index&quot;,&quot;loopCount&quot;:&quot;${list.length(tweets)}&quot;,&quot;shape&quot;:&quot;loop&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"8\\\" value=\\\"\\\" edge=\\\"1\\\" parent=\\\"1\\\" source=\\\"5\\\" target=\\\"7\\\">\\n      <mxGeometry relative=\\\"1\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;&quot;,&quot;condition&quot;:&quot;&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"9\\\" value=\\\"提取详细信息\\\" style=\\\"variable\\\" vertex=\\\"1\\\" parent=\\\"1\\\">\\n      <mxGeometry x=\\\"340\\\" y=\\\"340\\\" width=\\\"32\\\" height=\\\"32\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;提取详细信息&quot;,&quot;loopVariableName&quot;:&quot;&quot;,&quot;variable-name&quot;:[&quot;content&quot;,&quot;author&quot;,&quot;like&quot;,&quot;reply&quot;,&quot;publishTime&quot;],&quot;loopCount&quot;:&quot;&quot;,&quot;variable-value&quot;:[&quot;${tweets[index].selector(&#39;.text&#39;).text()}&quot;,&quot;${tweets[index].selector(&#39;.user&#39;).text()}&quot;,&quot;${tweets[index].selector(&#39;.like span&#39;).text()}&quot;,&quot;${tweets[index].selector(&#39;.reply span&#39;).text()}&quot;,&quot;${tweets[index].selector(&#39;.date&#39;).regx(&#39;(.*?)&amp;nbsp&#39;)}&quot;],&quot;shape&quot;:&quot;variable&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"10\\\" value=\\\"\\\" edge=\\\"1\\\" parent=\\\"1\\\" source=\\\"7\\\" target=\\\"9\\\">\\n      <mxGeometry relative=\\\"1\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;&quot;,&quot;condition&quot;:&quot;&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"11\\\" value=\\\"输出\\\" style=\\\"output\\\" vertex=\\\"1\\\" parent=\\\"1\\\">\\n      <mxGeometry x=\\\"340\\\" y=\\\"430\\\" width=\\\"32\\\" height=\\\"32\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;输出&quot;,&quot;loopVariableName&quot;:&quot;&quot;,&quot;output-name&quot;:[&quot;作者&quot;,&quot;内容&quot;,&quot;点赞数&quot;,&quot;评论数&quot;,&quot;发布时间&quot;],&quot;loopCount&quot;:&quot;&quot;,&quot;output-value&quot;:[&quot;${author}&quot;,&quot;${content}&quot;,&quot;${like}&quot;,&quot;${reply}&quot;,&quot;${publishTime}&quot;],&quot;shape&quot;:&quot;output&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"12\\\" value=\\\"\\\" edge=\\\"1\\\" parent=\\\"1\\\" source=\\\"9\\\" target=\\\"11\\\">\\n      <mxGeometry relative=\\\"1\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;&quot;,&quot;condition&quot;:&quot;&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"13\\\" value=\\\"爬取3页\\\" edge=\\\"1\\\" parent=\\\"1\\\" source=\\\"5\\\" target=\\\"3\\\">\\n      <mxGeometry x=\\\"-0.0312\\\" y=\\\"-20\\\" relative=\\\"1\\\" as=\\\"geometry\\\">\\n        <Array as=\\\"points\\\">\\n          <mxPoint x=\\\"356\\\" y=\\\"180\\\"/>\\n          <mxPoint x=\\\"236\\\" y=\\\"180\\\"/>\\n        </Array>\\n        <mxPoint as=\\\"offset\\\"/>\\n      </mxGeometry>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;爬取5页&quot;,&quot;condition&quot;:&quot;${fetchCount &lt; 3}&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n  </root>\\n</mxGraphModel>\\n', '', '0', '2019-11-03 17:02:49', '2019-11-04 10:11:31', '2019-11-03 17:30:56', '3');\nINSERT INTO `sp_flow` VALUES ('663aaa5e36a84c9594ef3cfd6738e9a7', '百度热点', '<mxGraphModel>\\n  <root>\\n    <mxCell id=\\\"0\\\">\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;spiderName&quot;:&quot;百度热点&quot;,&quot;threadCount&quot;:&quot;&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"1\\\" parent=\\\"0\\\"/>\\n    <mxCell id=\\\"2\\\" value=\\\"开始\\\" style=\\\"start\\\" parent=\\\"1\\\" vertex=\\\"1\\\">\\n      <mxGeometry x=\\\"80\\\" y=\\\"80\\\" width=\\\"32\\\" height=\\\"32\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;shape&quot;:&quot;start&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"3\\\" value=\\\"开始抓取\\\" style=\\\"request\\\" parent=\\\"1\\\" vertex=\\\"1\\\">\\n      <mxGeometry x=\\\"220\\\" y=\\\"80\\\" width=\\\"32\\\" height=\\\"32\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;开始抓取&quot;,&quot;loopVariableName&quot;:&quot;&quot;,&quot;sleep&quot;:&quot;&quot;,&quot;timeout&quot;:&quot;&quot;,&quot;response-charset&quot;:&quot;gbk&quot;,&quot;method&quot;:&quot;GET&quot;,&quot;body-type&quot;:&quot;none&quot;,&quot;body-content-type&quot;:&quot;text/plain&quot;,&quot;loopCount&quot;:&quot;&quot;,&quot;url&quot;:&quot;https://top.baidu.com/buzz?b=1&amp;fr=topindex&quot;,&quot;proxy&quot;:&quot;&quot;,&quot;request-body&quot;:&quot;&quot;,&quot;follow-redirect&quot;:&quot;1&quot;,&quot;tls-validate&quot;:&quot;1&quot;,&quot;shape&quot;:&quot;request&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"4\\\" value=\\\"定义变量\\\" style=\\\"variable\\\" parent=\\\"1\\\" vertex=\\\"1\\\">\\n      <mxGeometry x=\\\"360\\\" y=\\\"80\\\" width=\\\"32\\\" height=\\\"32\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;定义变量&quot;,&quot;loopVariableName&quot;:&quot;&quot;,&quot;variable-name&quot;:[&quot;elementbd&quot;],&quot;loopCount&quot;:&quot;&quot;,&quot;variable-value&quot;:[&quot;${resp.xpaths(&#39;//*[@id=\\\\&quot;main\\\\&quot;]/div[2]/div/table/tbody/tr&#39;)}&quot;],&quot;shape&quot;:&quot;variable&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"5\\\" value=\\\"输出\\\" style=\\\"output\\\" parent=\\\"1\\\" vertex=\\\"1\\\">\\n      <mxGeometry x=\\\"480\\\" y=\\\"80\\\" width=\\\"32\\\" height=\\\"32\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;输出&quot;,&quot;loopVariableName&quot;:&quot;i&quot;,&quot;output-name&quot;:[&quot;名称&quot;,&quot;地址&quot;,&quot;百度指数&quot;,&quot;2&quot;],&quot;loopCount&quot;:&quot;${elementbd.size()-1}&quot;,&quot;output-value&quot;:[&quot;${elementbd[i+1].xpath(&#39;//td[2]/a[1]/text()&#39;)}&quot;,&quot;${elementbd[i+1].xpath(&#39;//td[2]/a[1]/@href&#39;)}&quot;,&quot;${elementbd[i+1].xpath(&#39;//td[4]/span/text()&#39;)}&quot;,&quot;${elementbd[i+1].xpath(&#39;//td[3]/a[2]/text()&#39;)}&quot;],&quot;shape&quot;:&quot;output&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"6\\\" value=\\\"\\\" parent=\\\"1\\\" source=\\\"2\\\" target=\\\"3\\\" edge=\\\"1\\\">\\n      <mxGeometry relative=\\\"1\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;&quot;,&quot;condition&quot;:&quot;&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"7\\\" value=\\\"\\\" parent=\\\"1\\\" source=\\\"3\\\" target=\\\"4\\\" edge=\\\"1\\\">\\n      <mxGeometry relative=\\\"1\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;&quot;,&quot;condition&quot;:&quot;&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n    <mxCell id=\\\"8\\\" value=\\\"\\\" parent=\\\"1\\\" source=\\\"4\\\" target=\\\"5\\\" edge=\\\"1\\\">\\n      <mxGeometry relative=\\\"1\\\" as=\\\"geometry\\\"/>\\n      <JsonProperty as=\\\"data\\\">\\n        {&quot;value&quot;:&quot;&quot;,&quot;condition&quot;:&quot;&quot;}\\n      </JsonProperty>\\n    </mxCell>\\n  </root>\\n</mxGraphModel>\\n', '0 0/30 * * * ? *', '1', '2019-10-20 17:24:21', '2019-11-04 08:52:05', '2019-10-30 14:52:39', '45');\n\nDROP TABLE IF EXISTS `sp_datasource`;\nCREATE TABLE `sp_datasource` (\n  `id` varchar(32) NOT NULL,\n  `name` varchar(255) DEFAULT NULL,\n  `driver_class_name` varchar(255) DEFAULT NULL,\n  `jdbc_url` varchar(255) DEFAULT NULL,\n  `username` varchar(64) DEFAULT NULL,\n  `password` varchar(32) DEFAULT NULL,\n  `create_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n  PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;\n\nDROP TABLE IF EXISTS `sp_variable`;\nCREATE TABLE `sp_variable` (\n  `id` int(11) NOT NULL AUTO_INCREMENT,\n  `name` varchar(32) DEFAULT NULL COMMENT '变量名',\n  `value` varchar(512) DEFAULT NULL COMMENT '变量值',\n  `description` varchar(255) DEFAULT NULL COMMENT '变量描述',\n  `create_date` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n  PRIMARY KEY (`id`)\n) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;\n\n/* v0.3.0 新增 */\nDROP TABLE IF EXISTS `sp_task`;\nCREATE TABLE `sp_task` (\n  `id` int(11) NOT NULL AUTO_INCREMENT,\n  `flow_id` varchar(32) NOT NULL,\n  `begin_time` datetime DEFAULT NULL,\n  `end_time` datetime DEFAULT NULL,\n  PRIMARY KEY (`id`)\n) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;\n\n/* v0.4.0 新增 */\nDROP TABLE IF EXISTS `sp_function`;\nCREATE TABLE `sp_function`  (\n  `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,\n  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '函数名',\n  `parameter` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '参数',\n  `script` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT 'js脚本',\n  `create_date` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP,\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;\n\n/* v0.5.0 新增 */\nDROP TABLE IF EXISTS `sp_flow_notice`;\nCREATE TABLE `sp_flow_notice` (\n  `id` varchar(32) NOT NULL,\n  `recipients` varchar(200) DEFAULT NULL COMMENT '收件人',\n  `notice_way` char(10) DEFAULT NULL COMMENT '通知方式',\n  `start_notice` char(1) DEFAULT '0' COMMENT '流程开始通知:1:开启通知,0:关闭通知',\n  `exception_notice` char(1) DEFAULT '0' COMMENT '流程异常通知:1:开启通知,0:关闭通知',\n  `end_notice` char(1) DEFAULT '0' COMMENT '流程结束通知:1:开启通知,0:关闭通知',\n  PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '爬虫任务通知表';"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\t<modelVersion>4.0.0</modelVersion>\n\t<groupId>org.spiderflow</groupId>\n\t<artifactId>spider-flow</artifactId>\n\t<version>0.5.0</version>\n\t<packaging>pom</packaging>\n\t<name>spider-flow</name>\n\t<url>https://gitee.com/jmxd/spider-flow</url>\n\n\t<parent>\n\t\t<groupId>org.springframework.boot</groupId>\n\t\t<artifactId>spring-boot-starter-parent</artifactId>\n\t\t<version>2.0.7.RELEASE</version>\n\t</parent>\n\t<properties>\n\t\t<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n\t\t<spider-flow.version>${project.version}</spider-flow.version>\n\t\t<alibaba.fastjson.version>1.2.83</alibaba.fastjson.version>\n\t\t<alibaba.druid.version>1.1.16</alibaba.druid.version>\n\t\t<alibaba.transmittable.version>2.11.5</alibaba.transmittable.version>\n\t\t<mybatis.plus.version>3.1.0</mybatis.plus.version>\n\t\t<apache.commons.text.verion>1.6</apache.commons.text.verion>\n\t\t<apache.commons.csv.verion>1.8</apache.commons.csv.verion>\n\t\t<commons.io.version>2.7</commons.io.version>\n\t\t<guava.version>28.2-jre</guava.version>\n\t\t<jsoup.version>1.11.3</jsoup.version>\n\t\t<xsoup.version>0.3.1</xsoup.version>\n\t</properties>\n\n\t<dependencies>\n\t\t<!-- spring-boot相关配置 -->\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-web</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-quartz</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-mail</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework</groupId>\n\t\t\t<artifactId>spring-jdbc</artifactId>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t<artifactId>spring-boot-starter-websocket</artifactId>\n\t\t</dependency>\n\t\t<!-- 数据库相关 -->\n\t\t<dependency>\n\t\t\t<groupId>com.baomidou</groupId>\n\t\t\t<artifactId>mybatis-plus-boot-starter</artifactId>\n\t\t\t<version>${mybatis.plus.version}</version>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>mysql</groupId>\n\t\t\t<artifactId>mysql-connector-java</artifactId>\n\t\t</dependency>\n\t\t<!-- alibaba相关包 -->\n\t\t<dependency>\n\t\t\t<groupId>com.alibaba</groupId>\n\t\t\t<artifactId>fastjson</artifactId>\n\t\t\t<version>${alibaba.fastjson.version}</version>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>com.alibaba</groupId>\n\t\t\t<artifactId>druid-spring-boot-starter</artifactId>\n\t\t\t<version>${alibaba.druid.version}</version>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>com.alibaba</groupId>\n\t\t\t<artifactId>transmittable-thread-local</artifactId>\n\t\t\t<version>${alibaba.transmittable.version}</version>\n\t\t</dependency>\n\t\t<!-- apache commons相关 -->\n\t\t<dependency>\n\t\t\t<groupId>org.apache.commons</groupId>\n\t\t\t<artifactId>commons-text</artifactId>\n\t\t\t<version>${apache.commons.text.verion}</version>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.apache.commons</groupId>\n\t\t\t<artifactId>commons-csv</artifactId>\n\t\t\t<version>${apache.commons.csv.verion}</version>\n\t\t</dependency>\n\t\t<!-- commons包 -->\n\t\t<dependency>\n\t\t\t<groupId>commons-io</groupId>\n\t\t\t<artifactId>commons-io</artifactId>\n\t\t\t<version>${commons.io.version}</version>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>commons-codec</groupId>\n\t\t\t<artifactId>commons-codec</artifactId>\n\t\t</dependency>\n\t\t<!-- 其它包 -->\n\t\t<dependency>\n\t\t\t<groupId>com.google.guava</groupId>\n\t\t\t<artifactId>guava</artifactId>\n\t\t\t<version>${guava.version}</version>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>org.jsoup</groupId>\n\t\t\t<artifactId>jsoup</artifactId>\n\t\t\t<version>${jsoup.version}</version>\n\t\t</dependency>\n\t\t<dependency>\n\t\t\t<groupId>us.codecraft</groupId>\n\t\t\t<artifactId>xsoup</artifactId>\n\t\t\t<version>${xsoup.version}</version>\n\t\t</dependency>\n\t</dependencies>\n\n\t<dependencyManagement>\n\t\t<dependencies>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.spiderflow</groupId>\n\t\t\t\t<artifactId>spider-flow-api</artifactId>\n\t\t\t\t<version>${spider-flow.version}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.spiderflow</groupId>\n\t\t\t\t<artifactId>spider-flow-core</artifactId>\n\t\t\t\t<version>${spider-flow.version}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.spiderflow</groupId>\n\t\t\t\t<artifactId>spider-flow-selenium</artifactId>\n\t\t\t\t<version>${spider-flow.version}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.spiderflow</groupId>\n\t\t\t\t<artifactId>spider-flow-proxypool</artifactId>\n\t\t\t\t<version>${spider-flow.version}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.spiderflow</groupId>\n\t\t\t\t<artifactId>spider-flow-mongodb</artifactId>\n\t\t\t\t<version>${spider-flow.version}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.spiderflow</groupId>\n\t\t\t\t<artifactId>spider-flow-redis</artifactId>\n\t\t\t\t<version>${spider-flow.version}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.spiderflow</groupId>\n\t\t\t\t<artifactId>spider-flow-ocr</artifactId>\n\t\t\t\t<version>${spider-flow.version}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.spiderflow</groupId>\n\t\t\t\t<artifactId>spider-flow-oss</artifactId>\n\t\t\t\t<version>${spider-flow.version}</version>\n\t\t\t</dependency>\n\t\t\t<dependency>\n\t\t\t\t<groupId>org.spiderflow</groupId>\n\t\t\t\t<artifactId>spider-flow-mailbox</artifactId>\n\t\t\t\t<version>${spider-flow.version}</version>\n\t\t\t</dependency>\n\t\t</dependencies>\n\t</dependencyManagement>\n\t<modules>\n\t\t<module>spider-flow-api</module>\n\t\t<module>spider-flow-core</module>\n\t\t<module>spider-flow-web</module>\n\t</modules>\n</project>"
  },
  {
    "path": "spider-flow-api/pom.xml",
    "content": "<?xml version=\"1.0\"?>\n<project xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\" xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n\t<modelVersion>4.0.0</modelVersion>\n\t<parent>\n\t\t<groupId>org.spiderflow</groupId>\n\t\t<artifactId>spider-flow</artifactId>\n\t\t<version>0.5.0</version>\n\t</parent>\n\t<artifactId>spider-flow-api</artifactId>\n\t<name>spider-flow-api</name>\n\t<url>https://gitee.com/jmxd/spider-flow/tree/master/spider-flow-api</url>\n\t<properties>\n\t\t<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n\t</properties>\n</project>\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/ExpressionEngine.java",
    "content": "package org.spiderflow;\n\nimport java.util.Map;\n\n/**\n * 表达式引擎\n */\npublic interface ExpressionEngine {\n\n\t/**\n\t * 执行表达式\n\t * @param expression\t表达式\n\t * @param variables\t变量\n\t * @return\n\t */\n\tObject execute(String expression, Map<String, Object> variables);\n\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/Grammerable.java",
    "content": "package org.spiderflow;\n\nimport java.util.List;\nimport org.spiderflow.model.Grammer;\n\npublic interface Grammerable {\n\n\tList<Grammer> grammers();\n\t\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/annotation/Comment.java",
    "content": "package org.spiderflow.annotation;\r\n\r\nimport java.lang.annotation.Documented;\r\nimport java.lang.annotation.ElementType;\r\nimport java.lang.annotation.Retention;\r\nimport java.lang.annotation.RetentionPolicy;\r\nimport java.lang.annotation.Target;\r\n\r\n/**\r\n * 该注解用来标注自定义的方法注释，用来页面代码提示\r\n */\r\n@Documented\r\n@Retention(RetentionPolicy.RUNTIME)\r\n@Target({ElementType.METHOD,ElementType.TYPE})\r\npublic @interface Comment {\r\n\tString value();\r\n}\r\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/annotation/Example.java",
    "content": "package org.spiderflow.annotation;\r\n\r\nimport java.lang.annotation.Documented;\r\nimport java.lang.annotation.ElementType;\r\nimport java.lang.annotation.Retention;\r\nimport java.lang.annotation.RetentionPolicy;\r\nimport java.lang.annotation.Target;\r\n\r\n/**\r\n * 该注解用来标注自定义的方法注释，用来页面代码案例\r\n */\r\n@Documented\r\n@Retention(RetentionPolicy.RUNTIME)\r\n@Target(ElementType.METHOD)\r\npublic @interface Example {\r\n\tString value();\r\n}\r\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/annotation/Return.java",
    "content": "package org.spiderflow.annotation;\r\n\r\nimport java.lang.annotation.Documented;\r\nimport java.lang.annotation.ElementType;\r\nimport java.lang.annotation.Retention;\r\nimport java.lang.annotation.RetentionPolicy;\r\nimport java.lang.annotation.Target;\r\n\r\n/**\r\n * 该注解用来标注自定义的方法注释，用来页面提示返回值类型\r\n */\r\n@Documented\r\n@Retention(RetentionPolicy.RUNTIME)\r\n@Target(ElementType.METHOD)\r\npublic @interface Return {\r\n\tClass<?>[] value();\r\n}\r\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/common/CURDController.java",
    "content": "package org.spiderflow.common;\r\n\r\nimport org.spiderflow.model.JsonBean;\r\nimport org.springframework.beans.factory.annotation.Autowired;\r\nimport org.springframework.web.bind.annotation.RequestMapping;\r\nimport org.springframework.web.bind.annotation.RequestParam;\r\n\r\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\r\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\r\nimport com.baomidou.mybatisplus.core.metadata.IPage;\r\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\r\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\r\n\r\npublic abstract class CURDController<S extends ServiceImpl<M, T>,M extends BaseMapper<T>, T> {\r\n\t\r\n\t@Autowired\r\n\tprivate S service;\r\n\t\r\n\t@RequestMapping(\"/list\")\r\n\tpublic IPage<T> list(@RequestParam(name = \"page\",defaultValue = \"1\")Integer page, @RequestParam(name = \"limit\",defaultValue = \"1\")Integer size){\r\n\t\treturn service.page(new Page<T>(page, size), new QueryWrapper<T>().orderByDesc(\"create_date\"));\r\n\t}\r\n\t\r\n\t@RequestMapping(\"get\")\r\n\tpublic JsonBean<T> get(String id) {\r\n\t\treturn new JsonBean<T>(service.getById(id));\r\n\t}\r\n\t\r\n\t@RequestMapping(\"delete\")\r\n\tpublic JsonBean<Boolean> delete(String id){\r\n\t\treturn new JsonBean<Boolean>(service.removeById(id));\r\n\t}\r\n\t\r\n\t@RequestMapping(\"save\")\r\n\tpublic JsonBean<Boolean> save(T t){\r\n\t\treturn new JsonBean<Boolean>(service.saveOrUpdate(t));\r\n\t}\r\n\t\r\n}"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/concurrent/ChildPriorThreadSubmitStrategy.java",
    "content": "package org.spiderflow.concurrent;\n\nimport org.spiderflow.model.SpiderNode;\n\nimport java.util.Comparator;\nimport java.util.PriorityQueue;\n\npublic class ChildPriorThreadSubmitStrategy implements ThreadSubmitStrategy{\n\n    private Object mutex = this;\n\n    private Comparator<SpiderNode> comparator = (o1, o2) -> {\n        if(o1.hasLeftNode(o2.getNodeId())){\n            return -1;\n        }\n        return 1;\n    };\n\n    private PriorityQueue<SpiderFutureTask<?>> priorityQueue = new PriorityQueue<>((o1, o2) -> comparator.compare(o1.getNode(),o2.getNode()));\n\n    @Override\n    public Comparator<SpiderNode> comparator() {\n        return comparator;\n    }\n\n    @Override\n    public void add(SpiderFutureTask<?> task) {\n        synchronized (mutex){\n            priorityQueue.add(task);\n        }\n    }\n\n    @Override\n    public boolean isEmpty() {\n        synchronized (mutex){\n            return priorityQueue.isEmpty();\n        }\n    }\n\n    @Override\n    public SpiderFutureTask<?> get() {\n        synchronized (mutex){\n            return priorityQueue.poll();\n        }\n    }\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/concurrent/LinkedThreadSubmitStrategy.java",
    "content": "package org.spiderflow.concurrent;\n\nimport org.spiderflow.model.SpiderNode;\n\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\npublic class LinkedThreadSubmitStrategy implements ThreadSubmitStrategy{\n\n    private List<SpiderFutureTask<?>> taskList = new CopyOnWriteArrayList<>();\n\n    @Override\n    public Comparator<SpiderNode> comparator() {\n        return (o1, o2) -> -1;\n    }\n\n    @Override\n    public void add(SpiderFutureTask<?> task) {\n        taskList.add(task);\n    }\n\n    @Override\n    public boolean isEmpty() {\n        return taskList.isEmpty();\n    }\n\n    @Override\n    public SpiderFutureTask<?> get() {\n        return taskList.remove(0);\n    }\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/concurrent/ParentPriorThreadSubmitStrategy.java",
    "content": "package org.spiderflow.concurrent;\n\nimport org.spiderflow.model.SpiderNode;\n\nimport java.util.Comparator;\nimport java.util.PriorityQueue;\n\npublic class ParentPriorThreadSubmitStrategy implements ThreadSubmitStrategy {\n\n    private Object mutex = this;\n\n    private Comparator<SpiderNode> comparator = (o1, o2) -> {\n        if (o1.hasLeftNode(o2.getNodeId())) {\n            return 1;\n        }\n        return -1;\n    };\n\n    private PriorityQueue<SpiderFutureTask<?>> priorityQueue = new PriorityQueue<>((o1, o2) -> comparator.compare(o1.getNode(), o2.getNode()));\n\n    @Override\n    public Comparator<SpiderNode> comparator() {\n        return comparator;\n    }\n\n    @Override\n    public void add(SpiderFutureTask<?> task) {\n        synchronized (mutex) {\n            priorityQueue.add(task);\n        }\n    }\n\n    @Override\n    public boolean isEmpty() {\n        synchronized (mutex) {\n            return priorityQueue.isEmpty();\n        }\n    }\n\n    @Override\n    public SpiderFutureTask<?> get() {\n        synchronized (mutex) {\n            return priorityQueue.poll();\n        }\n    }\n\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/concurrent/RandomThreadSubmitStrategy.java",
    "content": "package org.spiderflow.concurrent;\n\nimport org.apache.commons.lang3.RandomUtils;\nimport org.spiderflow.model.SpiderNode;\n\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\npublic class RandomThreadSubmitStrategy implements ThreadSubmitStrategy{\n\n    private List<SpiderFutureTask<?>> taskList = new CopyOnWriteArrayList<>();\n\n    @Override\n    public Comparator<SpiderNode> comparator() {\n        return (o1, o2) -> RandomUtils.nextInt(0,3) - 1;\n    }\n\n    @Override\n    public void add(SpiderFutureTask<?> task) {\n        taskList.add(task);\n    }\n\n    @Override\n    public boolean isEmpty() {\n        return taskList.isEmpty();\n    }\n\n    @Override\n    public SpiderFutureTask<?> get() {\n        return taskList.remove(RandomUtils.nextInt(0, taskList.size()));\n    }\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/concurrent/SpiderFlowThreadPoolExecutor.java",
    "content": "package org.spiderflow.concurrent;\r\n\r\nimport org.spiderflow.model.SpiderNode;\r\n\r\nimport java.util.concurrent.*;\r\nimport java.util.concurrent.atomic.AtomicInteger;\r\n\r\npublic class SpiderFlowThreadPoolExecutor {\r\n\r\n\t/**\r\n\t * 最大线程数\r\n\t */\r\n\tprivate int maxThreads;\r\n\r\n\t/**\r\n\t * 真正线程池\r\n\t */\r\n\tprivate ThreadPoolExecutor executor;\r\n\r\n\t/**\r\n\t * 线程number计数器\r\n\t */\r\n\tprivate final AtomicInteger poolNumber = new AtomicInteger(1);\r\n\r\n\t/**\r\n\t * ThreadGroup\r\n\t */\r\n\tprivate static final ThreadGroup SPIDER_FLOW_THREAD_GROUP = new ThreadGroup(\"spider-flow-group\");\r\n\r\n\t/**\r\n\t * 线程名称前缀\r\n\t */\r\n\tprivate static final String THREAD_POOL_NAME_PREFIX = \"spider-flow-\";\r\n\r\n\tpublic SpiderFlowThreadPoolExecutor(int maxThreads) {\r\n\t\tsuper();\r\n\t\tthis.maxThreads = maxThreads;\r\n\t\t//创建线程池实例\r\n\t\tthis.executor = new ThreadPoolExecutor(maxThreads, maxThreads, 10, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), runnable -> {\r\n\t\t\t//重写线程名称\r\n\t\t\treturn new Thread(SPIDER_FLOW_THREAD_GROUP, runnable, THREAD_POOL_NAME_PREFIX + poolNumber.getAndIncrement());\r\n\t\t});\r\n\t}\r\n\r\n\tpublic Future<?> submit(Runnable runnable){\r\n\t\treturn this.executor.submit(runnable);\r\n\t}\r\n\r\n\r\n\t/**\r\n\t * 创建子线程池\r\n\t * @param threads\t线程池大小\r\n\t * @return\r\n\t */\r\n\tpublic SubThreadPoolExecutor createSubThreadPoolExecutor(int threads,ThreadSubmitStrategy submitStrategy){\r\n\t\treturn new SubThreadPoolExecutor(Math.min(maxThreads, threads),submitStrategy);\r\n\t}\r\n\r\n\t/**\r\n\t * 子线程池\r\n\t */\r\n\tpublic class SubThreadPoolExecutor{\r\n\r\n\t\t/**\r\n\t\t * 线程池大小\r\n\t\t */\r\n\t\tprivate int threads;\r\n\r\n\t\t/**\r\n\t\t * 正在执行中的任务\r\n\t\t */\r\n\t\tprivate Future<?>[] futures;\r\n\r\n\t\t/**\r\n\t\t * 执行中的数量\r\n\t\t */\r\n\t\tprivate AtomicInteger executing = new AtomicInteger(0);\r\n\r\n\t\t/**\r\n\t\t * 是否运行中\r\n\t\t */\r\n\t\tprivate volatile boolean running = true;\r\n\r\n\t\t/**\r\n\t\t * 是否提交任务中\r\n\t\t */\r\n\t\tprivate volatile boolean submitting = false;\r\n\r\n\t\tprivate ThreadSubmitStrategy submitStrategy;\r\n\r\n\t\tpublic SubThreadPoolExecutor(int threads,ThreadSubmitStrategy submitStrategy) {\r\n\t\t\tsuper();\r\n\t\t\tthis.threads = threads;\r\n\t\t\tthis.futures = new Future[threads];\r\n\t\t\tthis.submitStrategy = submitStrategy;\r\n\t\t}\r\n\t\t\r\n\t\t/**\r\n\t\t * 等待所有线程执行完毕\r\n\t\t */\r\n\t\tpublic void awaitTermination(){\r\n\t\t\twhile(executing.get() > 0){\r\n\t\t\t\tremoveDoneFuture();\r\n\t\t\t}\r\n\t\t\trunning = false;\r\n\t\t\t//当停止时,唤醒提交任务线程使其结束\r\n\t\t\tsynchronized (submitStrategy){\r\n\t\t\t\tsubmitStrategy.notifyAll();\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tprivate int index(){\r\n\t\t\tfor (int i = 0; i < threads; i++) {\r\n\t\t\t\tif(futures[i] == null || futures[i].isDone()){\r\n\t\t\t\t\treturn i;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\treturn -1;\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * 清除已完成的任务\r\n\t\t */\r\n\t\tprivate void removeDoneFuture(){\r\n\t\t\tfor (int i = 0; i < threads; i++) {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tif(futures[i] != null && futures[i].get(10,TimeUnit.MILLISECONDS) == null){\r\n\t\t\t\t\t\tfutures[i] = null;\r\n\t\t\t\t\t}\r\n\t\t\t\t} catch (Throwable t) {\r\n\t\t\t\t\t//忽略异常\r\n\t\t\t\t} \r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * 等待有空闲线程\r\n\t\t */\r\n\t\tprivate void await(){\r\n\t\t\twhile(index() == -1){\r\n\t\t\t\tremoveDoneFuture();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * 异步提交任务\r\n\t\t */\r\n\t\tpublic <T> Future<T> submitAsync(Runnable runnable, T value, SpiderNode node){\r\n\t\t\tSpiderFutureTask<T> future = new SpiderFutureTask<>(()-> {\r\n\t\t\t\ttry {\r\n\t\t\t\t\t//执行任务\r\n\t\t\t\t\trunnable.run();\r\n\t\t\t\t} finally {\r\n\t\t\t\t\t//正在执行的线程数-1\r\n\t\t\t\t\texecuting.decrementAndGet();\r\n\t\t\t\t}\r\n\t\t\t}, value,node,this);\r\n\r\n\t\t\tsubmitStrategy.add(future);\r\n\t\t\t//如果是第一次调用submitSync方法，则启动提交任务线程\r\n\t\t\tif(!submitting){\r\n\t\t\t\tsubmitting = true;\r\n\t\t\t\tCompletableFuture.runAsync(this::submit);\r\n\t\t\t}\r\n\t\t\tsynchronized (submitStrategy){\r\n\t\t\t\t//通知继续从集合中取任务提交到线程池中\r\n\t\t\t\tsubmitStrategy.notifyAll();\r\n\r\n\t\t\t}\r\n\t\t\treturn future;\r\n\t\t}\r\n\r\n\t\tprivate void submit(){\r\n\t\t\twhile(running){\r\n\t\t\t\ttry {\r\n\t\t\t\t\tsynchronized (submitStrategy){\r\n\t\t\t\t\t\t//如果集合是空的，则等待提交\r\n\t\t\t\t\t\tif(submitStrategy.isEmpty()){\r\n\t\t\t\t\t\t\tsubmitStrategy.wait();\t//等待唤醒\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t//当该线程被唤醒时，把集合中所有任务都提交到线程池中\r\n\t\t\t\t\twhile(!submitStrategy.isEmpty()){\r\n\t\t\t\t\t\t//从提交策略中获取任务提交到线程池中\r\n\t\t\t\t\t\tSpiderFutureTask<?> futureTask = submitStrategy.get();\r\n\t\t\t\t\t\t//如果没有空闲线程且在线程池中提交，则直接运行\r\n\t\t\t\t\t\tif(index() == -1 && Thread.currentThread().getThreadGroup() == SPIDER_FLOW_THREAD_GROUP){\r\n\t\t\t\t\t\t\tfutureTask.run();\r\n\t\t\t\t\t\t}else{\r\n\t\t\t\t\t\t\t//等待有空闲线程时在提交\r\n\t\t\t\t\t\t\tawait();\r\n\t\t\t\t\t\t\t//提交任务至线程池中\r\n\t\t\t\t\t\t\tfutures[index()] = executor.submit(futureTask);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t} catch (InterruptedException ignored) {\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/concurrent/SpiderFutureTask.java",
    "content": "package org.spiderflow.concurrent;\n\nimport java.util.concurrent.FutureTask;\nimport org.spiderflow.concurrent.SpiderFlowThreadPoolExecutor.SubThreadPoolExecutor;\nimport org.spiderflow.model.SpiderNode;\n\npublic class SpiderFutureTask<V> extends FutureTask {\n\n    private SubThreadPoolExecutor executor;\n\n    private SpiderNode node;\n\n    public SpiderFutureTask(Runnable runnable, V result, SpiderNode node,SubThreadPoolExecutor executor) {\n        super(runnable,result);\n        this.executor = executor;\n        this.node = node;\n    }\n\n    public SubThreadPoolExecutor getExecutor() {\n        return executor;\n    }\n\n    public SpiderNode getNode() {\n        return node;\n    }\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/concurrent/ThreadSubmitStrategy.java",
    "content": "package org.spiderflow.concurrent;\n\nimport org.spiderflow.model.SpiderNode;\n\nimport java.util.Comparator;\n\npublic interface ThreadSubmitStrategy {\n\n    Comparator<SpiderNode> comparator();\n\n    void add(SpiderFutureTask<?> task);\n\n    boolean isEmpty();\n\n    SpiderFutureTask<?> get();\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/context/CookieContext.java",
    "content": "package org.spiderflow.context;\n\nimport java.util.HashMap;\n\n/**\n * Cookie上下文\n */\npublic class CookieContext extends HashMap<String, String> {\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/context/SpiderContext.java",
    "content": "package org.spiderflow.context;\n\nimport org.spiderflow.concurrent.SpiderFlowThreadPoolExecutor.SubThreadPoolExecutor;\nimport org.spiderflow.model.SpiderNode;\nimport org.spiderflow.model.SpiderOutput;\n\nimport java.util.*;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.locks.ReentrantLock;\n\n/**\n * 爬虫上下文\n * @author jmxd\n *\n */\npublic class SpiderContext extends HashMap<String, Object>{\n\t\n\tprivate String id = UUID.randomUUID().toString().replace(\"-\", \"\");\n\n\t/**\n\t * 流程ID\n\t */\n\tprivate String flowId;\n\t\n\tprivate static final long serialVersionUID = 8379177178417619790L;\n\n\t/**\n\t * 流程执行线程\n\t */\n\tprivate SubThreadPoolExecutor threadPool;\n\n\t/**\n\t * 根节点\n\t */\n\tprivate SpiderNode rootNode;\n\n\t/**\n\t * 爬虫是否运行中\n\t */\n\tprivate volatile boolean running = true;\n\n\t/**\n\t * Future队列\n\t */\n\tprivate LinkedBlockingQueue<Future<?>> futureQueue = new LinkedBlockingQueue<>();\n\n\t/**\n\t * Cookie上下文\n\t */\n\tprivate CookieContext cookieContext = new CookieContext();\n\n\tpublic List<SpiderOutput> getOutputs() {\n\t\treturn Collections.emptyList();\n\t}\n\n\tpublic <T> T get(String key){\n\t\treturn (T) super.get(key);\n\t}\n\n\tpublic <T> T get(String key,T defaultValue){\n\t\tT value = this.get(key);\n\t\treturn value == null ? defaultValue : value;\n\t}\n\n\tpublic String getFlowId() {\n\t\treturn flowId;\n\t}\n\n\tpublic void setFlowId(String flowId) {\n\t\tthis.flowId = flowId;\n\t}\n\n\tpublic LinkedBlockingQueue<Future<?>> getFutureQueue() {\n\t\treturn futureQueue;\n\t}\n\n\tpublic boolean isRunning() {\n\t\treturn running;\n\t}\n\n\tpublic void setRunning(boolean running) {\n\t\tthis.running = running;\n\t}\n\n\tpublic void addOutput(SpiderOutput output){\n\n\t}\n\n\tpublic SubThreadPoolExecutor getThreadPool() {\n\t\treturn threadPool;\n\t}\n\n\tpublic void setThreadPool(SubThreadPoolExecutor threadPool) {\n\t\tthis.threadPool = threadPool;\n\t}\n\n\tpublic SpiderNode getRootNode() {\n\t\treturn rootNode;\n\t}\n\n\tpublic void setRootNode(SpiderNode rootNode) {\n\t\tthis.rootNode = rootNode;\n\t}\n\t\n\tpublic String getId() {\n\t\treturn id;\n\t}\n\t\n\tpublic CookieContext getCookieContext() {\n\t\treturn cookieContext;\n\t}\n\n\tpublic void pause(String nodeId,String event,String key,Object value){}\n\n\tpublic void resume(){}\n\n\tpublic void stop(){}\n\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/context/SpiderContextHolder.java",
    "content": "package org.spiderflow.context;\n\nimport com.alibaba.ttl.TransmittableThreadLocal;\n\npublic class SpiderContextHolder {\n\n\tprivate static final ThreadLocal<SpiderContext> THREAD_LOCAL = new TransmittableThreadLocal<>();\n\n\tpublic static SpiderContext get() {\n\t\treturn THREAD_LOCAL.get();\n\t}\n\n\tpublic static void set(SpiderContext context) {\n\t\tTHREAD_LOCAL.set(context);\n\t}\n\n\tpublic static void remove() {\n\t\tTHREAD_LOCAL.remove();\n\t}\n\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/enums/FlowNoticeType.java",
    "content": "package org.spiderflow.enums;\n\n/**\n * 流程通知类型\n * \n * @author BillDowney\n * @date 2020年4月4日 上午1:32:53\n */\npublic enum FlowNoticeType {\n\t/**\n\t * 流程开始通知\n\t */\n\tstartNotice,\n\t/**\n\t * 流程异常通知\n\t */\n\texceptionNotice,\n\t/**\n\t * 流程结束通知\n\t */\n\tendNotice\n\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/enums/FlowNoticeWay.java",
    "content": "package org.spiderflow.enums;\n\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\n/**\n * 流程通知方式\n * \n * @author BillDowney\n * @date 2020年4月3日 下午3:26:18\n */\npublic enum FlowNoticeWay {\n\n\temail(\"邮件通知\");\n\n\tprivate FlowNoticeWay(String title) {\n\t\tthis.title = title;\n\t}\n\n\tprivate String title;\n\n\t@Override\n\tpublic String toString() {\n\t\treturn this.name() + \":\" + this.title;\n\t}\n\n\tpublic static Map<String, String> getMap() {\n\t\tMap<String, String> map = new LinkedHashMap<String, String>();\n\t\tfor (FlowNoticeWay type : FlowNoticeWay.values()) {\n\t\t\tmap.put(type.name(), type.toString());\n\t\t}\n\t\treturn map;\n\t}\n\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/executor/FunctionExecutor.java",
    "content": "package org.spiderflow.executor;\n\npublic interface FunctionExecutor {\n\t\n\tString getFunctionPrefix();\n\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/executor/FunctionExtension.java",
    "content": "package org.spiderflow.executor;\n\npublic interface FunctionExtension {\n\t\n\tClass<?> support();\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/executor/PluginConfig.java",
    "content": "package org.spiderflow.executor;\r\n\r\nimport org.spiderflow.model.Plugin;\r\n\r\npublic interface PluginConfig {\r\n\t\r\n\tPlugin plugin();\r\n\t\r\n}"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/executor/ShapeExecutor.java",
    "content": "package org.spiderflow.executor;\n\nimport java.util.Map;\n\nimport org.spiderflow.context.SpiderContext;\nimport org.spiderflow.model.Shape;\nimport org.spiderflow.model.SpiderNode;\n/**\n * 执行器接口\n * @author jmxd\n *\n */\npublic interface ShapeExecutor {\n\t\n\tString LOOP_VARIABLE_NAME = \"loopVariableName\";\n\t\n\tString LOOP_COUNT = \"loopCount\";\n\t\n\tString THREAD_COUNT = \"threadCount\";\n\n\tdefault Shape shape(){\n\t\treturn null;\n\t}\n\t\n\t/**\n\t * 节点形状\n\t * @return 节点形状名称\n\t */\n\tString supportShape();\n\t\n\t/**\n\t * 执行器具体的功能实现\n\t * @param node 当前要执行的爬虫节点\n\t * @param context 爬虫上下文\n\t * @param variables 节点流程的全部变量的集合\n\t */\n\tvoid execute(SpiderNode node, SpiderContext context, Map<String, Object> variables);\n\t\n\tdefault boolean allowExecuteNext(SpiderNode node, SpiderContext context, Map<String, Object> variables){\n\t\treturn true;\n\t}\n\t\n\tdefault boolean isThread(){\n\t\treturn true;\n\t}\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/expression/DynamicMethod.java",
    "content": "package org.spiderflow.expression;\n\nimport java.util.List;\n\npublic interface DynamicMethod {\n\t\n\tObject execute(String methodName, List<Object> parameters);\n\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/io/Line.java",
    "content": "package org.spiderflow.io;\n\npublic class Line {\n\n\tprivate long from;\n\n\tprivate String text;\n\n\tprivate long to;\n\n\tpublic Line(long from, String text, long to) {\n\t\tthis.from = from;\n\t\tthis.text = text;\n\t\tthis.to = to;\n\t}\n\n\tpublic long getFrom() {\n\t\treturn from;\n\t}\n\n\tpublic void setFrom(long from) {\n\t\tthis.from = from;\n\t}\n\n\tpublic String getText() {\n\t\treturn text;\n\t}\n\n\tpublic void setText(String text) {\n\t\tthis.text = text;\n\t}\n\n\tpublic long getTo() {\n\t\treturn to;\n\t}\n\n\tpublic void setTo(long to) {\n\t\tthis.to = to;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"Line{\" +\n\t\t\t\t\"from=\" + from +\n\t\t\t\t\", text='\" + text + '\\'' +\n\t\t\t\t\", to=\" + to +\n\t\t\t\t'}';\n\t}\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/io/RandomAccessFileReader.java",
    "content": "package org.spiderflow.io;\n\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.io.RandomAccessFile;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.regex.Pattern;\n\npublic class RandomAccessFileReader implements Closeable {\n\n\tprivate RandomAccessFile raf;\n\n\t/**\n\t * 从index位置开始读取\n\t */\n\tprivate long index;\n\n\t/**\n\t * 读取顺序，默认倒叙\n\t */\n\tprivate boolean reversed;\n\n\t/**\n\t * 缓冲区大小\n\t */\n\tprivate int bufSize;\n\n\tpublic RandomAccessFileReader(RandomAccessFile raf, long index, boolean reversed) throws IOException {\n\t\tthis(raf, index, 1024, reversed);\n\t}\n\n\tpublic RandomAccessFileReader(RandomAccessFile raf, long index, int bufSize, boolean reversed) throws IOException {\n\t\tif (raf == null) {\n\t\t\tthrow new NullPointerException(\"file is null\");\n\t\t}\n\t\tthis.raf = raf;\n\t\tthis.reversed = reversed;\n\t\tthis.bufSize = bufSize;\n\t\tthis.index = index;\n\t\tthis.init();\n\t}\n\n\tprivate void init() throws IOException {\n\t\tif (reversed) {\n\t\t\tthis.index = this.index == -1 ? this.raf.length() : Math.min(this.index, this.raf.length());\n\t\t} else {\n\t\t\tthis.index = Math.min(Math.max(this.index, 0), this.raf.length());\n\t\t}\n\t\tif (this.index > 0) {\n\t\t\tthis.raf.seek(this.index);\n\t\t}\n\t}\n\n\t/**\n\t * 读取n行\n\t *\n\t * @param n        要读取的行数\n\t * @param keywords 搜索的关键词\n\t * @param matchcase 是否区分大小写\n\t * @param regx 是否是正则搜索\n\t * @return 返回Line对象，包含行的起始位置与终止位置\n\t */\n\tpublic List<Line> readLine(int n, String keywords, boolean matchcase, boolean regx) throws IOException {\n\t\tList<Line> lines = new ArrayList<>(n);\n\t\tlong lastCRLFIndex = reversed ? this.index : (this.index > 0 ? this.index + 1 : -1);\n\t\tboolean find = keywords == null || keywords.isEmpty();\n\t\tPattern pattern = regx && !find ? Pattern.compile(keywords) : null;\n\t\twhile (n > 0) {\n\t\t\tbyte[] buf = reversed ? new byte[(int) Math.min(this.bufSize, this.index)] : new byte[this.bufSize];\n\t\t\tif (this.reversed) {\n\t\t\t\tif (this.index == 0) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tthis.raf.seek(this.index -= buf.length);\n\t\t\t}\n\t\t\tint len = this.raf.read(buf, 0, buf.length);\n\t\t\tif (len == -1) {    //已读完\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tfor (int i = 0; i < len && n > 0; i++) {\n\t\t\t\tint readIndex = reversed ? len - i - 1 : i;\n\t\t\t\tif (isCRLF(buf[readIndex])) {    //如果读取到\\r或\\n\n\t\t\t\t\tif (Math.abs(this.index + readIndex - lastCRLFIndex) > 1) { //两行之间的间距,当=1时则代表有\\r\\n,\\n\\r,\\r\\r,\\n\\n四种情况之一\n\t\t\t\t\t\tlong fromIndex = reversed ? this.index + readIndex : lastCRLFIndex;    //计算起止位置\n\t\t\t\t\t\tlong endIndex = reversed ? lastCRLFIndex : this.index + readIndex;    //计算终止位置\n\t\t\t\t\t\tLine line = readLine(fromIndex + 1, endIndex);    //取出文本\n\t\t\t\t\t\tif (find || (find = (pattern == null ? find(line.getText(), keywords, matchcase) : find(line.getText(), pattern)))) {    //定位查找，使被查找的行始终在第一行\n\t\t\t\t\t\t\tif (reversed) {\n\t\t\t\t\t\t\t\tlines.add(0, line);    //反向查找时，插入到List头部\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tlines.add(line);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tn--;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tlastCRLFIndex = this.index + readIndex;    //记录上次读取到的\\r或\\n位置\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!reversed) {\n\t\t\t\tthis.index += buf.length;\n\t\t\t}\n\t\t}\n\t\tif (reversed && n > 0 && lastCRLFIndex > 1 && (find || lines.size() > 0)) {\n\t\t\tlines.add(0, readLine(0, lastCRLFIndex));\n\t\t}\n\t\treturn lines;\n\t}\n\n\tprivate boolean find(String text, String keywords, boolean matchcase) {\n\t\treturn matchcase ? text.contains(keywords) : text.toLowerCase().contains(keywords.toLowerCase());\n\t}\n\n\tprivate boolean find(String text, Pattern pattern) {\n\t\treturn pattern.matcher(text).find();\n\t}\n\n\t/**\n\t * 从指定位置读取一行\n\t *\n\t * @param fromIndex 开始位置\n\t * @param endIndex  结束位置\n\t * @return 返回Line对象\n\t * @throws IOException\n\t */\n\tprivate Line readLine(long fromIndex, long endIndex) throws IOException {\n\t\tlong index = this.raf.getFilePointer();\n\t\tthis.raf.seek(fromIndex);\n\t\tbyte[] buf = new byte[(int) (endIndex - fromIndex)];\n\t\tthis.raf.read(buf, 0, buf.length);\n\t\tLine line = new Line(fromIndex, new String(buf), endIndex);\n\t\tthis.raf.seek(index);\n\t\treturn line;\n\t}\n\n\tprivate boolean isCRLF(byte b) {\n\t\treturn b == 13 || b == 10;\n\t}\n\n\t@Override\n\tpublic void close() throws IOException {\n\t\tif (this.raf != null) {\n\t\t\tthis.raf.close();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/io/SpiderResponse.java",
    "content": "package org.spiderflow.io;\n\nimport java.io.InputStream;\nimport java.util.Map;\n\nimport org.spiderflow.annotation.Comment;\nimport org.spiderflow.annotation.Example;\n\nimport com.alibaba.fastjson.JSON;\n\npublic interface SpiderResponse {\n\n\t@Comment(\"获取返回状态码\")\n\t@Example(\"${resp.statusCode}\")\n\tint getStatusCode();\n\n\t@Comment(\"获取网页标题\")\n\t@Example(\"${resp.title}\")\n\tString getTitle();\n\n\t@Comment(\"获取网页html\")\n\t@Example(\"${resp.html}\")\n\tString getHtml();\n\n\t@Comment(\"获取json\")\n\t@Example(\"${resp.json}\")\n\tdefault Object getJson(){\n\t\treturn JSON.parse(getHtml());\n\t}\n\t@Comment(\"获取cookies\")\n\t@Example(\"${resp.cookies}\")\n\tMap<String,String> getCookies();\n\n\t@Comment(\"获取headers\")\n\t@Example(\"${resp.headers}\")\n\tMap<String,String> getHeaders();\n\n\t@Comment(\"获取byte[]\")\n\t@Example(\"${resp.bytes}\")\n\tbyte[] getBytes();\n\n\t@Comment(\"获取ContentType\")\n\t@Example(\"${resp.contentType}\")\n\tString getContentType();\n\n\t@Comment(\"获取当前url\")\n\t@Example(\"${resp.url}\")\n\tString getUrl();\n\n\t@Example(\"${resp.setCharset('UTF-8')}\")\n\tdefault void setCharset(String charset){\n\n\t}\n\t@Example(\"${resp.stream}\")\n\tdefault InputStream getStream(){\n\t\treturn null;\n\t}\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/listener/SpiderListener.java",
    "content": "package org.spiderflow.listener;\n\nimport org.spiderflow.context.SpiderContext;\n\npublic interface SpiderListener {\n\n\t/**\n\t * 开始执行之前\n\t */\n\tvoid beforeStart(SpiderContext context);\n\t\n\t/**\n\t * 执行完毕之后\n\t */\n\tvoid afterEnd(SpiderContext context);\n\t\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/model/Grammer.java",
    "content": "package org.spiderflow.model;\r\n\r\nimport java.lang.reflect.Method;\r\nimport java.lang.reflect.Modifier;\r\nimport java.util.ArrayList;\r\nimport java.util.Collections;\r\nimport java.util.List;\r\n\r\nimport org.spiderflow.annotation.Comment;\r\nimport org.spiderflow.annotation.Example;\r\nimport org.spiderflow.annotation.Return;\r\n\r\npublic class Grammer {\r\n\t\r\n\tprivate String owner;\r\n\r\n\tprivate String method;\r\n\t\r\n\tprivate String comment;\r\n\t\r\n\tprivate String example;\r\n\t\r\n\tprivate String function;\r\n\t\r\n\tprivate List<String> returns;\r\n\t\r\n\tpublic String getOwner() {\r\n\t\treturn owner;\r\n\t}\r\n\r\n\tpublic void setOwner(String owner) {\r\n\t\tthis.owner = owner;\r\n\t}\r\n\r\n\tpublic String getMethod() {\r\n\t\treturn method;\r\n\t}\r\n\r\n\tpublic void setMethod(String method) {\r\n\t\tthis.method = method;\r\n\t}\r\n\t\r\n\tpublic String getFunction() {\r\n\t\treturn function;\r\n\t}\r\n\r\n\tpublic void setFunction(String function) {\r\n\t\tthis.function = function;\r\n\t}\r\n\r\n\tpublic String getComment() {\r\n\t\treturn comment;\r\n\t}\r\n\r\n\tpublic void setComment(String comment) {\r\n\t\tthis.comment = comment;\r\n\t}\r\n\r\n\tpublic String getExample() {\r\n\t\treturn example;\r\n\t}\r\n\r\n\tpublic void setExample(String example) {\r\n\t\tthis.example = example;\r\n\t}\r\n\r\n\tpublic List<String> getReturns() {\r\n\t\treturn returns;\r\n\t}\r\n\t\r\n\tpublic void setReturns(List<String> returns) {\r\n\t\tthis.returns = returns;\r\n\t}\r\n\t\r\n\tpublic static List<Grammer> findGrammers(Class<?> clazz,String function,String owner,boolean mustStatic){\r\n\t\tMethod[] methods = clazz.getDeclaredMethods();\r\n\t\tList<Grammer> grammers = new ArrayList<>();\r\n\t\tfor (Method method : methods) {\r\n\t\t\tif(Modifier.isPublic(method.getModifiers()) && (Modifier.isStatic(method.getModifiers())||!mustStatic)){\r\n\t\t\t\tGrammer grammer = new Grammer();\r\n\t\t\t\tgrammer.setMethod(method.getName());\r\n\t\t\t\tComment comment = method.getAnnotation(Comment.class);\r\n\t\t\t\tif(comment != null){\r\n\t\t\t\t\tgrammer.setComment(comment.value());\r\n\t\t\t\t}\r\n\t\t\t\tExample example = method.getAnnotation(Example.class);\r\n\t\t\t\tif(example != null){\r\n\t\t\t\t\tgrammer.setExample(example.value());\r\n\t\t\t\t}\r\n\t\t\t\tReturn returns = method.getAnnotation(Return.class);\r\n\t\t\t\tif(returns != null){\r\n\t\t\t\t\tClass<?>[] clazzs = returns.value();\r\n\t\t\t\t\tList<String> returnTypes = new ArrayList<>();\r\n\t\t\t\t\tfor (int i = 0; i < clazzs.length; i++) {\r\n\t\t\t\t\t\treturnTypes.add(clazzs[i].getSimpleName());\r\n\t\t\t\t\t}\r\n\t\t\t\t\tgrammer.setReturns(returnTypes);\r\n\t\t\t\t}else{\r\n\t\t\t\t\tgrammer.setReturns(Collections.singletonList(method.getReturnType().getSimpleName()));\r\n\t\t\t\t}\r\n\t\t\t\tgrammer.setFunction(function);\r\n\t\t\t\tgrammer.setOwner(owner);\r\n\t\t\t\tgrammers.add(grammer);\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn grammers;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/model/JsonBean.java",
    "content": "package org.spiderflow.model;\n\npublic class JsonBean<T> {\n\n\tprivate Integer code = 1;\n\t\n\tprivate String message = \"执行成功\";\n\t\n\tprivate T data;\n\n\tpublic JsonBean(Integer code, String message, T data) {\n\t\tthis.code = code;\n\t\tthis.message = message;\n\t\tthis.data = data;\n\t}\n\n\tpublic JsonBean(Integer code, String message) {\n\t\tthis.code = code;\n\t\tthis.message = message;\n\t}\n\n\tpublic JsonBean(T data) {\n\t\tthis.data = data;\n\t}\n\n\tpublic Integer getCode() {\n\t\treturn code;\n\t}\n\n\tpublic void setCode(Integer code) {\n\t\tthis.code = code;\n\t}\n\n\tpublic String getMessage() {\n\t\treturn message;\n\t}\n\n\tpublic void setMessage(String message) {\n\t\tthis.message = message;\n\t}\n\n\tpublic T getData() {\n\t\treturn data;\n\t}\n\n\tpublic void setData(T data) {\n\t\tthis.data = data;\n\t}\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/model/Plugin.java",
    "content": "package org.spiderflow.model;\n\npublic class Plugin {\n\n\tprivate String name;\n\t\n\tprivate String url;\n\t\n\tpublic Plugin(String name, String url) {\n\t\tthis.name = name;\n\t\tthis.url = url;\n\t}\n\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\tpublic void setName(String name) {\n\t\tthis.name = name;\n\t}\n\n\tpublic String getUrl() {\n\t\treturn url;\n\t}\n\n\tpublic void setUrl(String url) {\n\t\tthis.url = url;\n\t}\n\t\n}\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/model/Shape.java",
    "content": "package org.spiderflow.model;\r\n\r\npublic class Shape {\r\n\t\r\n\tprivate String name;\r\n\t\r\n\tprivate String label;\r\n\t\r\n\tprivate String title;\r\n\t\r\n\tprivate String image;\r\n\r\n\tprivate String desc;\r\n\r\n\tpublic String getName() {\r\n\t\treturn name;\r\n\t}\r\n\r\n\tpublic void setName(String name) {\r\n\t\tthis.name = name;\r\n\t}\r\n\r\n\tpublic String getLabel() {\r\n\t\treturn label;\r\n\t}\r\n\r\n\tpublic void setLabel(String label) {\r\n\t\tthis.label = label;\r\n\t}\r\n\r\n\tpublic String getTitle() {\r\n\t\treturn title;\r\n\t}\r\n\r\n\tpublic void setTitle(String title) {\r\n\t\tthis.title = title;\r\n\t}\r\n\r\n\tpublic String getImage() {\r\n\t\treturn image;\r\n\t}\r\n\r\n\tpublic void setImage(String image) {\r\n\t\tthis.image = image;\r\n\t}\r\n\r\n\tpublic String getDesc() {\r\n\t\treturn desc;\r\n\t}\r\n\r\n\tpublic void setDesc(String desc) {\r\n\t\tthis.desc = desc;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/model/SpiderLog.java",
    "content": "package org.spiderflow.model;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\nimport org.apache.commons.lang3.exception.ExceptionUtils;\r\n\r\npublic class SpiderLog {\r\n\t\r\n\tprivate String level;\r\n\t\r\n\tprivate String message;\r\n\t\r\n\tprivate List<Object> variables;\r\n\r\n\tpublic SpiderLog(String level,String message, List<Object> variables) {\r\n\t\tif(variables != null && variables.size() > 0){\r\n\t\t\tList<Object> nVariables = new ArrayList<>(variables.size());\r\n\t\t\tfor (Object object : variables) {\r\n\t\t\t\tif(object instanceof Throwable){\r\n\t\t\t\t\tnVariables.add(ExceptionUtils.getStackTrace((Throwable) object));\r\n\t\t\t\t}else{\r\n\t\t\t\t\tnVariables.add(object);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tthis.variables = nVariables;\r\n\t\t}\r\n\t\tthis.level = level;\r\n\t\tthis.message = message;\r\n\t}\r\n\r\n\tpublic String getLevel() {\r\n\t\treturn level;\r\n\t}\r\n\r\n\tpublic void setLevel(String level) {\r\n\t\tthis.level = level;\r\n\t}\r\n\r\n\tpublic String getMessage() {\r\n\t\treturn message;\r\n\t}\r\n\r\n\tpublic void setMessage(String message) {\r\n\t\tthis.message = message;\r\n\t}\r\n\r\n\tpublic List<Object> getVariables() {\r\n\t\treturn variables;\r\n\t}\r\n\r\n\tpublic void setVariables(List<Object> variables) {\r\n\t\tthis.variables = variables;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/model/SpiderNode.java",
    "content": "package org.spiderflow.model;\r\n\r\nimport java.util.*;\r\nimport java.util.concurrent.atomic.AtomicInteger;\r\n\r\nimport org.apache.commons.lang3.StringUtils;\r\nimport org.apache.commons.text.StringEscapeUtils;\r\n\r\nimport com.alibaba.fastjson.JSONArray;\r\n\r\n\r\n/**\r\n * 爬虫节点\r\n * @author jmxd\r\n *\r\n */\r\npublic class SpiderNode {\r\n\t/**\r\n\t * 节点的Json属性\r\n\t */\r\n\tprivate Map<String,Object> jsonProperty = new HashMap<>();\r\n\t/**\r\n\t * 节点列表中的下一个节点\r\n\t */\r\n\tprivate List<SpiderNode> nextNodes = new ArrayList<>();\r\n\r\n\t/**\r\n\t * 节点列表中的上一个节点\r\n\t */\r\n\tprivate List<SpiderNode> prevNodes = new ArrayList<>();\r\n\r\n\t/**\r\n\t * 父级节点ID\r\n\t */\r\n\tprivate Set<String> parentNodes;\r\n\r\n\t/**\r\n\t * 节点流转条件\r\n\t */\r\n\tprivate Map<String,String> condition = new HashMap<>();\r\n\r\n\t/**\r\n\t * 异常流转\r\n\t */\r\n\tprivate Map<String,String> exception = new HashMap<>();\r\n\r\n\t/**\r\n\t * 传递变量\r\n\t */\r\n\tprivate Map<String,String> transmitVariable = new HashMap<>();\r\n\t/**\r\n\t * 节点名称\r\n\t */\r\n\tprivate String nodeName;\r\n\t/**\r\n\t * 节点ID\r\n\t */\r\n\tprivate String nodeId;\r\n\r\n\t/**\r\n\t * 计数器,用来计算当前节点执行中的个数\r\n\t */\r\n\tprivate AtomicInteger counter = new AtomicInteger();\r\n\r\n\tpublic String getNodeId() {\r\n\t\treturn nodeId;\r\n\t}\r\n\r\n\tpublic void setNodeId(String nodeId) {\r\n\t\tthis.nodeId = nodeId;\r\n\t}\r\n\r\n\tpublic String getNodeName() {\r\n\t\treturn nodeName;\r\n\t}\r\n\r\n\tpublic void setNodeName(String nodeName) {\r\n\t\tthis.nodeName = nodeName;\r\n\t}\r\n\r\n\tpublic String getStringJsonValue(String key){\r\n\t\tString value = (String) this.jsonProperty.get(key);\r\n\t\tif(value != null){\r\n\t\t\tvalue = StringEscapeUtils.unescapeHtml4(value);\r\n\t\t}\r\n\t\treturn value;\r\n\t}\r\n\r\n\tpublic String getStringJsonValue(String key,String defaultValue){\r\n\t\tString value = getStringJsonValue(key);\r\n\t\treturn StringUtils.isNotBlank(value) ? value : defaultValue;\r\n\t}\r\n\t\r\n\tpublic List<Map<String,String>> getListJsonValue(String ... keys){\r\n\t\tList<JSONArray> arrays = new ArrayList<>();\r\n\t\tint size = -1;\r\n\t\tList<Map<String,String>> result = new ArrayList<>();\r\n\t\tfor (int i = 0; i < keys.length; i++) {\r\n\t\t\tJSONArray jsonArray = (JSONArray) this.jsonProperty.get(keys[i]);\r\n\t\t\tif(jsonArray != null){\r\n\t\t\t\tif(size == -1){\r\n\t\t\t\t\tsize = jsonArray.size();\r\n\t\t\t\t}else if(size != jsonArray.size()){\r\n\t\t\t\t\tthrow new ArrayIndexOutOfBoundsException();\r\n\t\t\t\t}\r\n\t\t\t\tarrays.add(jsonArray);\r\n\t\t\t}\r\n\t\t}\r\n\t\tfor (int i = 0;i < size;i++) {\r\n\t\t\tMap<String,String> item = new HashMap<>();\r\n\t\t\tfor (int j = 0; j < keys.length; j++) {\r\n\t\t\t\tString val = arrays.get(j).getString(i);\r\n\t\t\t\tif(val != null){\r\n\t\t\t\t\tval = StringEscapeUtils.unescapeHtml4(val);\r\n\t\t\t\t}\r\n\t\t\t\titem.put(keys[j],val);\r\n\t\t\t}\r\n\t\t\tresult.add(item);\r\n\t\t}\r\n\t\treturn result;\r\n\t}\r\n\tpublic void setJsonProperty(Map<String, Object> jsonProperty) {\r\n\t\tthis.jsonProperty = jsonProperty;\r\n\t}\r\n\r\n\tpublic void addNextNode(SpiderNode nextNode){\r\n\t\tnextNode.prevNodes.add(this);\r\n\t\tthis.nextNodes.add(nextNode);\r\n\t}\r\n\r\n\tpublic String getExceptionFlow(String fromNodeId) {\r\n\t\treturn exception.get(fromNodeId);\r\n\t}\r\n\r\n\tpublic boolean isTransmitVariable(String fromNodeId) {\r\n\t\tString value = transmitVariable.get(fromNodeId);\r\n\t\treturn value == null || \"1\".equalsIgnoreCase(value);\r\n\t}\r\n\r\n\tpublic void setTransmitVariable(String fromNodeId,String value){\r\n\t\tthis.transmitVariable.put(fromNodeId,value);\r\n\t}\r\n\r\n\tpublic void setExceptionFlow(String fromNodeId,String value){\r\n\t\tthis.exception.put(fromNodeId,value);\r\n\t}\r\n\r\n\tpublic List<SpiderNode> getNextNodes() {\r\n\t\treturn nextNodes;\r\n\t}\r\n\r\n\tpublic String getCondition(String fromNodeId) {\r\n\t\treturn condition.get(fromNodeId);\r\n\t}\r\n\r\n\tpublic void setCondition(String fromNodeId,String condition) {\r\n\t\tthis.condition.put(fromNodeId, condition);\r\n\t}\r\n\r\n\tpublic void increment(){\r\n\t\tcounter.incrementAndGet();\r\n\t}\r\n\r\n\tpublic void decrement(){\r\n\t\tcounter.decrementAndGet();\r\n\t}\r\n\r\n\tpublic boolean hasLeftNode(String nodeId){\r\n\t\tif(parentNodes == null){\r\n\t\t\tSet<String> parents = new HashSet<>();\r\n\t\t\tgenerateParents(parents);\r\n\t\t\tthis.parentNodes = parents;\r\n\t\t}\r\n\t\treturn this.parentNodes.contains(nodeId);\r\n\t}\r\n\r\n\tprivate void generateParents(Set<String> parents){\r\n\t\tfor (SpiderNode prevNode : prevNodes) {\r\n\t\t\tif(parents.add(prevNode.nodeId)){\r\n\t\t\t\tprevNode.generateParents(parents);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tpublic boolean isDone(){\r\n\t\treturn isDone(new HashSet<>());\r\n\t}\r\n\tpublic boolean isDone(Set<String> visited){\r\n\t\tif(this.counter.get() == 0){\r\n\t\t\tfor (SpiderNode prevNode : prevNodes) {\r\n\t\t\t\tif(visited.add(nodeId)&&!prevNode.isDone(visited)){\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\treturn true;\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic String toString() {\r\n\t\treturn \"SpiderNode [jsonProperty=\" + jsonProperty + \", nextNodes=\" + nextNodes + \", condition=\" + condition\r\n\t\t\t\t+ \", nodeName=\" + nodeName + \", nodeId=\" + nodeId + \"]\";\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/model/SpiderOutput.java",
    "content": "package org.spiderflow.model;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\npublic class SpiderOutput {\r\n\t\r\n\t/**\r\n\t * 节点名称\r\n\t */\r\n\tprivate String nodeName;\r\n\t\r\n\t/**\r\n\t * 节点Id\r\n\t */\r\n\tprivate String nodeId;\r\n\t\r\n\t/**\r\n\t * 输出项的名\r\n\t */\r\n\tprivate List<String> outputNames = new ArrayList<>();\r\n\t\r\n\t/**\r\n\t * 输出项的值\r\n\t */\r\n\tprivate List<Object> values = new ArrayList<>();\r\n\r\n\tpublic String getNodeName() {\r\n\t\treturn nodeName;\r\n\t}\r\n\r\n\tpublic void setNodeName(String nodeName) {\r\n\t\tthis.nodeName = nodeName;\r\n\t}\r\n\r\n\tpublic List<String> getOutputNames() {\r\n\t\treturn outputNames;\r\n\t}\r\n\r\n\tpublic void setOutputNames(List<String> outputNames) {\r\n\t\tthis.outputNames = outputNames;\r\n\t}\r\n\r\n\tpublic List<Object> getValues() {\r\n\t\treturn values;\r\n\t}\r\n\r\n\tpublic void setValues(List<Object> values) {\r\n\t\tthis.values = values;\r\n\t}\r\n\t\r\n\tpublic void addOutput(String name,Object value){\r\n\t\tthis.outputNames.add(name);\r\n\t\tthis.values.add(value);\r\n\t}\r\n\r\n\tpublic String getNodeId() {\r\n\t\treturn nodeId;\r\n\t}\r\n\r\n\tpublic void setNodeId(String nodeId) {\r\n\t\tthis.nodeId = nodeId;\r\n\t}\r\n\r\n\t@Override\r\n\tpublic String toString() {\r\n\t\treturn \"SpiderOutput [nodeName=\" + nodeName + \", nodeId=\" + nodeId + \", outputNames=\" + outputNames\r\n\t\t\t\t+ \", values=\" + values + \"]\";\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spider-flow-api/src/main/java/org/spiderflow/utils/Maps.java",
    "content": "package org.spiderflow.utils;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\npublic class Maps {\r\n\r\n\tpublic static <K,V> Map<K,V> add(Map<K,V> srcMap,K k,V v){\r\n\t\tHashMap<K, V> destMap = new HashMap<>(srcMap);\r\n\t\tdestMap.put(k, v);\r\n\t\treturn destMap;\r\n\t}\r\n\t\r\n\tpublic static <K,V> Map<K,V> newMap(K key,V value){\r\n\t\tHashMap<K, V> map = new HashMap<>();\r\n\t\tmap.put(key, value);\r\n\t\treturn map;\r\n\t}\r\n\t\r\n\tpublic static <K,V> Map<K,V> add(Map<K,V> srcMap,List<K> ks,List<V> vs){\r\n\t\tHashMap<K, V> destMap = new HashMap<>(srcMap);\r\n\t\tif(ks != null && vs != null && ks.size() == vs.size()){\r\n\t\t\tint size = ks.size();\r\n\t\t\tfor (int i = 0; i < size; i++) {\r\n\t\t\t\tdestMap.put(ks.get(0), vs.get(0));\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn destMap;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/pom.xml",
    "content": "<?xml version=\"1.0\"?>\n<project xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\" xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n\t<modelVersion>4.0.0</modelVersion>\n\t<parent>\n\t\t<groupId>org.spiderflow</groupId>\n\t\t<artifactId>spider-flow</artifactId>\n\t\t<version>0.5.0</version>\n\t</parent>\n\t<artifactId>spider-flow-core</artifactId>\n\t<name>spider-flow-core</name>\n\t<url>https://gitee.com/jmxd/spider-flow/tree/master/spider-flow-core</url>\n\t<properties>\n\t\t<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n\t</properties>\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.spiderflow</groupId>\n\t\t\t<artifactId>spider-flow-api</artifactId>\n\t\t</dependency>\n\t</dependencies>\n</project>\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/Spider.java",
    "content": "package org.spiderflow.core;\n\nimport com.alibaba.ttl.TtlRunnable;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.math.NumberUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.spiderflow.concurrent.*;\nimport org.spiderflow.concurrent.SpiderFlowThreadPoolExecutor.SubThreadPoolExecutor;\nimport org.spiderflow.context.SpiderContext;\nimport org.spiderflow.context.SpiderContextHolder;\nimport org.spiderflow.core.executor.shape.LoopExecutor;\nimport org.spiderflow.core.model.SpiderFlow;\nimport org.spiderflow.core.service.FlowNoticeService;\nimport org.spiderflow.core.utils.ExecutorsUtils;\nimport org.spiderflow.core.utils.ExpressionUtils;\nimport org.spiderflow.core.utils.SpiderFlowUtils;\nimport org.spiderflow.enums.FlowNoticeType;\nimport org.spiderflow.executor.ShapeExecutor;\nimport org.spiderflow.listener.SpiderListener;\nimport org.spiderflow.model.SpiderNode;\nimport org.spiderflow.model.SpiderOutput;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Component;\n\nimport javax.annotation.PostConstruct;\nimport java.lang.reflect.Array;\nimport java.util.*;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.FutureTask;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * 爬虫的核心类\n *\n * @author jmxd\n */\n@Component\npublic class Spider {\n\n\t@Autowired(required = false)\n\tprivate List<SpiderListener> listeners;\n\n\t@Value(\"${spider.thread.max:64}\")\n\tprivate Integer totalThreads;\n\n\t@Value(\"${spider.thread.default:8}\")\n\tprivate Integer defaultThreads;\n\n\t@Value(\"${spider.detect.dead-cycle:5000}\")\n\tprivate Integer deadCycle;\n\t\n\t@Autowired\n\tprivate FlowNoticeService flowNoticeService;\n\n\tpublic static SpiderFlowThreadPoolExecutor executorInstance;\n\n\tprivate static final String ATOMIC_DEAD_CYCLE = \"__atomic_dead_cycle\";\n\n\tprivate static Logger logger = LoggerFactory.getLogger(Spider.class);\n\n\t@PostConstruct\n\tprivate void init() {\n\t\texecutorInstance = new SpiderFlowThreadPoolExecutor(totalThreads);\n\t}\n\n\tpublic List<SpiderOutput> run(SpiderFlow spiderFlow, SpiderContext context, Map<String, Object> variables) {\n\t\tif (variables == null) {\n\t\t\tvariables = new HashMap<>();\n\t\t}\n\t\tSpiderNode root = SpiderFlowUtils.loadXMLFromString(spiderFlow.getXml());\n\t\t// 流程开始通知\n\t\tflowNoticeService.sendFlowNotice(spiderFlow, FlowNoticeType.startNotice);\n\t\texecuteRoot(root, context, variables);\n\t\t// 流程结束通知\n\t\tflowNoticeService.sendFlowNotice(spiderFlow, FlowNoticeType.endNotice);\n\t\treturn context.getOutputs();\n\t}\n\n\tpublic List<SpiderOutput> run(SpiderFlow spiderFlow, SpiderContext context) {\n\t\treturn run(spiderFlow, context, new HashMap<>());\n\t}\n\n\tpublic void runWithTest(SpiderNode root, SpiderContext context) {\n\t\t//将上下文存到ThreadLocal里，以便后续使用\n\t\tSpiderContextHolder.set(context);\n\t\t//死循环检测的计数器（死循环检测只在测试时有效）\n\t\tAtomicInteger executeCount = new AtomicInteger(0);\n\t\t//存入到上下文中，以供后续检测\n\t\tcontext.put(ATOMIC_DEAD_CYCLE, executeCount);\n\t\t//执行根节点\n\t\texecuteRoot(root, context, new HashMap<>());\n\t\t//当爬虫任务执行完毕时,判断是否超过预期\n\t\tif (executeCount.get() > deadCycle) {\n\t\t\tlogger.error(\"检测到可能出现死循环,测试终止\");\n\t\t} else {\n\t\t\tlogger.info(\"测试完毕！\");\n\t\t}\n\t\t//将上下文从ThreadLocal移除，防止内存泄漏\n\t\tSpiderContextHolder.remove();\n\t}\n\n\t/**\n\t * 执行根节点\n\t */\n\tprivate void executeRoot(SpiderNode root, SpiderContext context, Map<String, Object> variables) {\n\t\t//获取当前流程执行线程数\n\t\tint nThreads = NumberUtils.toInt(root.getStringJsonValue(ShapeExecutor.THREAD_COUNT), defaultThreads);\n\t\tString strategy = root.getStringJsonValue(\"submit-strategy\");\n\t\tThreadSubmitStrategy submitStrategy;\n\t\t//选择提交策略，这里一定要使用new,不能与其他实例共享\n\t\tif(\"linked\".equalsIgnoreCase(strategy)){\n\t\t\tsubmitStrategy = new LinkedThreadSubmitStrategy();\n\t\t}else if(\"child\".equalsIgnoreCase(strategy)){\n\t\t\tsubmitStrategy = new ChildPriorThreadSubmitStrategy();\n\t\t}else if(\"parent\".equalsIgnoreCase(strategy)){\n\t\t\tsubmitStrategy = new ParentPriorThreadSubmitStrategy();\n\t\t}else{\n\t\t\tsubmitStrategy = new RandomThreadSubmitStrategy();\n\t\t}\n\t\t//创建子线程池，采用一父多子的线程池,子线程数不能超过总线程数（超过时进入队列等待）,+1是因为会占用一个线程用来调度执行下一级\n\t\tSubThreadPoolExecutor pool = executorInstance.createSubThreadPoolExecutor(Math.max(nThreads,1) + 1,submitStrategy);\n\t\tcontext.setRootNode(root);\n\t\tcontext.setThreadPool(pool);\n\t\t//触发监听器\n\t\tif (listeners != null) {\n\t\t\tlisteners.forEach(listener -> listener.beforeStart(context));\n\t\t}\n\t\tComparator<SpiderNode> comparator = submitStrategy.comparator();\n\t\t//启动一个线程开始执行任务,并监听其结束并执行下一级\n\t\tFuture<?> f = pool.submitAsync(TtlRunnable.get(() -> {\n\t\t\ttry {\n\t\t\t\t//执行具体节点\n\t\t\t\tSpider.this.executeNode(null, root, context, variables);\n\t\t\t\tQueue<Future<?>> queue = context.getFutureQueue();\n\t\t\t\t//循环从队列中获取Future,直到队列为空结束,当任务完成时，则执行下一级\n\t\t\t\twhile (!queue.isEmpty()) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\t//TODO 这里应该是取出最先执行完毕的任务\n\t\t\t\t\t\tOptional<Future<?>> max = queue.stream().filter(Future::isDone).max((o1, o2) -> {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\treturn comparator.compare(((SpiderTask) o1.get()).node, ((SpiderTask) o2.get()).node);\n\t\t\t\t\t\t\t} catch (InterruptedException | ExecutionException e) {\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn 0;\n\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (max.isPresent()) {\t//判断任务是否完成\n\t\t\t\t\t\t\tqueue.remove(max.get());\n\t\t\t\t\t\t\tif (context.isRunning()) {\t//检测是否运行中(当在页面中点击\"停止\"时,此值为false,其余为true)\n\t\t\t\t\t\t\t\tSpiderTask task = (SpiderTask) max.get().get();\n\t\t\t\t\t\t\t\ttask.node.decrement();\t//任务执行完毕,计数器减一(该计数器是给Join节点使用)\n\t\t\t\t\t\t\t\tif (task.executor.allowExecuteNext(task.node, context, task.variables)) {\t//判断是否允许执行下一级\n\t\t\t\t\t\t\t\t\tlogger.debug(\"执行节点[{}:{}]完毕\", task.node.getNodeName(), task.node.getNodeId());\n\t\t\t\t\t\t\t\t\t//执行下一级\n\t\t\t\t\t\t\t\t\tSpider.this.executeNextNodes(task.node, context, task.variables);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tlogger.debug(\"执行节点[{}:{}]完毕，忽略执行下一节点\", task.node.getNodeName(), task.node.getNodeId());\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t//睡眠1ms,让出cpu\n\t\t\t\t\t\tThread.sleep(1);\n\t\t\t\t\t} catch (InterruptedException ignored) {\n\t\t\t\t\t} catch (Throwable t){\n\t\t\t\t\t\tlogger.error(\"程序发生异常\",t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t//等待线程池结束\n\t\t\t\tpool.awaitTermination();\n\t\t\t} finally {\n\t\t\t\t//触发监听器\n\t\t\t\tif (listeners != null) {\n\t\t\t\t\tlisteners.forEach(listener -> listener.afterEnd(context));\n\t\t\t\t}\n\t\t\t}\n\t\t}), null, root);\n\t\ttry {\n\t\t\tf.get();\t//阻塞等待所有任务执行完毕\n\t\t} catch (InterruptedException | ExecutionException ignored) {}\n\t}\n\n\t/**\n\t * 执行下一级节点\n\t */\n\tprivate void executeNextNodes(SpiderNode node, SpiderContext context, Map<String, Object> variables) {\n\t\tList<SpiderNode> nextNodes = node.getNextNodes();\n\t\tif (nextNodes != null) {\n\t\t\tfor (SpiderNode nextNode : nextNodes) {\n\t\t\t\texecuteNode(node, nextNode, context, variables);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * 执行节点\n\t */\n\tpublic void executeNode(SpiderNode fromNode, SpiderNode node, SpiderContext context, Map<String, Object> variables) {\n\t\tString shape = node.getStringJsonValue(\"shape\");\n\t\tif (StringUtils.isBlank(shape)) {\n\t\t\texecuteNextNodes(node, context, variables);\n\t\t\treturn;\n\t\t}\n\t\t//判断箭头上的条件，如果不成立则不执行\n\t\tif (!executeCondition(fromNode, node, variables, context)) {\n\t\t\treturn;\n\t\t}\n\t\tlogger.debug(\"执行节点[{}:{}]\", node.getNodeName(), node.getNodeId());\n\t\t//找到对应的执行器\n\t\tShapeExecutor executor = ExecutorsUtils.get(shape);\n\t\tif (executor == null) {\n\t\t\tlogger.error(\"执行失败,找不到对应的执行器:{}\", shape);\n\t\t\tcontext.setRunning(false);\n\t\t}\n\t\tint loopCount = 1;\t//循环次数默认为1,如果节点有循环属性且填了循环次数/集合,则取出循环次数\n\t\tint loopStart = 0;\t//循环起始位置\n\t\tint loopEnd = 1;\t//循环结束位置\n\t\tString loopCountStr = node.getStringJsonValue(ShapeExecutor.LOOP_COUNT);\n\t\tObject loopArray = null;\n\t\tboolean isLoop = false;\n\t\tif (isLoop = StringUtils.isNotBlank(loopCountStr)) {\n\t\t\ttry {\n\t\t\t\tloopArray = ExpressionUtils.execute(loopCountStr, variables);\n\t\t\t\tif(loopArray == null){\n\t\t\t\t\tloopCount = 0;\n\t\t\t\t}else if(loopArray instanceof Collection){\n\t\t\t\t\tloopCount = ((Collection)loopArray).size();\n\t\t\t\t\tloopArray = ((Collection)loopArray).toArray();\n\t\t\t\t}else if(loopArray.getClass().isArray()){\n\t\t\t\t\tloopCount = Array.getLength(loopArray);\n\t\t\t\t}else{\n\t\t\t\t\tloopCount = NumberUtils.toInt(loopArray.toString(),0);\n\t\t\t\t\tloopArray = null;\n\t\t\t\t}\n\t\t\t\tloopEnd = loopCount;\n\t\t\t\tif(loopCount > 0){\n\t\t\t\t\tloopStart = Math.max(NumberUtils.toInt(node.getStringJsonValue(LoopExecutor.LOOP_START), 0),0);\n\t\t\t\t\tint end = NumberUtils.toInt(node.getStringJsonValue(LoopExecutor.LOOP_END), -1);\n\t\t\t\t\tif(end >=0){\n\t\t\t\t\t\tloopEnd = Math.min(end,loopEnd);\n\t\t\t\t\t}else{\n\t\t\t\t\t\tloopEnd = Math.max(loopEnd + end + 1,0);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlogger.info(\"获取循环次数{}={}\", loopCountStr, loopCount);\n\t\t\t} catch (Throwable t) {\n\t\t\t\tloopCount = 0;\n\t\t\t\tlogger.error(\"获取循环次数失败,异常信息：{}\", t);\n\t\t\t}\n\t\t}\n\t\tif (loopCount > 0) {\n\t\t\t//获取循环下标的变量名称\n\t\t\tString loopVariableName = node.getStringJsonValue(ShapeExecutor.LOOP_VARIABLE_NAME);\n\t\t\tString loopItem = node.getStringJsonValue(LoopExecutor.LOOP_ITEM,\"item\");\n\t\t\tList<SpiderTask> tasks = new ArrayList<>();\n\t\t\tfor (int i = loopStart; i < loopEnd; i++) {\n\t\t\t\tnode.increment();\t//节点执行次数+1(后续Join节点使用)\n\t\t\t\tif (context.isRunning()) {\n\t\t\t\t\tMap<String, Object> nVariables = new HashMap<>();\n\t\t\t\t\t// 判断是否需要传递变量\n\t\t\t\t\tif(fromNode == null || node.isTransmitVariable(fromNode.getNodeId())){\n\t\t\t\t\t\tnVariables.putAll(variables);\n\t\t\t\t\t}\n\t\t\t\t\tif(isLoop){\n\t\t\t\t\t\t// 存入下标变量\n\t\t\t\t\t\tif (!StringUtils.isBlank(loopVariableName)) {\n\t\t\t\t\t\t\tnVariables.put(loopVariableName, i);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// 存入item\n\t\t\t\t\t\tnVariables.put(loopItem,loopArray == null ? i : Array.get(loopArray, i));\n\t\t\t\t\t}\n\t\t\t\t\ttasks.add(new SpiderTask(TtlRunnable.get(() -> {\n\t\t\t\t\t\tif (context.isRunning()) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t//死循环检测，当执行节点次数大于阈值时，结束本次测试\n\t\t\t\t\t\t\t\tAtomicInteger executeCount = context.get(ATOMIC_DEAD_CYCLE);\n\t\t\t\t\t\t\t\tif (executeCount != null && executeCount.incrementAndGet() > deadCycle) {\n\t\t\t\t\t\t\t\t\tcontext.setRunning(false);\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t//执行节点具体逻辑\n\t\t\t\t\t\t\t\texecutor.execute(node, context, nVariables);\n\t\t\t\t\t\t\t\t//当未发生异常时，移除ex变量\n\t\t\t\t\t\t\t\tnVariables.remove(\"ex\");\n\t\t\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\t\t\tnVariables.put(\"ex\", t);\n\t\t\t\t\t\t\t\tlogger.error(\"执行节点[{}:{}]出错,异常信息：{}\", node.getNodeName(), node.getNodeId(), t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}), node, nVariables, executor));\n\t\t\t\t}\n\t\t\t}\n\t\t\tLinkedBlockingQueue<Future<?>> futureQueue = context.getFutureQueue();\n\t\t\tfor (SpiderTask task : tasks) {\n\t\t\t\tif(executor.isThread()){\t//判断节点是否是异步运行\n\t\t\t\t\t//提交任务至线程池中,并将Future添加到队列末尾\n\t\t\t\t\tfutureQueue.add(context.getThreadPool().submitAsync(task.runnable, task, node));\n\t\t\t\t}else{\n\t\t\t\t\tFutureTask<SpiderTask> futureTask = new FutureTask<>(task.runnable, task);\n\t\t\t\t\tfutureTask.run();\n\t\t\t\t\tfutureQueue.add(futureTask);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t *\t判断箭头上的表达式是否成立\n\t */\n\tprivate boolean executeCondition(SpiderNode fromNode, SpiderNode node, Map<String, Object> variables, SpiderContext context) {\n\t\tif (fromNode != null) {\n\t\t\tboolean hasException = variables.get(\"ex\") != null;\n\t\t\tString exceptionFlow = node.getExceptionFlow(fromNode.getNodeId());\n\t\t\t//当出现异常流转 : 1\n\t\t\t//未出现异常流转 : 2\n\t\t\tif((\"1\".equalsIgnoreCase(exceptionFlow) && !hasException) || (\"2\".equalsIgnoreCase(exceptionFlow) && hasException)){\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tString condition = node.getCondition(fromNode.getNodeId());\n\t\t\tif (StringUtils.isNotBlank(condition)) { // 判断是否有条件\n\t\t\t\tObject result = null;\n\t\t\t\ttry {\n\t\t\t\t\tresult = ExpressionUtils.execute(condition, variables);\n\t\t\t\t} catch (Exception e) {\n\t\t\t\t\tlogger.error(\"判断{}出错,异常信息：{}\", condition, e);\n\t\t\t\t}\n\t\t\t\tif (result != null) {\n\t\t\t\t\tboolean isContinue = \"true\".equals(result) || Objects.equals(result, true);\n\t\t\t\t\tlogger.debug(\"判断{}={}\", condition, isContinue);\n\t\t\t\t\treturn isContinue;\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n\n\tclass SpiderTask{\n\n\t\tRunnable runnable;\n\n\t\tSpiderNode node;\n\n\t\tMap<String,Object> variables;\n\n\t\tShapeExecutor executor;\n\n\t\tpublic SpiderTask(Runnable runnable, SpiderNode node, Map<String, Object> variables,ShapeExecutor executor) {\n\t\t\tthis.runnable = runnable;\n\t\t\tthis.node = node;\n\t\t\tthis.variables = variables;\n\t\t\tthis.executor = executor;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/function/Base64FunctionExecutor.java",
    "content": "package org.spiderflow.core.executor.function;\r\n\r\nimport org.apache.commons.codec.binary.Base64;\r\nimport org.spiderflow.annotation.Comment;\r\nimport org.spiderflow.annotation.Example;\r\nimport org.spiderflow.executor.FunctionExecutor;\r\nimport org.springframework.stereotype.Component;\r\n\r\n/**\r\n * 字符串内容和Base64互相转换 工具类 防止NPE\r\n * @author Administrator\r\n *\r\n */\r\n@Component\r\n@Comment(\"base64常用方法\")\r\npublic class Base64FunctionExecutor implements FunctionExecutor{\r\n\t\r\n\t@Override\r\n\tpublic String getFunctionPrefix() {\r\n\t\treturn \"base64\";\r\n\t}\r\n\t\r\n\t@Comment(\"根据byte[]进行base64加密\")\r\n\t@Example(\"${base64.encode(resp.bytes)}\")\r\n\tpublic static String encode(byte[] bytes){\r\n\t\treturn bytes != null ? Base64.encodeBase64String(bytes) : null;\r\n\t}\r\n\t\r\n\t@Comment(\"根据String进行base64加密\")\r\n\t@Example(\"${base64.encode(resp.bytes,'UTF-8')}\")\r\n\tpublic static String encode(String content,String charset){\r\n\t\treturn encode(StringFunctionExecutor.bytes(content,charset));\r\n\t}\r\n\t\r\n\t@Comment(\"根据String进行base64加密\")\r\n\t@Example(\"${base64.encode(resp.html)}\")\r\n\tpublic static String encode(String content){\r\n\t\treturn encode(StringFunctionExecutor.bytes(content));\r\n\t}\r\n\t\r\n\t@Comment(\"根据byte[]进行base64加密\")\r\n\t@Example(\"${base64.encodeBytes(resp.bytes)}\")\r\n\tpublic static byte[] encodeBytes(byte[] bytes){\r\n\t\treturn bytes != null ? Base64.encodeBase64(bytes) : null;\r\n\t}\r\n\t\r\n\t@Comment(\"根据String进行base64加密\")\r\n\t@Example(\"${base64.encodeBytes(resp.html,'UTF-8')}\")\r\n\tpublic static byte[] encodeBytes(String content,String charset){\r\n\t\treturn encodeBytes(StringFunctionExecutor.bytes(content,charset));\r\n\t}\r\n\t\r\n\t@Comment(\"根据String进行base64加密\")\r\n\t@Example(\"${base64.encodeBytes(resp.html)}\")\r\n\tpublic static byte[] encodeBytes(String content){\r\n\t\treturn encodeBytes(StringFunctionExecutor.bytes(content));\r\n\t}\r\n\t\r\n\t@Comment(\"根据String进行base64解密\")\r\n\t@Example(\"${base64.decode(resp.html)}\")\r\n\tpublic static byte[] decode(String base64){\r\n\t\treturn base64  != null ? Base64.decodeBase64(base64) :null;\r\n\t}\r\n\t\r\n\t@Comment(\"根据byte[]进行base64解密\")\r\n\t@Example(\"${base64.decode(resp.bytes)}\")\r\n\tpublic static byte[] decode(byte[] base64){\r\n\t\treturn base64  != null ? Base64.decodeBase64(base64) :null;\r\n\t}\r\n\t\r\n\t@Comment(\"根据String进行base64解密\")\r\n\t@Example(\"${base64.decodeString(resp.html)}\")\r\n\tpublic static String decodeString(String base64){\r\n\t\treturn base64  != null ? new String(Base64.decodeBase64(base64)) :null;\r\n\t}\r\n\t\r\n\t@Comment(\"根据byte[]进行base64解密\")\r\n\t@Example(\"${base64.decodeString(resp.bytes)}\")\r\n\tpublic static String decodeString(byte[] base64){\r\n\t\treturn base64  != null ? new String(Base64.decodeBase64(base64)) :null;\r\n\t}\r\n\t\r\n\t@Comment(\"根据byte[]进行base64解密\")\r\n\t@Example(\"${base64.decodeString(resp.bytes,'UTF-8')}\")\r\n\tpublic static String decodeString(byte[] base64,String charset){\r\n\t\treturn base64  != null ? StringFunctionExecutor.newString(Base64.decodeBase64(base64),charset) :null;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/function/DateFunctionExecutor.java",
    "content": "package org.spiderflow.core.executor.function;\r\n\r\nimport java.text.ParseException;\r\nimport java.util.Date;\r\n\r\nimport org.apache.commons.lang3.time.DateFormatUtils;\r\nimport org.apache.commons.lang3.time.DateUtils;\r\nimport org.spiderflow.annotation.Comment;\r\nimport org.spiderflow.annotation.Example;\r\nimport org.spiderflow.executor.FunctionExecutor;\r\nimport org.springframework.stereotype.Component;\r\n\r\n/**\r\n * 时间获取/格式化 工具类 防止NPE 默认格式(yyyy-MM-dd HH:mm:ss)\r\n * @author Administrator\r\n *\r\n */\r\n@Component\r\n@Comment(\"日期常用方法\")\r\npublic class DateFunctionExecutor implements FunctionExecutor{\r\n\t\r\n\t@Override\r\n\tpublic String getFunctionPrefix() {\r\n\t\treturn \"date\";\r\n\t}\r\n\t\r\n\tprivate static final String DEFAULT_PATTERN = \"yyyy-MM-dd HH:mm:ss\";\r\n\r\n\t@Comment(\"格式化日期\")\r\n\t@Example(\"${date.format(date.now())}\")\r\n\tpublic static String format(Date date) {\r\n\t\treturn format(date, DEFAULT_PATTERN);\r\n\t}\r\n\r\n\t@Comment(\"格式化日期\")\r\n\t@Example(\"${date.format(1569059534000l)}\")\r\n\tpublic static String format(Long millis) {\r\n\t\treturn format(millis, DEFAULT_PATTERN);\r\n\t}\r\n\r\n\t@Comment(\"格式化日期\")\r\n\t@Example(\"${date.format(date.now(),'yyyy-MM-dd')}\")\r\n\tpublic static String format(Date date, String pattern) {\r\n\t\treturn date != null ? DateFormatUtils.format(date, pattern) : null;\r\n\t}\r\n\r\n\t@Comment(\"格式化日期\")\r\n\t@Example(\"${date.format(1569059534000l,'yyyy-MM-dd')}\")\r\n\tpublic static String format(Long millis, String pattern) {\r\n\t\treturn millis != null ? DateFormatUtils.format(millis, pattern) : null;\r\n\t}\r\n\t\r\n\t@Comment(\"字符串转为日期类型\")\r\n\t@Example(\"${date.parse('2019-01-01 00:00:00')}\")\r\n\tpublic static Date parse(String date) throws ParseException{\r\n\t\treturn date != null ? DateUtils.parseDate(date, DEFAULT_PATTERN) : null;\r\n\t}\r\n\t\r\n\t@Comment(\"字符串转为日期类型\")\r\n\t@Example(\"${date.parse('2019-01-01','yyyy-MM-dd')}\")\r\n\tpublic static Date parse(String date,String pattern) throws ParseException{\r\n\t\treturn date != null ? DateUtils.parseDate(date, pattern) : null;\r\n\t}\r\n\r\n\t@Comment(\"数字为日期类型\")\r\n\t@Example(\"${date.parse(1569059534000l)}\")\r\n\tpublic static Date parse(Long millis){\r\n\t\treturn new Date(millis);\r\n\t}\r\n\t\r\n\t@Comment(\"获取当前时间\")\r\n\t@Example(\"${date.now()}\")\r\n\tpublic static Date now(){\r\n\t\treturn new Date();\r\n\t}\r\n\t\r\n\t@Comment(\"获取指定日期n年后的日期\")\r\n\t@Example(\"${date.addYears(date.now(),2)}\")\r\n\tpublic static Date addYears(Date date,int amount){\r\n\t\treturn DateUtils.addYears(date, amount);\r\n\t}\r\n\t\r\n\t@Comment(\"获取指定日期n月后的日期\")\r\n\t@Example(\"${date.addMonths(date.now(),2)}\")\r\n\tpublic static Date addMonths(Date date,int amount){\r\n\t\treturn DateUtils.addMonths(date, amount);\r\n\t}\r\n\t\r\n\t@Comment(\"获取指定日期n天后的日期\")\r\n\t@Example(\"${date.addDays(date.now(),2)}\")\r\n\tpublic static Date addDays(Date date,int amount){\r\n\t\treturn DateUtils.addDays(date, amount);\r\n\t}\r\n\t\r\n\t@Comment(\"获取指定日期n小时后的日期\")\r\n\t@Example(\"${date.addHours(date.now(),2)}\")\r\n\tpublic static Date addHours(Date date,int amount){\r\n\t\treturn DateUtils.addHours(date, amount);\r\n\t}\r\n\t\r\n\t@Comment(\"获取指定日期n分钟后的日期\")\r\n\t@Example(\"${date.addMinutes(date.now(),2)}\")\r\n\tpublic static Date addMinutes(Date date,int amount){\r\n\t\treturn DateUtils.addMinutes(date, amount);\r\n\t}\r\n\t\r\n\t@Comment(\"获取指定日期n秒后的日期\")\r\n\t@Example(\"${date.addSeconds(date.now(),2)}\")\r\n\tpublic static Date addSeconds(Date date,int amount){\r\n\t\treturn DateUtils.addSeconds(date, amount);\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/function/ExtractFunctionExecutor.java",
    "content": "package org.spiderflow.core.executor.function;\r\n\r\nimport java.util.List;\r\n\r\nimport org.jsoup.Jsoup;\r\nimport org.jsoup.nodes.Element;\r\nimport org.spiderflow.annotation.Comment;\r\nimport org.spiderflow.annotation.Example;\r\nimport org.spiderflow.core.utils.ExtractUtils;\r\nimport org.spiderflow.executor.FunctionExecutor;\r\nimport org.springframework.stereotype.Component;\r\n\r\n@Component\r\n@Comment(\"数据抽取常用方法\")\r\npublic class ExtractFunctionExecutor implements FunctionExecutor{\r\n\r\n\t@Override\r\n\tpublic String getFunctionPrefix() {\r\n\t\treturn \"extract\";\r\n\t}\r\n\t\r\n\t@Comment(\"根据jsonpath提取内容\")\r\n\t@Example(\"${extract.jsonpath(resp.json,'$.code')}\")\r\n\tpublic static Object jsonpath(Object root,String jsonpath){\r\n\t\treturn ExtractUtils.getValueByJsonPath(root, jsonpath);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取内容\")\r\n\t@Example(\"${extract.regx(resp.html,'<title>(.*?)</title>')}\")\r\n\tpublic static String regx(String content,String pattern){\r\n\t\treturn ExtractUtils.getFirstMatcher(content, pattern, true);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取内容\")\r\n\t@Example(\"${extract.regx(resp.html,'<title>(.*?)</title>',1)}\")\r\n\tpublic static String regx(String content,String pattern,int groupIndex){\r\n\t\treturn ExtractUtils.getFirstMatcher(content, pattern, groupIndex);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取内容\")\r\n\t@Example(\"${extract.regx(resp.html,'<a href=\\\"(.*?)\\\">(.*?)</a>',[1,2])}\")\r\n\tpublic static List<String> regx(String content,String pattern,List<Integer> groups){\r\n\t\treturn ExtractUtils.getFirstMatcher(content, pattern, groups);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取内容\")\r\n\t@Example(\"${extract.regxs(resp.html,'<h2>(.*?)</h2>')}\")\r\n\tpublic static List<String> regxs(String content,String pattern){\r\n\t\treturn ExtractUtils.getMatchers(content, pattern, true);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取内容\")\r\n\t@Example(\"${extract.regxs(resp.html,'<h2>(.*?)</h2>',1)}\")\r\n\tpublic static List<String> regxs(String content,String pattern,int groupIndex){\r\n\t\treturn ExtractUtils.getMatchers(content, pattern, groupIndex);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取内容\")\r\n\t@Example(\"${extract.regxs(resp.html,'<a href=\\\"(.*?)\\\">(.*?)</a>',[1,2])}\")\r\n\tpublic static List<List<String>> regxs(String content,String pattern,List<Integer> groups){\r\n\t\treturn ExtractUtils.getMatchers(content, pattern, groups);\r\n\t}\r\n\t\r\n\t@Comment(\"根据xpath提取内容\")\r\n\t@Example(\"${extract.xpath(resp.element(),'//title/text()')}\")\r\n\tpublic static String xpath(Element element,String xpath){\r\n\t\treturn ExtractUtils.getValueByXPath(element, xpath);\r\n\t}\r\n\t\r\n\t@Comment(\"根据xpath提取内容\")\r\n\t@Example(\"${extract.xpath(resp.html,'//title/text()')}\")\r\n\tpublic static String xpath(String content,String xpath){\r\n\t\treturn xpath(Jsoup.parse(content),xpath);\r\n\t}\r\n\t\r\n\t@Comment(\"根据xpaths提取内容\")\r\n\t@Example(\"${extract.xpaths(resp.element(),'//h2/text()')}\")\r\n\tpublic static List<String> xpaths(Element element,String xpath){\r\n\t\treturn ExtractUtils.getValuesByXPath(element, xpath);\r\n\t}\r\n\t\r\n\t@Comment(\"根据xpaths提取内容\")\r\n\t@Example(\"${extract.xpaths(resp.html,'//h2/text()')}\")\r\n\tpublic static List<String> xpaths(String content,String xpath){\r\n\t\treturn xpaths(Jsoup.parse(content),xpath);\r\n\t}\r\n\t\r\n\t@Comment(\"根据css选择器提取内容\")\r\n\t@Example(\"${extract.selectors(resp.html,'div > a')}\")\r\n\tpublic static List<String> selectors(Object object,String selector){\r\n\t\treturn ExtractUtils.getHTMLBySelector(getElement(object), selector);\r\n\t}\r\n\t\r\n\t@Comment(\"根据css选择器提取内容\")\r\n\t@Example(\"${extract.selector(resp.html,'div > a','text')}\")\r\n\tpublic static Object selector(Object object,String selector,String type){\r\n\t\tif(\"element\".equals(type)){\r\n\t\t\treturn ExtractUtils.getFirstElement(getElement(object), selector);\r\n\t\t}else if(\"text\".equals(type)){\r\n\t\t\treturn ExtractUtils.getFirstTextBySelector(getElement(object), selector);\r\n\t\t}else if(\"outerhtml\".equals(type)){\r\n\t\t\treturn ExtractUtils.getFirstOuterHTMLBySelector(getElement(object), selector);\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\t\r\n\t@Comment(\"根据css选择器提取内容\")\r\n\t@Example(\"${extract.selector(resp.html,'div > a','attr','href')}\")\r\n\tpublic static String selector(Object object,String selector,String type,String attrValue){\r\n\t\tif(\"attr\".equals(type)){\r\n\t\t\treturn ExtractUtils.getFirstAttrBySelector(getElement(object), selector,attrValue);\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\t\r\n\t@Comment(\"根据css选择器提取内容\")\r\n\t@Example(\"${extract.selector(resp.html,'div > a')}\")\r\n\tpublic static String selector(Object object,String selector){\r\n\t\treturn ExtractUtils.getFirstHTMLBySelector(getElement(object), selector);\r\n\t}\r\n\t\r\n\t@Comment(\"根据css选择器提取内容\")\r\n\t@Example(\"${extract.selectors(resp.html,'div > a','element')}\")\r\n\tpublic static Object selectors(Object object,String selector,String type){\r\n\t\tif(\"element\".equals(type)){\r\n\t\t\treturn ExtractUtils.getElements(getElement(object), selector);\r\n\t\t}else if(\"text\".equals(type)){\r\n\t\t\treturn ExtractUtils.getTextBySelector(getElement(object), selector);\r\n\t\t}else if(\"outerhtml\".equals(type)){\r\n\t\t\treturn ExtractUtils.getOuterHTMLBySelector(getElement(object), selector);\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\t\r\n\t@Comment(\"根据css选择器提取内容\")\r\n\t@Example(\"${extract.selectors(resp.html,'div > a','attr','href')}\")\r\n\tpublic static Object selectors(Object object,String selector,String type,String attrValue){\r\n\t\tif(\"attr\".equals(type)){\r\n\t\t\treturn ExtractUtils.getAttrBySelector(getElement(object), selector,attrValue);\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\t\r\n\tprivate static Element getElement(Object object){\r\n\t\tif(object != null){\r\n\t\t\treturn object instanceof Element ? (Element)object:Jsoup.parse((String) object);\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/function/FileFunctionExecutor.java",
    "content": "package org.spiderflow.core.executor.function;\n\nimport java.io.*;\nimport java.nio.charset.Charset;\nimport java.util.List;\n\nimport org.apache.commons.io.IOUtils;\nimport org.spiderflow.annotation.Comment;\nimport org.spiderflow.annotation.Example;\nimport org.spiderflow.core.utils.FileUtils;\nimport org.spiderflow.executor.FunctionExecutor;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.CollectionUtils;\n\n/**\n * 文件读写 工具类 防止NPE \n * @author Administrator\n *\n */\n@Component\n@Comment(\"file常用方法\")\npublic class FileFunctionExecutor implements FunctionExecutor{\n\t\n\t@Override\n\tpublic String getFunctionPrefix() {\n\t\treturn \"file\";\n\t}\n\t\n\t/**\n\t * \n\t * @param path 文件路径/名\n\t * @param createDirectory 是否需要创建\n\t * @return File 文件\n\t */\n\tprivate static File getFile(String path,boolean createDirectory){\n\t\tFile f = new File(path);\n\t\tif(createDirectory&&!f.getParentFile().exists()){\n\t\t\tf.getParentFile().mkdirs();\n\t\t}\n\t\treturn f;\n\t}\n\n\t@Comment(\"写出文件\")\n\t@Example(\"${file.write('e:/result.html',resp.html,false)}\")\n\tpublic static void write(String path,String content,boolean append) throws IOException{\n\t\twrite(path,content,Charset.defaultCharset().name(),append);\n\t}\n\t\n\t@Comment(\"写出文件\")\n\t@Example(\"${file.write('e:/result.html',resp.html,'UTF-8',false)}\")\n\tpublic static void write(String path,String content,String charset,boolean append) throws IOException{\n\t\twrite(path,StringFunctionExecutor.bytes(content, charset),append);\n\t}\n\t\n\t@Comment(\"写出文件\")\n\t@Example(\"${file.write('e:/result.html',resp.bytes,false)}\")\n\tpublic static void write(String path,byte[] bytes,boolean append) throws IOException{\n\t\twrite(path, new ByteArrayInputStream(bytes),append);\n\t}\n\n\t@Comment(\"写出文件\")\n\t@Example(\"${file.write('e:/result.html',resp.stream,false)}\")\n\tpublic static void write(String path, InputStream stream, boolean append) throws IOException {\n\t\ttry(FileOutputStream fos = new FileOutputStream(getFile(path,true),append)){\n\t\t\tIOUtils.copyLarge(stream, fos);\n\t\t}\n\t}\n\n\t@Comment(\"写出文件\")\n\t@Example(\"${file.write('e:/result.html',resp.bytes,false)}\")\n\tpublic static void write(String path, InputStream stream) throws IOException {\n\t\twrite(path, stream,false);\n\t}\n\t\n\t@Comment(\"写出文件\")\n\t@Example(\"${file.write('e:/result.html',resp.html)}\")\n\tpublic static void write(String path,String content) throws IOException{\n\t\twrite(path,content,false);\n\t}\n\t\n\t@Comment(\"写出文件\")\n\t@Example(\"${file.write('e:/result.html',resp.html,'UTF-8')}\")\n\tpublic static void write(String path,String content,String charset) throws IOException{\n\t\twrite(path,content,charset,false);\n\t}\n\t\n\t@Comment(\"写出文件\")\n\t@Example(\"${file.write('e:/result.html',resp.bytes)}\")\n\tpublic static void write(String path,byte[] bytes) throws IOException{\n\t\twrite(path,bytes,false);\n\t}\n\n\t@Comment(\"下载Url资源\")\n\t@Example(\"${file.download('e:/downloadPath',urls)}\")\n\tpublic static void download(String path, List<String> urls) throws IOException{\n\t\tif(!CollectionUtils.isEmpty(urls)) {\n\t\t\tfor (String url : urls) {\n\t\t\t\tFileUtils.downloadFile(path, url, true);\n\t\t\t}\n\t\t}\n\t}\n\n\t@Comment(\"下载Url资源\")\n\t@Example(\"${file.download('e:/downloadPath',urls)}\")\n\tpublic static void download(String path, String url) throws IOException {\n\t\tif (url != null) {\n\t\t\tFileUtils.downloadFile(path, url, true);\n\t\t}\n\t}\n\t\n\t@Comment(\"读取文件\")\n\t@Example(\"${file.bytes('e:/result.html')}\")\n\tpublic static byte[] bytes(String path) throws IOException{\n\t\ttry(FileInputStream fis = new FileInputStream(getFile(path, false))){\n\t\t\treturn IOUtils.toByteArray(fis);\t\n\t\t}\n\t}\n\t\n\t@Comment(\"读取文件\")\n\t@Example(\"${file.string('e:/result.html','UTF-8')}\")\n\tpublic static String string(String path,String charset) throws IOException{\n\t\treturn StringFunctionExecutor.newString(bytes(path), charset);\n\t}\n\t\n\t@Comment(\"读取文件\")\n\t@Example(\"${file.string('e:/result.html')}\")\n\tpublic static String string(String path) throws IOException{\n\t\treturn StringFunctionExecutor.newString(bytes(path), Charset.defaultCharset().name());\n\t}\n\t\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/function/JsonFunctionExecutor.java",
    "content": "package org.spiderflow.core.executor.function;\r\n\r\nimport org.spiderflow.annotation.Comment;\r\nimport org.spiderflow.annotation.Example;\r\nimport org.spiderflow.executor.FunctionExecutor;\r\nimport org.springframework.stereotype.Component;\r\n\r\nimport com.alibaba.fastjson.JSON;\r\n\r\n/**\r\n * Json和String互相转换 工具类 防止NPE \r\n * @author Administrator\r\n *\r\n */\r\n@Component\r\n@Comment(\"json常用方法\")\r\npublic class JsonFunctionExecutor implements FunctionExecutor{\r\n\t\r\n\t@Override\r\n\tpublic String getFunctionPrefix() {\r\n\t\treturn \"json\";\r\n\t}\r\n\r\n\t@Comment(\"将字符串转为json对象\")\r\n\t@Example(\"${json.parse('{code : 1}')}\")\r\n\tpublic static Object parse(String jsonString){\r\n\t\treturn jsonString != null ? JSON.parse(jsonString) : null;\r\n\t}\r\n\t\r\n\t@Comment(\"将对象转为json字符串\")\r\n\t@Example(\"${json.stringify(objVar)}\")\r\n\tpublic static String stringify(Object object){\r\n\t\treturn object != null ? JSON.toJSONString(object) : null;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/function/ListFunctionExecutor.java",
    "content": "package org.spiderflow.core.executor.function;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.regex.Pattern;\r\n\r\nimport org.spiderflow.annotation.Comment;\r\nimport org.spiderflow.annotation.Example;\r\nimport org.spiderflow.executor.FunctionExecutor;\r\nimport org.springframework.stereotype.Component;\r\n\r\n/**\r\n * List 工具类 防止NPE 添加了类似python的split()方法 \r\n * @author Administrator\r\n *\r\n */\r\n@Component\r\n@Comment(\"list常用方法\")\r\npublic class ListFunctionExecutor implements FunctionExecutor{\r\n\t\r\n\t@Override\r\n\tpublic String getFunctionPrefix() {\r\n\t\treturn \"list\";\r\n\t}\r\n\r\n\t@Comment(\"获取list的长度\")\r\n\t@Example(\"${list.length(listVar)}\")\r\n\tpublic static int length(List<?> list){\r\n\t\treturn list != null ? list.size() : 0;\r\n\t}\r\n\t\r\n\t/**\r\n\t * \r\n\t * @param list 原List\r\n\t * @param len 按多长进行分割\r\n\t * @return List<List<?>> 分割后的数组\r\n\t */\r\n\t@Comment(\"分割List\")\r\n\t@Example(\"${list.split(listVar,10)}\")\r\n\tpublic static List<List<?>> split(List<?> list,int len){\r\n\t\tList<List<?>> result = new ArrayList<>();\r\n\t\tif (list == null || list.size() == 0 || len < 1) {\r\n\t\t\treturn result;\r\n\t\t}\r\n\t\tint size = list.size();\r\n\t\tint count = (size + len - 1) / len;\r\n\t\tfor (int i = 0; i < count; i++) {\r\n\t\t\tList<?> subList = list.subList(i * len, ((i + 1) * len > size ? size : len * (i + 1)));\r\n\t\t\tresult.add(subList);\r\n\t\t}\r\n\t\treturn result;\r\n\t}\r\n\t\r\n\t@Comment(\"截取List\")\r\n\t@Example(\"${list.sublist(listVar,fromIndex,toIndex)}\")\r\n\tpublic static List<?> sublist(List<?> list,int fromIndex,int toIndex){\r\n\t\treturn list!= null ? list.subList(fromIndex, toIndex) : new ArrayList<>();\r\n\t}\r\n\r\n\t@Comment(\"过滤字符串list元素\")\r\n\t@Example(\"${listVar.filterStr(pattern)}\")\r\n\tpublic static List<String> filterStr(List<String> list, String pattern) {\r\n\t\tif (list == null || list.isEmpty()) {\r\n\t\t\treturn null;\r\n\t\t}\r\n\t\tList<String> result = new ArrayList<>(list.size());\r\n\t\tfor (String item : list) {\r\n\t\t\tif (Pattern.matches(pattern, item)) {\r\n\t\t\t\tresult.add(item);\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn result;\r\n\t}\r\n\t\t\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/function/MD5FunctionExecutor.java",
    "content": "package org.spiderflow.core.executor.function;\n\nimport org.apache.commons.codec.digest.DigestUtils;\nimport org.spiderflow.annotation.Comment;\nimport org.spiderflow.annotation.Example;\nimport org.spiderflow.executor.FunctionExecutor;\nimport org.springframework.stereotype.Component;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n@Component\n@Comment(\"MD5常用方法\")\npublic class MD5FunctionExecutor implements FunctionExecutor {\n\n    @Override\n    public String getFunctionPrefix() {\n        return \"md5\";\n    }\n\n    @Comment(\"md5加密\")\n    @Example(\"${md5.string(resp.html)}\")\n    public static String string(String str){\n        return DigestUtils.md5Hex(str);\n    }\n\n    @Comment(\"md5加密\")\n    @Example(\"${md5.string(resp.bytes)}\")\n    public static String string(byte[] bytes){\n        return DigestUtils.md5Hex(bytes);\n    }\n\n    @Comment(\"md5加密\")\n    @Example(\"${md5.string(resp.stream)}\")\n    public static String string(InputStream stream) throws IOException {\n        return DigestUtils.md5Hex(stream);\n    }\n\n    @Comment(\"md5加密\")\n    @Example(\"${md5.bytes(resp.html)}\")\n    public static byte[] bytes(String str){\n        return DigestUtils.md5(str);\n    }\n\n    @Comment(\"md5加密\")\n    @Example(\"${md5.bytes(resp.bytes)}\")\n    public static byte[] bytes(byte[] bytes){\n        return DigestUtils.md5(bytes);\n    }\n\n    @Comment(\"md5加密\")\n    @Example(\"${md5.bytes(resp.stream)}\")\n    public static byte[] bytes(InputStream stream) throws IOException {\n        return DigestUtils.md5(stream);\n    }\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/function/RandomFunctionExecutor.java",
    "content": "package org.spiderflow.core.executor.function;\r\n\r\nimport org.apache.commons.lang3.RandomUtils;\r\nimport org.spiderflow.annotation.Comment;\r\nimport org.spiderflow.annotation.Example;\r\nimport org.spiderflow.executor.FunctionExecutor;\r\nimport org.springframework.stereotype.Component;\r\n\r\n/**\r\n * 随机数/字符串 生成方法 \r\n * @author Administrator\r\n *\r\n */\r\n@Component\r\npublic class RandomFunctionExecutor implements FunctionExecutor{\r\n\t\r\n\t@Override\r\n\tpublic String getFunctionPrefix() {\r\n\t\treturn \"random\";\r\n\t}\r\n\t\r\n\t@Comment(\"随机获取int\")\r\n\t@Example(\"${random.randomInt(1,10)}\")\r\n\tpublic static int randomInt(int min,int max){\r\n\t\treturn RandomUtils.nextInt(min, max);\r\n\t}\r\n\t\r\n\t@Comment(\"随机获取double\")\r\n\t@Example(\"${random.randomDouble(1,10)}\")\r\n\tpublic static double randomDouble(double min,double max){\r\n\t\treturn RandomUtils.nextDouble(min, max);\r\n\t}\r\n\t\r\n\t@Comment(\"随机获取long\")\r\n\t@Example(\"${random.randomLong(1,10)}\")\r\n\tpublic static long randomLong(long min,long max){\r\n\t\treturn RandomUtils.nextLong(min, max);\r\n\t}\r\n\t\r\n\t/**\r\n\t * \r\n\t * @param chars 字符个数\r\n\t * @param length 字符范围\r\n\t * @return String 随机字符串\r\n\t */\r\n\t@Comment(\"随机获取字符串\")\r\n\t@Example(\"${random.string('abcde',10)}\")\r\n\tpublic static String string(String chars,int length){\r\n\t\tif (chars != null) {\r\n\t\t\tchar[] newChars = new char[length];\r\n\t\t\tint len = chars.length();\r\n\t\t\tfor (int i = 0; i < length; i++) {\r\n\t\t\t\tnewChars[i] = chars.charAt(randomInt(0,len));\r\n\t\t\t}\r\n\t\t\treturn new String(newChars);\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\t\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/function/StringFunctionExecutor.java",
    "content": "package org.spiderflow.core.executor.function;\r\n\r\nimport java.io.UnsupportedEncodingException;\r\nimport java.util.ArrayList;\r\nimport java.util.Arrays;\r\nimport java.util.List;\r\nimport java.util.UUID;\r\n\r\nimport org.spiderflow.annotation.Comment;\r\nimport org.spiderflow.annotation.Example;\r\nimport org.spiderflow.executor.FunctionExecutor;\r\nimport org.springframework.stereotype.Component;\r\n\r\n/**\r\n * String 工具类 防止NPE \r\n * @author Administrator\r\n *\r\n */\r\n@Component\r\n@Comment(\"string常用方法\")\r\npublic class StringFunctionExecutor implements FunctionExecutor{\r\n\t\r\n\t@Override\r\n\tpublic String getFunctionPrefix() {\r\n\t\treturn \"string\";\r\n\t}\r\n\r\n\t@Comment(\"截取字符串方法\")\r\n\t@Example(\"${string.substring(str,5)}\")\r\n\tpublic static String substring(String content, int beginIndex) {\r\n\t\treturn content != null ? content.substring(beginIndex) : null;\r\n\t}\r\n\r\n\t@Comment(\"截取字符串方法\")\r\n\t@Example(\"${string.substring(str,0,str.length() - 1)}\")\r\n\tpublic static String substring(String content, int beginIndex, int endIndex) {\r\n\t\treturn content != null ? content.substring(beginIndex, endIndex) : null;\r\n\t}\r\n\r\n\t@Comment(\"将字符串转为小写\")\r\n\t@Example(\"${string.lower(str)}\")\r\n\tpublic static String lower(String content) {\r\n\t\treturn content != null ? content.toLowerCase() : null;\r\n\t}\r\n\r\n\t@Comment(\"将字符串转为大写\")\r\n\t@Example(\"${string.upper(str)}\")\r\n\tpublic static String upper(String content) {\r\n\t\treturn content != null ? content.toUpperCase() : null;\r\n\t}\r\n\r\n\t@Comment(\"查找指定字符在字符串在中的位置\")\r\n\t@Example(\"${string.indexOf(content,str)}\")\r\n\tpublic static int indexOf(String content, String str) {\r\n\t\treturn content != null ? content.indexOf(str) : -1;\r\n\t}\r\n\r\n\t@Comment(\"查找指定字符在字符串中最后出现的位置\")\r\n\t@Example(\"${string.lastIndexOf(content,str)}\")\r\n\tpublic static int lastIndexOf(String content, String str) {\r\n\t\treturn content != null ? content.lastIndexOf(str) : -1;\r\n\t}\r\n\r\n\t@Comment(\"查找指定字符在字符串在中的位置\")\r\n\t@Example(\"${string.indexOf(content,str,fromIndex)}\")\r\n\tpublic static int indexOf(String content, String str, int fromIndex) {\r\n\t\treturn content != null ? content.indexOf(str, fromIndex) : -1;\r\n\t}\r\n\t\r\n\t@Comment(\"将字符串转为int\")\r\n\t@Example(\"${string.toInt(value)}\")\r\n\tpublic static int toInt(String value){\r\n\t\treturn Integer.parseInt(value);\r\n\t}\r\n\t\r\n\t@Comment(\"将字符串转为Integer\")\r\n\t@Example(\"${string.toInt(value,defaultValue)}\")\r\n\tpublic static Integer toInt(String value,Integer defaultValue){\r\n\t\ttry {\r\n\t\t\treturn Integer.parseInt(value);\r\n\t\t} catch (Exception e) {\r\n\t\t\treturn defaultValue;\r\n\t\t}\r\n\t}\r\n\t\r\n\t@Comment(\"字符串替换\")\r\n\t@Example(\"${string.replace(content,source,target)}\")\r\n\tpublic static String replace(String content,String source,String target){\r\n\t\treturn content != null ? content.replace(source, target): null;\r\n\t}\r\n\t\r\n\t@Comment(\"正则替换字符串\")\r\n\t@Example(\"${string.replaceAll(content,regx,target)}\")\r\n\tpublic static String replaceAll(String content,String regx,String target){\r\n\t\treturn content != null ? content.replaceAll(regx, target): null;\r\n\t}\r\n\t\r\n\t@Comment(\"正则替换字符串\")\r\n\t@Example(\"${string.replaceFirst(content,regx,target)}\")\r\n\tpublic static String replaceFirst(String content,String regx,String target){\r\n\t\treturn content != null ? content.replaceFirst(regx, target): null;\r\n\t}\r\n\t\r\n\t@Comment(\"正则替换字符串\")\r\n\t@Example(\"${string.length(content)}\")\r\n\tpublic static int length(String content){\r\n\t\treturn content != null ? content.length() : -1;\r\n\t}\r\n\t\r\n\t@Comment(\"去除字符串两边的空格\")\r\n\t@Example(\"${string.trim(content)}\")\r\n\tpublic static String trim(String content){\r\n\t\treturn content != null ? content.trim() : null;\r\n\t}\r\n\t\r\n\t@Comment(\"分割字符串\")\r\n\t@Example(\"${string.split(content,regx)}\")\r\n\tpublic static List<String> split(String content,String regx){\r\n\t\treturn content != null ? Arrays.asList(content.split(regx)) : new ArrayList<>(0);\r\n\t}\r\n\t\r\n\t@Comment(\"获取字符串的byte[]\")\r\n\t@Example(\"${string.bytes(content)}\")\r\n\tpublic static byte[] bytes(String content){\r\n\t\treturn content != null ? content.getBytes() : null;\r\n\t}\r\n\t\r\n\t@Comment(\"获取字符串的byte[]\")\r\n\t@Example(\"${string.bytes(content,charset)}\")\r\n\tpublic static byte[] bytes(String content,String charset){\r\n\t\ttry {\r\n\t\t\treturn content != null ? content.getBytes(charset) : null;\r\n\t\t} catch (UnsupportedEncodingException e) {\r\n\t\t\treturn null;\r\n\t\t}\r\n\t}\r\n\t\r\n\t@Comment(\"byte[]转String\")\r\n\t@Example(\"${string.newString(bytes)}\")\r\n\tpublic static String newString(byte[] bytes){\r\n\t\treturn bytes != null ? new String(bytes) : null;\r\n\t}\r\n\t\r\n\t@Comment(\"byte[]转String\")\r\n\t@Example(\"${string.newString(bytes,charset)}\")\r\n\tpublic static String newString(byte[] bytes,String charset){\r\n\t\ttry {\r\n\t\t\treturn bytes != null ? new String(bytes,charset) : null;\r\n\t\t} catch (UnsupportedEncodingException e) {\r\n\t\t\treturn null;\r\n\t\t}\r\n\t}\r\n\t\r\n\t@Comment(\"判断两个字符串是否相同\")\r\n\t@Example(\"${string.newString(bytes,charset)}\")\r\n\tpublic static boolean equals(String str1,String str2){\r\n\t\treturn str1 == null ? str2 == null : str1.equals(str2);\r\n\t}\r\n\t\r\n\t@Comment(\"生成UUID\")\r\n\t@Example(\"${string.uuid()}\")\r\n\tpublic static String uuid() {\r\n\t\treturn UUID.randomUUID().toString().replace(\"-\", \"\");\r\n\t}\r\n\t\r\n\t@Comment(\"生成多个UUID\")\r\n\t@Example(\"${string.uuid(size)}\")\r\n\tpublic static List<String> uuids(Integer size) {\r\n\t\tList<String> ids = new ArrayList<String>();\r\n\t\tfor (int i = 0; i < size; i++) {\r\n\t\t\tids.add(UUID.randomUUID().toString().replace(\"-\", \"\"));\r\n\t\t}\r\n\t\treturn ids;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/function/ThreadFunctionExecutor.java",
    "content": "package org.spiderflow.core.executor.function;\n\nimport org.spiderflow.annotation.Comment;\nimport org.spiderflow.annotation.Example;\nimport org.spiderflow.executor.FunctionExecutor;\nimport org.springframework.stereotype.Component;\n\n/**\n * Created on 2019-12-06\n *\n * @author Octopus\n */\n@Component\n@Comment(\"thread常用方法\")\npublic class ThreadFunctionExecutor implements FunctionExecutor {\n    @Override\n    public String getFunctionPrefix() {\n        return \"thread\";\n    }\n\n    @Comment(\"线程休眠\")\n    @Example(\"${thread.sleep(1000L)}\")\n    public static void sleep(Long sleepTime){\n        try {\n            Thread.sleep(sleepTime);\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n    }\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/function/UrlFunctionExecutor.java",
    "content": "package org.spiderflow.core.executor.function;\r\n\r\nimport java.io.UnsupportedEncodingException;\r\nimport java.net.URLDecoder;\r\nimport java.net.URLEncoder;\r\nimport java.nio.charset.Charset;\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\n\r\nimport org.apache.commons.lang3.StringUtils;\r\nimport org.spiderflow.annotation.Comment;\r\nimport org.spiderflow.annotation.Example;\r\nimport org.spiderflow.executor.FunctionExecutor;\r\nimport org.springframework.stereotype.Component;\r\n\r\n/**\r\n * url 按指定字符集进行编码/解码 默认字符集(UTF-8) 工具类 防止NPE \r\n */\r\n@Component\r\npublic class UrlFunctionExecutor implements FunctionExecutor{\r\n\t\r\n\t@Override\r\n\tpublic String getFunctionPrefix() {\r\n\t\treturn \"url\";\r\n\t}\r\n\t\r\n\t@Comment(\"获取url参数\")\r\n\t@Example(\"${url.parameter('http://www.baidu.com/s?wd=spider-flow','wd')}\")\r\n\tpublic static String parameter(String url,String key){\r\n\t\treturn parameterMap(url).get(key);\r\n\t}\r\n\t\r\n\t@Comment(\"获取url全部参数\")\r\n\t@Example(\"${url.parameterMap('http://www.baidu.com/s?wd=spider-flow&abbr=sf')}\")\r\n\tpublic static Map<String,String> parameterMap(String url){\r\n\t\tMap<String,String> map = new HashMap<String,String>();\r\n\t\tint index = url.indexOf(\"?\");\r\n\t\tif(index != -1) {\r\n\t        String param = url.substring(index+1);\r\n\t        if(StringUtils.isNotBlank(param)) {\r\n\t\t        String[] params = param.split(\"&\");\r\n\t\t        for (String item : params) {\r\n\t\t            String[] kv = item.split(\"=\");\r\n\t\t            if(kv.length > 0) {\r\n\t\t            \tif(StringUtils.isNotBlank(kv[0])) {\r\n\t\t            \t\tString value = \"\";\r\n\t\t            \t\tif(StringUtils.isNotBlank(kv[1])) {\r\n\t\t            \t\t\tint kv1Index = kv[1].indexOf(\"#\");\r\n\t\t            \t\t\tif(kv1Index != -1) {\r\n\t\t            \t\t\t\tvalue = kv[1].substring(0,kv1Index);\r\n\t\t            \t\t\t}else {\r\n\t\t            \t\t\t\tvalue = kv[1];\r\n\t\t            \t\t\t}\r\n\t\t            \t\t}\r\n\t\t            \t\tmap.put(kv[0],value);\r\n\t\t            \t}\r\n\t\t            }\r\n\t\t        }\r\n\t        }\r\n\t\t}\r\n\t\treturn map;\r\n\t}\r\n\t\r\n\t@Comment(\"url编码\")\r\n\t@Example(\"${url.encode('http://www.baidu.com/s?wd=spider-flow')}\")\r\n\tpublic static String encode(String url){\r\n\t\treturn encode(url,Charset.defaultCharset().name());\r\n\t}\r\n\t\r\n\t@Comment(\"url编码\")\r\n\t@Example(\"${url.encode('http://www.baidu.com/s?wd=spider-flow','UTF-8')}\")\r\n\tpublic static String encode(String url,String charset){\r\n\t\ttry {\r\n\t\t\treturn url != null ? URLEncoder.encode(url,charset) : null;\r\n\t\t} catch (UnsupportedEncodingException e) {\r\n\t\t\treturn null;\r\n\t\t}\r\n\t}\r\n\t\r\n\t@Comment(\"url解码\")\r\n\t@Example(\"${url.decode(strVar)}\")\r\n\tpublic static String decode(String url){\r\n\t\treturn decode(url,Charset.defaultCharset().name());\r\n\t}\r\n\t\r\n\t@Comment(\"url解码\")\r\n\t@Example(\"${url.decode(strVar,'UTF-8')}\")\r\n\tpublic static String decode(String url,String charset){\r\n\t\ttry {\r\n\t\t\treturn url != null ? URLDecoder.decode(url, charset) : null;\r\n\t\t} catch (UnsupportedEncodingException e) {\r\n\t\t\treturn null;\r\n\t\t}\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/function/extension/ArrayFunctionExtension.java",
    "content": "package org.spiderflow.core.executor.function.extension;\r\n\r\nimport java.util.Arrays;\r\nimport java.util.List;\r\n\r\nimport org.apache.commons.lang3.StringUtils;\r\nimport org.spiderflow.annotation.Comment;\r\nimport org.spiderflow.annotation.Example;\r\nimport org.spiderflow.executor.FunctionExtension;\r\nimport org.springframework.stereotype.Component;\r\n\r\n@Component\r\npublic class ArrayFunctionExtension implements FunctionExtension{\r\n\t\r\n\t@Override\r\n\tpublic Class<?> support() {\r\n\t\treturn Object[].class;\r\n\t}\r\n\t\r\n\t@Comment(\"获取数组的长度\")\r\n\t@Example(\"${arrayVar.size()}\")\r\n\tpublic static int size(Object[] objs){\r\n\t\treturn objs.length;\r\n\t}\r\n\t\r\n\t@Comment(\"将数组拼接起来\")\r\n\t@Example(\"${arrayVar.join()}\")\r\n\tpublic static String join(Object[] objs,String separator){\r\n\t\treturn StringUtils.join(objs,separator);\r\n\t}\r\n\t\r\n\t@Comment(\"将数组用separator拼接起来\")\r\n\t@Example(\"${arrayVar.join('-')}\")\r\n\tpublic static String join(Object[] objs){\r\n\t\treturn StringUtils.join(objs);\r\n\t}\r\n\t\r\n\t@Comment(\"将数组转为List\")\r\n\t@Example(\"${arrayVar.toList()}\")\r\n\tpublic static List<?> toList(Object[] objs){\r\n\t\treturn Arrays.asList(objs);\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/function/extension/DateFunctionExtension.java",
    "content": "package org.spiderflow.core.executor.function.extension;\r\n\r\nimport java.util.Date;\r\n\r\nimport org.apache.commons.lang3.time.DateFormatUtils;\r\nimport org.spiderflow.annotation.Comment;\r\nimport org.spiderflow.annotation.Example;\r\nimport org.spiderflow.executor.FunctionExtension;\r\nimport org.springframework.stereotype.Component;\r\n\r\n@Component\r\npublic class DateFunctionExtension implements FunctionExtension{\r\n\r\n\t@Override\r\n\tpublic Class<?> support() {\r\n\t\treturn Date.class;\r\n\t}\r\n\t\r\n\t@Comment(\"格式化日期\")\r\n\t@Example(\"${dateVar.format()}\")\r\n\tpublic static String format(Date date){\r\n\t\treturn format(date, \"yyyy-MM-dd HH:mm:ss\");\r\n\t}\r\n\t\r\n\t@Comment(\"格式化日期\")\r\n\t@Example(\"${dateVar.format('yyyy-MM-dd HH:mm:ss')}\")\r\n\tpublic static String format(Date date,String pattern){\r\n\t\treturn DateFormatUtils.format(date,pattern);\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/function/extension/ElementFunctionExtension.java",
    "content": "package org.spiderflow.core.executor.function.extension;\r\n\r\nimport java.util.List;\r\n\r\nimport org.jsoup.nodes.Element;\r\nimport org.jsoup.select.Elements;\r\nimport org.spiderflow.annotation.Comment;\r\nimport org.spiderflow.annotation.Example;\r\nimport org.spiderflow.annotation.Return;\r\nimport org.spiderflow.core.utils.ExtractUtils;\r\nimport org.spiderflow.executor.FunctionExtension;\r\nimport org.springframework.stereotype.Component;\r\n\r\n@Component\r\npublic class ElementFunctionExtension implements FunctionExtension{\r\n\r\n\t@Override\r\n\tpublic Class<?> support() {\r\n\t\treturn Element.class;\r\n\t}\r\n\t\r\n\t@Comment(\"根据xpath提取内容\")\r\n\t@Example(\"${elementVar.xpath('//title/text()')}\")\r\n\t@Return({Element.class,String.class})\r\n\tpublic static String xpath(Element element,String xpath){\r\n\t\treturn ExtractUtils.getValueByXPath(element, xpath);\r\n\t}\r\n\t\r\n\r\n\t@Comment(\"根据xpath提取内容\")\r\n\t@Example(\"${elementVar.xpaths('//h2/text()')}\")\r\n\t@Return({Element.class,String.class})\r\n\tpublic static List<String> xpaths(Element element,String xpath){\r\n\t\treturn ExtractUtils.getValuesByXPath(element, xpath);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取内容\")\r\n\t@Example(\"${elementVar.regx('<title>(.*?)</title>')}\")\r\n\tpublic static String regx(Element element,String regx){\r\n\t\treturn ExtractUtils.getFirstMatcher(element.html(), regx, true);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取内容\")\r\n\t@Example(\"${elementVar.regx('<title>(.*?)</title>',1)}\")\r\n\tpublic static String regx(Element element,String regx,int groupIndex){\r\n\t\treturn ExtractUtils.getFirstMatcher(element.html(), regx, groupIndex);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取内容\")\r\n\t@Example(\"${elementVar.regx('<a href=\\\"(.*?)\\\">(.*?)</a>',[1,2])}\")\r\n\tpublic static List<String> regx(Element element,String regx,List<Integer> groups){\r\n\t\treturn ExtractUtils.getFirstMatcher(element.html(), regx, groups);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取内容\")\r\n\t@Example(\"${elementVar.regxs('<h2>(.*?)</h2>')}\")\r\n\tpublic static List<String> regxs(Element element,String regx){\r\n\t\treturn ExtractUtils.getMatchers(element.html(), regx, true);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取内容\")\r\n\t@Example(\"${elementVar.regxs('<h2>(.*?)</h2>',1)}\")\r\n\tpublic static List<String> regxs(Element element,String regx,int groupIndex){\r\n\t\treturn ExtractUtils.getMatchers(element.html(), regx, groupIndex);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取内容\")\r\n\t@Example(\"${elementVar.regxs('<a href=\\\"(.*?)\\\">(.*?)</a>',[1,2])}\")\r\n\tpublic static List<List<String>> regxs(Element element,String regx,List<Integer> groups){\r\n\t\treturn ExtractUtils.getMatchers(element.html(), regx, groups);\r\n\t}\r\n\t\r\n\t@Comment(\"根据css选择器提取内容\")\r\n\t@Example(\"${elementVar.selector('div > a')}\")\r\n\tpublic static Element selector(Element element,String cssQuery){\r\n\t\treturn element.selectFirst(cssQuery);\r\n\t}\r\n\t\r\n\t@Comment(\"根据css选择器提取内容\")\r\n\t@Example(\"${elementVar.selectors('div > a')}\")\r\n\tpublic static Elements selectors(Element element,String cssQuery){\r\n\t\treturn element.select(cssQuery);\r\n\t}\r\n\r\n\t@Comment(\"获取同级节点\")\r\n\t@Example(\"${elementVar.subling()}\")\r\n\tpublic static Elements subling(Element element){\r\n\t\treturn element.siblingElements();\r\n\t}\r\n\r\n\t@Comment(\"获取上级节点\")\r\n\t@Example(\"${elementVar.parent()}\")\r\n\tpublic static Element parent(Element element){\r\n\t\treturn element.parent();\r\n\t}\r\n\r\n\t@Comment(\"获取上级节点\")\r\n\t@Example(\"${elementVar.parents()}\")\r\n\tpublic static Elements parents(Element element){\r\n\t\treturn element.parents();\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/function/extension/ElementsFunctionExtension.java",
    "content": "package org.spiderflow.core.executor.function.extension;\r\n\r\nimport org.jsoup.nodes.Element;\r\nimport org.jsoup.select.Elements;\r\nimport org.spiderflow.annotation.Comment;\r\nimport org.spiderflow.annotation.Example;\r\nimport org.spiderflow.core.utils.ExtractUtils;\r\nimport org.spiderflow.executor.FunctionExtension;\r\nimport org.springframework.stereotype.Component;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\n@Component\r\npublic class ElementsFunctionExtension implements FunctionExtension{\r\n\r\n\t@Override\r\n\tpublic Class<?> support() {\r\n\t\treturn Elements.class;\r\n\t}\r\n\t\r\n\t@Comment(\"根据xpath提取内容\")\r\n\t@Example(\"${elementsVar.xpath('//title/text()')}\")\r\n\tpublic static String xpath(Elements elements,String xpath){\r\n\t\treturn ExtractUtils.getValueByXPath(elements, xpath);\r\n\t}\r\n\t\r\n\t@Comment(\"根据xpath提取内容\")\r\n\t@Example(\"${elementsVar.xpaths('//h2/text()')}\")\r\n\tpublic static List<String> xpaths(Elements elements,String xpath){\r\n\t\treturn ExtractUtils.getValuesByXPath(elements, xpath);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取内容\")\r\n\t@Example(\"${elementsVar.regx('<title>(.*?)</title>')}\")\r\n\tpublic static String regx(Elements elements,String regx){\r\n\t\treturn ExtractUtils.getFirstMatcher(elements.html(), regx, true);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取内容\")\r\n\t@Example(\"${elementsVar.regx('<title>(.*?)</title>',1)}\")\r\n\tpublic static String regx(Elements elements,String regx,int groupIndex){\r\n\t\treturn ExtractUtils.getFirstMatcher(elements.html(), regx, groupIndex);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取内容\")\r\n\t@Example(\"${elementsVar.regx('<a href=\\\"(.*?)\\\">(.*?)</a>',[1,2])}\")\r\n\tpublic static List<String> regx(Elements elements,String regx,List<Integer> groups){\r\n\t\treturn ExtractUtils.getFirstMatcher(elements.html(), regx, groups);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取内容\")\r\n\t@Example(\"${elementsVar.regxs('<h2>(.*?)</h2>')}\")\r\n\tpublic static List<String> regxs(Elements elements,String regx){\r\n\t\treturn ExtractUtils.getMatchers(elements.html(), regx, true);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取内容\")\r\n\t@Example(\"${elementsVar.regxs('<h2>(.*?)</h2>',1)}\")\r\n\tpublic static List<String> regxs(Elements elements,String regx,int groupIndex){\r\n\t\treturn ExtractUtils.getMatchers(elements.html(), regx, groupIndex);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取内容\")\r\n\t@Example(\"${elementsVar.regxs('<a href=\\\"(.*?)\\\">(.*?)</a>',[1,2])}\")\r\n\tpublic static List<List<String>> regxs(Elements elements,String regx,List<Integer> groups){\r\n\t\treturn ExtractUtils.getMatchers(elements.html(), regx, groups);\r\n\t}\r\n\t\r\n\t@Comment(\"根据css选择器提取内容\")\r\n\t@Example(\"${elementsVar.selector('div > a')}\")\r\n\tpublic static Element selector(Elements elements,String selector){\r\n\t\tElements foundElements = elements.select(selector);\r\n\t\tif(foundElements.size() > 0){\r\n\t\t\treturn foundElements.get(0);\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\r\n\t@Comment(\"返回所有attr\")\r\n\t@Example(\"${elementsVar.attrs('href')}\")\r\n\tpublic static List<String> attrs(Elements elements,String key){\r\n\t\tList<String> list = new ArrayList<>(elements.size());\r\n\t\tfor (Element element : elements) {\r\n\t\t\tlist.add(element.attr(key));\r\n\t\t}\r\n\t\treturn list;\r\n\t}\r\n\r\n\t@Comment(\"返回所有value\")\r\n\t@Example(\"${elementsVar.vals()}\")\r\n\tpublic static List<String> vals(Elements elements){\r\n\t\tList<String> list = new ArrayList<>(elements.size());\r\n\t\tfor (Element element : elements) {\r\n\t\t\tlist.add(element.val());\r\n\t\t}\r\n\t\treturn list;\r\n\t}\r\n\r\n\t@Comment(\"返回所有text\")\r\n\t@Example(\"${elementsVar.texts()}\")\r\n\tpublic static List<String> texts(Elements elements){\r\n\t\tList<String> list = new ArrayList<>(elements.size());\r\n\t\tfor (Element element : elements) {\r\n\t\t\tlist.add(element.text());\r\n\t\t}\r\n\t\treturn list;\r\n\t}\r\n\r\n\t@Comment(\"返回所有html\")\r\n\t@Example(\"${elementsVar.htmls()}\")\r\n\tpublic static List<String> htmls(Elements elements){\r\n\t\tList<String> list = new ArrayList<>(elements.size());\r\n\t\tfor (Element element : elements) {\r\n\t\t\tlist.add(element.html());\r\n\t\t}\r\n\t\treturn list;\r\n\t}\r\n\r\n\t@Comment(\"返回所有outerHtml\")\r\n\t@Example(\"${elementsVar.outerHtmls()}\")\r\n\tpublic static List<String> outerHtmls(Elements elements){\r\n\t\tList<String> list = new ArrayList<>(elements.size());\r\n\t\tfor (Element element : elements) {\r\n\t\t\tlist.add(element.outerHtml());\r\n\t\t}\r\n\t\treturn list;\r\n\t}\r\n\r\n\t@Comment(\"返回所有ownTexts\")\r\n\t@Example(\"${elementsVar.ownTexts()}\")\r\n\tpublic static List<String> ownTexts(Elements elements){\r\n\t\tList<String> list = new ArrayList<>(elements.size());\r\n\t\tfor (Element element : elements) {\r\n\t\t\tlist.add(element.ownText());\r\n\t\t}\r\n\t\treturn list;\r\n\t}\r\n\r\n\t@Comment(\"返回所有wholeText\")\r\n\t@Example(\"${elementsVar.wholeTexts()}\")\r\n\tpublic static List<String> wholeTexts(Elements elements){\r\n\t\tList<String> list = new ArrayList<>(elements.size());\r\n\t\tfor (Element element : elements) {\r\n\t\t\tlist.add(element.wholeText());\r\n\t\t}\r\n\t\treturn list;\r\n\t}\r\n\t\r\n\t@Comment(\"根据css选择器提取内容\")\r\n\t@Example(\"${elementsVar.selectors('div > a')}\")\r\n\tpublic static Elements selectors(Elements elements,String selector){\r\n\t\treturn elements.select(selector);\r\n\t}\r\n\r\n\t@Comment(\"获取上级节点\")\r\n\t@Example(\"${elementsVar.parents()}\")\r\n\tpublic static Elements parents(Elements elements){\r\n\t\treturn elements.parents();\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/function/extension/ListFunctionExtension.java",
    "content": "package org.spiderflow.core.executor.function.extension;\r\n\r\nimport org.apache.commons.lang3.StringUtils;\r\nimport org.spiderflow.annotation.Comment;\r\nimport org.spiderflow.annotation.Example;\r\nimport org.spiderflow.executor.FunctionExtension;\r\nimport org.springframework.stereotype.Component;\r\n\r\nimport java.util.Collections;\r\nimport java.util.List;\r\n\r\n\r\n@Component\r\npublic class ListFunctionExtension implements FunctionExtension{\r\n\r\n\t@Override\r\n\tpublic Class<?> support() {\r\n\t\treturn List.class;\r\n\t}\r\n\t\r\n\t@Comment(\"获取list的长度\")\r\n\t@Example(\"${listVar.length()}\")\r\n\tpublic static int length(List<?> list){\r\n\t\treturn list.size();\r\n\t}\r\n\t\r\n\t@Comment(\"将list拼接起来\")\r\n\t@Example(\"${listVar.join()}\")\r\n\tpublic static String join(List<?> list){\r\n\t\treturn StringUtils.join(list.toArray());\r\n\t}\r\n\t\r\n\t@Comment(\"将list用separator拼接起来\")\r\n\t@Example(\"${listVar.join('-')}\")\r\n\tpublic static String join(List<?> list,String separator){\r\n\t\tif(list.size() == 1){\r\n\t\t\treturn list.get(0).toString();\r\n\t\t}else{\r\n\t\t\treturn StringUtils.join(list.toArray(),separator);\r\n\t\t}\r\n\t}\r\n\r\n\t@Comment(\"将list<String>排序\")\r\n\t@Example(\"${listVar.sort()}\")\r\n\tpublic static List<String> sort(List<String> list){\r\n\t\tCollections.sort(list);\r\n\t\treturn list;\r\n\t}\r\n\r\n\t@Comment(\"将list打乱顺序\")\r\n\t@Example(\"${listVar.shuffle()}\")\r\n\tpublic static List<?> shuffle(List<?> list){\r\n\t\tCollections.shuffle(list);\r\n\t\treturn list;\r\n\t}\r\n\t\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/function/extension/MapFunctionExtension.java",
    "content": "package org.spiderflow.core.executor.function.extension;\n\nimport org.spiderflow.annotation.Comment;\nimport org.spiderflow.annotation.Example;\nimport org.spiderflow.executor.FunctionExtension;\nimport org.springframework.stereotype.Component;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\n@Component\npublic class MapFunctionExtension implements FunctionExtension {\n\n\t@Override\n\tpublic Class<?> support() {\n\t\treturn Map.class;\n\t}\n\n\t@Comment(\"将map转换为List\")\n\t@Example(\"${mapmVar.toList('=')}\")\n\tpublic static List<String> toList(Map<?,?> map,String separator){\n\t\treturn map.entrySet().stream().map(entry-> entry.getKey() + separator + entry.getValue()).collect(Collectors.toList());\n\t}\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/function/extension/ObjectFunctionExtension.java",
    "content": "package org.spiderflow.core.executor.function.extension;\r\n\r\nimport java.util.Objects;\r\n\r\nimport org.spiderflow.annotation.Comment;\r\nimport org.spiderflow.annotation.Example;\r\nimport org.spiderflow.core.utils.ExtractUtils;\r\nimport org.spiderflow.executor.FunctionExtension;\r\nimport org.springframework.stereotype.Component;\r\n\r\nimport com.alibaba.fastjson.JSON;\r\n\r\n@Component\r\npublic class ObjectFunctionExtension implements FunctionExtension{\r\n\t\r\n\t@Override\r\n\tpublic Class<?> support() {\r\n\t\treturn Object.class;\r\n\t}\r\n\t\r\n\t@Comment(\"将对象转为string类型\")\r\n\t@Example(\"${objVar.string()}\")\r\n\tpublic static String string(Object obj){\r\n\t\tif (obj instanceof String) {\r\n\t\t\treturn (String) obj;\r\n\t\t}\r\n\t\treturn Objects.toString(obj);\r\n\t}\r\n\t\r\n\t@Comment(\"根据jsonpath提取内容\")\r\n\t@Example(\"${objVar.jsonpath('$.code')}\")\r\n\tpublic static Object jsonpath(Object obj,String path){\r\n\t\tif(obj instanceof String){\r\n\t\t\treturn ExtractUtils.getValueByJsonPath(JSON.parse((String)obj), path);\r\n\t\t}\r\n\t\treturn ExtractUtils.getValueByJsonPath(obj, path);\r\n\t}\r\n\r\n\t@Comment(\"睡眠等待一段时间\")\r\n\t@Example(\"${objVar.sleep(1000)}\")\r\n\tpublic static Object sleep(Object obj, int millis) {\r\n\t\ttry {\r\n\t\t\tThread.sleep(millis);\r\n\t\t} catch (InterruptedException ignored) {\r\n\t\t}\r\n\t\treturn obj;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/function/extension/ResponseFunctionExtension.java",
    "content": "package org.spiderflow.core.executor.function.extension;\r\n\r\nimport java.util.List;\r\nimport java.util.regex.Pattern;\r\nimport java.util.stream.Collectors;\r\n\r\nimport org.apache.commons.lang3.StringUtils;\r\nimport org.jsoup.Jsoup;\r\nimport org.jsoup.nodes.Element;\r\nimport org.jsoup.select.Elements;\r\nimport org.spiderflow.annotation.Comment;\r\nimport org.spiderflow.annotation.Example;\r\nimport org.spiderflow.annotation.Return;\r\nimport org.spiderflow.core.utils.ExtractUtils;\r\nimport org.spiderflow.executor.FunctionExtension;\r\nimport org.spiderflow.io.SpiderResponse;\r\nimport org.springframework.stereotype.Component;\r\n\r\n@Component\r\npublic class ResponseFunctionExtension implements FunctionExtension {\r\n\r\n    @Override\r\n    public Class<?> support() {\r\n        return SpiderResponse.class;\r\n    }\r\n\r\n    @Comment(\"将请求结果转为Element对象\")\r\n    @Example(\"${resp.element()}\")\r\n    public static Element element(SpiderResponse response) {\r\n        return Jsoup.parse(response.getHtml(),response.getUrl());\r\n    }\r\n\r\n    @Comment(\"根据xpath在请求结果中查找\")\r\n    @Example(\"${resp.xpath('//title/text()')}\")\r\n    @Return({Element.class, String.class})\r\n    public static String xpath(SpiderResponse response, String xpath) {\r\n        return ExtractUtils.getValueByXPath(element(response), xpath);\r\n    }\r\n\r\n    @Comment(\"根据xpath在请求结果中查找\")\r\n    @Example(\"${resp.xpaths('//a/@href')}\")\r\n    public static List<String> xpaths(SpiderResponse response, String xpath) {\r\n        return ExtractUtils.getValuesByXPath(element(response), xpath);\r\n    }\r\n\r\n    @Comment(\"根据正则表达式提取请求结果中的内容\")\r\n    @Example(\"${resp.regx('<title>(.*?)</title>')}\")\r\n    public static String regx(SpiderResponse response, String pattern) {\r\n        return ExtractUtils.getFirstMatcher(response.getHtml(), pattern, true);\r\n    }\r\n\r\n    @Comment(\"根据正则表达式提取请求结果中的内容\")\r\n    @Example(\"${resp.regx('<title>(.*?)</title>',1)}\")\r\n    public static String regx(SpiderResponse response, String pattern, int groupIndex) {\r\n        return ExtractUtils.getFirstMatcher(response.getHtml(), pattern, groupIndex);\r\n    }\r\n\r\n    @Comment(\"根据正则表达式提取请求结果中的内容\")\r\n    @Example(\"${resp.regx('<a href=\\\"(.*?)\\\">(.*?)</a>',[1,2])}\")\r\n    public static List<String> regx(SpiderResponse response, String pattern, List<Integer> groups) {\r\n        return ExtractUtils.getFirstMatcher(response.getHtml(), pattern, groups);\r\n    }\r\n\r\n    @Comment(\"根据正则表达式提取请求结果中的内容\")\r\n    @Example(\"${resp.regxs('<h2>(.*?)</h2>')}\")\r\n    public static List<String> regxs(SpiderResponse response, String pattern) {\r\n        return ExtractUtils.getMatchers(response.getHtml(), pattern, true);\r\n    }\r\n\r\n    @Comment(\"根据正则表达式提取请求结果中的内容\")\r\n    @Example(\"${resp.regxs('<h2>(.*?)</h2>',1)}\")\r\n    public static List<String> regxs(SpiderResponse response, String pattern, int groupIndex) {\r\n        return ExtractUtils.getMatchers(response.getHtml(), pattern, groupIndex);\r\n    }\r\n\r\n    @Comment(\"根据正则表达式提取请求结果中的内容\")\r\n    @Example(\"${resp.regxs('<a href=\\\"(.*?)\\\">(.*?)</a>',[1,2])}\")\r\n    public static List<List<String>> regxs(SpiderResponse response, String pattern, List<Integer> groups) {\r\n        return ExtractUtils.getMatchers(response.getHtml(), pattern, groups);\r\n    }\r\n\r\n    @Comment(\"根据css选择器提取请求结果\")\r\n    @Example(\"${resp.selector('div > a')}\")\r\n    public static Element selector(SpiderResponse response, String selector) {\r\n        return ElementFunctionExtension.selector(element(response), selector);\r\n    }\r\n\r\n    @Comment(\"根据css选择器提取请求结果\")\r\n    @Example(\"${resp.selectors('div > a')}\")\r\n    public static Elements selectors(SpiderResponse response, String selector) {\r\n        return ElementFunctionExtension.selectors(element(response), selector);\r\n    }\r\n\r\n    @Comment(\"根据jsonpath提取请求结果\")\r\n    @Example(\"${resp.jsonpath('$.code')}\")\r\n    public static Object jsonpath(SpiderResponse response, String path) {\r\n        return ExtractUtils.getValueByJsonPath(response.getJson(), path);\r\n    }\r\n\r\n    @Comment(\"获取页面上的链接\")\r\n    @Example(\"${resp.links()}\")\r\n    public static List<String> links(SpiderResponse response) {\r\n        return ExtractUtils.getAttrBySelector(element(response), \"a\", \"abs:href\")\r\n                .stream()\r\n                .filter(link -> StringUtils.isNotBlank(link))\r\n                .collect(Collectors.toList());\r\n    }\r\n\r\n    @Comment(\"获取页面上的链接\")\r\n    @Example(\"${resp.links('https://www\\\\.xxx\\\\.com/xxxx/(.*?)')}\")\r\n    public static List<String> links(SpiderResponse response, String regx) {\r\n        Pattern pattern = Pattern.compile(regx);\r\n        return links(response)\r\n\t\t\t\t.stream()\r\n                .filter(link -> pattern.matcher(link).matches())\r\n                .collect(Collectors.toList());\r\n    }\r\n\r\n    @Comment(\"获取当前页面所有图片链接\")\r\n    @Example(\"${resp.images()}\")\r\n    public static List<String> images(SpiderResponse response) {\r\n        return ExtractUtils.getAttrBySelector(element(response), \"img\", \"src\")\r\n                .stream()\r\n                .filter(link -> StringUtils.isNotBlank(link))\r\n                .collect(Collectors.toList());\r\n    }\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/function/extension/SqlRowSetExtension.java",
    "content": "package org.spiderflow.core.executor.function.extension;\n\nimport org.apache.commons.lang3.exception.ExceptionUtils;\nimport org.spiderflow.annotation.Example;\nimport org.spiderflow.executor.FunctionExtension;\nimport org.springframework.jdbc.support.rowset.SqlRowSet;\nimport org.springframework.stereotype.Component;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n@Component\npublic class SqlRowSetExtension implements FunctionExtension {\n    public static Map<String, String[]> tableMetaMap = new HashMap<>();\n\n    @Override\n    public Class<?> support() {\n        return SqlRowSet.class;\n    }\n\n    @Example(\"${rs.nextToMap()}\")\n    public static Map<String, Object> nextToMap(SqlRowSet sqlRowSet) {\n        try {\n            if (!sqlRowSet.next()) {\n                return null;\n            }\n            String[] columnNames = sqlRowSet.getMetaData().getColumnNames();\n            Map<String, Object> result = new HashMap<>();\n            for (String columnName : columnNames) {\n                result.put(columnName, sqlRowSet.getObject(columnName));\n            }\n            return result;\n        } catch (Exception e) {\n            ExceptionUtils.wrapAndThrow(e);\n        }\n        return null;\n    }\n\n\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/function/extension/StringFunctionExtension.java",
    "content": "package org.spiderflow.core.executor.function.extension;\r\n\r\nimport com.alibaba.fastjson.JSON;\r\nimport org.apache.commons.lang3.math.NumberUtils;\r\nimport org.apache.commons.text.StringEscapeUtils;\r\nimport org.jsoup.nodes.Element;\r\nimport org.jsoup.parser.Parser;\r\nimport org.jsoup.select.Elements;\r\nimport org.spiderflow.annotation.Comment;\r\nimport org.spiderflow.annotation.Example;\r\nimport org.spiderflow.annotation.Return;\r\nimport org.spiderflow.core.executor.function.DateFunctionExecutor;\r\nimport org.spiderflow.core.utils.ExtractUtils;\r\nimport org.spiderflow.executor.FunctionExtension;\r\nimport org.springframework.stereotype.Component;\r\n\r\nimport java.text.ParseException;\r\nimport java.util.Date;\r\nimport java.util.List;\r\n\r\n@Component\r\npublic class StringFunctionExtension implements FunctionExtension{\r\n\r\n\t@Override\r\n\tpublic Class<?> support() {\r\n\t\treturn String.class;\r\n\t}\t\r\n\t\r\n\t@Comment(\"根据正则表达式提取String中的内容\")\r\n\t@Example(\"${strVar.regx('<title>(.*?)</title>')}\")\r\n\tpublic static String regx(String source,String pattern){\r\n\t\treturn ExtractUtils.getFirstMatcher(source, pattern, true);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取String中的内容\")\r\n\t@Example(\"${strVar.regx('<title>(.*?)</title>',1)}\")\r\n\tpublic static String regx(String source,String pattern,int groupIndex){\r\n\t\treturn ExtractUtils.getFirstMatcher(source, pattern, groupIndex);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取String中的内容\")\r\n\t@Example(\"${strVar.regx('<a href=\\\"(.*?)\\\">(.*?)</a>',[1,2])}\")\r\n\tpublic static List<String> regx(String source,String pattern,List<Integer> groups){\r\n\t\treturn ExtractUtils.getFirstMatcher(source, pattern, groups);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取String中的内容\")\r\n\t@Example(\"${strVar.regxs('<h2>(.*?)</h2>')}\")\r\n\tpublic static List<String> regxs(String source,String pattern){\r\n\t\treturn ExtractUtils.getMatchers(source, pattern, true);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取String中的内容\")\r\n\t@Example(\"${strVar.regxs('<h2>(.*?)</h2>',1)}\")\r\n\tpublic static List<String> regxs(String source,String pattern,int groupIndex){\r\n\t\treturn ExtractUtils.getMatchers(source, pattern, groupIndex);\r\n\t}\r\n\t\r\n\t@Comment(\"根据正则表达式提取String中的内容\")\r\n\t@Example(\"${strVar.regxs('<a href=\\\"(.*?)\\\">(.*?)</a>',[1,2])}\")\r\n\tpublic static List<List<String>> regxs(String source,String pattern,List<Integer> groups){\r\n\t\treturn ExtractUtils.getMatchers(source, pattern, groups);\r\n\t}\r\n\t\r\n\t@Comment(\"根据xpath在String变量中查找\")\r\n\t@Example(\"${strVar.xpath('//title/text()')}\")\r\n\t@Return({Element.class,String.class})\r\n\tpublic static String xpath(String source,String xpath){\r\n\t\treturn ExtractUtils.getValueByXPath(element(source), xpath);\r\n\t}\r\n\t\r\n\t@Comment(\"根据xpath在String变量中查找\")\r\n\t@Example(\"${strVar.xpaths('//a/@href')}\")\r\n\tpublic static List<String> xpaths(String source,String xpath){\r\n\t\treturn ExtractUtils.getValuesByXPath(element(source), xpath);\r\n\t}\r\n\t\r\n\t@Comment(\"将String变量转为Element对象\")\r\n\t@Example(\"${strVar.element()}\")\r\n\tpublic static Element element(String source){\r\n\t\treturn Parser.xmlParser().parseInput(source,\"\");\r\n\t}\r\n\t\r\n\t@Comment(\"根据css选择器提取\")\r\n\t@Example(\"${strVar.selector('div > a')}\")\r\n\tpublic static Element selector(String source,String cssQuery){\r\n\t\treturn element(source).selectFirst(cssQuery);\r\n\t}\r\n\t\r\n\t@Comment(\"根据css选择器提取\")\r\n\t@Example(\"${strVar.selector('div > a')}\")\r\n\tpublic static Elements selectors(String source,String cssQuery){\r\n\t\treturn element(source).select(cssQuery);\r\n\t}\r\n\r\n\t@Comment(\"将string转为json对象\")\r\n\t@Example(\"${strVar.json()}\")\r\n\tpublic static Object json(String source){\r\n\t\treturn JSON.parse(source);\r\n\t}\r\n\t\r\n\t@Comment(\"根据jsonpath提取内容\")\r\n\t@Example(\"${strVar.jsonpath('$.code')}\")\r\n\tpublic static Object jsonpath(String source,String jsonPath){\r\n\t\treturn ExtractUtils.getValueByJsonPath(json(source), jsonPath);\r\n\t}\r\n\t\r\n\t@Comment(\"将字符串转为int类型\")\r\n\t@Example(\"${strVar.toInt(0)}\")\r\n\tpublic static Integer toInt(String source,int defaultValue){\r\n\t\treturn NumberUtils.toInt(source, defaultValue);\r\n\t}\r\n\t\r\n\t@Comment(\"将字符串转为int类型\")\r\n\t@Example(\"${strVar.toInt()}\")\r\n\tpublic static Integer toInt(String source){\r\n\t\treturn NumberUtils.toInt(source);\r\n\t}\r\n\t\r\n\t@Comment(\"将字符串转为double类型\")\r\n\t@Example(\"${strVar.toDouble()}\")\r\n\tpublic static Double toDouble(String source){\r\n\t\treturn NumberUtils.toDouble(source);\r\n\t}\r\n\t\r\n\t@Comment(\"将字符串转为long类型\")\r\n\t@Example(\"${strVar.toLong()}\")\r\n\tpublic static Long toLong(String source){\r\n\t\treturn NumberUtils.toLong(source);\r\n\t}\r\n\t\r\n\t@Comment(\"将字符串转为date类型\")\r\n\t@Example(\"${strVar.toDate('yyyy-MM-dd HH:mm:ss')}\")\r\n\tpublic static Date toDate(String source,String pattern) throws ParseException{\r\n\t\treturn DateFunctionExecutor.parse(source, pattern);\r\n\t}\r\n\r\n\t@Comment(\"反转义字符串\")\r\n\t@Example(\"${strVar.unescape()}\")\r\n\tpublic static String unescape(String source){\r\n\t\treturn StringEscapeUtils.unescapeJava(source);\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/shape/CommentExecutor.java",
    "content": "package org.spiderflow.core.executor.shape;\n\nimport org.spiderflow.context.SpiderContext;\nimport org.spiderflow.executor.ShapeExecutor;\nimport org.spiderflow.model.SpiderNode;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Map;\n\n@Component\npublic class CommentExecutor implements ShapeExecutor{\n\n\t@Override\n\tpublic void execute(SpiderNode node, SpiderContext context, Map<String,Object> variables) {\n\t\t\n\t}\n\n\t@Override\n\tpublic String supportShape() {\n\t\treturn \"comment\";\n\t}\n\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/shape/ExecuteSQLExecutor.java",
    "content": "package org.spiderflow.core.executor.shape;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.exception.ExceptionUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.spiderflow.Grammerable;\nimport org.spiderflow.context.SpiderContext;\nimport org.spiderflow.core.utils.DataSourceUtils;\nimport org.spiderflow.core.utils.ExpressionUtils;\nimport org.spiderflow.core.utils.ExtractUtils;\nimport org.spiderflow.executor.ShapeExecutor;\nimport org.spiderflow.model.Grammer;\nimport org.spiderflow.model.SpiderNode;\nimport org.springframework.jdbc.core.ArgumentPreparedStatementSetter;\nimport org.springframework.jdbc.core.JdbcTemplate;\nimport org.springframework.jdbc.support.GeneratedKeyHolder;\nimport org.springframework.jdbc.support.KeyHolder;\nimport org.springframework.stereotype.Component;\n\nimport java.lang.reflect.Array;\nimport java.sql.PreparedStatement;\nimport java.sql.Statement;\nimport java.util.*;\n\n/**\n * SQL执行器\n *\n * @author jmxd\n */\n@Component\npublic class ExecuteSQLExecutor implements ShapeExecutor, Grammerable {\n\n\tpublic static final String DATASOURCE_ID = \"datasourceId\";\n\n\tpublic static final String SQL = \"sql\";\n\n\tpublic static final String STATEMENT_TYPE = \"statementType\";\n\n\tpublic static final String STATEMENT_SELECT = \"select\";\n\n\tpublic static final String STATEMENT_SELECT_ONE = \"selectOne\";\n\n\tpublic static final String STATEMENT_SELECT_INT = \"selectInt\";\n\n\tpublic static final String STATEMENT_INSERT = \"insert\";\n\n\tpublic static final String STATEMENT_UPDATE = \"update\";\n\n\tpublic static final String STATEMENT_DELETE = \"delete\";\n\tpublic static final String SELECT_RESULT_STREAM = \"isStream\";\n\tpublic static final String STATEMENT_INSERT_PK = \"insertofPk\";\n\t\n\tprivate static final Logger logger = LoggerFactory.getLogger(ExecuteSQLExecutor.class);\n\n\t@Override\n\tpublic void execute(SpiderNode node, SpiderContext context, Map<String, Object> variables) {\n\t\tString dsId = node.getStringJsonValue(DATASOURCE_ID);\n\t\tString sql = node.getStringJsonValue(SQL);\n\t\tif (StringUtils.isBlank(dsId)) {\n\t\t\tlogger.warn(\"数据源ID为空！\");\n\t\t} else if (StringUtils.isBlank(sql)) {\n\t\t\tlogger.warn(\"sql为空！\");\n\t\t} else {\n\t\t\tJdbcTemplate template = new JdbcTemplate(DataSourceUtils.getDataSource(dsId));\n\t\t\t//把变量替换成占位符\n\t\t\tList<String> parameters = ExtractUtils.getMatchers(sql, \"#(.*?)#\", true);\n\t\t\tsql = sql.replaceAll(\"#(.*?)#\", \"?\");\n\t\t\ttry {\n\t\t\t\tObject sqlObject = ExpressionUtils.execute(sql, variables);\n\t\t\t\tif(sqlObject == null){\n\t\t\t\t\tlogger.warn(\"获取的sql为空！\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsql = sqlObject.toString();\n\t\t\t\tcontext.pause(node.getNodeId(),\"common\",SQL,sql);\n\t\t\t} catch (Exception e) {\n\t\t\t\tlogger.error(\"获取sql出错,异常信息:{}\", e.getMessage(), e);\n\t\t\t\tExceptionUtils.wrapAndThrow(e);\n\t\t\t}\n\t\t\tint size = parameters.size();\n\t\t\tObject[] params = new Object[size];\n\t\t\tboolean hasList = false;\n\t\t\tint parameterSize = 0;\n\t\t\t//当参数中存在List或者数组时，认为是批量操作\n\t\t\tfor (int i = 0; i < size; i++) {\n\t\t\t\tObject parameter = ExpressionUtils.execute(parameters.get(i), variables);\n\t\t\t\tif (parameter != null) {\n\t\t\t\t\tif (parameter instanceof List) {\n\t\t\t\t\t\thasList = true;\n\t\t\t\t\t\tparameterSize = Math.max(parameterSize, ((List<?>) parameter).size());\n\t\t\t\t\t} else if (parameter.getClass().isArray()) {\n\t\t\t\t\t\thasList = true;\n\t\t\t\t\t\tparameterSize = Math.max(parameterSize, Array.getLength(parameter));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tparams[i] = parameter;\n\t\t\t}\n\t\t\tString statementType = node.getStringJsonValue(STATEMENT_TYPE);\n\t\t\tlogger.debug(\"执行sql：{}\", sql);\n\t\t\tif (STATEMENT_SELECT.equals(statementType)) {\n\t\t\t\tboolean isStream = \"1\".equals(node.getStringJsonValue(SELECT_RESULT_STREAM));\n\t\t\t\ttry {\n\t\t\t\t\tif (isStream) {\n\t\t\t\t\t\tvariables.put(\"rs\", template.queryForRowSet(sql, params));\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvariables.put(\"rs\", template.queryForList(sql, params));\n\t\t\t\t\t}\n\t\t\t\t} catch (Exception e) {\n\t\t\t\t\tvariables.put(\"rs\", null);\n\t\t\t\t\tlogger.error(\"执行sql出错,异常信息:{}\", e.getMessage(), e);\n\t\t\t\t\tExceptionUtils.wrapAndThrow(e);\n\t\t\t\t}\n\t\t\t} else if (STATEMENT_SELECT_ONE.equals(statementType)) {\n\t\t\t\tMap<String, Object> rs;\n\t\t\t\ttry {\n\t\t\t\t\trs = template.queryForMap(sql, params);\n\t\t\t\t\tvariables.put(\"rs\", rs);\n\t\t\t\t} catch (Exception e) {\n\t\t\t\t\tvariables.put(\"rs\", null);\n\t\t\t\t\tlogger.error(\"执行sql出错,异常信息:{}\", e.getMessage(), e);\n\t\t\t\t\tExceptionUtils.wrapAndThrow(e);\n\t\t\t\t}\n\t\t\t} else if (STATEMENT_SELECT_INT.equals(statementType)) {\n\t\t\t\tInteger rs;\n\t\t\t\ttry {\n\t\t\t\t\trs = template.queryForObject(sql, params, Integer.class);\n\t\t\t\t\trs = rs == null ? 0 : rs;\n\t\t\t\t\tvariables.put(\"rs\", rs);\n\t\t\t\t} catch (Exception e) {\n\t\t\t\t\tvariables.put(\"rs\", 0);\n\t\t\t\t\tlogger.error(\"执行sql出错,异常信息:{}\", e.getMessage(), e);\n\t\t\t\t\tExceptionUtils.wrapAndThrow(e);\n\t\t\t\t}\n\t\t\t} else if (STATEMENT_UPDATE.equals(statementType) || STATEMENT_INSERT.equals(statementType) || STATEMENT_DELETE.equals(statementType)) {\n\t\t\t\ttry {\n\t\t\t\t\tint updateCount = 0;\n\t\t\t\t\tif (hasList) {\n\t\t\t\t\t\t/*\n\t\t\t\t\t\t  批量操作时，将参数Object[]转化为List<Object[]>\n\t\t\t\t\t\t  当参数不为数组或List时，自动转为Object[]\n\t\t\t\t\t\t  当数组或List长度不足时，自动取最后一项补齐\n\t\t\t\t\t\t */\n\t\t\t\t\t\tint[] rs = template.batchUpdate(sql, convertParameters(params, parameterSize));\n\t\t\t\t\t\tif (rs.length > 0) {\n\t\t\t\t\t\t\tupdateCount = Arrays.stream(rs).sum();\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tupdateCount = template.update(sql, params);\n\t\t\t\t\t}\n\t\t\t\t\tvariables.put(\"rs\", updateCount);\n\t\t\t\t} catch (Exception e) {\n\t\t\t\t\tlogger.error(\"执行sql出错,异常信息:{}\", e.getMessage(), e);\n\t\t\t\t\tvariables.put(\"rs\", -1);\n\t\t\t\t\tExceptionUtils.wrapAndThrow(e);\n\t\t\t\t}\n\t\t\t} else if(STATEMENT_INSERT_PK.equals(statementType)) {\n\t\t\t\ttry {\n\t\t\t\t\tKeyHolder keyHolder = new GeneratedKeyHolder();\n\t\t\t\t\tfinal String insertSQL = sql;\n\t\t\t\t\ttemplate.update(con -> {\n\t\t\t\t\t\tPreparedStatement ps = con.prepareStatement(insertSQL, Statement.RETURN_GENERATED_KEYS);\n\t\t\t\t\t\tnew ArgumentPreparedStatementSetter(params).setValues(ps);\n\t\t\t\t\t\treturn ps;\n\t\t\t\t\t}, keyHolder);\n\t\t\t\t\tvariables.put(\"rs\", keyHolder.getKey().intValue());\n\t\t\t\t} catch (Exception e) {\n\t\t\t\t\tlogger.error(\"执行sql出错,异常信息:{}\", e.getMessage(), e);\n\t\t\t\t\tvariables.put(\"rs\", -1);\n\t\t\t\t\tExceptionUtils.wrapAndThrow(e);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tprivate List<Object[]> convertParameters(Object[] params, int length) {\n\t\tList<Object[]> result = new ArrayList<>(length);\n\t\tint size = params.length;\n\t\tfor (int i = 0; i < length; i++) {\n\t\t\tObject[] parameters = new Object[size];\n\t\t\tfor (int j = 0; j < size; j++) {\n\t\t\t\tparameters[j] = getValue(params[j], i);\n\t\t\t}\n\t\t\tresult.add(parameters);\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate Object getValue(Object object, int index) {\n\t\tif (object == null) {\n\t\t\treturn null;\n\t\t} else if (object instanceof List) {\n\t\t\tList<?> list = (List<?>) object;\n\t\t\tint size = list.size();\n\t\t\tif (size > 0) {\n\t\t\t\treturn list.get(Math.min(list.size() - 1, index));\n\t\t\t}\n\t\t} else if (object.getClass().isArray()) {\n\t\t\tint size = Array.getLength(object);\n\t\t\tif (size > 0) {\n\t\t\t\tArray.get(object, Math.min(-1, index));\n\t\t\t}\n\t\t} else {\n\t\t\treturn object;\n\t\t}\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic String supportShape() {\n\t\treturn \"executeSql\";\n\t}\n\n\t@Override\n\tpublic List<Grammer> grammers() {\n\t\tGrammer grammer = new Grammer();\n\t\tgrammer.setComment(\"执行SQL结果\");\n\t\tgrammer.setFunction(\"rs\");\n\t\tgrammer.setReturns(Arrays.asList(\"List<Map<String,Object>>\", \"int\"));\n\t\treturn Collections.singletonList(grammer);\n\t}\n\n\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/shape/ForkJoinExecutor.java",
    "content": "package org.spiderflow.core.executor.shape;\n\nimport org.spiderflow.context.SpiderContext;\nimport org.spiderflow.executor.ShapeExecutor;\nimport org.spiderflow.model.SpiderNode;\nimport org.springframework.stereotype.Component;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * 等待执行结束执行器\n * \n */\n@Component\npublic class ForkJoinExecutor implements ShapeExecutor {\n\n\t/**\n\t * 缓存已完成节点的变量\n\t */\n\tprivate Map<String, Map<String, Object>> cachedVariables = new HashMap<>();\n\t\n\t@Override\n\tpublic void execute(SpiderNode node, SpiderContext context, Map<String, Object> variables) {\n\t}\n\n\t@Override\n\tpublic String supportShape() {\n\t\treturn \"forkJoin\";\n\t}\n\n\t@Override\n\tpublic boolean allowExecuteNext(SpiderNode node, SpiderContext context, Map<String, Object> variables) {\n\t\tString key = context.getId() + \"-\" + node.getNodeId();\n\t\tsynchronized (node){\n\t\t\tboolean isDone = node.isDone();\n\t\t\tMap<String, Object> cached = cachedVariables.get(key);\n\t\t\tif(!isDone){\n\t\t\t\tif(cached == null){\n\t\t\t\t\tcached = new HashMap<>();\n\t\t\t\t\tcachedVariables.put(key, cached);\n\t\t\t\t}\n\t\t\t\tcached.putAll(variables);\n\t\t\t}else if(cached != null){\n\t\t\t\t//将缓存的变量存入到当前变量中,传递给下一级\n\t\t\t\tvariables.putAll(cached);\n\t\t\t\tcachedVariables.remove(key);\n\t\t\t}\n\t\t\treturn isDone;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/shape/FunctionExecutor.java",
    "content": "package org.spiderflow.core.executor.shape;\r\n\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\nimport org.apache.commons.lang3.StringUtils;\r\nimport org.apache.commons.lang3.exception.ExceptionUtils;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.spiderflow.context.SpiderContext;\r\nimport org.spiderflow.core.utils.ExpressionUtils;\r\nimport org.spiderflow.executor.ShapeExecutor;\r\nimport org.spiderflow.model.SpiderNode;\r\nimport org.springframework.stereotype.Component;\r\n\r\n/**\r\n * 函数执行器\r\n * @author Administrator\r\n *\r\n */\r\n@Component\r\npublic class FunctionExecutor implements ShapeExecutor{\r\n\t\r\n\tpublic static final String FUNCTION = \"function\";\r\n\r\n\tprivate static final Logger logger = LoggerFactory.getLogger(FunctionExecutor.class);\r\n\t\r\n\t@Override\r\n\tpublic void execute(SpiderNode node, SpiderContext context, Map<String,Object> variables) {\r\n\t\tList<Map<String, String>> functions = node.getListJsonValue(FUNCTION);\r\n\t\tfor (Map<String, String> item : functions) {\r\n\t\t\tString function = item.get(FUNCTION);\r\n\t\t\tif(StringUtils.isNotBlank(function)){\r\n\t\t\t\ttry {\r\n\t\t\t\t\tlogger.debug(\"执行函数{}\",function);\r\n\t\t\t\t\tExpressionUtils.execute(function, variables);\r\n\t\t\t\t} catch (Exception e) {\r\n\t\t\t\t\tlogger.error(\"执行函数{}失败,异常信息:{}\",function,e);\r\n\t\t\t\t\tExceptionUtils.wrapAndThrow(e);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t@Override\r\n\tpublic String supportShape() {\r\n\t\treturn \"function\";\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/shape/LoopExecutor.java",
    "content": "package org.spiderflow.core.executor.shape;\r\n\r\nimport java.util.Map;\r\n\r\nimport org.spiderflow.context.SpiderContext;\r\nimport org.spiderflow.executor.ShapeExecutor;\r\nimport org.spiderflow.model.SpiderNode;\r\nimport org.springframework.stereotype.Component;\r\n\r\n/**\r\n * 循环执行器\r\n * @author Administrator\r\n *\r\n */\r\n@Component\r\npublic class LoopExecutor implements ShapeExecutor{\r\n\t\r\n\tpublic static final String LOOP_ITEM = \"loopItem\";\r\n\t\r\n\tpublic static final String LOOP_START = \"loopStart\";\r\n\r\n\tpublic static final String LOOP_END = \"loopEnd\";\r\n\t\r\n\t@Override\r\n\tpublic void execute(SpiderNode node, SpiderContext context, Map<String,Object> variables) {\r\n\t}\r\n\r\n\t@Override\r\n\tpublic String supportShape() {\r\n\t\treturn \"loop\";\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/shape/OutputExecutor.java",
    "content": "package org.spiderflow.core.executor.shape;\r\n\r\nimport com.alibaba.fastjson.JSON;\r\nimport org.apache.commons.csv.CSVFormat;\r\nimport org.apache.commons.csv.CSVPrinter;\r\nimport org.apache.commons.lang3.StringUtils;\r\nimport org.apache.commons.lang3.exception.ExceptionUtils;\r\nimport org.apache.ibatis.jdbc.SQL;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.spiderflow.context.SpiderContext;\r\nimport org.spiderflow.core.serializer.FastJsonSerializer;\r\nimport org.spiderflow.core.utils.DataSourceUtils;\r\nimport org.spiderflow.core.utils.ExpressionUtils;\r\nimport org.spiderflow.executor.ShapeExecutor;\r\nimport org.spiderflow.io.SpiderResponse;\r\nimport org.spiderflow.listener.SpiderListener;\r\nimport org.spiderflow.model.SpiderNode;\r\nimport org.spiderflow.model.SpiderOutput;\r\nimport org.springframework.jdbc.core.JdbcTemplate;\r\nimport org.springframework.stereotype.Component;\r\n\r\nimport java.io.*;\r\nimport java.util.*;\r\n\r\n/**\r\n * 输出执行器\r\n * @author Administrator\r\n *\r\n */\r\n@Component\r\npublic class OutputExecutor implements ShapeExecutor, SpiderListener {\r\n\r\n\tpublic static final String OUTPUT_ALL = \"output-all\";\r\n\r\n\tpublic static final String OUTPUT_NAME = \"output-name\";\r\n\r\n\tpublic static final String OUTPUT_VALUE = \"output-value\";\r\n\r\n\tpublic static final String DATASOURCE_ID = \"datasourceId\";\r\n\r\n\tpublic static final String OUTPUT_DATABASE = \"output-database\";\r\n\r\n\tpublic static final String OUTPUT_CSV = \"output-csv\";\r\n\r\n\tpublic static final String TABLE_NAME = \"tableName\";\r\n\r\n\tpublic static final String CSV_NAME = \"csvName\";\r\n\r\n\tpublic static final String CSV_ENCODING = \"csvEncoding\";\r\n\r\n\tprivate static Logger logger = LoggerFactory.getLogger(OutputExecutor.class);\r\n\r\n\t/**\r\n\t * 输出CSVPrinter节点变量\r\n\t */\r\n\tprivate Map<String, CSVPrinter> cachePrinter = new HashMap<>();\r\n\r\n\t@Override\r\n\tpublic void execute(SpiderNode node, SpiderContext context, Map<String,Object> variables) {\r\n\t\tSpiderOutput output = new SpiderOutput();\r\n\t\toutput.setNodeName(node.getNodeName());\r\n\t\toutput.setNodeId(node.getNodeId());\r\n\t\tboolean outputAll = \"1\".equals(node.getStringJsonValue(OUTPUT_ALL));\r\n\t\tboolean databaseFlag = \"1\".equals(node.getStringJsonValue(OUTPUT_DATABASE));\r\n\t\tboolean csvFlag = \"1\".equals(node.getStringJsonValue(OUTPUT_CSV));\r\n\t\tif (outputAll) {\r\n\t\t\toutputAll(output, variables);\r\n\t\t}\r\n\t\tList<Map<String, String>> outputs = node.getListJsonValue(OUTPUT_NAME, OUTPUT_VALUE);\r\n\t\tMap<String, Object> outputData = null;\r\n\t\tif (databaseFlag || csvFlag) {\r\n\t\t\toutputData = new HashMap<>(outputs.size());\r\n\t\t}\r\n\t\tfor (Map<String, String> item : outputs) {\r\n\t\t\tObject value = null;\r\n\t\t\tString outputValue = item.get(OUTPUT_VALUE);\r\n\t\t\tString outputName = item.get(OUTPUT_NAME);\r\n\t\t\ttry {\r\n\t\t\t\tvalue = ExpressionUtils.execute(outputValue, variables);\r\n\t\t\t\tcontext.pause(node.getNodeId(),\"common\",outputName,value);\r\n\t\t\t\tlogger.debug(\"输出{}={}\", outputName,value);\r\n\t\t\t} catch (Exception e) {\r\n\t\t\t\tlogger.error(\"输出{}出错，异常信息：{}\", outputName,e);\r\n\t\t\t}\r\n\t\t\toutput.addOutput(outputName, value);\r\n\t\t\tif ((databaseFlag || csvFlag) && value != null) {\r\n\t\t\t\toutputData.put(outputName, value.toString());\r\n\t\t\t}\r\n\t\t}\r\n\t\tif(databaseFlag){\r\n\t\t\tString dsId = node.getStringJsonValue(DATASOURCE_ID);\r\n\t\t\tString tableName = node.getStringJsonValue(TABLE_NAME);\r\n\t\t\tif (StringUtils.isBlank(dsId)) {\r\n\t\t\t\tlogger.warn(\"数据源ID为空！\");\r\n\t\t\t} else if (StringUtils.isBlank(tableName)) {\r\n\t\t\t\tlogger.warn(\"表名为空！\");\r\n\t\t\t} else {\r\n\t\t\t\toutputDB(dsId, tableName, outputData);\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (csvFlag) {\r\n\t\t\tString csvName = node.getStringJsonValue(CSV_NAME);\r\n\t\t\toutputCSV(node, context, csvName, outputData);\r\n\t\t}\r\n\t\tcontext.addOutput(output);\r\n\t}\r\n\r\n\t/**\r\n\t * 输出所有参数\r\n\t * @param output\r\n\t * @param variables\r\n\t */\r\n\tprivate void outputAll(SpiderOutput output,Map<String,Object> variables){\r\n\t\tfor (Map.Entry<String, Object> item : variables.entrySet()) {\r\n\t\t\tObject value = item.getValue();\r\n\t\t\tif (value instanceof SpiderResponse) {\r\n\t\t\t\tSpiderResponse resp = (SpiderResponse) value;\r\n\t\t\t\toutput.addOutput(item.getKey() + \".html\", resp.getHtml());\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\t//去除不输出的信息\r\n\t\t\tif (\"ex\".equals(item.getKey())) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\t//去除不能序列化的参数\r\n\t\t\ttry {\r\n\t\t\t\tJSON.toJSONString(value, FastJsonSerializer.serializeConfig);\r\n\t\t\t} catch (Exception e) {\r\n\t\t\t\te.printStackTrace();\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\t//输出信息\r\n\t\t\toutput.addOutput(item.getKey(), item.getValue());\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void outputDB(String databaseId, String tableName, Map<String, Object> data) {\r\n\t\tif (data == null || data.isEmpty()) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tJdbcTemplate template = new JdbcTemplate(DataSourceUtils.getDataSource(databaseId));\r\n\t\tSet<String> keySet = data.keySet();\r\n\t\tObject[] params = new Object[data.size()];\r\n\t\tSQL sql = new SQL();\r\n\t\t//设置表名\r\n\t\tsql.INSERT_INTO(tableName);\r\n\t\tint index = 0;\r\n\t\t//设置字段名\r\n\t\tfor (String key : keySet) {\r\n\t\t\tsql.VALUES(key, \"?\");\r\n\t\t\tparams[index] = data.get(key);\r\n\t\t\tindex++;\r\n\t\t}\r\n\t\ttry {\r\n\t\t\t//执行sql\r\n\t\t\ttemplate.update(sql.toString(), params);\r\n\t\t} catch (Exception e) {\r\n\t\t\tlogger.error(\"执行sql出错,异常信息:{}\", e.getMessage(), e);\r\n\t\t\tExceptionUtils.wrapAndThrow(e);\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void outputCSV(SpiderNode node, SpiderContext context, String csvName, Map<String, Object> data) {\r\n\t\tif (data == null || data.isEmpty()) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tString key = context.getId() + \"-\" + node.getNodeId();\r\n\t\tCSVPrinter printer = cachePrinter.get(key);\r\n\t\tList<String> records = new ArrayList<>(data.size());\r\n\t\tString[] headers = data.keySet().toArray(new String[data.size()]);\r\n\t\ttry {\r\n\t\t\tif (printer == null) {\r\n\t\t\t\tsynchronized (cachePrinter) {\r\n\t\t\t\t\tprinter = cachePrinter.get(key);\r\n\t\t\t\t\tif (printer == null) {\r\n\t\t\t\t\t\tCSVFormat format = CSVFormat.DEFAULT.withHeader(headers);\r\n\t\t\t\t\t\tFileOutputStream os = new FileOutputStream(csvName);\r\n\t\t\t\t\t\tString csvEncoding = node.getStringJsonValue(CSV_ENCODING);\r\n\t\t\t\t\t\tif (\"UTF-8BOM\".equals(csvEncoding)) {\r\n\t\t\t\t\t\t\tcsvEncoding = csvEncoding.substring(0, 5);\r\n\t\t\t\t\t\t\tbyte[] bom = {(byte) 0xEF, (byte) 0xBB, (byte) 0xBF};\r\n\t\t\t\t\t\t\tos.write(bom);\r\n\t\t\t\t\t\t\tos.flush();\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tOutputStreamWriter osw = new OutputStreamWriter(os, csvEncoding);\r\n\t\t\t\t\t\tprinter = new CSVPrinter(osw, format);\r\n\t\t\t\t\t\tcachePrinter.put(key, printer);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tfor (int i = 0; i < headers.length; i++) {\r\n\t\t\t\trecords.add(data.get(headers[i]).toString());\r\n\t\t\t}\r\n\t\t\tsynchronized (cachePrinter) {\r\n\t\t\t\tprinter.printRecord(records);\r\n\t\t\t}\r\n\t\t} catch (IOException e) {\r\n\t\t\tlogger.error(\"文件输出错误,异常信息:{}\", e.getMessage(), e);\r\n\t\t\tExceptionUtils.wrapAndThrow(e);\r\n\t\t}\r\n\t}\r\n\r\n\t@Override\r\n\tpublic String supportShape() {\r\n\t\treturn \"output\";\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void beforeStart(SpiderContext context) {\r\n\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void afterEnd(SpiderContext context) {\r\n\t\tthis.releasePrinters();\r\n\t}\r\n\r\n\tprivate void releasePrinters() {\r\n\t\tfor (Iterator<Map.Entry<String, CSVPrinter>> iterator = this.cachePrinter.entrySet().iterator(); iterator.hasNext(); ) {\r\n\t\t\tMap.Entry<String, CSVPrinter> entry = iterator.next();\r\n\t\t\tCSVPrinter printer = entry.getValue();\r\n\t\t\tif (printer != null) {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tprinter.flush();\r\n\t\t\t\t\tprinter.close();\r\n\t\t\t\t\tthis.cachePrinter.remove(entry.getKey());\r\n\t\t\t\t} catch (IOException e) {\r\n\t\t\t\t\tlogger.error(\"文件输出错误,异常信息:{}\", e.getMessage(), e);\r\n\t\t\t\t\tExceptionUtils.wrapAndThrow(e);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/shape/ProcessExecutor.java",
    "content": "package org.spiderflow.core.executor.shape;\r\n\r\nimport java.util.Map;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.spiderflow.context.SpiderContext;\r\nimport org.spiderflow.core.Spider;\r\nimport org.spiderflow.core.model.SpiderFlow;\r\nimport org.spiderflow.core.service.SpiderFlowService;\r\nimport org.spiderflow.core.utils.SpiderFlowUtils;\r\nimport org.spiderflow.executor.ShapeExecutor;\r\nimport org.spiderflow.model.SpiderNode;\r\nimport org.springframework.beans.factory.annotation.Autowired;\r\nimport org.springframework.stereotype.Component;\r\n\r\n/**\r\n * 子流程执行器\r\n * @author Administrator\r\n *\r\n */\r\n@Component\r\npublic class ProcessExecutor implements ShapeExecutor{\r\n\t\r\n\tpublic static final String FLOW_ID = \"flowId\";\r\n\r\n\tprivate static Logger logger = LoggerFactory.getLogger(ProcessExecutor.class);\r\n\t\r\n\t@Autowired\r\n\tprivate SpiderFlowService spiderFlowService;\r\n\t\r\n\t@Autowired\r\n\tprivate Spider spider;\r\n\t\r\n\t@Override\r\n\tpublic void execute(SpiderNode node, SpiderContext context, Map<String,Object> variables) {\r\n\t\tString flowId = node.getStringJsonValue(\"flowId\");\r\n\t\tSpiderFlow spiderFlow = spiderFlowService.getById(flowId);\r\n\t\tif(spiderFlow != null){\r\n\t\t\tlogger.info(\"执行子流程:{}\", spiderFlow.getName());\r\n\t\t\tSpiderNode root = SpiderFlowUtils.loadXMLFromString(spiderFlow.getXml());\r\n\t\t\tspider.executeNode(null,root,context,variables);\r\n\t\t}else{\r\n\t\t\tlogger.info(\"执行子流程:{}失败，找不到该子流程\", flowId);\r\n\t\t}\r\n\t}\r\n\r\n\t@Override\r\n\tpublic String supportShape() {\r\n\t\treturn \"process\";\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/shape/RequestExecutor.java",
    "content": "package org.spiderflow.core.executor.shape;\n\nimport com.google.common.hash.BloomFilter;\nimport com.google.common.hash.Funnel;\nimport com.google.common.hash.Funnels;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.exception.ExceptionUtils;\nimport org.apache.commons.lang3.math.NumberUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.spiderflow.Grammerable;\nimport org.spiderflow.context.CookieContext;\nimport org.spiderflow.context.SpiderContext;\nimport org.spiderflow.core.executor.function.MD5FunctionExecutor;\nimport org.spiderflow.core.io.HttpRequest;\nimport org.spiderflow.core.io.HttpResponse;\nimport org.spiderflow.core.utils.ExpressionUtils;\nimport org.spiderflow.executor.ShapeExecutor;\nimport org.spiderflow.io.SpiderResponse;\nimport org.spiderflow.listener.SpiderListener;\nimport org.spiderflow.model.Grammer;\nimport org.spiderflow.model.SpiderNode;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Component;\n\nimport javax.annotation.PostConstruct;\nimport java.io.*;\nimport java.nio.charset.Charset;\nimport java.util.*;\n\n/**\n * 请求执行器\n * @author Administrator\n *\n */\n@Component\npublic class RequestExecutor implements ShapeExecutor,Grammerable, SpiderListener {\n\t\n\tpublic static final String SLEEP = \"sleep\";\n\t\n\tpublic static final String URL = \"url\";\n\t\n\tpublic static final String PROXY = \"proxy\";\n\t\n\tpublic static final String REQUEST_METHOD = \"method\";\n\t\n\tpublic static final String PARAMETER_NAME = \"parameter-name\";\n\t\n\tpublic static final String PARAMETER_VALUE = \"parameter-value\";\n\n\tpublic static final String COOKIE_NAME = \"cookie-name\";\n\n\tpublic static final String COOKIE_VALUE = \"cookie-value\";\n\n\tpublic static final String PARAMETER_FORM_NAME = \"parameter-form-name\";\n\t\n\tpublic static final String PARAMETER_FORM_VALUE = \"parameter-form-value\";\n\t\n\tpublic static final String PARAMETER_FORM_FILENAME = \"parameter-form-filename\";\n\t\n\tpublic static final String PARAMETER_FORM_TYPE = \"parameter-form-type\";\n\t\n\tpublic static final String BODY_TYPE = \"body-type\";\n\t\n\tpublic static final String BODY_CONTENT_TYPE = \"body-content-type\";\n\t\n\tpublic static final String REQUEST_BODY = \"request-body\";\n\t\n\tpublic static final String HEADER_NAME = \"header-name\";\n\t\n\tpublic static final String HEADER_VALUE = \"header-value\";\n\t\n\tpublic static final String TIMEOUT = \"timeout\";\n\n\tpublic static final String RETRY_COUNT = \"retryCount\";\n\n\tpublic static final String RETRY_INTERVAL = \"retryInterval\";\n\n\tpublic static final String RESPONSE_CHARSET = \"response-charset\";\n\t\n\tpublic static final String FOLLOW_REDIRECT = \"follow-redirect\";\n\t\n\tpublic static final String TLS_VALIDATE = \"tls-validate\";\n\n\tpublic static final String LAST_EXECUTE_TIME = \"__last_execute_time_\";\n\n\tpublic static final String COOKIE_AUTO_SET = \"cookie-auto-set\";\n\n\tpublic static final String REPEAT_ENABLE = \"repeat-enable\";\n\n\tpublic static final String BLOOM_FILTER_KEY = \"_bloomfilter\";\n\n\t@Value(\"${spider.workspace}\")\n\tprivate String workspcace;\n\n\t@Value(\"${spider.bloomfilter.capacity:5000000}\")\n\tprivate Integer capacity;\n\n\t@Value(\"${spider.bloomfilter.error-rate:0.00001}\")\n\tprivate Double errorRate;\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(RequestExecutor.class);\n\t\n\t@Override\n\tpublic String supportShape() {\n\t\treturn \"request\";\n\t}\n\n\t@PostConstruct\n\tvoid init(){\n\t\t//允许设置被限制的请求头\n\t\tSystem.setProperty(\"sun.net.http.allowRestrictedHeaders\", \"true\");\n\t}\n\n\t@Override\n\tpublic void execute(SpiderNode node, SpiderContext context, Map<String,Object> variables) {\n\t\tCookieContext cookieContext = context.getCookieContext();\n\t\tString sleepCondition = node.getStringJsonValue(SLEEP);\n\t\tif(StringUtils.isNotBlank(sleepCondition)){\n\t\t\ttry {\n\t\t\t\tObject value = ExpressionUtils.execute(sleepCondition, variables);\n\t\t\t\tif(value != null){\n\t\t\t\t\tlong sleepTime = NumberUtils.toLong(value.toString(), 0L);\n\t\t\t\t\tsynchronized (node.getNodeId().intern()) {\n\t\t\t\t\t\t//实际等待时间 = 上次执行时间 + 睡眠时间 - 当前时间\n\t\t\t\t\t\tLong lastExecuteTime = context.get(LAST_EXECUTE_TIME + node.getNodeId(), 0L);\n\t\t\t\t\t\tif (lastExecuteTime != 0) {\n\t\t\t\t\t\t\tsleepTime = lastExecuteTime + sleepTime - System.currentTimeMillis();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (sleepTime > 0) {\n\t\t\t\t\t\t\tcontext.pause(node.getNodeId(),\"common\",SLEEP,sleepTime);\n\t\t\t\t\t\t\tlogger.debug(\"设置延迟时间:{}ms\", sleepTime);\n\t\t\t\t\t\t\tThread.sleep(sleepTime);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontext.put(LAST_EXECUTE_TIME + node.getNodeId(), System.currentTimeMillis());\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (Throwable t) {\n\t\t\t\tlogger.error(\"设置延迟时间失败\", t);\n\t\t\t}\n\t\t}\n\t\tBloomFilter<String> bloomFilter = null;\n\t\t//重试次数\n\t\tint retryCount = NumberUtils.toInt(node.getStringJsonValue(RETRY_COUNT), 0) + 1;\n\t\t//重试间隔时间，单位毫秒\n\t\tint retryInterval = NumberUtils.toInt(node.getStringJsonValue(RETRY_INTERVAL), 0);\n        boolean successed = false;\n\t\tfor (int i = 0; i < retryCount && !successed; i++) {\n\t\t\tHttpRequest request = HttpRequest.create();\n\t\t\t//设置请求url\n\t\t\tString url = null;\n\t\t\ttry {\n\t\t\t\turl = ExpressionUtils.execute(node.getStringJsonValue(URL), variables).toString();\n\t\t\t} catch (Exception e) {\n\t\t\t\tlogger.error(\"设置请求url出错，异常信息\", e);\n\t\t\t\tExceptionUtils.wrapAndThrow(e);\n\t\t\t}\n\t\t\tif(\"1\".equalsIgnoreCase(node.getStringJsonValue(REPEAT_ENABLE,\"0\"))){\n\t\t\t\tbloomFilter = createBloomFilter(context);\n\t\t\t\tsynchronized (bloomFilter){\n\t\t\t\t\tif(bloomFilter.mightContain(MD5FunctionExecutor.string(url))){\n\t\t\t\t\t\tlogger.info(\"过滤重复URL:{}\",url);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontext.pause(node.getNodeId(),\"common\",URL,url);\n\t\t\tlogger.info(\"设置请求url:{}\", url);\n\t\t\trequest.url(url);\n\t\t\t//设置请求超时时间\n\t\t\tint timeout = NumberUtils.toInt(node.getStringJsonValue(TIMEOUT), 60000);\n\t\t\tlogger.debug(\"设置请求超时时间:{}\", timeout);\n\t\t\trequest.timeout(timeout);\n\n\t\t\tString method = Objects.toString(node.getStringJsonValue(REQUEST_METHOD), \"GET\");\n\t\t\t//设置请求方法\n\t\t\trequest.method(method);\n\t\t\tlogger.debug(\"设置请求方法:{}\", method);\n\n\t\t\t//是否跟随重定向\n\t\t\tboolean followRedirects = !\"0\".equals(node.getStringJsonValue(FOLLOW_REDIRECT));\n\t\t\trequest.followRedirect(followRedirects);\n\t\t\tlogger.debug(\"设置跟随重定向：{}\", followRedirects);\n\n\t\t\t//是否验证TLS证书,默认是验证\n\t\t\tif(\"0\".equals(node.getStringJsonValue(TLS_VALIDATE))){\n\t\t\t\trequest.validateTLSCertificates(false);\n\t\t\t\tlogger.debug(\"设置TLS证书验证：{}\", false);\n\t\t\t}\n\t\t\tSpiderNode root = context.getRootNode();\n\t\t\t//设置请求header\n\t\t\tsetRequestHeader(root, request, root.getListJsonValue(HEADER_NAME,HEADER_VALUE), context, variables);\n\t\t\tsetRequestHeader(node, request, node.getListJsonValue(HEADER_NAME,HEADER_VALUE), context, variables);\n\n\t\t\t//设置全局Cookie\n\t\t\tMap<String, String> cookies = getRequestCookie(root, root.getListJsonValue(COOKIE_NAME, COOKIE_VALUE), context, variables);\n\t\t\tif(!cookies.isEmpty()){\n\t\t\t\tlogger.info(\"设置全局Cookie：{}\", cookies);\n\t\t\t\trequest.cookies(cookies);\n\t\t\t}\n\t\t\t//设置自动管理的Cookie\n\t\t\tboolean cookieAutoSet = !\"0\".equals(node.getStringJsonValue(COOKIE_AUTO_SET));\n\t\t\tif(cookieAutoSet && !cookieContext.isEmpty()){\n\t\t\t\tcontext.pause(node.getNodeId(),COOKIE_AUTO_SET,COOKIE_AUTO_SET,cookieContext);\n\t\t\t\trequest.cookies(cookieContext);\n\t\t\t\tlogger.info(\"自动设置Cookie：{}\", cookieContext);\n\t\t\t}\n\t\t\t//设置本节点Cookie\n\t\t\tcookies = getRequestCookie(node, node.getListJsonValue(COOKIE_NAME, COOKIE_VALUE), context, variables);\n\t\t\tif(!cookies.isEmpty()){\n\t\t\t\trequest.cookies(cookies);\n\t\t\t\tlogger.debug(\"设置Cookie：{}\", cookies);\n\t\t\t}\n\t\t\tif(cookieAutoSet){\n\t\t\t\tcookieContext.putAll(cookies);\n\t\t\t}\n\n\t\t\tString bodyType = node.getStringJsonValue(BODY_TYPE);\n\t\t\tList<InputStream> streams = null;\n\t\t\tif(\"raw\".equals(bodyType)){\n\t\t\t\tString contentType = node.getStringJsonValue(BODY_CONTENT_TYPE);\n\t\t\t\trequest.contentType(contentType);\n\t\t\t\ttry {\n\t\t\t\t\tObject requestBody = ExpressionUtils.execute(node.getStringJsonValue(REQUEST_BODY), variables);\n\t\t\t\t\tcontext.pause(node.getNodeId(),\"request-body\",REQUEST_BODY,requestBody);\n\t\t\t\t\trequest.data(requestBody);\n\t\t\t\t\tlogger.info(\"设置请求Body:{}\", requestBody);\n\t\t\t\t} catch (Exception e) {\n\t\t\t\t\tlogger.debug(\"设置请求Body出错\", e);\n\t\t\t\t}\n\t\t\t}else if(\"form-data\".equals(bodyType)){\n\t\t\t\tList<Map<String, String>> formParameters = node.getListJsonValue(PARAMETER_FORM_NAME,PARAMETER_FORM_VALUE,PARAMETER_FORM_TYPE,PARAMETER_FORM_FILENAME);\n\t\t\t\tstreams = setRequestFormParameter(node,request,formParameters,context,variables);\n\t\t\t}else{\n\t\t\t\t//设置请求参数\n\t\t\t\tsetRequestParameter(root, request, root.getListJsonValue(PARAMETER_NAME,PARAMETER_VALUE), context, variables);\n\t\t\t\tsetRequestParameter(node, request, node.getListJsonValue(PARAMETER_NAME,PARAMETER_VALUE), context, variables);\n\t\t\t}\n\t\t\t//设置代理\n\t\t\tString proxy = node.getStringJsonValue(PROXY);\n\t\t\tif(StringUtils.isNotBlank(proxy)){\n\t\t\t\ttry {\n\t\t\t\t\tObject value = ExpressionUtils.execute(proxy, variables);\n\t\t\t\t\tcontext.pause(node.getNodeId(),\"common\",PROXY,value);\n\t\t\t\t\tif(value != null){\n\t\t\t\t\t\tString[] proxyArr = value.toString().split(\":\");\n\t\t\t\t\t\tif(proxyArr.length == 2){\n\t\t\t\t\t\t\trequest.proxy(proxyArr[0], Integer.parseInt(proxyArr[1]));\n\t\t\t\t\t\t\tlogger.info(\"设置代理：{}\",proxy);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (Exception e) {\n\t\t\t\t\tlogger.error(\"设置代理出错，异常信息:{}\",e);\n\t\t\t\t}\n\t\t\t}\n\t\t\tThrowable exception = null;\n\t\t\ttry {\n\t\t\t\tHttpResponse response = request.execute();\n                successed = response.getStatusCode() == 200;\n                if(successed){\n                \tif(bloomFilter != null){\n                \t\tsynchronized (bloomFilter){\n\t\t\t\t\t\t\tbloomFilter.put(MD5FunctionExecutor.string(url));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n                    String charset = node.getStringJsonValue(RESPONSE_CHARSET);\n                    if(StringUtils.isNotBlank(charset)){\n                        response.setCharset(charset);\n                        logger.debug(\"设置response charset:{}\",charset);\n                    }\n                    //cookie存入cookieContext\n                    cookieContext.putAll(response.getCookies());\n                    //结果存入变量\n                    variables.put(\"resp\", response);\n                }\n\t\t\t} catch (IOException e) {\n\t\t\t\tsuccessed = false;\n                exception = e;\n\t\t\t} finally{\n                if(streams != null){\n                    for (InputStream is : streams) {\n                        try {\n                            is.close();\n                        } catch (Exception e) {\n                        }\n                    }\n                }\n                if(!successed){\n                    if(i + 1 < retryCount){\n                        if(retryInterval > 0){\n                            try {\n                                Thread.sleep(retryInterval);\n                            } catch (InterruptedException ignored) {\n                            }\n                        }\n                        logger.info(\"第{}次重试:{}\",i + 1,url);\n                    }else{\n                        //记录访问失败的日志\n\t\t\t\t\t\tif(context.getFlowId() != null){ //测试环境\n\t\t\t\t\t\t\t//TODO 需增加记录请求参数\n\t\t\t\t\t\t\tFile file = new File(workspcace, context.getFlowId() + File.separator + \"logs\" + File.separator + \"access_error.log\");\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tFile directory = file.getParentFile();\n\t\t\t\t\t\t\t\tif(!directory.exists()){\n\t\t\t\t\t\t\t\t\tdirectory.mkdirs();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tFileUtils.write(file,url + \"\\r\\n\",\"UTF-8\",true);\n\t\t\t\t\t\t\t} catch (IOException ignored) {\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n                        logger.error(\"请求{}出错,异常信息:{}\",url,exception);\n                    }\n                }\n\t\t\t}\n\t\t}\n\t}\n\t\n\tprivate List<InputStream> setRequestFormParameter(SpiderNode node, HttpRequest request,List<Map<String, String>> parameters,SpiderContext context,Map<String,Object> variables){\n\t\tList<InputStream> streams = new ArrayList<>();\n\t\tif(parameters != null){\n\t\t\tfor (Map<String,String> nameValue : parameters) {\n\t\t\t\tObject value;\n\t\t\t\tString parameterName = nameValue.get(PARAMETER_FORM_NAME);\n\t\t\t\tif(StringUtils.isNotBlank(parameterName)){\n\t\t\t\t\tString parameterValue = nameValue.get(PARAMETER_FORM_VALUE);\n\t\t\t\t\tString parameterType = nameValue.get(PARAMETER_FORM_TYPE);\n\t\t\t\t\tString parameterFilename = nameValue.get(PARAMETER_FORM_FILENAME);\n\t\t\t\t\tboolean hasFile = \"file\".equals(parameterType);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tvalue = ExpressionUtils.execute(parameterValue, variables);\n\t\t\t\t\t\tif(hasFile){\n\t\t\t\t\t\t\tInputStream stream = null;\n\t\t\t\t\t\t\tif(value instanceof byte[]){\n\t\t\t\t\t\t\t\tstream = new ByteArrayInputStream((byte[]) value);\n\t\t\t\t\t\t\t}else if(value instanceof String){\n\t\t\t\t\t\t\t\tstream = new ByteArrayInputStream(((String)value).getBytes());\n\t\t\t\t\t\t\t}else if(value instanceof InputStream){\n\t\t\t\t\t\t\t\tstream = (InputStream) value;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif(stream != null){\n\t\t\t\t\t\t\t\tstreams.add(stream);\n\t\t\t\t\t\t\t\trequest.data(parameterName, parameterFilename, stream);\n\t\t\t\t\t\t\t\tcontext.pause(node.getNodeId(),\"request-body\",parameterName,parameterFilename);\n\t\t\t\t\t\t\t\tlogger.info(\"设置请求参数：{}={}\",parameterName,parameterFilename);\n\t\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\t\tlogger.warn(\"设置请求参数：{}失败，无二进制内容\",parameterName);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\trequest.data(parameterName, value);\n\t\t\t\t\t\t\tcontext.pause(node.getNodeId(),\"request-body\",parameterName,value);\n\t\t\t\t\t\t\tlogger.info(\"设置请求参数：{}={}\",parameterName,value);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t} catch (Exception e) {\n\t\t\t\t\t\tlogger.error(\"设置请求参数：{}出错,异常信息:{}\",parameterName,e);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn streams;\n\t}\n\n\tprivate Map<String,String> getRequestCookie(SpiderNode node, List<Map<String, String>> cookies, SpiderContext context, Map<String, Object> variables) {\n\t\tMap<String,String> cookieMap = new HashMap<>();\n\t\tif (cookies != null) {\n\t\t\tfor (Map<String, String> nameValue : cookies) {\n\t\t\t\tObject value;\n\t\t\t\tString cookieName = nameValue.get(COOKIE_NAME);\n\t\t\t\tif (StringUtils.isNotBlank(cookieName)) {\n\t\t\t\t\tString cookieValue = nameValue.get(COOKIE_VALUE);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tvalue = ExpressionUtils.execute(cookieValue, variables);\n\t\t\t\t\t\tif (value != null) {\n\t\t\t\t\t\t\tcookieMap.put(cookieName, value.toString());\n\t\t\t\t\t\t\tcontext.pause(node.getNodeId(),\"request-cookie\",cookieName,value.toString());\n\t\t\t\t\t\t\tlogger.info(\"设置请求Cookie：{}={}\", cookieName, value);\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (Exception e) {\n\t\t\t\t\t\tlogger.error(\"设置请求Cookie：{}出错,异常信息：{}\", cookieName, e);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn cookieMap;\n\t}\n\n\tprivate void setRequestParameter(SpiderNode node, HttpRequest request, List<Map<String, String>> parameters, SpiderContext context, Map<String, Object> variables) {\n\t\tif (parameters != null) {\n\t\t\tfor (Map<String, String> nameValue : parameters) {\n\t\t\t\tObject value = null;\n\t\t\t\tString parameterName = nameValue.get(PARAMETER_NAME);\n\t\t\t\tif (StringUtils.isNotBlank(parameterName)) {\n\t\t\t\t\tString parameterValue = nameValue.get(PARAMETER_VALUE);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tvalue = ExpressionUtils.execute(parameterValue, variables);\n\t\t\t\t\t\tcontext.pause(node.getNodeId(),\"request-parameter\",parameterName,value);\n\t\t\t\t\t\tlogger.info(\"设置请求参数：{}={}\", parameterName, value);\n\t\t\t\t\t} catch (Exception e) {\n\t\t\t\t\t\tlogger.error(\"设置请求参数：{}出错,异常信息：{}\", parameterName, e);\n\t\t\t\t\t}\n\t\t\t\t\trequest.data(parameterName, value);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void setRequestHeader(SpiderNode node,HttpRequest request, List<Map<String, String>> headers, SpiderContext context, Map<String, Object> variables) {\n\t\tif (headers != null) {\n\t\t\tfor (Map<String, String> nameValue : headers) {\n\t\t\t\tObject value = null;\n\t\t\t\tString headerName = nameValue.get(HEADER_NAME);\n\t\t\t\tif (StringUtils.isNotBlank(headerName)) {\n\t\t\t\t\tString headerValue = nameValue.get(HEADER_VALUE);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tvalue = ExpressionUtils.execute(headerValue, variables);\n\t\t\t\t\t\tcontext.pause(node.getNodeId(),\"request-header\",headerName,value);\n\t\t\t\t\t\tlogger.info(\"设置请求Header：{}={}\", headerName, value);\n\t\t\t\t\t} catch (Exception e) {\n\t\t\t\t\t\tlogger.error(\"设置请求Header：{}出错,异常信息：{}\", headerName, e);\n\t\t\t\t\t}\n\t\t\t\t\trequest.header(headerName, value);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic List<Grammer> grammers() {\n\t\tList<Grammer> grammers = Grammer.findGrammers(SpiderResponse.class,\"resp\" , \"SpiderResponse\", false);\n\t\tGrammer grammer = new Grammer();\n\t\tgrammer.setFunction(\"resp\");\n\t\tgrammer.setComment(\"抓取结果\");\n\t\tgrammer.setOwner(\"SpiderResponse\");\n\t\tgrammers.add(grammer);\n\t\treturn grammers;\n\t}\n\n\t@Override\n\tpublic void beforeStart(SpiderContext context) {\n\n\t}\n\n\tprivate BloomFilter<String> createBloomFilter(SpiderContext context){\n\t\tBloomFilter<String> filter = context.get(BLOOM_FILTER_KEY);\n\t\tif(filter == null){\n\t\t\tFunnel<CharSequence> funnel = Funnels.stringFunnel(Charset.forName(\"UTF-8\"));\n\t\t\tString fileName = context.getFlowId() + File.separator + \"url.bf\";\n\t\t\tFile file = new File(workspcace,fileName);\n\t\t\tif(file.exists()){\n\t\t\t\ttry(FileInputStream fis = new FileInputStream(file)){\n\t\t\t\t\tfilter = BloomFilter.readFrom(fis,funnel);\n\t\t\t\t} catch (IOException e) {\n\t\t\t\t\tlogger.error(\"读取布隆过滤器出错\",e);\n\t\t\t\t}\n\n\t\t\t}else{\n\t\t\t\tfilter = BloomFilter.create(funnel,capacity,errorRate);\n\t\t\t}\n\t\t\tcontext.put(BLOOM_FILTER_KEY,filter);\n\t\t}\n\t\treturn filter;\n\t}\n\n\t@Override\n\tpublic void afterEnd(SpiderContext context) {\n\t\tBloomFilter<String> filter = context.get(BLOOM_FILTER_KEY);\n\t\tif(filter != null){\n\t\t\tFile file = new File(workspcace,context.getFlowId() + File.separator + \"url.bf\");\n\t\t\tif(!file.getParentFile().exists()){\n\t\t\t\tfile.getParentFile().mkdirs();\n\t\t\t}\n\t\t\ttry(FileOutputStream fos = new FileOutputStream(file)){\n\t\t\t\tfilter.writeTo(fos);\n\t\t\t\tfos.flush();\n\t\t\t}catch(IOException e){\n\t\t\t\tlogger.error(\"保存布隆过滤器出错\",e);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/shape/StartExecutor.java",
    "content": "package org.spiderflow.core.executor.shape;\r\n\r\nimport java.util.Map;\r\n\r\nimport org.spiderflow.context.SpiderContext;\r\nimport org.spiderflow.executor.ShapeExecutor;\r\nimport org.spiderflow.model.SpiderNode;\r\nimport org.springframework.stereotype.Component;\r\n\r\n/**\r\n * 开始执行器\r\n * @author Administrator\r\n *\r\n */\r\n@Component\r\npublic class StartExecutor implements ShapeExecutor{\r\n\r\n\t@Override\r\n\tpublic void execute(SpiderNode node, SpiderContext context, Map<String,Object> variables) {\r\n\t\t\r\n\t}\r\n\r\n\t@Override\r\n\tpublic String supportShape() {\r\n\t\treturn \"start\";\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/executor/shape/VariableExecutor.java",
    "content": "package org.spiderflow.core.executor.shape;\r\n\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\nimport org.apache.commons.lang3.exception.ExceptionUtils;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.spiderflow.context.SpiderContext;\r\nimport org.spiderflow.core.utils.ExpressionUtils;\r\nimport org.spiderflow.executor.ShapeExecutor;\r\nimport org.spiderflow.model.SpiderNode;\r\nimport org.springframework.stereotype.Component;\r\n\r\n/**\r\n * 定义变量执行器\r\n * @author Administrator\r\n *\r\n */\r\n@Component\r\npublic class VariableExecutor implements ShapeExecutor{\r\n\t\r\n\tprivate static final String VARIABLE_NAME = \"variable-name\";\r\n\t\r\n\tprivate static final String VARIABLE_VALUE = \"variable-value\";\r\n\r\n\tprivate static final Logger logger = LoggerFactory.getLogger(VariableExecutor.class);\r\n\t\r\n\t@Override\r\n\tpublic void execute(SpiderNode node, SpiderContext context, Map<String,Object> variables) {\r\n\t\tList<Map<String, String>> variableList = node.getListJsonValue(VARIABLE_NAME,VARIABLE_VALUE);\r\n\t\tfor (Map<String, String> nameValue : variableList) {\r\n\t\t\tObject value = null;\r\n\t\t\tString variableName = nameValue.get(VARIABLE_NAME);\r\n\t\t\tString variableValue = nameValue.get(VARIABLE_VALUE);\r\n\t\t\ttry {\r\n\t\t\t\tvalue = ExpressionUtils.execute(variableValue, variables);\r\n\t\t\t\tlogger.debug(\"设置变量{}={}\",variableName,value);\r\n\t\t\t\tcontext.pause(node.getNodeId(),\"common\",variableName,value);\r\n\t\t\t} catch (Exception e) {\r\n\t\t\t\tlogger.error(\"设置变量{}出错，异常信息：{}\",variableName,e);\r\n\t\t\t\tExceptionUtils.wrapAndThrow(e);\r\n\t\t\t}\r\n\t\t\tvariables.put(variableName, value);\r\n\t\t}\r\n\t}\r\n\r\n\t@Override\r\n\tpublic String supportShape() {\r\n\t\treturn \"variable\";\r\n\t}\r\n\t\r\n\t@Override\r\n\tpublic boolean isThread() {\r\n\t\treturn false;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/expression/DefaultExpressionEngine.java",
    "content": "package org.spiderflow.core.expression;\r\n\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\nimport javax.annotation.PostConstruct;\r\n\r\nimport org.apache.commons.lang3.StringUtils;\r\nimport org.spiderflow.ExpressionEngine;\r\nimport org.spiderflow.core.expression.interpreter.Reflection;\r\nimport org.spiderflow.executor.FunctionExecutor;\r\nimport org.spiderflow.executor.FunctionExtension;\r\nimport org.springframework.beans.factory.annotation.Autowired;\r\nimport org.springframework.stereotype.Component;\r\n\r\n@Component\r\npublic class DefaultExpressionEngine implements ExpressionEngine{\r\n\t\r\n\t@Autowired\r\n\tprivate List<FunctionExecutor> functionExecutors;\r\n\t\r\n\t@Autowired\r\n\tprivate List<FunctionExtension> functionExtensions;\r\n\t\r\n\t@PostConstruct\r\n\tprivate void init(){\r\n\t\tfor (FunctionExtension extension : functionExtensions) {\r\n\t\t\tReflection.getInstance().registerExtensionClass(extension.support(), extension.getClass());\r\n\t\t}\r\n\t}\r\n\t\r\n\t@Override\r\n\tpublic Object execute(String expression, Map<String, Object> variables) {\r\n\t\tif(StringUtils.isBlank(expression)){\r\n\t\t\treturn expression;\r\n\t\t}\r\n\t\tExpressionTemplateContext context = new ExpressionTemplateContext(variables);\r\n\t\tfor (FunctionExecutor executor : functionExecutors) {\r\n\t\t\tcontext.set(executor.getFunctionPrefix(), executor);\r\n\t\t}\r\n\t\tExpressionGlobalVariables.getVariables().entrySet().forEach(entry->{\r\n\t\t\tcontext.set(entry.getKey(),ExpressionTemplate.create(entry.getValue()).render(context));\r\n\t\t});\r\n\t\ttry {\r\n\t\t\tExpressionTemplateContext.set(context);\r\n\t\t\treturn ExpressionTemplate.create(expression).render(context);\r\n\t\t} finally {\r\n\t\t\tExpressionTemplateContext.remove();\r\n\t\t}\r\n\t}\r\n\t\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/expression/ExpressionError.java",
    "content": "\npackage org.spiderflow.core.expression;\n\nimport org.spiderflow.core.expression.parsing.Span;\nimport org.spiderflow.core.expression.parsing.Span.Line;\nimport org.spiderflow.core.expression.parsing.TokenStream;\n\n/** All errors reported by the library go through the static functions of this class. */\npublic class ExpressionError {\n\n\t/**\n\t * <p>\n\t * Create an error message based on the provided message and stream, highlighting the line on which the error happened. If the\n\t * stream has more tokens, the next token will be highlighted. Otherwise the end of the source of the stream will be\n\t * highlighted.\n\t * </p>\n\t *\n\t * <p>\n\t * Throws a {@link RuntimeException}\n\t * </p>\n\t */\n\tpublic static void error (String message, TokenStream stream) {\n\t\tif (stream.hasMore())\n\t\t\terror(message, stream.consume().getSpan());\n\t\telse {\n\t\t\tString source = stream.getSource();\n\t\t\tif (source == null)\n\t\t\t\terror(message, new Span(\" \", 0, 1));\n\t\t\telse\n\t\t\t\terror(message, new Span(source, source.length() - 1, source.length()));\n\t\t}\n\t}\n\n\t/** Create an error message based on the provided message and location, highlighting the location in the line on which the\n\t * error happened. Throws a {@link TemplateException} **/\n\tpublic static void error (String message, Span location, Throwable cause) {\n\n\t\tLine line = location.getLine();\n\t\tmessage = \"Error (\" + line.getLineNumber() + \"): \" + message + \"\\n\\n\";\n\t\tmessage += line.getText();\n\t\tmessage += \"\\n\";\n\n\t\tint errorStart = location.getStart() - line.getStart();\n\t\tint errorEnd = errorStart + location.getText().length() - 1;\n\t\tfor (int i = 0, n = line.getText().length(); i < n; i++) {\n\t\t\tboolean useTab = line.getText().charAt(i) == '\\t';\n\t\t\tmessage += i >= errorStart && i <= errorEnd ? \"^\" : useTab ? \"\\t\" : \" \";\n\t\t}\n\n\t\tif (cause == null)\n\t\t\tthrow new TemplateException(message, location);\n\t\telse\n\t\t\tthrow new TemplateException(message, location, cause);\n\t}\n\n\t/** Create an error message based on the provided message and location, highlighting the location in the line on which the\n\t * error happened. Throws a {@link TemplateException} **/\n\tpublic static void error (String message, Span location) {\n\t\terror(message, location, null);\n\t}\n\n\t/** Exception thrown by all basis-template code via {@link ExpressionError#error(String, Span)}. In case an error happens deep inside a\n\t * list of included templates, the {@link #getMessage()} method will return a condensed error message. **/\n\tpublic static class TemplateException extends RuntimeException {\n\t\tprivate static final long serialVersionUID = 1L;\n\t\tprivate final Span location;\n\t\tprivate final String errorMessage;\n\n\t\tprivate TemplateException (String message, Span location) {\n\t\t\tsuper(message);\n\t\t\tthis.errorMessage = message;\n\t\t\tthis.location = location;\n\t\t}\n\n\t\tpublic TemplateException (String message, Span location, Throwable cause) {\n\t\t\tsuper(message, cause);\n\t\t\tthis.errorMessage = message;\n\t\t\tthis.location = location;\n\t\t}\n\n\t\t/** Returns the location in the template at which the error happened. **/\n\t\tpublic Span getLocation () {\n\t\t\treturn location;\n\t\t}\n\n\t\t@Override\n\t\tpublic String getMessage () {\n\t\t\tStringBuilder builder = new StringBuilder();\n\n\t\t\tif (getCause() == null || getCause() == this) {\n\t\t\t\treturn super.getMessage();\n\t\t\t}\n\n\t\t\tbuilder.append(errorMessage.substring(0, errorMessage.indexOf('\\n')));\n\t\t\tbuilder.append(\"\\n\");\n\n\t\t\tThrowable cause = getCause();\n\t\t\twhile (cause != null && cause != this) {\n\t\t\t\tif (cause instanceof TemplateException) {\n\t\t\t\t\tTemplateException ex = (TemplateException)cause;\n\t\t\t\t\tif (ex.getCause() == null || ex.getCause() == ex)\n\t\t\t\t\t\tbuilder.append(ex.errorMessage);\n\t\t\t\t\telse\n\t\t\t\t\t\tbuilder.append(ex.errorMessage.substring(0, ex.errorMessage.indexOf('\\n')));\n\t\t\t\t\tbuilder.append(\"\\n\");\n\t\t\t\t}\n\t\t\t\tcause = cause.getCause();\n\t\t\t}\n\t\t\treturn builder.toString();\n\t\t}\n\t}\n\t\n\tpublic static class StringLiteralException extends RuntimeException {\n\n\t\tprivate static final long serialVersionUID = 1L;\n\t\t\n\t}\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/expression/ExpressionGlobalVariables.java",
    "content": "package org.spiderflow.core.expression;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantReadWriteLock;\n\npublic class ExpressionGlobalVariables {\n\n\tprivate static Map<String, String> variables = new HashMap<>();\n\n\tprivate static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();\n\n\tpublic static void reset(Map<String, String> map){\n\t\tLock lock = readWriteLock.writeLock();\n\t\tlock.lock();\n\t\ttry {\n\t\t\tvariables.clear();\n\t\t\tvariables.putAll(map);\n\t\t} finally {\n\t\t\tlock.unlock();\n\t\t}\n\t}\n\n\tpublic static Map<String, String> getVariables(){\n\t\tLock lock = readWriteLock.readLock();\n\t\tlock.lock();\n\t\ttry {\n\t\t\treturn variables;\n\t\t} finally {\n\t\t\tlock.unlock();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/expression/ExpressionTemplate.java",
    "content": "\npackage org.spiderflow.core.expression;\n\nimport java.io.OutputStream;\nimport java.util.List;\n\nimport org.spiderflow.core.expression.interpreter.AstInterpreter;\nimport org.spiderflow.core.expression.parsing.Ast;\nimport org.spiderflow.core.expression.parsing.Ast.Node;\nimport org.spiderflow.core.expression.parsing.Parser;\n\n\n/** A template is loaded by a {@link TemplateLoader} from a file marked up with the basis-template language. The template can be\n * rendered to a {@link String} or {@link OutputStream} by calling one of the <code>render()</code> methods. The\n * {@link ExpressionTemplateContext} passed to the <code>render()</code> methods is used to look up variable values referenced in the\n * template. */\npublic class ExpressionTemplate {\n\tprivate final List<Node> nodes;\n\n\t/** Internal. Created by {@link Parser}. **/\n\tprivate ExpressionTemplate (List<Node> nodes) {\n\t\tthis.nodes = nodes;\n\t}\n\t\n\tpublic static ExpressionTemplate create(String source){\n\t\treturn new ExpressionTemplate(Parser.parse(source));\n\t}\n\t\n\t/** Internal. The AST nodes representing this template after parsing. See {@link Ast}. Used by {@link AstInterpreter}. **/\n\tpublic List<Node> getNodes () {\n\t\treturn nodes;\n\t}\n\n\t/** Renders the template using the TemplateContext to resolve variable values referenced in the template. **/\n\tpublic Object render (ExpressionTemplateContext context) {\n\t\treturn AstInterpreter.interpret(this, context);\n\t}\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/expression/ExpressionTemplateContext.java",
    "content": "\npackage org.spiderflow.core.expression;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport org.spiderflow.core.expression.interpreter.AstInterpreter;\n\n\n/**\n * <p>\n * A template context stores mappings from variable names to user provided variable values. A {@link ExpressionTemplate} is given a context\n * for rendering to resolve variable values it references in template expressions.\n * </p>\n *\n * <p>\n * Internally, a template context is a stack of these mappings, similar to scopes in a programming language, and used as such by\n * the {@link AstInterpreter}.\n * </p>\n */\npublic class ExpressionTemplateContext {\n\tprivate final List<Map<String, Object>> scopes = new ArrayList<Map<String, Object>>();\n\n\t/** Keeps track of previously allocated, unused scopes. New scopes are first tried to be retrieved from this pool to avoid\n\t * generating garbage. **/\n\tprivate final List<Map<String, Object>> freeScopes = new ArrayList<Map<String, Object>>();\n\n\tprivate final static ThreadLocal<ExpressionTemplateContext> CONTEXT_THREAD_LOCAL = new ThreadLocal<>();\n\n\tpublic static ExpressionTemplateContext get(){\n\t\treturn CONTEXT_THREAD_LOCAL.get();\n\t}\n\n\tpublic static void remove(){\n\t\tCONTEXT_THREAD_LOCAL.remove();\n\t}\n\n\tpublic static void set(ExpressionTemplateContext context){\n\t\tCONTEXT_THREAD_LOCAL.set(context);\n\t}\n\n\tpublic ExpressionTemplateContext () {\n\t\tpush();\n\t}\n\t\n\tpublic ExpressionTemplateContext(Map<String,Object> variables) {\n\t\tthis();\n\t\tif(variables != null){\n\t\t\tvariables.forEach(this::set);\n\t\t}\n\t}\n\n\t/** Sets the value of the variable with the given name. If the variable already exists in one of the scopes, that variable is\n\t * set. Otherwise the variable is set on the last pushed scope. */\n\tpublic ExpressionTemplateContext set (String name, Object value) {\n\t\tfor (int i = scopes.size() - 1; i >= 0; i--) {\n\t\t\tMap<String, Object> ctx = scopes.get(i);\n\t\t\tif (ctx.isEmpty()) continue;\n\t\t\tif (ctx.containsKey(name)) {\n\t\t\t\tctx.put(name, value);\n\t\t\t\treturn this;\n\t\t\t}\n\t\t}\n\n\t\tscopes.get(scopes.size() - 1).put(name, value);\n\t\treturn this;\n\t}\n\n\t/** Sets the value of the variable with the given name on the last pushed scope **/\n\tpublic ExpressionTemplateContext setOnCurrentScope (String name, Object value) {\n\t\tscopes.get(scopes.size() - 1).put(name, value);\n\t\treturn this;\n\t}\n\n\t/** Internal. Returns the value of the variable with the given name, walking the scope stack from top to bottom, similar to how\n\t * scopes in programming languages are searched for variables. */\n\tpublic Object get (String name) {\n\t\tfor (int i = scopes.size() - 1; i >= 0; i--) {\n\t\t\tMap<String, Object> ctx = scopes.get(i);\n\t\t\tif (ctx.isEmpty()) continue;\n\t\t\tObject value = ctx.get(name);\n\t\t\tif (value != null) return value;\n\t\t}\n\t\treturn null;\n\t}\n\n\t/** Internal. Returns all variables currently defined in this context. */\n\tpublic Set<String> getVariables () {\n\t\tSet<String> variables = new HashSet<String>();\n\t\tfor (int i = 0, n = scopes.size(); i < n; i++) {\n\t\t\tvariables.addAll(scopes.get(i).keySet());\n\t\t}\n\t\treturn variables;\n\t}\n\n\t/** Internal. Pushes a new \"scope\" onto the stack. **/\n\tpublic void push () {\n\t\tMap<String, Object> newScope = freeScopes.size() > 0 ? freeScopes.remove(freeScopes.size() - 1) : new HashMap<String, Object>();\n\t\tscopes.add(newScope);\n\t}\n\n\t/** Internal. Pops the top of the \"scope\" stack. **/\n\tpublic void pop () {\n\t\tMap<String, Object> oldScope = scopes.remove(scopes.size() - 1);\n\t\toldScope.clear();\n\t\tfreeScopes.add(oldScope);\n\t}\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/expression/interpreter/AstInterpreter.java",
    "content": "\npackage org.spiderflow.core.expression.interpreter;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport org.spiderflow.core.expression.ExpressionError;\nimport org.spiderflow.core.expression.ExpressionError.TemplateException;\nimport org.spiderflow.core.expression.ExpressionTemplate;\nimport org.spiderflow.core.expression.ExpressionTemplateContext;\nimport org.spiderflow.core.expression.parsing.Ast;\nimport org.spiderflow.core.expression.parsing.Ast.Node;\nimport org.spiderflow.core.expression.parsing.Ast.Text;\n\n/**\n * <p>\n * Interprets a Template given a TemplateContext to lookup variable values in and writes the evaluation results to an output\n * stream. Uses the global {@link Reflection} instance as returned by {@link Reflection#getInstance()} to access members and call\n * methods.\n * </p>\n *\n * <p>\n * The interpeter traverses the AST as stored in {@link ExpressionTemplate#getNodes()}. the interpeter has a method for each AST node type\n * (see {@link Ast} that evaluates that node. A node may return a value, to be used in the interpretation of a parent node or to\n * be written to the output stream.\n * </p>\n **/\npublic class AstInterpreter {\n\tpublic static Object interpret (ExpressionTemplate template, ExpressionTemplateContext context) {\n\t\ttry {\n\t\t\treturn interpretNodeList(template.getNodes(), template, context);\n\t\t} catch (Throwable t) {\n\t\t\tif (t instanceof TemplateException)\n\t\t\t\tthrow (TemplateException)t;\n\t\t\telse {\n\t\t\t\tExpressionError.error(\"执行表达式出错 \" + t.getMessage(), template.getNodes().get(0).getSpan(),t);\n\t\t\t\treturn null; // never reached\n\t\t\t}\n\t\t} \n\t}\n\n\tpublic static Object interpretNodeList (List<Node> nodes, ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\tString result = \"\";\n\t\tfor (int i = 0, n = nodes.size(); i < n; i++) {\n\t\t\tNode node = nodes.get(i);\n\t\t\tObject value = node.evaluate(template, context);\n\t\t\tif(node instanceof Text){\n\t\t\t\tresult += node.getSpan().getText();\n\t\t\t}else if(value == null){\n\t\t\t\tif(i ==\t 0 && i + 1 == n){\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tresult += \"null\";\n\t\t\t}else if(value instanceof String || value instanceof Number || value instanceof Boolean){\n\t\t\t\tif(i ==0 && i + 1 ==n){\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t\tresult += value;\n\t\t\t}else if(i + 1 < n){\n\t\t\t\tExpressionError.error(\"表达式执行错误\", node.getSpan());\n\t\t\t}else{\n\t\t\t\treturn value;\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/expression/interpreter/JavaReflection.java",
    "content": "\npackage org.spiderflow.core.expression.interpreter;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\npublic class JavaReflection extends Reflection {\n\tprivate final Map<Class<?>, Map<String, Field>> fieldCache = new ConcurrentHashMap<Class<?>, Map<String, Field>>();\n\tprivate final Map<Class<?>, Map<JavaReflection.MethodSignature, Method>> methodCache = new ConcurrentHashMap<Class<?>, Map<JavaReflection.MethodSignature, Method>>();\n\tprivate final Map<Class<?>, Map<String,List<Method>>> extensionmethodCache = new ConcurrentHashMap<>();\n\n\t@SuppressWarnings(\"rawtypes\")\n\t@Override\n\tpublic Object getField (Object obj, String name) {\n\t\tClass cls = obj instanceof Class ? (Class)obj : obj.getClass();\n\t\tMap<String, Field> fields = fieldCache.get(cls);\n\t\tif (fields == null) {\n\t\t\tfields = new ConcurrentHashMap<String, Field>();\n\t\t\tfieldCache.put(cls, fields);\n\t\t}\n\n\t\tField field = fields.get(name);\n\t\tif (field == null) {\n\t\t\ttry {\n\t\t\t\tfield = cls.getDeclaredField(name);\n\t\t\t\tfield.setAccessible(true);\n\t\t\t\tfields.put(name, field);\n\t\t\t} catch (Throwable t) {\n\t\t\t\t// fall through, try super classes\n\t\t\t}\n\n\t\t\tif (field == null) {\n\t\t\t\tClass parentClass = cls.getSuperclass();\n\t\t\t\twhile (parentClass != Object.class && parentClass != null) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tfield = parentClass.getDeclaredField(name);\n\t\t\t\t\t\tfield.setAccessible(true);\n\t\t\t\t\t\tfields.put(name, field);\n\t\t\t\t\t} catch (NoSuchFieldException e) {\n\t\t\t\t\t\t// fall through\n\t\t\t\t\t}\n\t\t\t\t\tparentClass = parentClass.getSuperclass();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn field;\n\t}\n\n\t@Override\n\tpublic Object getFieldValue (Object obj, Object field) {\n\t\tField javaField = (Field)field;\n\t\ttry {\n\t\t\treturn javaField.get(obj);\n\t\t} catch (Throwable e) {\n\t\t\tthrow new RuntimeException(\"Couldn't get value of field '\" + javaField.getName() + \"' from object of type '\" + obj.getClass().getSimpleName() + \"'\");\n\t\t}\n\t}\n\t\n\t@Override\n\tpublic void registerExtensionClass(Class<?> target,Class<?> clazz){\n\t\tMethod[] methods = clazz.getDeclaredMethods();\n\t\tif(methods != null){\n\t\t\tMap<String, List<Method>> cachedMethodMap = extensionmethodCache.get(target);\n\t\t\tif(cachedMethodMap == null){\n\t\t\t\tcachedMethodMap = new HashMap<>();\n\t\t\t\textensionmethodCache.put(target,cachedMethodMap);\n\t\t\t}\n\t\t\tfor (Method method : methods) {\n\t\t\t\tif(Modifier.isStatic(method.getModifiers()) && method.getParameterCount() > 0){\n\t\t\t\t\tList<Method> cachedList = cachedMethodMap.get(method.getName());\n\t\t\t\t\tif(cachedList == null){\n\t\t\t\t\t\tcachedList = new ArrayList<>();\n\t\t\t\t\t\tcachedMethodMap.put(method.getName(), cachedList);\n\t\t\t\t\t}\n\t\t\t\t\tcachedList.add(method);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t\n\t@Override\n\tpublic Object getExtensionMethod(Object obj, String name, Object... arguments) {\n\t\tClass<?> cls = obj instanceof Class ? (Class<?>)obj : obj.getClass();\n\t\tif(cls.isArray()){\n\t\t\tcls = Object[].class;\n\t\t}\n\t\treturn getExtensionMethod(cls,name,arguments);\n\t}\n\t\n\tprivate Object getExtensionMethod(Class<?> cls, String name, Object... arguments) {\n\t\tif(cls == null){\n\t\t\tcls = Object.class;\n\t\t}\n\t\tMap<String, List<Method>> methodMap = extensionmethodCache.get(cls);\n\t\tif(methodMap != null){\n\t\t\tList<Method> methodList = methodMap.get(name);\n\t\t\tif(methodList != null){\n\t\t\t\tClass<?>[] parameterTypes = new Class[arguments.length + 1];\n\t\t\t\tparameterTypes[0] = cls;\n\t\t\t\tfor (int i = 0; i < arguments.length; i++) {\n\t\t\t\t\tparameterTypes[i + 1] = arguments[i] == null ? null : arguments[i].getClass();\n\t\t\t\t}\n\t\t\t\treturn findMethod(methodList, parameterTypes);\n\t\t\t}\n\t\t}\n\t\tif(cls != Object.class){\n\t\t\tClass<?>[] interfaces = cls.getInterfaces();\n\t\t\tif(interfaces != null){\n\t\t\t\tfor (Class<?> clazz : interfaces) {\n\t\t\t\t\tObject method = getExtensionMethod(clazz,name,arguments);\n\t\t\t\t\tif(method != null){\n\t\t\t\t\t\treturn method;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn getExtensionMethod(cls.getSuperclass(),name,arguments);\n\t\t}\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic Object getMethod (Object obj, String name, Object... arguments) {\n\t\tClass<?> cls = obj instanceof Class ? (Class<?>)obj : obj.getClass();\n\t\tMap<JavaReflection.MethodSignature, Method> methods = methodCache.get(cls);\n\t\tif (methods == null) {\n\t\t\tmethods = new ConcurrentHashMap<JavaReflection.MethodSignature, Method>();\n\t\t\tmethodCache.put(cls, methods);\n\t\t}\n\n\t\tClass<?>[] parameterTypes = new Class[arguments.length];\n\t\tfor (int i = 0; i < arguments.length; i++) {\n\t\t\tparameterTypes[i] = arguments[i] == null ? null : arguments[i].getClass();\n\t\t}\n\n\t\tJavaReflection.MethodSignature signature = new MethodSignature(name, parameterTypes);\n\t\tMethod method = methods.get(signature);\n\n\t\tif (method == null) {\n\t\t\ttry {\n\t\t\t\tif (name == null) {\n\t\t\t\t\tmethod = findApply(cls);\n\t\t\t\t} else {\n\t\t\t\t\tmethod = findMethod(cls, name, parameterTypes);\n\t\t\t\t\tif(method == null && parameterTypes != null){\n\t\t\t\t\t\tmethod = findMethod(cls, name, new Class<?>[]{Object[].class});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tmethod.setAccessible(true);\n\t\t\t\tmethods.put(signature, method);\n\t\t\t} catch (Throwable e) {\n\t\t\t\t// fall through\n\t\t\t}\n\n\t\t\tif (method == null) {\n\t\t\t\tClass<?> parentClass = cls.getSuperclass();\n\t\t\t\twhile (parentClass != Object.class && parentClass != null) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif (name == null)\n\t\t\t\t\t\t\tmethod = findApply(parentClass);\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tmethod = findMethod(parentClass, name, parameterTypes);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmethod.setAccessible(true);\n\t\t\t\t\t\tmethods.put(signature, method);\n\t\t\t\t\t} catch (Throwable e) {\n\t\t\t\t\t\t// fall through\n\t\t\t\t\t}\n\t\t\t\t\tparentClass = parentClass.getSuperclass();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn method;\n\t}\n\n\t/** Returns the <code>apply()</code> method of a functional interface. **/\n\tprivate static Method findApply (Class<?> cls) {\n\t\tfor (Method method : cls.getDeclaredMethods()) {\n\t\t\tif (method.getName().equals(\"apply\")) return method;\n\t\t}\n\t\treturn null;\n\t}\n\n\tprivate static Method findMethod (List<Method> methods, Class<?>[] parameterTypes) {\n\t\tMethod foundMethod = null;\n\t\tint foundScore = 0;\n\t\tfor (Method method : methods) {\n\t\t\t// Check if the types match.\n\t\t\tClass<?>[] otherTypes = method.getParameterTypes();\n\t\t\tif(parameterTypes.length != otherTypes.length){\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tboolean match = true;\n\t\t\tint score = 0;\n\t\t\tfor (int ii = 0, nn = parameterTypes.length; ii < nn; ii++) {\n\t\t\t\tClass<?> type = parameterTypes[ii];\n\t\t\t\tClass<?> otherType = otherTypes[ii];\n\n\t\t\t\tif (!otherType.isAssignableFrom(type)) {\n\t\t\t\t\tscore++;\n\t\t\t\t\tif (!isPrimitiveAssignableFrom(type, otherType)) {\n\t\t\t\t\t\tscore++;\n\t\t\t\t\t\tif (!isCoercible(type, otherType)) {\n\t\t\t\t\t\t\tmatch = false;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tscore++;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}else if(type == null && otherType.isPrimitive()){\n\t\t\t\t\tmatch = false;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (match) {\n\t\t\t\tif (foundMethod == null) {\n\t\t\t\t\tfoundMethod = method;\n\t\t\t\t\tfoundScore = score;\n\t\t\t\t} else {\n\t\t\t\t\tif (score < foundScore) {\n\t\t\t\t\t\tfoundScore = score;\n\t\t\t\t\t\tfoundMethod = method;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn foundMethod;\n\t}\n\t\n\t/** Returns the method best matching the given signature, including type coercion, or null. **/\n\tprivate static Method findMethod (Class<?> cls, String name, Class<?>[] parameterTypes) {\n\t\tMethod[] methods = cls.getDeclaredMethods();\n\t\tList<Method> methodList = new ArrayList<>();\n\t\tfor (int i = 0, n = methods.length; i < n; i++) {\n\t\t\tMethod method = methods[i];\n\n\t\t\t// if neither name or parameter list size match, bail on this method\n\t\t\tif (!method.getName().equals(name)) continue;\n\t\t\tif (method.getParameterTypes().length != parameterTypes.length) continue;\n\t\t\tmethodList.add(method);\n\t\t}\n\t\treturn findMethod(methodList,parameterTypes);\n\t}\n\n\t/** Returns whether the from type can be assigned to the to type, assuming either type is a (boxed) primitive type. We can\n\t * relax the type constraint a little, as we'll invoke a method via reflection. That means the from type will always be boxed,\n\t * as the {@link Method#invoke(Object, Object...)} method takes objects. **/\n\tprivate static boolean isPrimitiveAssignableFrom (Class<?> from, Class<?> to) {\n\t\tif ((from == Boolean.class || from == boolean.class) && (to == boolean.class || to == Boolean.class)) return true;\n\t\tif ((from == Integer.class || from == int.class) && (to == int.class || to == Integer.class)) return true;\n\t\tif ((from == Float.class || from == float.class) && (to == float.class || to == Float.class)) return true;\n\t\tif ((from == Double.class || from == double.class) && (to == double.class || to == Double.class)) return true;\n\t\tif ((from == Byte.class || from == byte.class) && (to == byte.class || to == Byte.class)) return true;\n\t\tif ((from == Short.class || from == short.class) && (to == short.class || to == Short.class)) return true;\n\t\tif ((from == Long.class || from == long.class) && (to == long.class || to == Long.class)) return true;\n\t\tif ((from == Character.class || from == char.class) && (to == char.class || to == Character.class)) return true;\n\t\treturn false;\n\t}\n\t\n\tpublic static String[] getStringTypes(Object[] objects){\n\t\tString[] parameterTypes = new String[objects == null ? 0: objects.length];\n\t\tif(objects != null){\n\t\t\tfor(int i=0,len = objects.length;i<len;i++){\n\t\t\t\tObject value = objects[i];\n\t\t\t\tparameterTypes[i] = value == null ? \"null\" : value.getClass().getSimpleName();  \n\t\t\t}\n\t\t}\n\t\treturn parameterTypes;\n\t}\n\n\t/** Returns whether the from type can be coerced to the to type. The coercion rules follow those of Java. See JLS 5.1.2\n\t * https://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html **/\n\tprivate static boolean isCoercible (Class<?> from, Class<?> to) {\n\t\tif (from == Integer.class || from == int.class) {\n\t\t\treturn to == float.class || to == Float.class || to == double.class || to == Double.class || to == long.class || to == Long.class;\n\t\t}\n\n\t\tif (from == Float.class || from == float.class) {\n\t\t\treturn to == double.class || to == Double.class;\n\t\t}\n\n\t\tif (from == Double.class || from == double.class) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (from == Character.class || from == char.class) {\n\t\t\treturn to == int.class || to == Integer.class || to == float.class || to == Float.class || to == double.class || to == Double.class || to == long.class\n\t\t\t\t|| to == Long.class;\n\t\t}\n\n\t\tif (from == Byte.class || from == byte.class) {\n\t\t\treturn to == int.class || to == Integer.class || to == float.class || to == Float.class || to == double.class || to == Double.class || to == long.class\n\t\t\t\t|| to == Long.class || to == short.class || to == Short.class;\n\t\t}\n\n\t\tif (from == Short.class || from == short.class) {\n\t\t\treturn to == int.class || to == Integer.class || to == float.class || to == Float.class || to == double.class || to == Double.class || to == long.class\n\t\t\t\t|| to == Long.class;\n\t\t}\n\n\t\tif (from == Long.class || from == long.class) {\n\t\t\treturn to == float.class || to == Float.class || to == double.class || to == Double.class;\n\t\t}\n\t\t\n\t\tif(from == int[].class || from == Integer[].class){\n\t\t\treturn to == Object[].class || to == float[].class || to == Float[].class || to == double[].class || to == Double[].class || to == long[].class || to == Long[].class;\n\t\t}\n\t\t\n\t\treturn false;\n\t}\n\n\t@Override\n\tpublic Object callMethod (Object obj, Object method, Object... arguments) {\n\t\tMethod javaMethod = (Method)method;\n\t\ttry {\n\t\t\treturn javaMethod.invoke(obj, arguments);\n\t\t} catch (Throwable t) {\n\t\t\tthrow new RuntimeException(\"Couldn't call method '\" + javaMethod.getName() + \"' with arguments '\" + Arrays.toString(arguments)\n\t\t\t\t+ \"' on object of type '\" + obj.getClass().getSimpleName() + \"'.\", t);\n\t\t}\n\t}\n\n\tprivate static class MethodSignature {\n\t\tprivate final String name;\n\t\t@SuppressWarnings(\"rawtypes\") private final Class[] parameters;\n\t\tprivate final int hashCode;\n\n\t\t@SuppressWarnings(\"rawtypes\")\n\t\tpublic MethodSignature (String name, Class[] parameters) {\n\t\t\tthis.name = name;\n\t\t\tthis.parameters = parameters;\n\t\t\tfinal int prime = 31;\n\t\t\tint hash = 1;\n\t\t\thash = prime * hash + ((name == null) ? 0 : name.hashCode());\n\t\t\thash = prime * hash + Arrays.hashCode(parameters);\n\t\t\thashCode = hash;\n\t\t}\n\n\t\t@Override\n\t\tpublic int hashCode () {\n\t\t\treturn hashCode;\n\t\t}\n\n\t\t@Override\n\t\tpublic boolean equals (Object obj) {\n\t\t\tif (this == obj) return true;\n\t\t\tif (obj == null) return false;\n\t\t\tif (getClass() != obj.getClass()) return false;\n\t\t\tJavaReflection.MethodSignature other = (JavaReflection.MethodSignature)obj;\n\t\t\tif (name == null) {\n\t\t\t\tif (other.name != null) return false;\n\t\t\t} else if (!name.equals(other.name)) return false;\n\t\t\tif (!Arrays.equals(parameters, other.parameters)) return false;\n\t\t\treturn true;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/expression/interpreter/Reflection.java",
    "content": "\npackage org.spiderflow.core.expression.interpreter;\n\n/** Used by {@link AstInterpreter} to access fields and methods of objects. This is a singleton class used by all\n * {@link AstInterpreter} instances. Replace the default implementation via {@link #setInstance(Reflection)}. The implementation\n * must be thread-safe. */\npublic abstract class Reflection {\n\tprivate static Reflection instance = new JavaReflection();\n\n\t/** Sets the Reflection instance to be used by all Template interpreters **/\n\tpublic synchronized static void setInstance (Reflection reflection) {\n\t\tinstance = reflection;\n\t}\n\n\t/** Returns the Reflection instance used to fetch field and call methods **/\n\tpublic synchronized static Reflection getInstance () {\n\t\treturn instance;\n\t}\n\n\t/** Returns an opaque handle to a field with the given name or null if the field could not be found **/\n\tpublic abstract Object getField (Object obj, String name);\n\n\t/** Returns an opaque handle to the method with the given name best matching the signature implied by the given arguments, or\n\t * null if the method could not be found. If obj is an instance of Class, the matching static method is returned. If the name\n\t * is null and the object is a {@link FunctionalInterface}, the first declared method on the object is returned. **/\n\tpublic abstract Object getMethod (Object obj, String name, Object... arguments);\n\t\n\tpublic abstract Object getExtensionMethod (Object obj, String name,Object ... arguments);\n\t\n\tpublic abstract void registerExtensionClass(Class<?> target,Class<?> clazz);\n\n\t/** Returns the value of the field from the object. The field must have been previously retrieved via\n\t * {@link #getField(Object, String)}. **/\n\tpublic abstract Object getFieldValue (Object obj, Object field);\n\n\t/** Calls the method on the object with the given arguments. The method must have been previously retrieved via\n\t * {@link #getMethod(Object, String, Object...)}. **/\n\tpublic abstract Object callMethod (Object obj, Object method, Object... arguments);\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/expression/parsing/Ast.java",
    "content": "\npackage org.spiderflow.core.expression.parsing;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.exception.ExceptionUtils;\nimport org.spiderflow.core.expression.ExpressionError;\nimport org.spiderflow.core.expression.ExpressionError.TemplateException;\nimport org.spiderflow.core.expression.ExpressionTemplate;\nimport org.spiderflow.core.expression.ExpressionTemplateContext;\nimport org.spiderflow.core.expression.interpreter.AstInterpreter;\nimport org.spiderflow.core.expression.interpreter.JavaReflection;\nimport org.spiderflow.core.expression.interpreter.Reflection;\nimport org.spiderflow.core.script.ScriptManager;\nimport org.spiderflow.expression.DynamicMethod;\n\nimport javax.xml.transform.Source;\nimport java.io.IOException;\nimport java.lang.reflect.Array;\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.*;\n\n\n/** Templates are parsed into an abstract syntax tree (AST) nodes by a Parser. This class contains all AST node types. */\npublic abstract class Ast {\n\n\t/** Base class for all AST nodes. A node minimally stores the {@link Span} that references its location in the\n\t * {@link Source}. **/\n\tpublic abstract static class Node {\n\t\tprivate final Span span;\n\n\t\tpublic Node (Span span) {\n\t\t\tthis.span = span;\n\t\t}\n\n\t\t/** Returns the {@link Span} referencing this node's location in the {@link Source}. **/\n\t\tpublic Span getSpan () {\n\t\t\treturn span;\n\t\t}\n\n\t\t@Override\n\t\tpublic String toString () {\n\t\t\treturn span.getText();\n\t\t}\n\n\t\tpublic abstract Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException;\n\t}\n\n\t/** A text node represents an \"un-templated\" span in the source that should be emitted verbatim. **/\n\tpublic static class Text extends Node {\n\t\tprivate final String content;\n\n\t\tpublic Text (Span text) {\n\t\t\tsuper(text);\n\t\t\tString unescapedValue = text.getText();\n\t\t\tStringBuilder builder = new StringBuilder();\n\n\t\t\tCharacterStream stream = new CharacterStream(unescapedValue);\n\t\t\twhile (stream.hasMore()) {\n\t\t\t\tif (stream.match(\"\\\\{\", true)) {\n\t\t\t\t\tbuilder.append('{');\n\t\t\t\t} else if (stream.match(\"\\\\}\", true)) {\n\t\t\t\t\tbuilder.append('}');\n\t\t\t\t} else {\n\t\t\t\t\tbuilder.append(stream.consume());\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontent = builder.toString();\n\t\t}\n\n\t\t/** Returns the UTF-8 representation of this text node. **/\n\t\tpublic String getContent () {\n\t\t\treturn content;\n\t\t}\n\n\t\t@Override\n\t\tpublic Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/** All expressions are subclasses of this node type. Expressions are separated into unary operations (!, -), binary operations\n\t * (+, -, *, /, etc.) and ternary operations (?:). */\n\tpublic abstract static class Expression extends Node {\n\t\tpublic Expression (Span span) {\n\t\t\tsuper(span);\n\t\t}\n\t}\n\n\t/** An unary operation node represents a logical or numerical negation. **/\n\tpublic static class UnaryOperation extends Expression {\n\n\t\tpublic static enum UnaryOperator {\n\t\t\tNot, Negate, Positive;\n\n\t\t\tpublic static UnaryOperator getOperator (Token op) {\n\t\t\t\tif (op.getType() == TokenType.Not) {\n\t\t\t\t\treturn UnaryOperator.Not;\n\t\t\t\t}\n\t\t\t\tif (op.getType() == TokenType.Plus) {\n\t\t\t\t\treturn UnaryOperator.Positive;\n\t\t\t\t}\n\t\t\t\tif (op.getType() == TokenType.Minus) {\n\t\t\t\t\treturn UnaryOperator.Negate;\n\t\t\t\t}\n\t\t\t\tExpressionError.error(\"Unknown unary operator \" + op + \".\", op.getSpan());\n\t\t\t\treturn null; // not reached\n\t\t\t}\n\t\t}\n\n\t\tprivate final UnaryOperator operator;\n\t\tprivate final Expression operand;\n\n\t\tpublic UnaryOperation (Token operator, Expression operand) {\n\t\t\tsuper(operator.getSpan());\n\t\t\tthis.operator = UnaryOperator.getOperator(operator);\n\t\t\tthis.operand = operand;\n\t\t}\n\n\t\tpublic UnaryOperator getOperator () {\n\t\t\treturn operator;\n\t\t}\n\n\t\tpublic Expression getOperand () {\n\t\t\treturn operand;\n\t\t}\n\n\t\t@Override\n\t\tpublic Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\tObject operand = getOperand().evaluate(template, context);\n\n\t\t\tif (getOperator() == UnaryOperator.Negate) {\n\t\t\t\tif (operand instanceof Integer) {\n\t\t\t\t\treturn -(Integer)operand;\n\t\t\t\t} else if (operand instanceof Float) {\n\t\t\t\t\treturn -(Float)operand;\n\t\t\t\t} else if (operand instanceof Double) {\n\t\t\t\t\treturn -(Double)operand;\n\t\t\t\t} else if (operand instanceof Byte) {\n\t\t\t\t\treturn -(Byte)operand;\n\t\t\t\t} else if (operand instanceof Short) {\n\t\t\t\t\treturn -(Short)operand;\n\t\t\t\t} else if (operand instanceof Long) {\n\t\t\t\t\treturn -(Long)operand;\n\t\t\t\t} else {\n\t\t\t\t\tExpressionError.error(\"Operand of operator '\" + getOperator().name() + \"' must be a number, got \" + operand, getSpan());\n\t\t\t\t\treturn null; // never reached\n\t\t\t\t}\n\t\t\t} else if (getOperator() == UnaryOperator.Not) {\n\t\t\t\tif (!(operand instanceof Boolean)) {\n\t\t\t\t\tExpressionError.error(\"Operand of operator '\" + getOperator().name() + \"' must be a boolean\", getSpan());\n\t\t\t\t}\n\t\t\t\treturn !(Boolean)operand;\n\t\t\t} else {\n\t\t\t\treturn operand;\n\t\t\t}\n\t\t}\n\t}\n\n\t/** A binary operation represents arithmetic operators, like addition or division, comparison operators, like less than or\n\t * equals, logical operators, like and, or an assignment. **/\n\tpublic static class BinaryOperation extends Expression {\n\n\t\tpublic static enum BinaryOperator {\n\t\t\tAddition, Subtraction, Multiplication, Division, Modulo, Equal, NotEqual, Less, LessEqual, Greater, GreaterEqual, And, Or, Xor, Assignment;\n\n\t\t\tpublic static BinaryOperator getOperator (Token op) {\n\t\t\t\tif (op.getType() == TokenType.Plus) {\n\t\t\t\t\treturn BinaryOperator.Addition;\n\t\t\t\t}\n\t\t\t\tif (op.getType() == TokenType.Minus) {\n\t\t\t\t\treturn BinaryOperator.Subtraction;\n\t\t\t\t}\n\t\t\t\tif (op.getType() == TokenType.Asterisk) {\n\t\t\t\t\treturn BinaryOperator.Multiplication;\n\t\t\t\t}\n\t\t\t\tif (op.getType() == TokenType.ForwardSlash) {\n\t\t\t\t\treturn BinaryOperator.Division;\n\t\t\t\t}\n\t\t\t\tif (op.getType() == TokenType.Percentage) {\n\t\t\t\t\treturn BinaryOperator.Modulo;\n\t\t\t\t}\n\t\t\t\tif (op.getType() == TokenType.Equal) {\n\t\t\t\t\treturn BinaryOperator.Equal;\n\t\t\t\t}\n\t\t\t\tif (op.getType() == TokenType.NotEqual) {\n\t\t\t\t\treturn BinaryOperator.NotEqual;\n\t\t\t\t}\n\t\t\t\tif (op.getType() == TokenType.Less) {\n\t\t\t\t\treturn BinaryOperator.Less;\n\t\t\t\t}\n\t\t\t\tif (op.getType() == TokenType.LessEqual) {\n\t\t\t\t\treturn BinaryOperator.LessEqual;\n\t\t\t\t}\n\t\t\t\tif (op.getType() == TokenType.Greater) {\n\t\t\t\t\treturn BinaryOperator.Greater;\n\t\t\t\t}\n\t\t\t\tif (op.getType() == TokenType.GreaterEqual) {\n\t\t\t\t\treturn BinaryOperator.GreaterEqual;\n\t\t\t\t}\n\t\t\t\tif (op.getType() == TokenType.And) {\n\t\t\t\t\treturn BinaryOperator.And;\n\t\t\t\t}\n\t\t\t\tif (op.getType() == TokenType.Or) {\n\t\t\t\t\treturn BinaryOperator.Or;\n\t\t\t\t}\n\t\t\t\tif (op.getType() == TokenType.Xor) {\n\t\t\t\t\treturn BinaryOperator.Xor;\n\t\t\t\t}\n\t\t\t\tif (op.getType() == TokenType.Assignment) {\n\t\t\t\t\treturn BinaryOperator.Assignment;\n\t\t\t\t}\n\t\t\t\tExpressionError.error(\"Unknown binary operator \" + op + \".\", op.getSpan());\n\t\t\t\treturn null; // not reached\n\t\t\t}\n\t\t}\n\n\t\tprivate final Expression leftOperand;\n\t\tprivate final BinaryOperator operator;\n\t\tprivate final Expression rightOperand;\n\n\t\tpublic BinaryOperation (Expression leftOperand, Token operator, Expression rightOperand) {\n\t\t\tsuper(operator.getSpan());\n\t\t\tthis.leftOperand = leftOperand;\n\t\t\tthis.operator = BinaryOperator.getOperator(operator);\n\t\t\tthis.rightOperand = rightOperand;\n\t\t}\n\n\t\tpublic Expression getLeftOperand () {\n\t\t\treturn leftOperand;\n\t\t}\n\n\t\tpublic BinaryOperator getOperator () {\n\t\t\treturn operator;\n\t\t}\n\n\t\tpublic Expression getRightOperand () {\n\t\t\treturn rightOperand;\n\t\t}\n\n\t\tprivate Object evaluateAddition (Object left, Object right) {\n\t\t\tif (left instanceof String || right instanceof String) {\n\t\t\t\treturn left.toString() + right.toString();\n\t\t\t}\n\t\t\tif (left instanceof Double || right instanceof Double) {\n\t\t\t\treturn ((Number)left).doubleValue() + ((Number)right).doubleValue();\n\t\t\t}\n\t\t\tif (left instanceof Float || right instanceof Float) {\n\t\t\t\treturn ((Number)left).floatValue() + ((Number)right).floatValue();\n\t\t\t}\n\t\t\tif (left instanceof Long || right instanceof Long) {\n\t\t\t\treturn ((Number)left).longValue() + ((Number)right).longValue();\n\t\t\t}\n\t\t\tif (left instanceof Integer || right instanceof Integer) {\n\t\t\t\treturn ((Number)left).intValue() + ((Number)right).intValue();\n\t\t\t}\n\t\t\tif (left instanceof Short || right instanceof Short) {\n\t\t\t\treturn ((Number)left).shortValue() + ((Number)right).shortValue();\n\t\t\t}\n\t\t\tif (left instanceof Byte || right instanceof Byte) {\n\t\t\t\treturn ((Number)left).byteValue() + ((Number)right).byteValue();\n\t\t\t}\n\n\t\t\tExpressionError.error(\"Operands for addition operator must be numbers or strings, got \" + left + \", \" + right + \".\", getSpan());\n\t\t\treturn null; // never reached\n\t\t}\n\n\t\tprivate Object evaluateSubtraction (Object left, Object right) {\n\t\t\tif (left instanceof Double || right instanceof Double) {\n\t\t\t\treturn ((Number)left).doubleValue() - ((Number)right).doubleValue();\n\t\t\t} else if (left instanceof Float || right instanceof Float) {\n\t\t\t\treturn ((Number)left).floatValue() - ((Number)right).floatValue();\n\t\t\t} else if (left instanceof Long || right instanceof Long) {\n\t\t\t\treturn ((Number)left).longValue() - ((Number)right).longValue();\n\t\t\t} else if (left instanceof Integer || right instanceof Integer) {\n\t\t\t\treturn ((Number)left).intValue() - ((Number)right).intValue();\n\t\t\t} else if (left instanceof Short || right instanceof Short) {\n\t\t\t\treturn ((Number)left).shortValue() - ((Number)right).shortValue();\n\t\t\t} else if (left instanceof Byte || right instanceof Byte) {\n\t\t\t\treturn ((Number)left).byteValue() - ((Number)right).byteValue();\n\t\t\t} else {\n\t\t\t\tExpressionError.error(\"Operands for subtraction operator must be numbers\" + left + \", \" + right + \".\", getSpan());\n\t\t\t\treturn null; // never reached\n\t\t\t}\n\t\t}\n\n\t\tprivate Object evaluateMultiplication (Object left, Object right) {\n\t\t\tif (left instanceof Double || right instanceof Double) {\n\t\t\t\treturn ((Number)left).doubleValue() * ((Number)right).doubleValue();\n\t\t\t} else if (left instanceof Float || right instanceof Float) {\n\t\t\t\treturn ((Number)left).floatValue() * ((Number)right).floatValue();\n\t\t\t} else if (left instanceof Long || right instanceof Long) {\n\t\t\t\treturn ((Number)left).longValue() * ((Number)right).longValue();\n\t\t\t} else if (left instanceof Integer || right instanceof Integer) {\n\t\t\t\treturn ((Number)left).intValue() * ((Number)right).intValue();\n\t\t\t} else if (left instanceof Short || right instanceof Short) {\n\t\t\t\treturn ((Number)left).shortValue() * ((Number)right).shortValue();\n\t\t\t} else if (left instanceof Byte || right instanceof Byte) {\n\t\t\t\treturn ((Number)left).byteValue() * ((Number)right).byteValue();\n\t\t\t} else {\n\t\t\t\tExpressionError.error(\"Operands for multiplication operator must be numbers\" + left + \", \" + right + \".\", getSpan());\n\t\t\t\treturn null; // never reached\n\t\t\t}\n\t\t}\n\n\t\tprivate Object evaluateDivision (Object left, Object right) {\n\t\t\tif (left instanceof Double || right instanceof Double) {\n\t\t\t\treturn ((Number)left).doubleValue() / ((Number)right).doubleValue();\n\t\t\t} else if (left instanceof Float || right instanceof Float) {\n\t\t\t\treturn ((Number)left).floatValue() / ((Number)right).floatValue();\n\t\t\t} else if (left instanceof Long || right instanceof Long) {\n\t\t\t\treturn ((Number)left).longValue() / ((Number)right).longValue();\n\t\t\t} else if (left instanceof Integer || right instanceof Integer) {\n\t\t\t\treturn ((Number)left).intValue() / ((Number)right).intValue();\n\t\t\t} else if (left instanceof Short || right instanceof Short) {\n\t\t\t\treturn ((Number)left).shortValue() / ((Number)right).shortValue();\n\t\t\t} else if (left instanceof Byte || right instanceof Byte) {\n\t\t\t\treturn ((Number)left).byteValue() / ((Number)right).byteValue();\n\t\t\t} else {\n\t\t\t\tExpressionError.error(\"Operands for division operator must be numbers\" + left + \", \" + right + \".\", getSpan());\n\t\t\t\treturn null; // never reached\n\t\t\t}\n\t\t}\n\n\t\tprivate Object evaluateModulo (Object left, Object right) {\n\t\t\tif (left instanceof Double || right instanceof Double) {\n\t\t\t\treturn ((Number)left).doubleValue() % ((Number)right).doubleValue();\n\t\t\t} else if (left instanceof Float || right instanceof Float) {\n\t\t\t\treturn ((Number)left).floatValue() % ((Number)right).floatValue();\n\t\t\t} else if (left instanceof Long || right instanceof Long) {\n\t\t\t\treturn ((Number)left).longValue() % ((Number)right).longValue();\n\t\t\t} else if (left instanceof Integer || right instanceof Integer) {\n\t\t\t\treturn ((Number)left).intValue() % ((Number)right).intValue();\n\t\t\t} else if (left instanceof Short || right instanceof Short) {\n\t\t\t\treturn ((Number)left).shortValue() % ((Number)right).shortValue();\n\t\t\t} else if (left instanceof Byte || right instanceof Byte) {\n\t\t\t\treturn ((Number)left).byteValue() % ((Number)right).byteValue();\n\t\t\t} else {\n\t\t\t\tExpressionError.error(\"Operands for modulo operator must be numbers\" + left + \", \" + right + \".\", getSpan());\n\t\t\t\treturn null; // never reached\n\t\t\t}\n\t\t}\n\n\t\tprivate boolean evaluateLess (Object left, Object right) {\n\t\t\tif (left instanceof Double || right instanceof Double) {\n\t\t\t\treturn ((Number)left).doubleValue() < ((Number)right).doubleValue();\n\t\t\t} else if (left instanceof Float || right instanceof Float) {\n\t\t\t\treturn ((Number)left).floatValue() < ((Number)right).floatValue();\n\t\t\t} else if (left instanceof Long || right instanceof Long) {\n\t\t\t\treturn ((Number)left).longValue() < ((Number)right).longValue();\n\t\t\t} else if (left instanceof Integer || right instanceof Integer) {\n\t\t\t\treturn ((Number)left).intValue() < ((Number)right).intValue();\n\t\t\t} else if (left instanceof Short || right instanceof Short) {\n\t\t\t\treturn ((Number)left).shortValue() < ((Number)right).shortValue();\n\t\t\t} else if (left instanceof Byte || right instanceof Byte) {\n\t\t\t\treturn ((Number)left).byteValue() < ((Number)right).byteValue();\n\t\t\t} else {\n\t\t\t\tExpressionError.error(\"Operands for less operator must be numbers\" + left + \", \" + right + \".\", getSpan());\n\t\t\t\treturn false; // never reached\n\t\t\t}\n\t\t}\n\n\t\tprivate Object evaluateLessEqual (Object left, Object right) {\n\t\t\tif (left instanceof Double || right instanceof Double) {\n\t\t\t\treturn ((Number)left).doubleValue() <= ((Number)right).doubleValue();\n\t\t\t} else if (left instanceof Float || right instanceof Float) {\n\t\t\t\treturn ((Number)left).floatValue() <= ((Number)right).floatValue();\n\t\t\t} else if (left instanceof Long || right instanceof Long) {\n\t\t\t\treturn ((Number)left).longValue() <= ((Number)right).longValue();\n\t\t\t} else if (left instanceof Integer || right instanceof Integer) {\n\t\t\t\treturn ((Number)left).intValue() <= ((Number)right).intValue();\n\t\t\t} else if (left instanceof Short || right instanceof Short) {\n\t\t\t\treturn ((Number)left).shortValue() <= ((Number)right).shortValue();\n\t\t\t} else if (left instanceof Byte || right instanceof Byte) {\n\t\t\t\treturn ((Number)left).byteValue() <= ((Number)right).byteValue();\n\t\t\t} else {\n\t\t\t\tExpressionError.error(\"Operands for less/equal operator must be numbers\" + left + \", \" + right + \".\", getSpan());\n\t\t\t\treturn null; // never reached\n\t\t\t}\n\t\t}\n\n\t\tprivate Object evaluateGreater (Object left, Object right) {\n\t\t\tif (left instanceof Double || right instanceof Double) {\n\t\t\t\treturn ((Number)left).doubleValue() > ((Number)right).doubleValue();\n\t\t\t} else if (left instanceof Float || right instanceof Float) {\n\t\t\t\treturn ((Number)left).floatValue() > ((Number)right).floatValue();\n\t\t\t} else if (left instanceof Long || right instanceof Long) {\n\t\t\t\treturn ((Number)left).longValue() > ((Number)right).longValue();\n\t\t\t} else if (left instanceof Integer || right instanceof Integer) {\n\t\t\t\treturn ((Number)left).intValue() > ((Number)right).intValue();\n\t\t\t} else if (left instanceof Short || right instanceof Short) {\n\t\t\t\treturn ((Number)left).shortValue() > ((Number)right).shortValue();\n\t\t\t} else if (left instanceof Byte || right instanceof Byte) {\n\t\t\t\treturn ((Number)left).byteValue() > ((Number)right).byteValue();\n\t\t\t} else {\n\t\t\t\tExpressionError.error(\"Operands for greater operator must be numbers\" + left + \", \" + right + \".\", getSpan());\n\t\t\t\treturn null; // never reached\n\t\t\t}\n\t\t}\n\n\t\tprivate Object evaluateGreaterEqual (Object left, Object right) {\n\t\t\tif (left instanceof Double || right instanceof Double) {\n\t\t\t\treturn ((Number)left).doubleValue() >= ((Number)right).doubleValue();\n\t\t\t} else if (left instanceof Float || right instanceof Float) {\n\t\t\t\treturn ((Number)left).floatValue() >= ((Number)right).floatValue();\n\t\t\t} else if (left instanceof Long || right instanceof Long) {\n\t\t\t\treturn ((Number)left).longValue() >= ((Number)right).longValue();\n\t\t\t} else if (left instanceof Integer || right instanceof Integer) {\n\t\t\t\treturn ((Number)left).intValue() >= ((Number)right).intValue();\n\t\t\t} else if (left instanceof Short || right instanceof Short) {\n\t\t\t\treturn ((Number)left).shortValue() >= ((Number)right).shortValue();\n\t\t\t} else if (left instanceof Byte || right instanceof Byte) {\n\t\t\t\treturn ((Number)left).byteValue() >= ((Number)right).byteValue();\n\t\t\t} else {\n\t\t\t\tExpressionError.error(\"Operands for greater/equal operator must be numbers\" + left + \", \" + right + \".\", getSpan());\n\t\t\t\treturn null; // never reached\n\t\t\t}\n\t\t}\n\n\t\tprivate Object evaluateAnd (Object left, ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\tif (!(left instanceof Boolean)) {\n\t\t\t\tExpressionError.error(\"Left operand must be a boolean, got \" + left + \".\", getLeftOperand().getSpan());\n\t\t\t}\n\t\t\tif (!(Boolean)left) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tObject right = getRightOperand().evaluate(template, context);\n\t\t\tif (!(right instanceof Boolean)) {\n\t\t\t\tExpressionError.error(\"Right operand must be a boolean, got \" + right + \".\", getRightOperand().getSpan());\n\t\t\t}\n\t\t\treturn (Boolean)left && (Boolean)right;\n\t\t}\n\n\t\tprivate Object evaluateOr (Object left, ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\tif (!(left instanceof Boolean)) {\n\t\t\t\tExpressionError.error(\"Left operand must be a boolean, got \" + left + \".\", getLeftOperand().getSpan());\n\t\t\t}\n\t\t\tif ((Boolean)left) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tObject right = getRightOperand().evaluate(template, context);\n\t\t\tif (!(right instanceof Boolean)) {\n\t\t\t\tExpressionError.error(\"Right operand must be a boolean, got \" + right + \".\", getRightOperand().getSpan());\n\t\t\t}\n\t\t\treturn (Boolean)left || (Boolean)right;\n\t\t}\n\n\t\tprivate Object evaluateXor (Object left, Object right) {\n\t\t\tif (!(left instanceof Boolean)) {\n\t\t\t\tExpressionError.error(\"Left operand must be a boolean, got \" + left + \".\", getLeftOperand().getSpan());\n\t\t\t}\n\t\t\tif (!(right instanceof Boolean)) {\n\t\t\t\tExpressionError.error(\"Right operand must be a boolean, got \" + right + \".\", getRightOperand().getSpan());\n\t\t\t}\n\t\t\treturn (Boolean)left ^ (Boolean)right;\n\t\t}\n\n\t\tprivate Object evaluateEqual (Object left, Object right) {\n\t\t\tif (left != null) {\n\t\t\t\treturn left.equals(right);\n\t\t\t}\n\t\t\tif (right != null) {\n\t\t\t\treturn right.equals(left);\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\tprivate Object evaluateNotEqual (Object left, Object right) {\n\t\t\treturn !(Boolean)evaluateEqual(left, right);\n\t\t}\n\n\t\t@Override\n\t\tpublic Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\tif (getOperator() == BinaryOperator.Assignment) {\n\t\t\t\tif (!(getLeftOperand() instanceof VariableAccess)) {\n\t\t\t\t\tExpressionError.error(\"Can only assign to top-level variables in context.\", getLeftOperand().getSpan());\n\t\t\t\t}\n\t\t\t\tObject value = getRightOperand().evaluate(template, context);\n\t\t\t\tcontext.set(((VariableAccess)getLeftOperand()).getVariableName().getText(), value);\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tObject left = getLeftOperand().evaluate(template, context);\n\t\t\tObject right = getOperator() == BinaryOperator.And || getOperator() == BinaryOperator.Or ? null : getRightOperand().evaluate(template, context);\n\n\t\t\tswitch (getOperator()) {\n\t\t\tcase Addition:\n\t\t\t\treturn evaluateAddition(left, right);\n\t\t\tcase Subtraction:\n\t\t\t\treturn evaluateSubtraction(left, right);\n\t\t\tcase Multiplication:\n\t\t\t\treturn evaluateMultiplication(left, right);\n\t\t\tcase Division:\n\t\t\t\treturn evaluateDivision(left, right);\n\t\t\tcase Modulo:\n\t\t\t\treturn evaluateModulo(left, right);\n\t\t\tcase Less:\n\t\t\t\treturn evaluateLess(left, right);\n\t\t\tcase LessEqual:\n\t\t\t\treturn evaluateLessEqual(left, right);\n\t\t\tcase Greater:\n\t\t\t\treturn evaluateGreater(left, right);\n\t\t\tcase GreaterEqual:\n\t\t\t\treturn evaluateGreaterEqual(left, right);\n\t\t\tcase Equal:\n\t\t\t\treturn evaluateEqual(left, right);\n\t\t\tcase NotEqual:\n\t\t\t\treturn evaluateNotEqual(left, right);\n\t\t\tcase And:\n\t\t\t\treturn evaluateAnd(left, template, context);\n\t\t\tcase Or:\n\t\t\t\treturn evaluateOr(left, template, context);\n\t\t\tcase Xor:\n\t\t\t\treturn evaluateXor(left, right);\n\t\t\tdefault:\n\t\t\t\tExpressionError.error(\"Binary operator \" + getOperator().name() + \" not implemented\", getSpan());\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\t}\n\n\t/** A ternary operation is an abbreviated if/then/else operation, and equivalent to the the ternary operator in Java. **/\n\tpublic static class TernaryOperation extends Expression {\n\t\tprivate final Expression condition;\n\t\tprivate final Expression trueExpression;\n\t\tprivate final Expression falseExpression;\n\n\t\tpublic TernaryOperation (Expression condition, Expression trueExpression, Expression falseExpression) {\n\t\t\tsuper(new Span(condition.getSpan(), falseExpression.getSpan()));\n\t\t\tthis.condition = condition;\n\t\t\tthis.trueExpression = trueExpression;\n\t\t\tthis.falseExpression = falseExpression;\n\t\t}\n\n\t\tpublic Expression getCondition () {\n\t\t\treturn condition;\n\t\t}\n\n\t\tpublic Expression getTrueExpression () {\n\t\t\treturn trueExpression;\n\t\t}\n\n\t\tpublic Expression getFalseExpression () {\n\t\t\treturn falseExpression;\n\t\t}\n\n\t\t@Override\n\t\tpublic Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\tObject condition = getCondition().evaluate(template, context);\n\t\t\tif (!(condition instanceof Boolean)) {\n\t\t\t\tExpressionError.error(\"Condition of ternary operator must be a boolean, got \" + condition + \".\", getSpan());\n\t\t\t}\n\t\t\treturn ((Boolean)condition) ? getTrueExpression().evaluate(template, context) : getFalseExpression().evaluate(template, context);\n\t\t}\n\t}\n\n\t/** A null literal, with the single value <code>null</code> **/\n\tpublic static class NullLiteral extends Expression {\n\t\tpublic NullLiteral (Span span) {\n\t\t\tsuper(span);\n\t\t}\n\n\t\t@Override\n\t\tpublic Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/** A boolean literal, with the values <code>true</code> and <code>false</code> **/\n\tpublic static class BooleanLiteral extends Expression {\n\t\tprivate final Boolean value;\n\n\t\tpublic BooleanLiteral (Span literal) {\n\t\t\tsuper(literal);\n\t\t\tthis.value = Boolean.parseBoolean(literal.getText());\n\t\t}\n\n\t\tpublic Boolean getValue () {\n\t\t\treturn value;\n\t\t}\n\n\t\t@Override\n\t\tpublic Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\treturn value;\n\t\t}\n\t}\n\n\t/** A double precision floating point literal. Must be marked with the <code>d</code> suffix, e.g. \"1.0d\". **/\n\tpublic static class DoubleLiteral extends Expression {\n\t\tprivate final Double value;\n\n\t\tpublic DoubleLiteral (Span literal) {\n\t\t\tsuper(literal);\n\t\t\tthis.value = Double.parseDouble(literal.getText().substring(0, literal.getText().length() - 1));\n\t\t}\n\n\t\tpublic Double getValue () {\n\t\t\treturn value;\n\t\t}\n\n\t\t@Override\n\t\tpublic Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\treturn value;\n\t\t}\n\t}\n\n\t/** A single precision floating point literla. May be optionally marked with the <code>f</code> suffix, e.g. \"1.0f\". **/\n\tpublic static class FloatLiteral extends Expression {\n\t\tprivate final Float value;\n\n\t\tpublic FloatLiteral (Span literal) {\n\t\t\tsuper(literal);\n\t\t\tString text = literal.getText();\n\t\t\tif (text.charAt(text.length() - 1) == 'f') {\n\t\t\t\ttext = text.substring(0, text.length() - 1);\n\t\t\t}\n\t\t\tthis.value = Float.parseFloat(text);\n\t\t}\n\n\t\tpublic Float getValue () {\n\t\t\treturn value;\n\t\t}\n\n\t\t@Override\n\t\tpublic Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\treturn value;\n\t\t}\n\t}\n\n\t/** A byte literal. Must be marked with the <code>b</code> suffix, e.g. \"123b\". **/\n\tpublic static class ByteLiteral extends Expression {\n\t\tprivate final Byte value;\n\n\t\tpublic ByteLiteral (Span literal) {\n\t\t\tsuper(literal);\n\t\t\tthis.value = Byte.parseByte(literal.getText().substring(0, literal.getText().length() - 1));\n\t\t}\n\n\t\tpublic Byte getValue () {\n\t\t\treturn value;\n\t\t}\n\n\t\t@Override\n\t\tpublic Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\treturn value;\n\t\t}\n\t}\n\n\t/** A short literal. Must be marked with the <code>s</code> suffix, e.g. \"123s\". **/\n\tpublic static class ShortLiteral extends Expression {\n\t\tprivate final Short value;\n\n\t\tpublic ShortLiteral (Span literal) {\n\t\t\tsuper(literal);\n\t\t\tthis.value = Short.parseShort(literal.getText().substring(0, literal.getText().length() - 1));\n\t\t}\n\n\t\tpublic Short getValue () {\n\t\t\treturn value;\n\t\t}\n\n\t\t@Override\n\t\tpublic Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\treturn value;\n\t\t}\n\t}\n\n\t/** An integer literal. **/\n\tpublic static class IntegerLiteral extends Expression {\n\t\tprivate final Integer value;\n\n\t\tpublic IntegerLiteral (Span literal) {\n\t\t\tsuper(literal);\n\t\t\tthis.value = Integer.parseInt(literal.getText());\n\t\t}\n\n\t\tpublic Integer getValue () {\n\t\t\treturn value;\n\t\t}\n\n\t\t@Override\n\t\tpublic Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\treturn value;\n\t\t}\n\t}\n\n\t/** A long integer literal. Must be marked with the <code>l</code> suffix, e.g. \"123l\". **/\n\tpublic static class LongLiteral extends Expression {\n\t\tprivate final Long value;\n\n\t\tpublic LongLiteral (Span literal) {\n\t\t\tsuper(literal);\n\t\t\tthis.value = Long.parseLong(literal.getText().substring(0, literal.getText().length() - 1));\n\t\t}\n\n\t\tpublic Long getValue () {\n\t\t\treturn value;\n\t\t}\n\n\t\t@Override\n\t\tpublic Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\treturn value;\n\t\t}\n\t}\n\n\t/** A character literal, enclosed in single quotes. Supports escape sequences \\n, \\r,\\t, \\' and \\\\. **/\n\tpublic static class CharacterLiteral extends Expression {\n\t\tprivate final Character value;\n\n\t\tpublic CharacterLiteral (Span literal) {\n\t\t\tsuper(literal);\n\n\t\t\tString text = literal.getText();\n\t\t\tif (text.length() > 3) {\n\t\t\t\tif (text.charAt(2) == 'n') {\n\t\t\t\t\tvalue = '\\n';\n\t\t\t\t} else if (text.charAt(2) == 'r') {\n\t\t\t\t\tvalue = '\\r';\n\t\t\t\t} else if (text.charAt(2) == 't') {\n\t\t\t\t\tvalue = '\\t';\n\t\t\t\t} else if (text.charAt(2) == '\\\\') {\n\t\t\t\t\tvalue = '\\\\';\n\t\t\t\t} else if (text.charAt(2) == '\\'') {\n\t\t\t\t\tvalue = '\\'';\n\t\t\t\t} else {\n\t\t\t\t\tExpressionError.error(\"Unknown escape sequence '\" + literal.getText() + \"'.\", literal);\n\t\t\t\t\tvalue = 0; // never reached\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis.value = literal.getText().charAt(1);\n\t\t\t}\n\t\t}\n\n\t\tpublic Character getValue () {\n\t\t\treturn value;\n\t\t}\n\n\t\t@Override\n\t\tpublic Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\treturn value;\n\t\t}\n\t}\n\n\t/** A string literal, enclosed in double quotes. Supports escape sequences \\n, \\r, \\t, \\\" and \\\\. **/\n\tpublic static class StringLiteral extends Expression {\n\t\tprivate final String value;\n\n\t\tpublic StringLiteral (Span literal) {\n\t\t\tsuper(literal);\n\t\t\tString text = getSpan().getText();\n\t\t\tString unescapedValue = text.substring(1, text.length() - 1);\n\t\t\tStringBuilder builder = new StringBuilder();\n\n\t\t\tCharacterStream stream = new CharacterStream(unescapedValue);\n\t\t\twhile (stream.hasMore()) {\n\t\t\t\tif (stream.match(\"\\\\\\\\\", true)) {\n\t\t\t\t\tbuilder.append('\\\\');\n\t\t\t\t} else if (stream.match(\"\\\\n\", true)) {\n\t\t\t\t\tbuilder.append('\\n');\n\t\t\t\t} else if (stream.match(\"\\\\r\", true)) {\n\t\t\t\t\tbuilder.append('\\r');\n\t\t\t\t} else if (stream.match(\"\\\\t\", true)) {\n\t\t\t\t\tbuilder.append('\\t');\n\t\t\t\t} else if (stream.match(\"\\\\\\\"\", true)) {\n\t\t\t\t\tbuilder.append('\"');\n\t\t\t\t} else {\n\t\t\t\t\tbuilder.append(stream.consume());\n\t\t\t\t}\n\t\t\t}\n\t\t\tvalue = builder.toString();\n\t\t}\n\n\t\t/** Returns the literal without quotes **/\n\t\tpublic String getValue () {\n\t\t\treturn value;\n\t\t}\n\n\t\t@Override\n\t\tpublic Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\treturn value;\n\t\t}\n\t}\n\n\t/** Represents a top-level variable access by name. E.g. in the expression \"a + 1\", <code>a</code> would be encoded as a\n\t * VariableAccess node. Variables can be both read (in expressions) and written to (in assignments). Variable values are looked\n\t * up and written to a {@link ExpressionTemplateContext}. **/\n\tpublic static class VariableAccess extends Expression {\n\t\tpublic VariableAccess (Span name) {\n\t\t\tsuper(name);\n\t\t}\n\n\t\tpublic Span getVariableName () {\n\t\t\treturn getSpan();\n\t\t}\n\n\t\t@Override\n\t\tpublic Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\tObject value = context.get(getSpan().getText());\n\t\t\t//if (value == null) ExpressionError.error(\"找不到变量'\" + getSpan().getText() + \"'或变量值为null\", getSpan());\n\t\t\treturn value;\n\t\t}\n\t}\n\n\t/** Represents a map or array element access of the form <code>mapOrArray[keyOrIndex]</code>. Maps and arrays may only be read\n\t * from. **/\n\tpublic static class MapOrArrayAccess extends Expression {\n\t\tprivate final Expression mapOrArray;\n\t\tprivate final Expression keyOrIndex;\n\n\t\tpublic MapOrArrayAccess (Span span, Expression mapOrArray, Expression keyOrIndex) {\n\t\t\tsuper(span);\n\t\t\tthis.mapOrArray = mapOrArray;\n\t\t\tthis.keyOrIndex = keyOrIndex;\n\t\t}\n\n\t\t/** Returns an expression that must evaluate to a map or array. **/\n\t\tpublic Expression getMapOrArray () {\n\t\t\treturn mapOrArray;\n\t\t}\n\n\t\t/** Returns an expression that is used as the key or index to fetch a map or array element. **/\n\t\tpublic Expression getKeyOrIndex () {\n\t\t\treturn keyOrIndex;\n\t\t}\n\n\t\t@SuppressWarnings(\"rawtypes\")\n\t\t@Override\n\t\tpublic Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\tObject mapOrArray = getMapOrArray().evaluate(template, context);\n\t\t\tif (mapOrArray == null) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tObject keyOrIndex = getKeyOrIndex().evaluate(template, context);\n\t\t\tif (keyOrIndex == null) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tif (mapOrArray instanceof Map) {\n\t\t\t\treturn ((Map)mapOrArray).get(keyOrIndex);\n\t\t\t} else if (mapOrArray instanceof List) {\n\t\t\t\tif (!(keyOrIndex instanceof Number)) {\n\t\t\t\t\tExpressionError.error(\"List index must be an integer, but was \" + keyOrIndex.getClass().getSimpleName(), getKeyOrIndex().getSpan());\n\t\t\t\t}\n\t\t\t\tint index = ((Number)keyOrIndex).intValue();\n\t\t\t\treturn ((List)mapOrArray).get(index);\n\t\t\t} else {\n\t\t\t\tif (!(keyOrIndex instanceof Number)) {\n\t\t\t\t\tExpressionError.error(\"Array index must be an integer, but was \" + keyOrIndex.getClass().getSimpleName(), getKeyOrIndex().getSpan());\n\t\t\t\t}\n\t\t\t\tint index = ((Number)keyOrIndex).intValue();\n\t\t\t\tif (mapOrArray instanceof int[]) {\n\t\t\t\t\treturn ((int[])mapOrArray)[index];\n\t\t\t\t} else if (mapOrArray instanceof float[]) {\n\t\t\t\t\treturn ((float[])mapOrArray)[index];\n\t\t\t\t} else if (mapOrArray instanceof double[]) {\n\t\t\t\t\treturn ((double[])mapOrArray)[index];\n\t\t\t\t} else if (mapOrArray instanceof boolean[]) {\n\t\t\t\t\treturn ((boolean[])mapOrArray)[index];\n\t\t\t\t} else if (mapOrArray instanceof char[]) {\n\t\t\t\t\treturn ((char[])mapOrArray)[index];\n\t\t\t\t} else if (mapOrArray instanceof short[]) {\n\t\t\t\t\treturn ((short[])mapOrArray)[index];\n\t\t\t\t} else if (mapOrArray instanceof long[]) {\n\t\t\t\t\treturn ((long[])mapOrArray)[index];\n\t\t\t\t} else if (mapOrArray instanceof byte[]) {\n\t\t\t\t\treturn ((byte[])mapOrArray)[index];\n\t\t\t\t} else if (mapOrArray instanceof String) {\n\t\t\t\t\treturn Character.toString(((String)mapOrArray).charAt(index));\n\t\t\t\t} else {\n\t\t\t\t\treturn ((Object[])mapOrArray)[index];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Represents an access of a member (field or method or entry in a map) of the form <code>object.member</code>. Members may\n\t * only be read from. **/\n\tpublic static class MemberAccess extends Expression {\n\t\tprivate final Expression object;\n\t\tprivate final Span name;\n\t\tprivate Object cachedMember;\n\n\t\tpublic MemberAccess (Expression object, Span name) {\n\t\t\tsuper(name);\n\t\t\tthis.object = object;\n\t\t\tthis.name = name;\n\t\t}\n\n\t\t/** Returns the object on which to access the member. **/\n\t\tpublic Expression getObject () {\n\t\t\treturn object;\n\t\t}\n\n\t\t/** The name of the member. **/\n\t\tpublic Span getName () {\n\t\t\treturn name;\n\t\t}\n\n\t\t/** Returns the cached member descriptor as returned by {@link Reflection#getField(Object, String)} or\n\t\t * {@link Reflection#getMethod(Object, String, Object...)}. See {@link #setCachedMember(Object)}. **/\n\t\tpublic Object getCachedMember () {\n\t\t\treturn cachedMember;\n\t\t}\n\n\t\t/** Sets the member descriptor as returned by {@link Reflection#getField(Object, String)} or\n\t\t * {@link Reflection#getMethod(Object, String, Object...)} for faster member lookups. Called by {@link AstInterpreter} the\n\t\t * first time this node is evaluated. Subsequent evaluations can use the cached descriptor, avoiding a costly reflective\n\t\t * lookup. **/\n\t\tpublic void setCachedMember (Object cachedMember) {\n\t\t\tthis.cachedMember = cachedMember;\n\t\t}\n\n\t\t@SuppressWarnings(\"rawtypes\")\n\t\t@Override\n\t\tpublic Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\tObject object = getObject().evaluate(template, context);\n\t\t\tif (object == null) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// special case for array.length\n\t\t\tif (object.getClass().isArray() && getName().getText().equals(\"length\")) {\n\t\t\t\treturn Array.getLength(object);\n\t\t\t}\n\n\t\t\t// special case for map, allows to do map.key instead of map[key]\n\t\t\tif (object instanceof Map) {\n\t\t\t\tMap map = (Map)object;\n\t\t\t\treturn map.get(getName().getText());\n\t\t\t}\n\n\t\t\tObject field = getCachedMember();\n\t\t\tif (field != null) {\n\t\t\t\ttry {\n\t\t\t\t\treturn Reflection.getInstance().getFieldValue(object, field);\n\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t// fall through\n\t\t\t\t}\n\t\t\t}\n\t\t\tString text = getName().getText();\n\t\t\tfield = Reflection.getInstance().getField(object, text);\n\t\t\tif (field == null) {\n\t\t\t\tString methodName = null;\n\t\t\t\tif(text.length() > 1){\n\t\t\t\t\tmethodName = text.substring(0,1).toUpperCase() + text.substring(1);\n\t\t\t\t}else{\n\t\t\t\t\tmethodName = text.toUpperCase();\n\t\t\t\t}\n\t\t\t\tMemberAccess access = new MemberAccess(this.object, new Span(\"get\" + methodName));\n\t\t\t\tMethodCall methodCall = new MethodCall(getName(),access, Collections.emptyList());\n\t\t\t\ttry {\n\t\t\t\t\treturn methodCall.evaluate(template, context);\n\t\t\t\t} catch (TemplateException e) {\n\t\t\t\t\tif(ExceptionUtils.indexOfThrowable(e, InvocationTargetException.class) > -1){\n\t\t\t\t\t\tExpressionError.error(String.format(\"在%s中调用方法get%s发生异常\"\n\t\t\t\t\t\t\t\t,object.getClass()\n\t\t\t\t\t\t\t\t,methodName), getSpan(),e);\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}\n\t\t\t\t\taccess = new MemberAccess(this.object, new Span(\"is\" + methodName));\n\t\t\t\t\tmethodCall = new MethodCall(getName(),access, Collections.emptyList());\n\t\t\t\t\ttry {\n\t\t\t\t\t\treturn methodCall.evaluate(template, context);\n\t\t\t\t\t} catch (TemplateException e1) {\n\t\t\t\t\t\tif(ExceptionUtils.indexOfThrowable(e1, InvocationTargetException.class) > -1){\n\t\t\t\t\t\t\tExpressionError.error(String.format(\"在%s中调用方法is%s发生异常\"\n\t\t\t\t\t\t\t\t\t,object.getClass()\n\t\t\t\t\t\t\t\t\t,methodName), getSpan(),e);\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tExpressionError.error(String.format(\"在%s中找不到属性%s或者方法get%s、方法is%s\"\n\t\t\t\t\t\t\t\t,object.getClass()\n\t\t\t\t\t\t\t\t,getName().getText()\n\t\t\t\t\t\t\t\t,methodName\n\t\t\t\t\t\t\t\t,methodName), getSpan());\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tsetCachedMember(field);\n\t\t\treturn Reflection.getInstance().getFieldValue(object, field);\n\t\t}\n\t}\n\n\t/** Represents a call to a top-level function. A function may either be a {@link FunctionalInterface} stored in a\n\t * {@link ExpressionTemplateContext}, or a {@link Macro} defined in a template. */\n\tpublic static class FunctionCall extends Expression {\n\t\tprivate final Expression function;\n\t\tprivate final List<Expression> arguments;\n\t\tprivate Object cachedFunction;\n\t\tprivate final ThreadLocal<Object[]> cachedArguments;\n\n\t\tpublic FunctionCall (Span span, Expression function, List<Expression> arguments) {\n\t\t\tsuper(span);\n\t\t\tthis.function = function;\n\t\t\tthis.arguments = arguments;\n\t\t\tthis.cachedArguments = new ThreadLocal<Object[]>();\n\t\t}\n\n\t\t/** Return the expression that must evaluate to a {@link FunctionalInterface} or a {@link Macro}. **/\n\t\tpublic Expression getFunction () {\n\t\t\treturn function;\n\t\t}\n\n\t\t/** Returns the list of expressions to be passed to the function as arguments. **/\n\t\tpublic List<Expression> getArguments () {\n\t\t\treturn arguments;\n\t\t}\n\n\t\t/** Returns the cached \"function\" descriptor as returned by {@link Reflection#getMethod(Object, String, Object...)} or the\n\t\t * {@link Macro}. See {@link #setCachedFunction(Object)}. **/\n\t\tpublic Object getCachedFunction () {\n\t\t\treturn cachedFunction;\n\t\t}\n\n\t\t/** Sets the \"function\" descriptor as returned by {@link Reflection#getMethod(Object, String, Object...)} for faster\n\t\t * lookups, or the {@link Macro} to be called. Called by {@link AstInterpreter} the first time this node is evaluated.\n\t\t * Subsequent evaluations can use the cached descriptor, avoiding a costly reflective lookup. **/\n\t\tpublic void setCachedFunction (Object cachedFunction) {\n\t\t\tthis.cachedFunction = cachedFunction;\n\t\t}\n\n\t\t/** Returns a scratch buffer to store arguments in when calling the function in {@link AstInterpreter}. Avoids generating\n\t\t * garbage. **/\n\t\tpublic Object[] getCachedArguments () {\n\t\t\tObject[] args = cachedArguments.get();\n\t\t\tif (args == null) {\n\t\t\t\targs = new Object[arguments.size()];\n\t\t\t\tcachedArguments.set(args);\n\t\t\t}\n\t\t\treturn args;\n\t\t}\n\n\t\t/** Must be invoked when this node is done evaluating so we don't leak memory **/\n\t\tpublic void clearCachedArguments () {\n\t\t\tObject[] args = getCachedArguments();\n\t\t\tfor (int i = 0; i < args.length; i++) {\n\t\t\t\targs[i] = null;\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\ttry {\n\t\t\t\tObject[] argumentValues = getCachedArguments();\n\t\t\t\tList<Expression> arguments = getArguments();\n\t\t\t\tfor (int i = 0, n = argumentValues.length; i < n; i++) {\n\t\t\t\t\tExpression expr = arguments.get(i);\n\t\t\t\t\targumentValues[i] = expr.evaluate(template, context);\n\t\t\t\t}\n\n\t\t\t\t// This is a special case to handle template level macros. If a call to a macro is\n\t\t\t\t// made, evaluating the function expression will result in an exception, as the\n\t\t\t\t// function name can't be found in the context. Instead we need to manually check\n\t\t\t\t// if the function expression is a VariableAccess and if so, if it can be found\n\t\t\t\t// in the context.\n\t\t\t\tObject function = null;\n\t\t\t\tif (getFunction() instanceof VariableAccess) {\n\t\t\t\t\tVariableAccess varAccess = (VariableAccess)getFunction();\n\t\t\t\t\tfunction = context.get(varAccess.getVariableName().getText());\n\t\t\t\t} else {\n\t\t\t\t\tfunction = getFunction().evaluate(template, context);\n\t\t\t\t}\n\n\t\t\t\tif (function != null) {\n\t\t\t\t\tObject method = getCachedFunction();\n\t\t\t\t\tif (method != null) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\treturn Reflection.getInstance().callMethod(function, method, argumentValues);\n\t\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\t\t// fall through\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tmethod = Reflection.getInstance().getMethod(function, null, argumentValues);\n\t\t\t\t\tif (method == null) {\n\t\t\t\t\t\tExpressionError.error(\"Couldn't find function.\", getSpan());\n\t\t\t\t\t}\n\t\t\t\t\tsetCachedFunction(method);\n\t\t\t\t\ttry {\n\t\t\t\t\t\treturn Reflection.getInstance().callMethod(function, method, argumentValues);\n\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\tExpressionError.error(t.getMessage(), getSpan(), t);\n\t\t\t\t\t\treturn null; // never reached\n\t\t\t\t\t}\n\t\t\t\t} else if(ScriptManager.containsFunction(getFunction().getSpan().getText())){\n\t\t\t\t\ttry {\n\t\t\t\t\t\treturn ScriptManager.eval(context,getFunction().getSpan().getText(),argumentValues);\n\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\tExpressionError.error(t.getMessage(), getSpan(), t);\n\t\t\t\t\t\treturn null; // never reached\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tExpressionError.error(\"Couldn't find function \" + getFunction(), getSpan());\n\t\t\t\t\treturn null; // never reached\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tclearCachedArguments();\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Represents a call to a method of the form <code>object.method(a, b, c)</code>. **/\n\tpublic static class MethodCall extends Expression {\n\t\tprivate final MemberAccess method;\n\t\tprivate final List<Expression> arguments;\n\t\tprivate Object cachedMethod;\n\t\tprivate final ThreadLocal<Object[]> cachedArguments;\n\n\t\tpublic MethodCall (Span span, MemberAccess method, List<Expression> arguments) {\n\t\t\tsuper(span);\n\t\t\tthis.method = method;\n\t\t\tthis.arguments = arguments;\n\t\t\tthis.cachedArguments = new ThreadLocal<Object[]>();\n\t\t}\n\n\t\t/** Returns the object on which to call the method. **/\n\t\tpublic Expression getObject () {\n\t\t\treturn method.getObject();\n\t\t}\n\n\t\t/** Returns the method to call. **/\n\t\tpublic MemberAccess getMethod () {\n\t\t\treturn method;\n\t\t}\n\n\t\t/** Returns the list of expressions to be passed to the function as arguments. **/\n\t\tpublic List<Expression> getArguments () {\n\t\t\treturn arguments;\n\t\t}\n\n\t\t/** Returns the cached member descriptor as returned by {@link Reflection#getMethod(Object, String, Object...)}. See\n\t\t * {@link #setCachedMember(Object)}. **/\n\t\tpublic Object getCachedMethod () {\n\t\t\treturn cachedMethod;\n\t\t}\n\n\t\t/** Sets the method descriptor as returned by {@link Reflection#getMethod(Object, String, Object...)} for faster lookups.\n\t\t * Called by {@link AstInterpreter} the first time this node is evaluated. Subsequent evaluations can use the cached\n\t\t * descriptor, avoiding a costly reflective lookup. **/\n\t\tpublic void setCachedMethod (Object cachedMethod) {\n\t\t\tthis.cachedMethod = cachedMethod;\n\t\t}\n\n\t\t/** Returns a scratch buffer to store arguments in when calling the function in {@link AstInterpreter}. Avoids generating\n\t\t * garbage. **/\n\t\tpublic Object[] getCachedArguments () {\n\t\t\tObject[] args = cachedArguments.get();\n\t\t\tif (args == null) {\n\t\t\t\targs = new Object[arguments.size()];\n\t\t\t\tcachedArguments.set(args);\n\t\t\t}\n\t\t\treturn args;\n\t\t}\n\n\t\t/** Must be invoked when this node is done evaluating so we don't leak memory **/\n\t\tpublic void clearCachedArguments () {\n\t\t\tObject[] args = getCachedArguments();\n\t\t\tfor (int i = 0; i < args.length; i++) {\n\t\t\t\targs[i] = null;\n\t\t\t}\n\t\t}\n\n\t\t@Override\n\t\tpublic Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\ttry {\n\t\t\t\tObject object = getObject().evaluate(template, context);\n\t\t\t\tif (object == null) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\n\t\t\t\tObject[] argumentValues = getCachedArguments();\n\t\t\t\tList<Expression> arguments = getArguments();\n\t\t\t\tfor (int i = 0, n = argumentValues.length; i < n; i++) {\n\t\t\t\t\tExpression expr = arguments.get(i);\n\t\t\t\t\targumentValues[i] = expr.evaluate(template, context);\n\t\t\t\t}\n\t\t\t\tif(object instanceof DynamicMethod){\n\t\t\t\t\ttry {\n\t\t\t\t\t\tObject method = DynamicMethod.class.getDeclaredMethod(\"execute\", String.class,List.class);\n\t\t\t\t\t\tObject[] newArgumentValues = new Object[]{getMethod().getName().getText(),Arrays.asList(argumentValues)};\n\t\t\t\t\t\treturn Reflection.getInstance().callMethod(object, method, newArgumentValues);\n\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\tExpressionError.error(t.getMessage(), getSpan(), t);\n\t\t\t\t\t\treturn null; // never reached\n\t\t\t\t\t} \n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// Otherwise try to find a corresponding method or field pointing to a lambda.\n\t\t\t\tObject method = getCachedMethod();\n\t\t\t\tif (method != null) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\treturn Reflection.getInstance().callMethod(object, method, argumentValues);\n\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\t// fall through\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tmethod = Reflection.getInstance().getMethod(object, getMethod().getName().getText(), argumentValues);\n\t\t\t\tif (method != null) {\n\t\t\t\t\t// found the method on the object, call it\n\t\t\t\t\tsetCachedMethod(method);\n\t\t\t\t\ttry {\n\t\t\t\t\t\treturn Reflection.getInstance().callMethod(object, method, argumentValues);\n\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\tExpressionError.error(t.getMessage(), getSpan(), t);\n\t\t\t\t\t\treturn null; // never reached\n\t\t\t\t\t}\n\t\t\t\t} \n\t\t\t\tmethod = Reflection.getInstance().getExtensionMethod(object, getMethod().getName().getText(), argumentValues);\n\t\t\t\tif(method != null){\n\t\t\t\t\ttry {\n\t\t\t\t\t\tint argumentLength = argumentValues == null ? 0 : argumentValues.length;\n\t\t\t\t\t\tObject[] parameters = new Object[argumentLength + 1];\n\t\t\t\t\t\tif(argumentLength > 0){\n\t\t\t\t\t\t\tfor (int i = 0; i < argumentLength; i++) {\n\t\t\t\t\t\t\t\tparameters[i + 1] = argumentValues[i];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tparameters[0] = object;\n\t\t\t\t\t\tif(object.getClass().isArray()){\n\t\t\t\t\t\t\tObject[] objs = new Object[Array.getLength(object)];\n\t\t\t\t\t\t\tfor (int i = 0,len = objs.length; i < len; i++) {\n\t\t\t\t\t\t\t\tArray.set(objs, i, Array.get(object, i));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tparameters[0] = objs;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn Reflection.getInstance().callMethod(object, method, parameters);\n\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\tExpressionError.error(t.getMessage(), getSpan(), t);\n\t\t\t\t\t\t// fall through\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t}\n\t\t\t\t}else {\n\t\t\t\t\t// didn't find the method on the object, try to find a field pointing to a lambda\n\t\t\t\t\tObject field = Reflection.getInstance().getField(object, getMethod().getName().getText());\n\t\t\t\t\tif (field == null){\n\t\t\t\t\t\tExpressionError.error(\"在'\" + object.getClass() + \"'中找不到方法 \" + getMethod().getName().getText() + \"(\" + StringUtils.join(JavaReflection.getStringTypes(argumentValues),\",\") + \")\",\n\t\t\t\t\t\t\tgetSpan());\n\t\t\t\t\t}\n\t\t\t\t\tObject function = Reflection.getInstance().getFieldValue(object, field);\n\t\t\t\t\tmethod = Reflection.getInstance().getMethod(function, null, argumentValues);\n\t\t\t\t\tif (method == null){\n\t\t\t\t\t\tExpressionError.error(\"在'\" + object.getClass() + \"'中找不到方法 \" + getMethod().getName().getText() + \"(\"+ StringUtils.join(JavaReflection.getStringTypes(argumentValues),\",\") +\")\",\n\t\t\t\t\t\t\t\tgetSpan());\n\t\t\t\t\t} \n\t\t\t\t\ttry {\n\t\t\t\t\t\treturn Reflection.getInstance().callMethod(function, method, argumentValues);\n\t\t\t\t\t} catch (Throwable t) {\n\t\t\t\t\t\tExpressionError.error(t.getMessage(), getSpan(), t);\n\t\t\t\t\t\treturn null; // never reached\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tclearCachedArguments();\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Represents a map literal of the form <code>{ key: value, key2: value, ... }</code> which can be nested. */\n\tpublic static class MapLiteral extends Expression {\n\t\tprivate final List<Token> keys;\n\t\tprivate final List<Expression> values;\n\n\t\tpublic MapLiteral (Span span, List<Token> keys, List<Expression> values) {\n\t\t\tsuper(span);\n\t\t\tthis.keys = keys;\n\t\t\tthis.values = values;\n\t\t}\n\n\t\tpublic List<Token> getKeys () {\n\t\t\treturn keys;\n\t\t}\n\n\t\tpublic List<Expression> getValues () {\n\t\t\treturn values;\n\t\t}\n\n\t\t@Override\n\t\tpublic Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\tMap<String, Object> map = new HashMap<>();\n\t\t\tfor (int i = 0, n = keys.size(); i < n; i++) {\n\t\t\t\tObject value = values.get(i).evaluate(template, context);\n\t\t\t\tToken tokenKey = keys.get(i);\n\t\t\t\tString key = tokenKey.getSpan().getText();\n\t\t\t\tif(tokenKey.getType() == TokenType.StringLiteral){\n\t\t\t\t\tkey = (String) new StringLiteral(tokenKey.getSpan()).evaluate(template, context);\n\t\t\t\t}else if(key != null && key.startsWith(\"$\")){\n\t\t\t\t\tObject objKey = context.get(key.substring(1));\n\t\t\t\t\tif(objKey != null){\n\t\t\t\t\t\tkey = objKey.toString();\n\t\t\t\t\t}else{\n\t\t\t\t\t\tkey = null;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tmap.put(key, value);\n\t\t\t}\n\t\t\treturn map;\n\t\t}\n\t}\n\n\t/** Represents a list literal of the form <code>[ value, value2, value3, ...]</code> which can be nested. */\n\tpublic static class ListLiteral extends Expression {\n\t\tpublic final List<Expression> values;\n\n\t\tpublic ListLiteral (Span span, List<Expression> values) {\n\t\t\tsuper(span);\n\t\t\tthis.values = values;\n\t\t}\n\n\t\tpublic List<Expression> getValues () {\n\t\t\treturn values;\n\t\t}\n\n\t\t@Override\n\t\tpublic Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {\n\t\t\tList<Object> list = new ArrayList<>();\n\t\t\tfor (int i = 0, n = values.size(); i < n; i++) {\n\t\t\t\tlist.add(values.get(i).evaluate(template, context));\n\t\t\t}\n\t\t\treturn list;\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/expression/parsing/CharacterStream.java",
    "content": "\npackage org.spiderflow.core.expression.parsing;\n\nimport javax.xml.transform.Source;\n\n/** Wraps a the content of a {@link Source} and handles traversing the contained characters. Manages a current {@link Span} via\n * the {@link #startSpan()} and {@link #endSpan()} methods. */\npublic class CharacterStream {\n\tprivate final String source;\n\tprivate int index = 0;\n\tprivate final int end;\n\n\tprivate int spanStart = 0;\n\n\tpublic CharacterStream (String source) {\n\t\tthis(source, 0, source.length());\n\t}\n\n\tpublic CharacterStream (String source, int start, int end) {\n\t\tif (start > end) throw new IllegalArgumentException(\"Start must be <= end.\");\n\t\tif (start < 0) throw new IndexOutOfBoundsException(\"Start must be >= 0.\");\n\t\tif (start > Math.max(0, source.length() - 1)) throw new IndexOutOfBoundsException(\"Start outside of string.\");\n\t\tif (end > source.length()) throw new IndexOutOfBoundsException(\"End outside of string.\");\n\n\t\tthis.source = source;\n\t\tthis.index = start;\n\t\tthis.end = end;\n\t}\n\n\t/** Returns whether there are more characters in the stream **/\n\tpublic boolean hasMore () {\n\t\treturn index < end;\n\t}\n\n\t/** Returns the next character without advancing the stream **/\n\tpublic char peek () {\n\t\tif (!hasMore()) throw new RuntimeException(\"No more characters in stream.\");\n\t\treturn source.charAt(index++);\n\t}\n\n\t/** Returns the next character and advance the stream **/\n\tpublic char consume () {\n\t\tif (!hasMore()) throw new RuntimeException(\"No more characters in stream.\");\n\t\treturn source.charAt(index++);\n\t}\n\n\t/** Matches the given needle with the next characters. Returns true if the needle is matched, false otherwise. If there's a\n\t * match and consume is true, the stream is advanced by the needle's length. */\n\tpublic boolean match (String needle, boolean consume) {\n\t\tint needleLength = needle.length();\n\t\tif(needleLength + index >end){\n\t\t\treturn false;\n\t\t}\n\t\tfor (int i = 0, j = index; i < needleLength; i++, j++) {\n\t\t\tif (index >= end) return false;\n\t\t\tif (needle.charAt(i) != source.charAt(j)) return false;\n\t\t}\n\t\tif (consume) index += needleLength;\n\t\treturn true;\n\t}\n\n\t/** Returns whether the next character is a digit and optionally consumes it. **/\n\tpublic boolean matchDigit (boolean consume) {\n\t\tif (index >= end) return false;\n\t\tchar c = source.charAt(index);\n\t\tif (Character.isDigit(c)) {\n\t\t\tif (consume) index++;\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/** Returns whether the next character is the start of an identifier and optionally consumes it. Adheres to\n\t * {@link Character#isJavaIdentifierStart(char)}. **/\n\tpublic boolean matchIdentifierStart (boolean consume) {\n\t\tif (index >= end) return false;\n\t\tchar c = source.charAt(index);\n\t\tif (Character.isJavaIdentifierStart(c) || c == '@') {\n\t\t\tif (consume) index++;\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/** Returns whether the next character is the start of an identifier and optionally consumes it. Adheres to\n\t * {@link Character#isJavaIdentifierPart(char)}. **/\n\tpublic boolean matchIdentifierPart (boolean consume) {\n\t\tif (index >= end) return false;\n\t\tchar c = source.charAt(index);\n\t\tif (Character.isJavaIdentifierPart(c)) {\n\t\t\tif (consume) index++;\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/** Skips any number of successive whitespace characters. **/\n\tpublic void skipWhiteSpace () {\n\t\twhile (true) {\n\t\t\tif (index >= end) return;\n\t\t\tchar c = source.charAt(index);\n\t\t\tif (c == ' ' || c == '\\n' || c == '\\r' || c == '\\t') {\n\t\t\t\tindex++;\n\t\t\t\tcontinue;\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Start a new Span at the current stream position. Call {@link #endSpan()} to complete the span. **/\n\tpublic void startSpan () {\n\t\tspanStart = index;\n\t}\n\n\t/** Completes the span started with {@link #startSpan()} at the current stream position. **/\n\tpublic Span endSpan () {\n\t\treturn new Span(source, spanStart, index);\n\t}\n\n\tpublic boolean isSpanEmpty () {\n\t\treturn spanStart == this.index;\n\t}\n\n\t/** Returns the current character position in the stream. **/\n\tpublic int getPosition () {\n\t\treturn index;\n\t}\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/expression/parsing/Parser.java",
    "content": "package org.spiderflow.core.expression.parsing;\r\n\r\n\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\nimport javax.xml.transform.Source;\r\n\r\nimport org.spiderflow.core.expression.ExpressionError;\r\nimport org.spiderflow.core.expression.ExpressionTemplate;\r\nimport org.spiderflow.core.expression.parsing.Ast.BinaryOperation;\r\nimport org.spiderflow.core.expression.parsing.Ast.BooleanLiteral;\r\nimport org.spiderflow.core.expression.parsing.Ast.ByteLiteral;\r\nimport org.spiderflow.core.expression.parsing.Ast.CharacterLiteral;\r\nimport org.spiderflow.core.expression.parsing.Ast.DoubleLiteral;\r\nimport org.spiderflow.core.expression.parsing.Ast.Expression;\r\nimport org.spiderflow.core.expression.parsing.Ast.FloatLiteral;\r\nimport org.spiderflow.core.expression.parsing.Ast.FunctionCall;\r\nimport org.spiderflow.core.expression.parsing.Ast.IntegerLiteral;\r\nimport org.spiderflow.core.expression.parsing.Ast.ListLiteral;\r\nimport org.spiderflow.core.expression.parsing.Ast.LongLiteral;\r\nimport org.spiderflow.core.expression.parsing.Ast.MapLiteral;\r\nimport org.spiderflow.core.expression.parsing.Ast.MapOrArrayAccess;\r\nimport org.spiderflow.core.expression.parsing.Ast.MemberAccess;\r\nimport org.spiderflow.core.expression.parsing.Ast.MethodCall;\r\nimport org.spiderflow.core.expression.parsing.Ast.Node;\r\nimport org.spiderflow.core.expression.parsing.Ast.NullLiteral;\r\nimport org.spiderflow.core.expression.parsing.Ast.ShortLiteral;\r\nimport org.spiderflow.core.expression.parsing.Ast.StringLiteral;\r\nimport org.spiderflow.core.expression.parsing.Ast.TernaryOperation;\r\nimport org.spiderflow.core.expression.parsing.Ast.Text;\r\nimport org.spiderflow.core.expression.parsing.Ast.UnaryOperation;\r\nimport org.spiderflow.core.expression.parsing.Ast.VariableAccess;\r\n\r\n\r\n/** Parses a {@link Source} into a {@link ExpressionTemplate}. The implementation is a simple recursive descent parser with a lookahead of\r\n * 1. **/\r\npublic class Parser {\r\n\r\n\t/** Parses a {@link Source} into a {@link ExpressionTemplate}. **/\r\n\tpublic static List<Node> parse (String source) {\r\n\t\tList<Node> nodes = new ArrayList<Node>();\r\n\t\tTokenStream stream = new TokenStream(new Tokenizer().tokenize(source));\r\n\t\twhile (stream.hasMore()) {\r\n\t\t\tnodes.add(parseStatement(stream));\r\n\t\t}\r\n\t\treturn nodes;\r\n\t}\r\n\r\n\t/** Parse a statement, which may either be a text block, if statement, for statement, while statement, macro definition,\r\n\t * include statement or an expression. **/\r\n\tprivate static Node parseStatement (TokenStream tokens) {\r\n\t\tNode result = null;\r\n\r\n\t\tif (tokens.match(TokenType.TextBlock, false))\r\n\t\t\tresult = new Text(tokens.consume().getSpan());\r\n\t\telse\r\n\t\t\tresult = parseExpression(tokens);\r\n\r\n\t\t// consume semi-colons as statement delimiters\r\n\t\twhile (tokens.match(\";\", true))\r\n\t\t\t;\r\n\r\n\t\treturn result;\r\n\t}\r\n\r\n\r\n\tprivate static Expression parseExpression (TokenStream stream) {\r\n\t\treturn parseTernaryOperator(stream);\r\n\t}\r\n\r\n\tprivate static Expression parseTernaryOperator (TokenStream stream) {\r\n\t\tExpression condition = parseBinaryOperator(stream, 0);\r\n\t\tif (stream.match(TokenType.Questionmark, true)) {\r\n\t\t\tExpression trueExpression = parseTernaryOperator(stream);\r\n\t\t\tstream.expect(TokenType.Colon);\r\n\t\t\tExpression falseExpression = parseTernaryOperator(stream);\r\n\t\t\treturn new TernaryOperation(condition, trueExpression, falseExpression);\r\n\t\t} else {\r\n\t\t\treturn condition;\r\n\t\t}\r\n\t}\r\n\r\n\tprivate static final TokenType[][] binaryOperatorPrecedence = new TokenType[][] {new TokenType[] {TokenType.Assignment},\r\n\t\tnew TokenType[] {TokenType.Or, TokenType.And, TokenType.Xor}, new TokenType[] {TokenType.Equal, TokenType.NotEqual},\r\n\t\tnew TokenType[] {TokenType.Less, TokenType.LessEqual, TokenType.Greater, TokenType.GreaterEqual}, new TokenType[] {TokenType.Plus, TokenType.Minus},\r\n\t\tnew TokenType[] {TokenType.ForwardSlash, TokenType.Asterisk, TokenType.Percentage}};\r\n\r\n\tprivate static Expression parseBinaryOperator (TokenStream stream, int level) {\r\n\t\tint nextLevel = level + 1;\r\n\t\tExpression left = nextLevel == binaryOperatorPrecedence.length ? parseUnaryOperator(stream) : parseBinaryOperator(stream, nextLevel);\r\n\r\n\t\tTokenType[] operators = binaryOperatorPrecedence[level];\r\n\t\twhile (stream.hasMore() && stream.match(false, operators)) {\r\n\t\t\tToken operator = stream.consume();\r\n\t\t\tExpression right = nextLevel == binaryOperatorPrecedence.length ? parseUnaryOperator(stream) : parseBinaryOperator(stream, nextLevel);\r\n\t\t\tleft = new BinaryOperation(left, operator, right);\r\n\t\t}\r\n\r\n\t\treturn left;\r\n\t}\r\n\r\n\tprivate static final TokenType[] unaryOperators = new TokenType[] {TokenType.Not, TokenType.Plus, TokenType.Minus};\r\n\r\n\tprivate static Expression parseUnaryOperator (TokenStream stream) {\r\n\t\tif (stream.match(false, unaryOperators)) {\r\n\t\t\treturn new UnaryOperation(stream.consume(), parseUnaryOperator(stream));\r\n\t\t} else {\r\n\t\t\tif (stream.match(TokenType.LeftParantheses, true)) {\r\n\t\t\t\tExpression expression = parseExpression(stream);\r\n\t\t\t\tstream.expect(TokenType.RightParantheses);\r\n\t\t\t\treturn expression;\r\n\t\t\t} else {\r\n\t\t\t\treturn parseAccessOrCallOrLiteral(stream);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprivate static Expression parseAccessOrCallOrLiteral (TokenStream stream) {\r\n\t\tif (stream.match(TokenType.Identifier, false)) {\r\n\t\t\treturn parseAccessOrCall(stream,TokenType.Identifier);\r\n\t\t} else if (stream.match(TokenType.LeftCurly, false)) {\r\n\t\t\treturn parseMapLiteral(stream);\r\n\t\t} else if (stream.match(TokenType.LeftBracket, false)) {\r\n\t\t\treturn parseListLiteral(stream);\r\n\t\t} else if (stream.match(TokenType.StringLiteral, false)) {\r\n\t\t\tif(stream.hasNext()){\r\n\t\t\t\tif(stream.next().getType() == TokenType.Period){\r\n\t\t\t\t\tstream.prev();\r\n\t\t\t\t\treturn parseAccessOrCall(stream,TokenType.StringLiteral);\r\n\t\t\t\t}\r\n\t\t\t\tstream.prev();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\treturn new StringLiteral(stream.expect(TokenType.StringLiteral).getSpan());\r\n\t\t} else if (stream.match(TokenType.BooleanLiteral, false)) {\r\n\t\t\treturn new BooleanLiteral(stream.expect(TokenType.BooleanLiteral).getSpan());\r\n\t\t} else if (stream.match(TokenType.DoubleLiteral, false)) {\r\n\t\t\treturn new DoubleLiteral(stream.expect(TokenType.DoubleLiteral).getSpan());\r\n\t\t} else if (stream.match(TokenType.FloatLiteral, false)) {\r\n\t\t\treturn new FloatLiteral(stream.expect(TokenType.FloatLiteral).getSpan());\r\n\t\t} else if (stream.match(TokenType.ByteLiteral, false)) {\r\n\t\t\treturn new ByteLiteral(stream.expect(TokenType.ByteLiteral).getSpan());\r\n\t\t} else if (stream.match(TokenType.ShortLiteral, false)) {\r\n\t\t\treturn new ShortLiteral(stream.expect(TokenType.ShortLiteral).getSpan());\r\n\t\t} else if (stream.match(TokenType.IntegerLiteral, false)) {\r\n\t\t\treturn new IntegerLiteral(stream.expect(TokenType.IntegerLiteral).getSpan());\r\n\t\t} else if (stream.match(TokenType.LongLiteral, false)) {\r\n\t\t\treturn new LongLiteral(stream.expect(TokenType.LongLiteral).getSpan());\r\n\t\t} else if (stream.match(TokenType.CharacterLiteral, false)) {\r\n\t\t\treturn new CharacterLiteral(stream.expect(TokenType.CharacterLiteral).getSpan());\r\n\t\t} else if (stream.match(TokenType.NullLiteral, false)) {\r\n\t\t\treturn new NullLiteral(stream.expect(TokenType.NullLiteral).getSpan());\r\n\t\t} else {\r\n\t\t\tExpressionError.error(\"Expected a variable, field, map, array, function or method call, or literal.\", stream);\r\n\t\t\treturn null; // not reached\r\n\t\t}\r\n\t}\r\n\r\n\tprivate static Expression parseMapLiteral (TokenStream stream) {\r\n\t\tSpan openCurly = stream.expect(TokenType.LeftCurly).getSpan();\r\n\r\n\t\tList<Token> keys = new ArrayList<>();\r\n\t\tList<Expression> values = new ArrayList<>();\r\n\t\twhile (stream.hasMore() && !stream.match(\"}\", false)) {\r\n\t\t\tif(stream.match(TokenType.StringLiteral, false)){\r\n\t\t\t\tkeys.add(stream.expect(TokenType.StringLiteral));\r\n\t\t\t}else{\r\n\t\t\t\tkeys.add(stream.expect(TokenType.Identifier));\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tstream.expect(\":\");\r\n\t\t\tvalues.add(parseExpression(stream));\r\n\t\t\tif (!stream.match(\"}\", false)) stream.expect(TokenType.Comma);\r\n\t\t}\r\n\t\tSpan closeCurly = stream.expect(\"}\").getSpan();\r\n\t\treturn new MapLiteral(new Span(openCurly, closeCurly), keys, values);\r\n\t}\r\n\r\n\tprivate static Expression parseListLiteral (TokenStream stream) {\r\n\t\tSpan openBracket = stream.expect(TokenType.LeftBracket).getSpan();\r\n\r\n\t\tList<Expression> values = new ArrayList<>();\r\n\t\twhile (stream.hasMore() && !stream.match(TokenType.RightBracket, false)) {\r\n\t\t\tvalues.add(parseExpression(stream));\r\n\t\t\tif (!stream.match(TokenType.RightBracket, false)) stream.expect(TokenType.Comma);\r\n\t\t}\r\n\r\n\t\tSpan closeBracket = stream.expect(TokenType.RightBracket).getSpan();\r\n\t\treturn new ListLiteral(new Span(openBracket, closeBracket), values);\r\n\t}\r\n\r\n\tprivate static Expression parseAccessOrCall (TokenStream stream,TokenType tokenType) {\r\n\t\t//Span identifier = stream.expect(TokenType.Identifier);\r\n\t\t//Expression result = new VariableAccess(identifier);\r\n\t\tSpan identifier = stream.expect(tokenType).getSpan();\r\n\t\tExpression result = tokenType == TokenType.StringLiteral ? new StringLiteral(identifier) :new VariableAccess(identifier);\r\n\r\n\t\twhile (stream.hasMore() && stream.match(false, TokenType.LeftParantheses, TokenType.LeftBracket, TokenType.Period)) {\r\n\r\n\t\t\t// function or method call\r\n\t\t\tif (stream.match(TokenType.LeftParantheses, false)) {\r\n\t\t\t\tList<Expression> arguments = parseArguments(stream);\r\n\t\t\t\tSpan closingSpan = stream.expect(TokenType.RightParantheses).getSpan();\r\n\t\t\t\tif (result instanceof VariableAccess || result instanceof MapOrArrayAccess)\r\n\t\t\t\t\tresult = new FunctionCall(new Span(result.getSpan(), closingSpan), result, arguments);\r\n\t\t\t\telse if (result instanceof MemberAccess) {\r\n\t\t\t\t\tresult = new MethodCall(new Span(result.getSpan(), closingSpan), (MemberAccess)result, arguments);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tExpressionError.error(\"Expected a variable, field or method.\", stream);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// map or array access\r\n\t\t\telse if (stream.match(TokenType.LeftBracket, true)) {\r\n\t\t\t\tExpression keyOrIndex = parseExpression(stream);\r\n\t\t\t\tSpan closingSpan = stream.expect(TokenType.RightBracket).getSpan();\r\n\t\t\t\tresult = new MapOrArrayAccess(new Span(result.getSpan(), closingSpan), result, keyOrIndex);\r\n\t\t\t}\r\n\r\n\t\t\t// field or method access\r\n\t\t\telse if (stream.match(TokenType.Period, true)) {\r\n\t\t\t\tidentifier = stream.expect(TokenType.Identifier).getSpan();\r\n\t\t\t\tresult = new MemberAccess(result, identifier);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn result;\r\n\t}\r\n\r\n\t/** Does not consume the closing parentheses. **/\r\n\tprivate static List<Expression> parseArguments (TokenStream stream) {\r\n\t\tstream.expect(TokenType.LeftParantheses);\r\n\t\tList<Expression> arguments = new ArrayList<Expression>();\r\n\t\twhile (stream.hasMore() && !stream.match(TokenType.RightParantheses, false)) {\r\n\t\t\targuments.add(parseExpression(stream));\r\n\t\t\tif (!stream.match(TokenType.RightParantheses, false)) stream.expect(TokenType.Comma);\r\n\t\t}\r\n\t\treturn arguments;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/expression/parsing/Span.java",
    "content": "\npackage org.spiderflow.core.expression.parsing;\n\n/** A span within a source string denoted by start and end index, with the latter being exclusive. */\npublic class Span {\n\t/** the source string this span refers to **/\n\tprivate final String source;\n\n\t/** start index in source string, starting at 0 **/\n\tprivate int start;\n\n\t/** end index in source string, exclusive, starting at 0 **/\n\tprivate int end;\n\n\t/** Cached String instance to reduce pressure on GC **/\n\tprivate final String cachedText;\n\n\tpublic Span (String source) {\n\t\tthis(source, 0, source.length());\n\t}\n\n\tpublic Span (String source, int start, int end) {\n\t\tif (start > end) throw new IllegalArgumentException(\"Start must be <= end.\");\n\t\tif (start < 0) throw new IndexOutOfBoundsException(\"Start must be >= 0.\");\n\t\tif (start > source.length() - 1) \n\t\t\tthrow new IndexOutOfBoundsException(\"Start outside of string.\");\n\t\tif (end >source.length()) throw new IndexOutOfBoundsException(\"End outside of string.\");\n\n\t\tthis.source = source;\n\t\tthis.start = start;\n\t\tthis.end = end;\n\t\tthis.cachedText = source.substring(start, end);\n\t}\n\n\tpublic Span (Span start, Span end) {\n\t\tif (!start.source.equals(end.source)) throw new IllegalArgumentException(\"The two spans do not reference the same source.\");\n\t\tif (start.start > end.end) throw new IllegalArgumentException(\"Start must be <= end.\");\n\t\tif (start.start < 0) throw new IndexOutOfBoundsException(\"Start must be >= 0.\");\n\t\tif (start.start > start.source.length() - 1) throw new IndexOutOfBoundsException(\"Start outside of string.\");\n\t\tif (end.end > start.source.length()) throw new IndexOutOfBoundsException(\"End outside of string.\");\n\n\t\tthis.source = start.source;\n\t\tthis.start = start.start;\n\t\tthis.end = end.end;\n\t\tthis.cachedText = source.substring(this.start, this.end);\n\t}\n\n\t/** Returns the text referenced by this span **/\n\tpublic String getText () {\n\t\treturn cachedText;\n\t}\n\n\t/** Returns the index of the first character of this span. **/\n\tpublic int getStart () {\n\t\treturn start;\n\t}\n\n\t/** Returns the index of the last character of this span plus 1. **/\n\tpublic int getEnd () {\n\t\treturn end;\n\t}\n\n\t/** Returns the source string this span references. **/\n\tpublic String getSource () {\n\t\treturn source;\n\t}\n\n\t@Override\n\tpublic String toString () {\n\t\treturn \"Span [text=\" + getText() + \", start=\" + start + \", end=\" + end + \"]\";\n\t}\n\n\t/** Returns the line this span is on. Does not return a correct result for spans across multiple lines. **/\n\tpublic Line getLine () {\n\t\tint lineStart = start;\n\t\twhile (true) {\n\t\t\tif (lineStart < 0) break;\n\t\t\tchar c = source.charAt(lineStart);\n\t\t\tif (c == '\\n') {\n\t\t\t\tlineStart = lineStart + 1;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tlineStart--;\n\t\t}\n\t\tif (lineStart < 0) lineStart = 0;\n\n\t\tint lineEnd = end;\n\t\twhile (true) {\n\t\t\tif (lineEnd > source.length() - 1) break;\n\t\t\tchar c = source.charAt(lineEnd);\n\t\t\tif (c == '\\n') {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tlineEnd++;\n\t\t}\n\n\t\tint lineNumber = 0;\n\t\tint idx = lineStart;\n\t\twhile (idx > 0) {\n\t\t\tchar c = source.charAt(idx);\n\t\t\tif (c == '\\n') {\n\t\t\t\tlineNumber++;\n\t\t\t}\n\t\t\tidx--;\n\t\t}\n\t\tlineNumber++;\n\n\t\treturn new Line(source, lineStart, lineEnd, lineNumber);\n\t}\n\n\t/** A line within a Source **/\n\tpublic static class Line {\n\t\tprivate final String source;\n\t\tprivate final int start;\n\t\tprivate final int end;\n\t\tprivate final int lineNumber;\n\n\t\tpublic Line (String source, int start, int end, int lineNumber) {\n\t\t\tthis.source = source;\n\t\t\tthis.start = start;\n\t\t\tthis.end = end;\n\t\t\tthis.lineNumber = lineNumber;\n\t\t}\n\n\t\tpublic String getSource () {\n\t\t\treturn source;\n\t\t}\n\n\t\tpublic int getStart () {\n\t\t\treturn start;\n\t\t}\n\n\t\tpublic int getEnd () {\n\t\t\treturn end;\n\t\t}\n\n\t\tpublic int getLineNumber () {\n\t\t\treturn lineNumber;\n\t\t}\n\n\t\tpublic String getText () {\n\t\t\treturn source.substring(start, end);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/expression/parsing/Token.java",
    "content": "\npackage org.spiderflow.core.expression.parsing;\n\n/** A token produced by the {@link Tokenizer}. */\npublic class Token {\n\tprivate final TokenType type;\n\tprivate final Span span;\n\n\tpublic Token (TokenType type, Span span) {\n\t\tthis.type = type;\n\t\tthis.span = span;\n\t}\n\n\tpublic TokenType getType () {\n\t\treturn type;\n\t}\n\n\tpublic Span getSpan () {\n\t\treturn span;\n\t}\n\n\tpublic String getText () {\n\t\treturn span.getText();\n\t}\n\n\t@Override\n\tpublic String toString () {\n\t\treturn \"Token [type=\" + type + \", span=\" + span + \"]\";\n\t}\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/expression/parsing/TokenStream.java",
    "content": "\npackage org.spiderflow.core.expression.parsing;\n\nimport java.util.List;\n\nimport javax.xml.transform.Source;\n\nimport org.spiderflow.core.expression.ExpressionError;\n\n\n/** Iterates over a list of {@link Token} instances, provides methods to match expected tokens and throw errors in case of a\n * mismatch. */\npublic class TokenStream {\n\tprivate final List<Token> tokens;\n\tprivate int index;\n\tprivate final int end;\n\n\tpublic TokenStream (List<Token> tokens) {\n\t\tthis.tokens = tokens;\n\t\tthis.index = 0;\n\t\tthis.end = tokens.size();\n\t}\n\n\t/** Returns whether there are more tokens in the stream. **/\n\tpublic boolean hasMore () {\n\t\treturn index < end;\n\t}\n\t\n\tpublic boolean hasNext(){\n\t\treturn index + 1 < end;\n\t}\n\t\n\tpublic boolean hasPrev(){\n\t\treturn index > 0;\n\t}\n\n\t/** Consumes the next token and returns it. **/\n\tpublic Token consume () {\n\t\tif (!hasMore()) throw new RuntimeException(\"Reached the end of the source.\");\n\t\treturn tokens.get(index++);\n\t}\n\t\n\tpublic Token next(){\n\t\tif (!hasMore()) throw new RuntimeException(\"Reached the end of the source.\");\n\t\treturn tokens.get(++index);\n\t}\n\t\n\tpublic Token prev(){\n\t\tif(index == 0){\n\t\t\tthrow new RuntimeException(\"Reached the end of the source.\");\n\t\t}\n\t\treturn tokens.get(--index);\n\t}\n\n\t/** Checks if the next token has the give type and optionally consumes, or throws an error if the next token did not match the\n\t * type. */\n\tpublic Token expect (TokenType type) {\n\t\tboolean result = match(type, true);\n\t\tif (!result) {\n\t\t\tToken token = index < tokens.size() ? tokens.get(index) : null;\n\t\t\tSpan span = token != null ? token.getSpan() : null;\n\t\t\tif (span == null)\n\t\t\t\tExpressionError.error(\"Expected '\" + type.getError() + \"', but reached the end of the source.\", this);\n\t\t\telse\n\t\t\t\tExpressionError.error(\"Expected '\" + type.getError() + \"', but got '\" + token.getText() + \"'\", span);\n\t\t\treturn null; // never reached\n\t\t} else {\n\t\t\treturn tokens.get(index - 1);\n\t\t}\n\t}\n\n\t/** Checks if the next token matches the given text and optionally consumes, or throws an error if the next token did not match\n\t * the text. */\n\tpublic Token expect (String text) {\n\t\tboolean result = match(text, true);\n\t\tif (!result) {\n\t\t\tToken token = index < tokens.size() ? tokens.get(index) : null;\n\t\t\tSpan span = token != null ? token.getSpan() : null;\n\t\t\tif (span == null)\n\t\t\t\tExpressionError.error(\"Expected '\" + text + \"', but reached the end of the source.\", this);\n\t\t\telse\n\t\t\t\tExpressionError.error(\"Expected '\" + text + \"', but got '\" + token.getText() + \"'\", span);\n\t\t\treturn null; // never reached\n\t\t} else {\n\t\t\treturn tokens.get(index - 1);\n\t\t}\n\t}\n\n\t/** Matches and optionally consumes the next token in case of a match. Returns whether the token matched. */\n\tpublic boolean match (TokenType type, boolean consume) {\n\t\tif (index >= end) return false;\n\t\tif (tokens.get(index).getType() == type) {\n\t\t\tif (consume) index++;\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/** Matches and optionally consumes the next token in case of a match. Returns whether the token matched. */\n\tpublic boolean match (String text, boolean consume) {\n\t\tif (index >= end) return false;\n\t\tif (tokens.get(index).getText().equals(text)) {\n\t\t\tif (consume) index++;\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/** Matches any of the token types and optionally consumes the next token in case of a match. Returns whether the token\n\t * matched. */\n\tpublic boolean match (boolean consume, TokenType... types) {\n\t\tfor (TokenType type : types) {\n\t\t\tif (match(type, consume)) return true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/** Matches any of the token texts and optionally consumes the next token in case of a match. Returns whether the token\n\t * matched. */\n\tpublic boolean match (boolean consume, String... tokenTexts) {\n\t\tfor (String text : tokenTexts) {\n\t\t\tif (match(text, consume)) return true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/** Returns the {@link Source} this stream wraps. */\n\tpublic String getSource () {\n\t\tif (tokens.size() == 0) return null;\n\t\treturn tokens.get(0).getSpan().getSource();\n\t}\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/expression/parsing/TokenType.java",
    "content": "\npackage org.spiderflow.core.expression.parsing;\n\nimport java.util.Arrays;\nimport java.util.Comparator;\n\n/** Enumeration of token types. A token type consists of a representation for error messages, and may optionally specify a literal\n * to be used by the {@link CharacterStream} to recognize the token. Token types are sorted by their literal length to easy\n * matching of token types with common prefixes, e.g. \"<\" and \"<=\". Token types with longer literals are matched first. */\npublic enum TokenType {\n\t// @off\n\tTextBlock(\"a text block\"),\n\tPeriod(\".\", \".\"),\n\tComma(\",\", \",\"),\n\tSemicolon(\";\", \";\"),\n\tColon(\":\", \":\"),\n\tPlus(\"+\", \"+\"),\n\tMinus(\"-\", \"-\"),\n\tAsterisk(\"*\", \"*\"),\n\tForwardSlash(\"/\", \"/\"),\n\tPostSlash(\"\\\\\", \"\\\\\"),\n\tPercentage(\"%\", \"%\"),\n\tLeftParantheses(\"(\", \")\"),\n\tRightParantheses(\")\", \")\"),\n\tLeftBracket(\"[\", \"[\"),\n\tRightBracket(\"]\", \"]\"),\n\tLeftCurly(\"{\", \"{\"),\n\tRightCurly(\"}\"), // special treatment!\n\tLess(\"<\", \"<\"),\n\tGreater(\">\", \">\"),\n\tLessEqual(\"<=\", \"<=\"),\n\tGreaterEqual(\">=\", \">=\"),\n\tEqual(\"==\", \"==\"),\n\tNotEqual(\"!=\", \"!=\"),\n\tAssignment(\"=\", \"=\"),\n\tAnd(\"&&\", \"&&\"),\n\tOr(\"||\", \"||\"),\n\tXor(\"^\", \"^\"),\n\tNot(\"!\", \"!\"),\n\tQuestionmark(\"?\", \"?\"),\n\tDoubleQuote(\"\\\"\", \"\\\"\"),\n\tSingleQuote(\"'\", \"'\"),\n\tBooleanLiteral(\"true or false\"),\n\tDoubleLiteral(\"a double floating point number\"),\n\tFloatLiteral(\"a floating point number\"),\n\tLongLiteral(\"a long integer number\"),\n\tIntegerLiteral(\"an integer number\"),\n\tShortLiteral(\"a short integer number\"),\n\tByteLiteral(\"a byte integer number\"),\n\tCharacterLiteral(\"a character\"),\n\tStringLiteral(\"a string\"),\n\tNullLiteral(\"null\"),\n\tIdentifier(\"an identifier\");\n\t// @on\n\n\tprivate static TokenType[] values;\n\n\tstatic {\n\t\t// Sort the token types by their literal length. The character stream uses this\n\t\t// this order to match tokens with the longest length first.\n\t\tvalues = TokenType.values();\n\t\tArrays.sort(values, new Comparator<TokenType>() {\n\t\t\t@Override\n\t\t\tpublic int compare (TokenType o1, TokenType o2) {\n\t\t\t\tif (o1.literal == null && o2.literal == null) return 0;\n\t\t\t\tif (o1.literal == null && o2.literal != null) return 1;\n\t\t\t\tif (o1.literal != null && o2.literal == null) return -1;\n\t\t\t\treturn o2.literal.length() - o1.literal.length();\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate final String literal;\n\tprivate final String error;\n\n\tTokenType (String error) {\n\t\tthis.literal = null;\n\t\tthis.error = error;\n\t}\n\n\tTokenType (String literal, String error) {\n\t\tthis.literal = literal;\n\t\tthis.error = error;\n\t}\n\n\t/** The literal to match, may be null. **/\n\tpublic String getLiteral () {\n\t\treturn literal;\n\t}\n\n\t/** The error string to use when reporting this token type in an error message. **/\n\tpublic String getError () {\n\t\treturn error;\n\t}\n\n\t/** Returns an array of token types, sorted in descending order based on their literal length. This is used by the\n\t * {@link CharacterStream} to match token types with the longest literal first. E.g. \"<=\" will be matched before \"<\". **/\n\tpublic static TokenType[] getSortedValues () {\n\t\treturn values;\n\t}\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/expression/parsing/Tokenizer.java",
    "content": "\npackage org.spiderflow.core.expression.parsing;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.spiderflow.core.expression.ExpressionError;\nimport org.spiderflow.core.expression.ExpressionError.StringLiteralException;\nimport org.spiderflow.core.expression.ExpressionError.TemplateException;\n\n\npublic class Tokenizer {\n\n\t/** Tokenizes the source into tokens with a {@link TokenType}. Text blocks not enclosed in {{ }} are returned as a single token\n\t * of type {@link TokenType.TextBlock}. {{ and }} are not returned as individual tokens. See {@link TokenType} for the list of\n\t * tokens this tokenizer understands. */\n\tpublic List<Token> tokenize (String source) {\n\t\tList<Token> tokens = new ArrayList<Token>();\n\t\tif (source.length() == 0) return tokens;\n\t\tCharacterStream stream = new CharacterStream(source);\n\t\tstream.startSpan();\n\n\t\tRuntimeException re = null;\n\t\twhile (stream.hasMore()) {\n\t\t\tif (stream.match(\"${\", false)) {\n\t\t\t\tif (!stream.isSpanEmpty()) tokens.add(new Token(TokenType.TextBlock, stream.endSpan()));\n\t\t\t\tstream.startSpan();\n\t\t\t\tboolean isContinue = false;\n\t\t\t\tdo{\n\t\t\t\t\twhile (!stream.match(\"}\", true)) {\n\t\t\t\t\t\tif (!stream.hasMore()) ExpressionError.error(\"Did not find closing }.\", stream.endSpan());\n\t\t\t\t\t\tstream.consume();\n\t\t\t\t\t}\n\t\t\t\t\ttry{\n\t\t\t\t\t\ttokens.addAll(tokenizeCodeSpan(stream.endSpan()));\n\t\t\t\t\t\tisContinue = false;\n\t\t\t\t\t\tre = null;\n\t\t\t\t\t}catch(TemplateException e){\n\t\t\t\t\t\tre = e;\n\t\t\t\t\t\tif(e.getCause() != null || stream.hasMore()){\n\t\t\t\t\t\t\tisContinue = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t}while(isContinue);\n\t\t\t\tif(re != null){\n\t\t\t\t\tthrow re;\n\t\t\t\t}\n\t\t\t\tstream.startSpan();\n\t\t\t} else {\n\t\t\t\tstream.consume();\n\t\t\t}\n\t\t}\n\t\tif (!stream.isSpanEmpty()) tokens.add(new Token(TokenType.TextBlock, stream.endSpan()));\n\t\treturn tokens;\n\t}\n\n\tprivate static List<Token> tokenizeCodeSpan (Span span) {\n\t\tString source = span.getSource();\n\t\tCharacterStream stream = new CharacterStream(source, span.getStart(), span.getEnd());\n\t\tList<Token> tokens = new ArrayList<Token>();\n\n\t\t// match opening tag and throw it away\n\t\tif (!stream.match(\"${\", true)) ExpressionError.error(\"Expected ${\", new Span(source, stream.getPosition(), stream.getPosition() + 1));\n\t\tint leftCount = 0;\n\t\tint rightCount = 0;\n\t\touter:\n\t\twhile (stream.hasMore()) {\n\t\t\t// skip whitespace\n\t\t\tstream.skipWhiteSpace();\n\n\t\t\t// Number literal, both integers and floats. Number literals may be suffixed by a type identifier.\n\t\t\tif (stream.matchDigit(false)) {\n\t\t\t\tTokenType type = TokenType.IntegerLiteral;\n\t\t\t\tstream.startSpan();\n\t\t\t\twhile (stream.matchDigit(true))\n\t\t\t\t\t;\n\t\t\t\tif (stream.match(TokenType.Period.getLiteral(), true)) {\n\t\t\t\t\ttype = TokenType.FloatLiteral;\n\t\t\t\t\twhile (stream.matchDigit(true))\n\t\t\t\t\t\t;\n\t\t\t\t}\n\t\t\t\tif (stream.match(\"b\", true) || stream.match(\"B\", true)) {\n\t\t\t\t\tif (type == TokenType.FloatLiteral) ExpressionError.error(\"Byte literal can not have a decimal point.\", stream.endSpan());\n\t\t\t\t\ttype = TokenType.ByteLiteral;\n\t\t\t\t} else if (stream.match(\"s\", true) || stream.match(\"S\", true)) {\n\t\t\t\t\tif (type == TokenType.FloatLiteral) ExpressionError.error(\"Short literal can not have a decimal point.\", stream.endSpan());\n\t\t\t\t\ttype = TokenType.ShortLiteral;\n\t\t\t\t} else if (stream.match(\"l\", true) || stream.match(\"L\", true)) {\n\t\t\t\t\tif (type == TokenType.FloatLiteral) ExpressionError.error(\"Long literal can not have a decimal point.\", stream.endSpan());\n\t\t\t\t\ttype = TokenType.LongLiteral;\n\t\t\t\t} else if (stream.match(\"f\", true) || stream.match(\"F\", true)) {\n\t\t\t\t\ttype = TokenType.FloatLiteral;\n\t\t\t\t} else if (stream.match(\"d\", true) || stream.match(\"D\", true)) {\n\t\t\t\t\ttype = TokenType.DoubleLiteral;\n\t\t\t\t}\n\t\t\t\tSpan numberSpan = stream.endSpan();\n\t\t\t\ttokens.add(new Token(type, numberSpan));\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// String literal\n\t\t\tif (stream.match(TokenType.SingleQuote.getLiteral(), true)) {\n\t\t\t\tstream.startSpan();\n\t\t\t\tboolean matchedEndQuote = false;\n\t\t\t\twhile (stream.hasMore()) {\n\t\t\t\t\t// Note: escape sequences like \\n are parsed in StringLiteral\n\t\t\t\t\tif (stream.match(\"\\\\\", true)) {\n\t\t\t\t\t\tstream.consume();\n\t\t\t\t\t}\n\t\t\t\t\tif (stream.match(TokenType.SingleQuote.getLiteral(), true)) {\n\t\t\t\t\t\tmatchedEndQuote = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tstream.consume();\n\t\t\t\t}\n\t\t\t\tif (!matchedEndQuote) ExpressionError.error(\"字符串没有结束符\\'\", stream.endSpan(),new StringLiteralException());\n\t\t\t\tSpan stringSpan = stream.endSpan();\n\t\t\t\tstringSpan = new Span(stringSpan.getSource(), stringSpan.getStart() - 1, stringSpan.getEnd());\n\t\t\t\ttokens.add(new Token(TokenType.StringLiteral, stringSpan));\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// String literal\n\t\t\tif (stream.match(TokenType.DoubleQuote.getLiteral(), true)) {\n\t\t\t\tstream.startSpan();\n\t\t\t\tboolean matchedEndQuote = false;\n\t\t\t\twhile (stream.hasMore()) {\n\t\t\t\t\t// Note: escape sequences like \\n are parsed in StringLiteral\n\t\t\t\t\tif (stream.match(\"\\\\\", true)) {\n\t\t\t\t\t\tstream.consume();\n\t\t\t\t\t}\n\t\t\t\t\tif (stream.match(TokenType.DoubleQuote.getLiteral(), true)) {\n\t\t\t\t\t\tmatchedEndQuote = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tstream.consume();\n\t\t\t\t}\n\t\t\t\tif (!matchedEndQuote) ExpressionError.error(\"字符串没有结束符\\\"\", stream.endSpan(),new StringLiteralException());\n\t\t\t\tSpan stringSpan = stream.endSpan();\n\t\t\t\tstringSpan = new Span(stringSpan.getSource(), stringSpan.getStart() - 1, stringSpan.getEnd());\n\t\t\t\ttokens.add(new Token(TokenType.StringLiteral, stringSpan));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t\n\t\t\t// Identifier, keyword, boolean literal, or null literal\n\t\t\tif (stream.matchIdentifierStart(true)) {\n\t\t\t\tstream.startSpan();\n\t\t\t\twhile (stream.matchIdentifierPart(true))\n\t\t\t\t\t;\n\t\t\t\tSpan identifierSpan = stream.endSpan();\n\t\t\t\tidentifierSpan = new Span(identifierSpan.getSource(), identifierSpan.getStart() - 1, identifierSpan.getEnd());\n\n\t\t\t\tif (identifierSpan.getText().equals(\"true\") || identifierSpan.getText().equals(\"false\")) {\n\t\t\t\t\ttokens.add(new Token(TokenType.BooleanLiteral, identifierSpan));\n\t\t\t\t} else if (identifierSpan.getText().equals(\"null\")) {\n\t\t\t\t\ttokens.add(new Token(TokenType.NullLiteral, identifierSpan));\n\t\t\t\t} else {\n\t\t\t\t\ttokens.add(new Token(TokenType.Identifier, identifierSpan));\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Simple tokens\n\t\t\tfor (TokenType t : TokenType.getSortedValues()) {\n\t\t\t\tif (t.getLiteral() != null) {\n\t\t\t\t\tif (stream.match(t.getLiteral(), true)) {\n\t\t\t\t\t\tif(t == TokenType.LeftCurly){\n\t\t\t\t\t\t\tleftCount ++;\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttokens.add(new Token(t, new Span(source, stream.getPosition() - t.getLiteral().length(), stream.getPosition())));\n\t\t\t\t\t\tcontinue outer;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif(leftCount!=rightCount&&stream.match(\"}\", true)){\n\t\t\t\trightCount++;\n\t\t\t\ttokens.add(new Token(TokenType.RightCurly, new Span(source, stream.getPosition() - 1, stream.getPosition())));\n\t\t\t\tcontinue outer;\n\t\t\t}\n\t\t\t// match closing tag\n\t\t\tif (stream.match(\"}\", false)) break;\n\n\t\t\tExpressionError.error(\"Unknown token\", new Span(source, stream.getPosition(), stream.getPosition() + 1));\n\t\t}\n\n\t\t// code spans must end with }\n\t\tif (!stream.match(\"}\", true)) ExpressionError.error(\"Expected }\", new Span(source, stream.getPosition(), stream.getPosition() + 1));\n\t\treturn tokens;\n\t}\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/io/HttpRequest.java",
    "content": "package org.spiderflow.core.io;\r\n\r\nimport java.io.IOException;\r\nimport java.io.InputStream;\r\nimport java.util.Map;\r\n\r\nimport org.jsoup.Connection;\r\nimport org.jsoup.Connection.Method;\r\nimport org.jsoup.Connection.Response;\r\nimport org.jsoup.Jsoup;\r\n\r\n/**\r\n * 请求对象包装类\r\n * @author Administrator\r\n *\r\n */\r\npublic class HttpRequest {\r\n\t\r\n\tprivate Connection connection = null;\r\n\t\r\n\tpublic static HttpRequest create(){\r\n\t\treturn new HttpRequest();\r\n\t}\r\n\t\r\n\tpublic HttpRequest url(String url){\r\n\t\tthis.connection = Jsoup.connect(url);\r\n\t\tthis.connection.method(Method.GET);\r\n\t\tthis.connection.timeout(60000);\r\n\t\treturn this;\r\n\t}\r\n\t\r\n\tpublic HttpRequest headers(Map<String,String> headers){\r\n\t\tthis.connection.headers(headers);\r\n\t\treturn this;\r\n\t}\r\n\t\r\n\tpublic HttpRequest header(String key,String value){\r\n\t\tthis.connection.header(key, value);\r\n\t\treturn this;\r\n\t}\r\n\t\r\n\tpublic HttpRequest header(String key,Object value){\r\n\t\tif(value != null){\r\n\t\t\tthis.connection.header(key,value.toString());\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\r\n\tpublic HttpRequest cookies(Map<String,String> cookies){\r\n\t\tthis.connection.cookies(cookies);\r\n\t\treturn this;\r\n\t}\r\n\r\n\tpublic HttpRequest cookie(String name, String value) {\r\n\t\tif (value != null) {\r\n\t\t\tthis.connection.cookie(name, value);\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\t\r\n\tpublic HttpRequest contentType(String contentType){\r\n\t\tthis.connection.header(\"Content-Type\", contentType);\r\n\t\treturn this;\r\n\t}\r\n\t\r\n\tpublic HttpRequest data(String key,String value){\r\n\t\tthis.connection.data(key, value);\r\n\t\treturn this;\r\n\t}\r\n\t\r\n\tpublic HttpRequest data(String key,Object value){\r\n\t\tif(value != null){\r\n\t\t\tthis.connection.data(key, value.toString());\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\t\r\n\tpublic HttpRequest data(String key,String filename,InputStream is){\r\n\t\tthis.connection.data(key, filename, is);\r\n\t\treturn this;\r\n\t}\r\n\t\r\n\tpublic HttpRequest data(Object body){\r\n\t\tif(body != null){\r\n\t\t\tthis.connection.requestBody(body.toString());\t\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\t\r\n\tpublic HttpRequest data(Map<String,String> data){\r\n\t\tthis.connection.data(data);\r\n\t\treturn this;\r\n\t}\r\n\t\r\n\tpublic HttpRequest method(String method){\r\n\t\tthis.connection.method(Method.valueOf(method));\r\n\t\treturn this;\r\n\t}\r\n\t\r\n\tpublic HttpRequest followRedirect(boolean followRedirects){\r\n\t\tthis.connection.followRedirects(followRedirects);\r\n\t\treturn this;\r\n\t}\r\n\t\r\n\tpublic HttpRequest timeout(int timeout){\r\n\t\tthis.connection.timeout(timeout);\r\n\t\treturn this;\r\n\t}\r\n\t\r\n\tpublic HttpRequest proxy(String host,int port){\r\n\t\tthis.connection.proxy(host, port);\r\n\t\treturn this;\r\n\t}\r\n\t\r\n\t@SuppressWarnings(\"deprecation\")\r\n\tpublic HttpRequest validateTLSCertificates(boolean value){\r\n\t\tthis.connection.validateTLSCertificates(value);\r\n\t\treturn this;\r\n\t}\r\n\t\r\n\tpublic HttpResponse execute() throws IOException{\r\n\t\tthis.connection.ignoreContentType(true);\r\n\t\tthis.connection.ignoreHttpErrors(true);\r\n\t\tthis.connection.maxBodySize(0);\r\n\r\n\t\tResponse response = connection.execute();\r\n\t\treturn new HttpResponse(response);\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/io/HttpResponse.java",
    "content": "package org.spiderflow.core.io;\n\nimport com.alibaba.fastjson.JSON;\nimport org.jsoup.Connection.Response;\nimport org.jsoup.Jsoup;\nimport org.spiderflow.io.SpiderResponse;\n\nimport java.io.InputStream;\nimport java.util.Map;\n\n/**\n * 响应对象包装类\n * @author Administrator\n *\n */\npublic class HttpResponse implements SpiderResponse{\n\n\tprivate Response response;\n\n\tprivate int statusCode;\n\n\tprivate String urlLink;\n\n\tprivate String htmlValue;\n\n\tprivate String titleName;\n\n\tprivate Object jsonValue;\n\n\tpublic HttpResponse(Response response){\n\t\tsuper();\n\t\tthis.response = response;\n\t\tthis.statusCode = response.statusCode();\n\t\tthis.urlLink = response.url().toExternalForm();\n\t}\n\t\n\t@Override\n\tpublic int getStatusCode(){\n\t\treturn statusCode;\n\t}\n\n\t@Override\n\tpublic String getTitle() {\n\t\tif (titleName == null) {\n\t\t\tsynchronized (this){\n\t\t\t\ttitleName = Jsoup.parse(getHtml()).title();\n\t\t\t}\n\t\t}\n\t\treturn titleName;\n\t}\n\n\t@Override\n\tpublic String getHtml(){\n\t\tif(htmlValue == null){\n\t\t\tsynchronized (this){\n\t\t\t\thtmlValue = response.body();\n\t\t\t}\n\t\t}\n\t\treturn htmlValue;\n\t}\n\t\n\t@Override\n\tpublic Object getJson(){\n\t\tif(jsonValue == null){\n\t\t\tjsonValue = JSON.parse(getHtml());\n\t\t}\n\t\treturn jsonValue;\n\t}\n\t\n\t@Override\n\tpublic Map<String,String> getCookies(){\n\t\treturn response.cookies();\n\t}\n\t\n\t@Override\n\tpublic Map<String,String> getHeaders(){\n\t\treturn response.headers();\n\t}\n\t\n\t@Override\n\tpublic byte[] getBytes(){\n\t\treturn response.bodyAsBytes();\n\t}\n\t\n\t@Override\n\tpublic String getContentType(){\n\t\treturn response.contentType();\n\t}\n\n\t@Override\n\tpublic void setCharset(String charset) {\n\t\tthis.response.charset(charset);\n\t}\n\n\t@Override\n\tpublic String getUrl() {\n\t\treturn urlLink;\n\t}\n\n\t@Override\n\tpublic InputStream getStream() {\n\t\treturn response.bodyStream();\n\t}\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/job/SpiderJob.java",
    "content": "package org.spiderflow.core.job;\r\n\r\nimport org.apache.commons.lang3.time.DateFormatUtils;\r\nimport org.quartz.JobDataMap;\r\nimport org.quartz.JobExecutionContext;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.spiderflow.context.SpiderContext;\r\nimport org.spiderflow.context.SpiderContextHolder;\r\nimport org.spiderflow.core.Spider;\r\nimport org.spiderflow.core.model.SpiderFlow;\r\nimport org.spiderflow.core.model.Task;\r\nimport org.spiderflow.core.service.SpiderFlowService;\r\nimport org.spiderflow.core.service.TaskService;\r\nimport org.springframework.beans.factory.annotation.Autowired;\r\nimport org.springframework.beans.factory.annotation.Value;\r\nimport org.springframework.scheduling.quartz.QuartzJobBean;\r\nimport org.springframework.stereotype.Component;\r\n\r\nimport java.util.Date;\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\n\r\n/**\r\n * 爬虫定时执行\r\n *\r\n * @author Administrator\r\n */\r\n@Component\r\npublic class SpiderJob extends QuartzJobBean {\r\n\r\n\t@Autowired\r\n\tprivate Spider spider;\r\n\r\n\t@Autowired\r\n\tprivate SpiderFlowService spiderFlowService;\r\n\r\n\t@Autowired\r\n\tprivate TaskService taskService;\r\n\r\n\tprivate static Map<Integer, SpiderContext> contextMap = new HashMap<>();\r\n\r\n\t@Value(\"${spider.job.enable:true}\")\r\n\tprivate boolean spiderJobEnable;\r\n\r\n\t@Value(\"${spider.workspace}\")\r\n\tprivate String workspace;\r\n\r\n\tprivate static Logger logger = LoggerFactory.getLogger(SpiderJob.class);\r\n\r\n\t@Override\r\n\tprotected void executeInternal(JobExecutionContext context) {\r\n\t\tif (!spiderJobEnable) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tJobDataMap dataMap = context.getMergedJobDataMap();\r\n\t\tSpiderFlow spiderFlow = (SpiderFlow) dataMap.get(SpiderJobManager.JOB_PARAM_NAME);\r\n\t\tif(\"1\".equalsIgnoreCase(spiderFlow.getEnabled())){\r\n\t\t\trun(spiderFlow, context.getNextFireTime());\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void run(String id) {\r\n\t\trun(spiderFlowService.getById(id), null);\r\n\t}\r\n\r\n\tpublic void run(SpiderFlow spiderFlow, Date nextExecuteTime) {\r\n\t\tTask task = new Task();\r\n\t\ttask.setFlowId(spiderFlow.getId());\r\n\t\ttask.setBeginTime(new Date());\r\n\t\ttaskService.save(task);\r\n\t\trun(spiderFlow,task,nextExecuteTime);\r\n\t}\r\n\r\n\tpublic void run(SpiderFlow spiderFlow, Task task,Date nextExecuteTime) {\r\n\t\tSpiderJobContext context = null;\r\n\t\tDate now = new Date();\r\n\t\ttry {\r\n\t\t\tcontext = SpiderJobContext.create(this.workspace, spiderFlow.getId(),task.getId(),false);\r\n\t\t\tSpiderContextHolder.set(context);\r\n\t\t\tcontextMap.put(task.getId(), context);\r\n\t\t\tlogger.info(\"开始执行任务{}\", spiderFlow.getName());\r\n\t\t\tspider.run(spiderFlow, context);\r\n\t\t\tlogger.info(\"执行任务{}完毕，下次执行时间：{}\", spiderFlow.getName(), nextExecuteTime == null ? null : DateFormatUtils.format(nextExecuteTime, \"yyyy-MM-dd HH:mm:ss\"));\r\n\t\t} catch (Exception e) {\r\n\t\t\tlogger.error(\"执行任务{}出错\", spiderFlow.getName(), e);\r\n\t\t} finally {\r\n\t\t\tif (context != null) {\r\n\t\t\t\tcontext.close();\r\n\t\t\t}\r\n\t\t\ttask.setEndTime(new Date());\r\n\t\t\ttaskService.saveOrUpdate(task);\r\n\t\t\tcontextMap.remove(task.getId());\r\n\t\t\tSpiderContextHolder.remove();\r\n\t\t}\r\n\t\tspiderFlowService.executeCountIncrement(spiderFlow.getId(), now, nextExecuteTime);\r\n\t}\r\n\r\n\tpublic static SpiderContext getSpiderContext(Integer taskId) {\r\n\t\treturn contextMap.get(taskId);\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/job/SpiderJobContext.java",
    "content": "package org.spiderflow.core.job;\r\n\r\nimport java.io.File;\r\nimport java.io.FileOutputStream;\r\nimport java.io.OutputStream;\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.spiderflow.context.SpiderContext;\r\nimport org.spiderflow.model.SpiderOutput;\r\n\r\npublic class SpiderJobContext extends SpiderContext{\r\n\r\n\tprivate static final long serialVersionUID = 9099787449108938453L;\r\n\t\r\n\tprivate static Logger logger = LoggerFactory.getLogger(SpiderJobContext.class);\r\n\t\r\n\tprivate OutputStream outputstream;\r\n\r\n\tprivate List<SpiderOutput> outputs = new ArrayList<>();\r\n\r\n\tprivate boolean output;\r\n\r\n\tpublic SpiderJobContext(OutputStream outputstream,boolean output) {\r\n\t\tsuper();\r\n\t\tthis.outputstream = outputstream;\r\n\t\tthis.output = output;\r\n\t}\r\n\t\r\n\tpublic void close(){\r\n\t\ttry {\r\n\t\t\tthis.outputstream.close();\r\n\t\t} catch (Exception e) {\r\n\t\t}\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void addOutput(SpiderOutput output) {\r\n\t\tif(this.output){\r\n\t\t\tsynchronized (this.outputs){\r\n\t\t\t\tthis.outputs.add(output);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t@Override\r\n\tpublic List<SpiderOutput> getOutputs() {\r\n\t\treturn outputs;\r\n\t}\r\n\r\n\tpublic OutputStream getOutputstream(){\r\n\t\treturn this.outputstream;\r\n\t}\r\n\t\r\n\tpublic static SpiderJobContext create(String directory,String id,Integer taskId,boolean output){\r\n\t\tOutputStream os = null;\r\n\t\ttry {\r\n\t\t\tFile file = new File(new File(directory),id + File.separator + \"logs\" + File.separator + taskId + \".log\");\r\n\t\t\tFile dirFile = file.getParentFile();\r\n\t\t\tif(!dirFile.exists()){\r\n\t\t\t\tdirFile.mkdirs();\r\n\t\t\t}\r\n\t\t\tos = new FileOutputStream(file, true);\r\n\t\t} catch (Exception e) {\r\n\t\t\tlogger.error(\"创建日志文件出错\",e);\r\n\t\t}\r\n\t\tSpiderJobContext context = new SpiderJobContext(os, output);\r\n\t\tcontext.setFlowId(id);\r\n\t\treturn context;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/job/SpiderJobManager.java",
    "content": "package org.spiderflow.core.job;\r\n\r\nimport org.quartz.CronScheduleBuilder;\r\nimport org.quartz.CronTrigger;\r\nimport org.quartz.JobBuilder;\r\nimport org.quartz.JobDetail;\r\nimport org.quartz.JobKey;\r\nimport org.quartz.Scheduler;\r\nimport org.quartz.SchedulerException;\r\nimport org.quartz.TriggerBuilder;\r\nimport org.quartz.TriggerKey;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.spiderflow.core.Spider;\r\nimport org.spiderflow.core.model.SpiderFlow;\r\nimport org.springframework.beans.factory.annotation.Autowired;\r\nimport org.springframework.stereotype.Component;\r\n\r\nimport java.util.Date;\r\n\r\n/**\r\n * 爬虫定时执行管理\r\n * @author Administrator\r\n *\r\n */\r\n@Component\r\npublic class SpiderJobManager {\r\n\t\r\n\tprivate static Logger logger = LoggerFactory.getLogger(SpiderJobManager.class);\r\n\t\r\n\tprivate final static String JOB_NAME = \"SPIDER_TASK_\";\r\n\t\r\n\tpublic final static String JOB_PARAM_NAME = \"SPIDER_FLOW\";\r\n\t\r\n\t@Autowired\r\n\tprivate SpiderJob spiderJob;\r\n\t\r\n\t/**\r\n\t * 调度器\r\n\t */\r\n\t@Autowired\r\n\tprivate Scheduler scheduler;\r\n\t\r\n\tprivate JobKey getJobKey(String id){\r\n\t\treturn JobKey.jobKey(JOB_NAME + id);\r\n\t}\r\n\t\r\n\tprivate TriggerKey getTriggerKey(String id){\r\n\t\treturn TriggerKey.triggerKey(JOB_NAME + id);\r\n\t}\r\n\t\r\n\t/**\r\n\t * 新建定时任务\r\n\t * @param spiderFlow 爬虫流程图\r\n\t * @return boolean true/false\r\n\t */\r\n\tpublic Date addJob(SpiderFlow spiderFlow){\r\n\t\ttry {\r\n\t\t\tJobDetail job = JobBuilder.newJob(SpiderJob.class).withIdentity(getJobKey(spiderFlow.getId())).build();\r\n\t\t\tjob.getJobDataMap().put(JOB_PARAM_NAME, spiderFlow);\r\n\t\t\t\r\n\t\t\tCronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(spiderFlow.getCron()).withMisfireHandlingInstructionDoNothing();\r\n\t\t\t\r\n\t\t\tCronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(spiderFlow.getId())).withSchedule(cronScheduleBuilder).build();\r\n\t\t\t\r\n\t\t\treturn scheduler.scheduleJob(job,trigger);\r\n\t\t} catch (SchedulerException e) {\r\n\t\t\tlogger.error(\"创建定时任务出错\",e);\r\n\t\t\treturn null;\r\n\t\t}\r\n\t}\r\n\t\r\n\tpublic void run(String id){\r\n\t\tSpider.executorInstance.submit(()->{\r\n\t\t\tspiderJob.run(id);\r\n\t\t});\r\n\t}\r\n\t\r\n\tpublic boolean remove(String id){\r\n\t\ttry {\r\n\t\t\tscheduler.deleteJob(getJobKey(id));\r\n\t\t\treturn true;\r\n\t\t} catch (SchedulerException e) {\r\n\t\t\tlogger.error(\"删除定时任务失败\",e);\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/mapper/DataSourceMapper.java",
    "content": "package org.spiderflow.core.mapper;\n\nimport java.util.List;\n\nimport org.apache.ibatis.annotations.Select;\nimport org.spiderflow.core.model.DataSource;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\n\npublic interface DataSourceMapper extends BaseMapper<DataSource>{\n\t\n\t@Select(\"select id,name from sp_datasource\")\n\tList<DataSource> selectAll();\n\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/mapper/FlowNoticeMapper.java",
    "content": "package org.spiderflow.core.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.spiderflow.core.model.FlowNotice;\n\n@Mapper\npublic interface FlowNoticeMapper extends BaseMapper<FlowNotice> {\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/mapper/FunctionMapper.java",
    "content": "package org.spiderflow.core.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.spiderflow.core.model.Function;\n\n@Mapper\npublic interface FunctionMapper extends BaseMapper<Function> {\n\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/mapper/SpiderFlowMapper.java",
    "content": "package org.spiderflow.core.mapper;\n\nimport java.util.Date;\nimport java.util.List;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport org.apache.ibatis.annotations.Insert;\nimport org.apache.ibatis.annotations.Param;\nimport org.apache.ibatis.annotations.Select;\nimport org.apache.ibatis.annotations.Update;\nimport org.spiderflow.core.model.SpiderFlow;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\n\n/**\n * 爬虫资源库 实现爬虫的入库\n * @author Administrator\n *\n */\npublic interface SpiderFlowMapper extends BaseMapper<SpiderFlow>{\n\n\t@Select({\n\t\t\t\"<script>\",\n\t\t\t\t\"select\",\n\t\t\t\t\t\"id,name,enabled,last_execute_time,next_execute_time,cron,create_date,execute_count,\",\n\t\t\t\t\t\"(select count(*) from sp_task where flow_id = sf.id and end_time is null) running\",\n\t\t\t\t\"from sp_flow sf\",\n\t\t\t\t\"<if test=\\\"name != null and name != ''\\\">\",\n\t\t\t\t\t\"where name like concat('%',#{name},'%')\",\n\t\t\t\t\"</if>\",\n\t\t\t\t\"order by create_date desc\",\n\t\t\t\"</script>\"\n\t})\n\tIPage<SpiderFlow> selectSpiderPage(Page<SpiderFlow> page,@Param(\"name\") String name);\n\n\t@Insert(\"insert into sp_flow(id,name,xml,enabled) values(#{id},#{name},#{xml},'0')\")\n\tint insertSpiderFlow(@Param(\"id\") String id, @Param(\"name\") String name, @Param(\"xml\") String xml);\n\t\n\t@Update(\"update sp_flow set name = #{name},xml = #{xml} where id = #{id}\")\n\tint updateSpiderFlow(@Param(\"id\") String id, @Param(\"name\") String name, @Param(\"xml\") String xml);\n\t\n\t@Update(\"update sp_flow set execute_count = 0 where id = #{id}\")\n\tint resetExecuteCount(@Param(\"id\") String id);\n\t\n\t@Update(\"update sp_flow set execute_count = ifnull(execute_count,0) + 1,last_execute_time = #{lastExecuteTime},next_execute_time = #{nextExecuteTime} where id = #{id}\")\n\tint executeCountIncrementAndExecuteTime(@Param(\"id\") String id, @Param(\"lastExecuteTime\") Date lastExecuteTime, @Param(\"nextExecuteTime\") Date nextExecuteTime);\n\t\n\t@Update(\"update sp_flow set execute_count = ifnull(execute_count,0) + 1,last_execute_time = #{lastExecuteTime} where id = #{id}\")\n\tint executeCountIncrement(@Param(\"id\") String id, @Param(\"lastExecuteTime\") Date lastExecuteTime);\n\t\n\t@Update(\"update sp_flow set cron = #{cron},next_execute_time = #{nextExecuteTime} where id = #{id}\")\n\tint resetCornExpression(@Param(\"id\") String id, @Param(\"cron\") String cron, @Param(\"nextExecuteTime\") Date nextExecuteTime);\n\t\n\t@Update(\"update sp_flow set enabled = #{enabled} where id = #{id}\")\n\tint resetSpiderStatus(@Param(\"id\") String id, @Param(\"enabled\") String enabled);\n\n\t@Update(\"update sp_flow set next_execute_time = null where id = #{id}\")\n\tint resetNextExecuteTime(@Param(\"id\") String id);\n\n\t@Update(\"update sp_flow set next_execute_time = null\")\n\tint resetNextExecuteTime();\n\t\n\t@Select(\"select id,name from sp_flow\")\n\tList<SpiderFlow> selectFlows();\n\t\n\t@Select(\"select id,name from sp_flow where id != #{id}\")\n\tList<SpiderFlow> selectOtherFlows(@Param(\"id\") String id);\n\n\t@Select(\"select max(a.id) from `sp_task` a left join sp_flow b on a.flow_id = b.id where b.id = #{id}\")\n\tInteger getFlowMaxTaskId(@Param(\"id\")String id);\n\t\n\t@Select(\"select COUNT(id) from sp_flow where id = #{id}\")\n\tInteger getCountById(@Param(\"id\")String id);\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/mapper/TaskMapper.java",
    "content": "package org.spiderflow.core.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.spiderflow.core.model.Task;\n\n@Mapper\npublic interface TaskMapper extends BaseMapper<Task> {\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/mapper/VariableMapper.java",
    "content": "package org.spiderflow.core.mapper;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport org.spiderflow.core.model.Variable;\n\npublic interface VariableMapper extends BaseMapper<Variable> {\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/model/DataSource.java",
    "content": "package org.spiderflow.core.model;\n\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\n\nimport java.util.Date;\n\n@TableName(\"sp_datasource\")\npublic class DataSource {\n\n    @TableId(type = IdType.UUID)\n    private String id;\n\n    private String name;\n\n    private String driverClassName;\n\n    private String jdbcUrl;\n\n    private String username;\n\n    private String password;\n\n    private Date createDate;\n\n    public DataSource() {\n    }\n\n    public DataSource(String id, String name) {\n        this.id = id;\n        this.name = name;\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    public void setId(String id) {\n        this.id = id;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getDriverClassName() {\n        return driverClassName;\n    }\n\n    public void setDriverClassName(String driverClassName) {\n        this.driverClassName = driverClassName;\n    }\n\n    public String getJdbcUrl() {\n        return jdbcUrl;\n    }\n\n    public void setJdbcUrl(String jdbcUrl) {\n        this.jdbcUrl = jdbcUrl;\n    }\n\n    public String getUsername() {\n        return username;\n    }\n\n    public void setUsername(String username) {\n        this.username = username;\n    }\n\n    public String getPassword() {\n        return password;\n    }\n\n    public void setPassword(String password) {\n        this.password = password;\n    }\n\n    public Date getCreateDate() {\n        return createDate;\n    }\n\n    public void setCreateDate(Date createDate) {\n        this.createDate = createDate;\n    }\n\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/model/FlowNotice.java",
    "content": "package org.spiderflow.core.model;\n\nimport org.spiderflow.enums.FlowNoticeWay;\n\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\n\n/**\n * 爬虫任务通知实体\n * \n * @author BillDowney\n * @date 2020年4月3日 下午2:57:46\n */\n@TableName(\"sp_flow_notice\")\npublic class FlowNotice {\n\n\t@TableField(exist = false)\n\tprivate final String START_FLAG = \"1\";\n\n\t/**\n\t * 主键,对应{@link SpiderFlow}中的流程id\n\t */\n\t@TableId(type = IdType.UUID)\n\tprivate String id;\n\t/**\n\t * 收件人,多个收件人用\",\"隔开，每个收件人可添加单独通知标记,如不添加通知标记则使用默认配置通知方式\n\t * 例：sms:13012345678,email:12345678@qq.com,13012345670\n\t */\n\tprivate String recipients;\n\t/**\n\t * 通知方式{@link FlowNoticeWay}\n\t */\n\tprivate String noticeWay;\n\t/**\n\t * 流程开始通知:1:开启通知,0:关闭通知\n\t */\n\tprivate String startNotice;\n\t/**\n\t * 流程异常通知:1:开启通知,0:关闭通知\n\t */\n\tprivate String exceptionNotice;\n\t/**\n\t * 流程结束通知:1:开启通知,0:关闭通知\n\t */\n\tprivate String endNotice;\n\n\tpublic String getId() {\n\t\treturn id;\n\t}\n\n\tpublic void setId(String id) {\n\t\tthis.id = id;\n\t}\n\n\tpublic String getRecipients() {\n\t\treturn recipients;\n\t}\n\n\tpublic void setRecipients(String recipients) {\n\t\tthis.recipients = recipients;\n\t}\n\n\tpublic String getNoticeWay() {\n\t\treturn noticeWay;\n\t}\n\n\tpublic void setNoticeWay(String noticeWay) {\n\t\tthis.noticeWay = noticeWay;\n\t}\n\n\tpublic String getStartNotice() {\n\t\treturn startNotice;\n\t}\n\n\tpublic void setStartNotice(String startNotice) {\n\t\tthis.startNotice = startNotice;\n\t}\n\n\tpublic String getExceptionNotice() {\n\t\treturn exceptionNotice;\n\t}\n\n\tpublic void setExceptionNotice(String exceptionNotice) {\n\t\tthis.exceptionNotice = exceptionNotice;\n\t}\n\n\tpublic String getEndNotice() {\n\t\treturn endNotice;\n\t}\n\n\tpublic void setEndNotice(String endNotice) {\n\t\tthis.endNotice = endNotice;\n\t}\n\n\tpublic boolean judgeStartNotice() {\n\t\tif (START_FLAG.equals(this.startNotice)) {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tpublic boolean judgeExceptionNotice() {\n\t\tif (START_FLAG.equals(this.exceptionNotice)) {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tpublic boolean judgeEndNotice() {\n\t\tif (START_FLAG.equals(this.endNotice)) {\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/model/Function.java",
    "content": "package org.spiderflow.core.model;\n\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\n\nimport java.util.Date;\n\n@TableName(\"sp_function\")\npublic class Function {\n\n    @TableId(type = IdType.UUID)\n    private String id;\n\n    private String name;\n\n    private String parameter;\n\n    private String script;\n\n    private Date createDate;\n\n    public String getId() {\n        return id;\n    }\n\n    public void setId(String id) {\n        this.id = id;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getParameter() {\n        return parameter;\n    }\n\n    public void setParameter(String parameter) {\n        this.parameter = parameter;\n    }\n\n    public String getScript() {\n        return script;\n    }\n\n    public void setScript(String script) {\n        this.script = script;\n    }\n\n    public Date getCreateDate() {\n        return createDate;\n    }\n\n    public void setCreateDate(Date createDate) {\n        this.createDate = createDate;\n    }\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/model/SpiderFlow.java",
    "content": "package org.spiderflow.core.model;\n\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\n\nimport java.util.Date;\n\n/**\n * 爬虫持久化实体类\n */\n@TableName(\"sp_flow\")\npublic class SpiderFlow {\n\n    @TableId(type = IdType.UUID)\n    private String id;\n\n    /**\n     * 定时任务表达式\n     */\n    private String cron;\n\n    private String name;\n\n    /**\n     * xml流程图\n     */\n    private String xml;\n\n    private String enabled;\n\n    private Date createDate;\n\n    private Date lastExecuteTime;\n\n    private Date nextExecuteTime;\n\n    /**\n     * 定时执行的执行次数\n     */\n    private Integer executeCount;\n\n    @TableField(exist = false)\n    private Integer running;\n\n\n    public SpiderFlow() {\n    }\n\n    public SpiderFlow(String id, String name) {\n        this.id = id;\n        this.name = name;\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    public void setId(String id) {\n        this.id = id;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getXml() {\n        return xml;\n    }\n\n    public void setXml(String xml) {\n        this.xml = xml;\n    }\n\n    public String getCron() {\n        return cron;\n    }\n\n    public void setCron(String cron) {\n        this.cron = cron;\n    }\n\n    public String getEnabled() {\n        return enabled;\n    }\n\n    public void setEnabled(String enabled) {\n        this.enabled = enabled;\n    }\n\n    public Date getCreateDate() {\n        return createDate;\n    }\n\n    public void setCreateDate(Date createDate) {\n        this.createDate = createDate;\n    }\n\n    public Date getLastExecuteTime() {\n        return lastExecuteTime;\n    }\n\n    public void setLastExecuteTime(Date lastExecuteTime) {\n        this.lastExecuteTime = lastExecuteTime;\n    }\n\n    public Date getNextExecuteTime() {\n        return nextExecuteTime;\n    }\n\n    public void setNextExecuteTime(Date nextExecuteTime) {\n        this.nextExecuteTime = nextExecuteTime;\n    }\n\n    public Integer getExecuteCount() {\n        return executeCount;\n    }\n\n    public void setExecuteCount(Integer executeCount) {\n        this.executeCount = executeCount;\n    }\n\n    public Integer getRunning() {\n        return running;\n    }\n\n    public void setRunning(Integer running) {\n        this.running = running;\n    }\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/model/Task.java",
    "content": "package org.spiderflow.core.model;\n\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\n\nimport java.util.Date;\n\n@TableName(\"sp_task\")\npublic class Task {\n\n\t@TableId(type = IdType.AUTO)\n\tprivate Integer id;\n\n\tprivate String flowId;\n\n\tprivate Date beginTime;\n\n\tprivate Date endTime;\n\n\tpublic Integer getId() {\n\t\treturn id;\n\t}\n\n\tpublic void setId(Integer id) {\n\t\tthis.id = id;\n\t}\n\n\tpublic String getFlowId() {\n\t\treturn flowId;\n\t}\n\n\tpublic void setFlowId(String flowId) {\n\t\tthis.flowId = flowId;\n\t}\n\n\tpublic Date getBeginTime() {\n\t\treturn beginTime;\n\t}\n\n\tpublic void setBeginTime(Date beginTime) {\n\t\tthis.beginTime = beginTime;\n\t}\n\n\tpublic Date getEndTime() {\n\t\treturn endTime;\n\t}\n\n\tpublic void setEndTime(Date endTime) {\n\t\tthis.endTime = endTime;\n\t}\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/model/Variable.java",
    "content": "package org.spiderflow.core.model;\n\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\n\nimport java.util.Date;\n\n@TableName(\"sp_variable\")\npublic class Variable {\n\n\t@TableId(type = IdType.AUTO)\n\tprivate Integer id;\n\n\tprivate String name;\n\n\tprivate String value;\n\n\tprivate String description;\n\n\tprivate Date createDate;\n\n\tpublic Integer getId() {\n\t\treturn id;\n\t}\n\n\tpublic void setId(Integer id) {\n\t\tthis.id = id;\n\t}\n\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\tpublic void setName(String name) {\n\t\tthis.name = name;\n\t}\n\n\tpublic String getValue() {\n\t\treturn value;\n\t}\n\n\tpublic void setValue(String value) {\n\t\tthis.value = value;\n\t}\n\n\tpublic String getDescription() {\n\t\treturn description;\n\t}\n\n\tpublic void setDescription(String description) {\n\t\tthis.description = description;\n\t}\n\n\tpublic Date getCreateDate() {\n\t\treturn createDate;\n\t}\n\n\tpublic void setCreateDate(Date createDate) {\n\t\tthis.createDate = createDate;\n\t}\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/script/ScriptManager.java",
    "content": "package org.spiderflow.core.script;\n\nimport jdk.nashorn.api.scripting.ScriptObjectMirror;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.spiderflow.core.expression.ExpressionTemplate;\nimport org.spiderflow.core.expression.ExpressionTemplateContext;\n\nimport javax.script.Invocable;\nimport javax.script.ScriptEngine;\nimport javax.script.ScriptEngineManager;\nimport javax.script.ScriptException;\nimport java.util.Date;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.concurrent.locks.ReadWriteLock;\nimport java.util.concurrent.locks.ReentrantReadWriteLock;\n\npublic class ScriptManager {\n\n    private static Logger logger = LoggerFactory.getLogger(ScriptManager.class);\n\n    private static ScriptEngine scriptEngine;\n\n    private static Set<String> functions = new HashSet<>();\n\n    private static ReadWriteLock lock = new ReentrantReadWriteLock();\n\n    public static void setScriptEngine(ScriptEngine engine){\n        scriptEngine = engine;\n        StringBuffer script = new StringBuffer();\n        script.append(\"var ExpressionTemplate = Java.type('\")\n                .append(ExpressionTemplate.class.getName())\n                .append(\"');\")\n                .append(\"var ExpressionTemplateContext = Java.type('\")\n                .append(ExpressionTemplateContext.class.getName())\n                .append(\"');\")\n                .append(\"function _eval(expression) {\")\n                .append(\"return ExpressionTemplate.create(expression).render(ExpressionTemplateContext.get());\")\n                .append(\"}\");\n        try {\n            scriptEngine.eval(script.toString());\n        } catch (ScriptException e) {\n            logger.error(\"注册_eval函数失败\",e);\n        }\n    }\n\n    public static void clearFunctions(){\n        functions.clear();\n    }\n\n    public static ScriptEngine createEngine(){\n        return new ScriptEngineManager().getEngineByName(\"nashorn\");\n    }\n\n    public static void lock(){\n        lock.writeLock().lock();\n    }\n\n    public static void unlock(){\n        lock.writeLock().unlock();\n    }\n\n    public static void registerFunction(ScriptEngine engine,String functionName,String parameters,String script){\n        try {\n            engine.eval(concatScript(functionName,parameters,script));\n            functions.add(functionName);\n            logger.info(\"注册自定义函数{}成功\",functionName);\n        } catch (ScriptException e) {\n            logger.warn(\"注册自定义函数{}失败\",functionName,e);\n        }\n    }\n\n    private static String concatScript(String functionName,String parameters,String script){\n        StringBuffer scriptBuffer = new StringBuffer();\n        scriptBuffer.append(\"function \")\n                .append(functionName)\n                .append(\"(\")\n                .append(parameters == null ? \"\" : parameters)\n                .append(\"){\")\n                .append(script)\n                .append(\"}\");\n        return scriptBuffer.toString();\n    }\n\n    public static boolean containsFunction(String functionName){\n        try {\n            lock.readLock().lock();\n            return functions.contains(functionName);\n        } finally {\n            lock.readLock().unlock();\n        }\n    }\n\n    public static void validScript(String functionName,String parameters,String script) throws Exception {\n        new ScriptEngineManager().getEngineByName(\"nashorn\").eval(concatScript(functionName,parameters,script));\n    }\n\n    public static Object eval(ExpressionTemplateContext context, String functionName, Object ... args) throws ScriptException, NoSuchMethodException {\n        if(\"_eval\".equals(functionName)){\n            if(args == null || args.length != 1){\n                throw new ScriptException(\"_eval必须要有一个参数\");\n            }else{\n                return ExpressionTemplate.create(args[0].toString()).render(context);\n            }\n        }\n        if(scriptEngine == null){\n            throw new NoSuchMethodException(functionName);\n        }\n        try{\n            lock.readLock().lock();\n            return convertObject(((Invocable) scriptEngine).invokeFunction(functionName, args));\n        } finally{\n            lock.readLock().unlock();\n        }\n    }\n\n    private static Object convertObject(Object object){\n        if(object instanceof ScriptObjectMirror){\n            ScriptObjectMirror mirror = (ScriptObjectMirror) object;\n            if(mirror.isArray()){\n                int size = mirror.size();\n                Object[] array = new Object[size];\n                for (int i = 0; i < size; i++) {\n                    array[i] = convertObject(mirror.getSlot(i));\n                }\n                return array;\n            }else{\n                String className = mirror.getClassName();\n                if(\"Date\".equalsIgnoreCase(className)){\n                    return new Date(mirror.to(Long.class));\n                }\n                //其它类型待处理\n            }\n            \n        }\n        return object;\n    }\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/serializer/FastJsonSerializer.java",
    "content": "package org.spiderflow.core.serializer;\n\nimport com.alibaba.fastjson.serializer.JSONSerializer;\nimport com.alibaba.fastjson.serializer.ObjectSerializer;\nimport com.alibaba.fastjson.serializer.SerializeConfig;\nimport com.alibaba.fastjson.serializer.SerializerFeature;\n\nimport java.io.IOException;\nimport java.lang.reflect.Type;\nimport java.math.BigDecimal;\nimport java.math.BigInteger;\n\n/**\n * Created on 2019-12-23\n */\npublic class FastJsonSerializer implements ObjectSerializer {\n\n    public static SerializeConfig serializeConfig;\n\n    static {\n        serializeConfig = new SerializeConfig();\n        FastJsonSerializer serializer = new FastJsonSerializer();\n        serializeConfig.put(Long.TYPE, serializer);\n        serializeConfig.put(Long.class, serializer);\n        serializeConfig.put(BigDecimal.class, serializer);\n        serializeConfig.put(BigInteger.class, serializer);\n    }\n\n    @Override\n    public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {\n        if(object == null){\n            if(serializer.isEnabled(SerializerFeature.WriteNullNumberAsZero)){\n                serializer.out.write(\"0\");\n            }else{\n                serializer.out.writeNull();\n            }\n            return;\n        }\n        serializer.out.writeString(object.toString());\n    }\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/service/DataSourceService.java",
    "content": "package org.spiderflow.core.service;\r\n\r\nimport org.spiderflow.core.mapper.DataSourceMapper;\r\nimport org.spiderflow.core.model.DataSource;\r\nimport org.springframework.stereotype.Service;\r\n\r\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\r\n\r\n\r\n@Service\r\npublic class DataSourceService extends ServiceImpl<DataSourceMapper, DataSource> {\r\n\t\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/service/FlowNoticeService.java",
    "content": "package org.spiderflow.core.service;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.spiderflow.core.expression.ExpressionTemplate;\nimport org.spiderflow.core.expression.ExpressionTemplateContext;\nimport org.spiderflow.core.mapper.FlowNoticeMapper;\nimport org.spiderflow.core.mapper.SpiderFlowMapper;\nimport org.spiderflow.core.model.FlowNotice;\nimport org.spiderflow.core.model.SpiderFlow;\nimport org.spiderflow.core.utils.EmailUtils;\nimport org.spiderflow.core.utils.ExpressionUtils;\nimport org.spiderflow.enums.FlowNoticeType;\nimport org.spiderflow.enums.FlowNoticeWay;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.cglib.beans.BeanMap;\nimport org.springframework.stereotype.Service;\nimport org.springframework.util.StringUtils;\n\n@Service\npublic class FlowNoticeService extends ServiceImpl<FlowNoticeMapper, FlowNotice> {\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(FlowNoticeService.class);\n\t@Autowired\n\tprivate SpiderFlowMapper spiderFlowMapper;\n\t@Autowired\n\tprivate EmailUtils emailUtils;\n\t@Value(\"${spider.notice.subject:spider-flow流程通知}\")\n\tprivate String subject;\n\t@Value(\"${spider.notice.content.start}\")\n\tprivate String startContext;\n\t@Value(\"${spider.notice.content.end}\")\n\tprivate String endContext;\n\t@Value(\"${spider.notice.content.exception}\")\n\tprivate String exceptionContext;\n\n\t@Override\n\tpublic boolean saveOrUpdate(FlowNotice entity) {\n\t\tif (spiderFlowMapper.getCountById(entity.getId()) == 0) {\n\t\t\tthrow new RuntimeException(\"没有找到对应的流程\");\n\t\t}\n\t\treturn super.saveOrUpdate(entity);\n\t}\n\n\t/**\n\t * 发送对应的流程通知\n\t * \n\t * @param spiderFlow 流程信息\n\t * @param type       通知类型\n\t * @author BillDowney\n\t * @date 2020年4月4日 上午1:37:50\n\t */\n\tpublic void sendFlowNotice(SpiderFlow spiderFlow, FlowNoticeType type) {\n\t\tFlowNotice notice = baseMapper.selectById(spiderFlow.getId());\n\t\tif (notice != null && !StringUtils.isEmpty(notice.getRecipients())\n\t\t\t\t&& !StringUtils.isEmpty(notice.getNoticeWay())) {\n\t\t\tString content = null;\n\t\t\tString sendSubject = this.subject;\n\t\t\tswitch (type) {\n\t\t\tcase startNotice:\n\t\t\t\tif (notice.judgeStartNotice()) {\n\t\t\t\t\tcontent = startContext;\n\t\t\t\t\tsendSubject += \" - 流程开始执行\";\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase endNotice:\n\t\t\t\tif (notice.judgeEndNotice()) {\n\t\t\t\t\tcontent = endContext;\n\t\t\t\t\tsendSubject += \" - 流程执行完毕\";\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase exceptionNotice:\n\t\t\t\tif (notice.judgeExceptionNotice()) {\n\t\t\t\t\tcontent = exceptionContext;\n\t\t\t\t\tsendSubject += \" - 流程发生异常\";\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (StringUtils.isEmpty(content)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// 定义一个上下文变量\n\t\t\tMap<String, Object> variables = new HashMap<String, Object>();\n\t\t\t// 放入流程信息\n\t\t\tBeanMap beanMap = BeanMap.create(spiderFlow);\n\t\t\tfor (Object key : beanMap.keySet()) {\n\t\t\t\tvariables.put(key + \"\", beanMap.get(key));\n\t\t\t}\n\t\t\t// 放入当前时间\n\t\t\tvariables.put(\"currentDate\", this.getCurrentDate());\n\t\t\tcontent = ExpressionUtils.execute(content.replaceAll(\"[{]\", \"\\\\${\"), variables) + \"\";\n\t\t\t// 整理收件人\n\t\t\tString recipients = notice.getRecipients();\n\t\t\tfor (String recipient : recipients.split(\",\")) {\n\t\t\t\tString noticeWay = notice.getNoticeWay();\n\t\t\t\tString people = recipient;\n\t\t\t\t// 如果含有\":\"证明单独配置了发送方式\n\t\t\t\tif (recipient.contains(\":\")) {\n\t\t\t\t\tString[] strs = recipient.split(\":\");\n\t\t\t\t\tnoticeWay = strs[0];\n\t\t\t\t\tpeople = strs[1];\n\t\t\t\t}\n\t\t\t\tFlowNoticeWay way = FlowNoticeWay.email;\n\t\t\t\ttry {\n\t\t\t\t\tway = FlowNoticeWay.valueOf(noticeWay);\n\t\t\t\t} catch (Exception e) {\n\t\t\t\t\tlogger.error(e.getMessage(), e);\n\t\t\t\t}\n\t\t\t\tswitch (way) {\n\t\t\t\tcase email:\n\t\t\t\t\temailUtils.sendSimpleMail(sendSubject, content, people);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate String getCurrentDate() {\n\t\tSimpleDateFormat sdf = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss.SSS\");\n\t\treturn sdf.format(new Date());\n\t}\n\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/service/FunctionService.java",
    "content": "package org.spiderflow.core.service;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.apache.commons.lang3.exception.ExceptionUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.spiderflow.core.mapper.FunctionMapper;\nimport org.spiderflow.core.model.Function;\nimport org.spiderflow.core.script.ScriptManager;\nimport org.springframework.stereotype.Service;\n\nimport javax.annotation.PostConstruct;\nimport javax.script.ScriptEngine;\nimport java.io.Serializable;\n\n@Service\npublic class FunctionService extends ServiceImpl<FunctionMapper, Function> {\n\n    private static Logger logger = LoggerFactory.getLogger(FunctionService.class);\n\n    /**\n     * 初始化/重置自定义函数\n     */\n    @PostConstruct\n    private void init(){\n        try {\n            ScriptManager.lock();\n            ScriptManager.clearFunctions();\n            ScriptEngine engine = ScriptManager.createEngine();\n            super.list().forEach(function -> {\n                ScriptManager.registerFunction(engine,function.getName(),function.getParameter(),function.getScript());\n            });\n            ScriptManager.setScriptEngine(engine);\n        } finally {\n            ScriptManager.unlock();\n        }\n    }\n\n    public String saveFunction(Function entity) {\n        try {\n            ScriptManager.validScript(entity.getName(),entity.getParameter(),entity.getScript());\n            super.saveOrUpdate(entity);\n            init();\n            return null;\n        } catch (Exception e) {\n            logger.error(\"保存自定义函数出错\",e);\n            return ExceptionUtils.getStackTrace(e);\n        }\n    }\n\n    @Override\n    public boolean removeById(Serializable id) {\n        boolean ret =  super.removeById(id);\n        init();\n        return ret;\n    }\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/service/SpiderFlowService.java",
    "content": "package org.spiderflow.core.service;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.quartz.CronScheduleBuilder;\nimport org.quartz.CronTrigger;\nimport org.quartz.TriggerBuilder;\nimport org.quartz.TriggerUtils;\nimport org.quartz.spi.OperableTrigger;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.spiderflow.core.job.SpiderJobManager;\nimport org.spiderflow.core.mapper.FlowNoticeMapper;\nimport org.spiderflow.core.mapper.SpiderFlowMapper;\nimport org.spiderflow.core.model.SpiderFlow;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\n\nimport javax.annotation.PostConstruct;\nimport java.io.File;\nimport java.io.IOException;\nimport java.text.SimpleDateFormat;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * 爬虫流程执行服务\n * @author Administrator\n *\n */\n@Service\npublic class SpiderFlowService extends ServiceImpl<SpiderFlowMapper, SpiderFlow> {\n\n\t@Autowired\n\tprivate SpiderFlowMapper sfMapper;\n\n\t@Autowired\n\tprivate SpiderJobManager spiderJobManager;\n\n\t@Autowired\n\tprivate FlowNoticeMapper flowNoticeMapper;\n\n\tprivate static Logger logger = LoggerFactory.getLogger(SpiderFlowService.class);\n\n\t@Value(\"${spider.workspace}\")\n\tprivate String workspace;\n\n\t//项目启动后自动查询需要执行的任务进行爬取\n\t@PostConstruct\n\tprivate void initJobs(){\n\t\t//清空所有任务下次执行时间\n\t\tsfMapper.resetNextExecuteTime();\n\t\t//获取启用corn的任务\n\t\tList<SpiderFlow> spiderFlows = sfMapper.selectList(new QueryWrapper<SpiderFlow>().eq(\"enabled\", \"1\"));\n\t\tif(spiderFlows != null && !spiderFlows.isEmpty()){\n\t\t\tfor (SpiderFlow sf : spiderFlows) {\n\t\t\t\tif(StringUtils.isNotEmpty(sf.getCron())){\n\t\t\t\t\tDate nextExecuteTimt = spiderJobManager.addJob(sf);\n\t\t\t\t\tif (nextExecuteTimt != null) {\n\t\t\t\t\t\tsf.setNextExecuteTime(nextExecuteTimt);\n\t\t\t\t\t\tsfMapper.updateById(sf);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic IPage<SpiderFlow> selectSpiderPage(Page<SpiderFlow> page, String name){\n\t\treturn sfMapper.selectSpiderPage(page,name);\n\t}\n\n\tpublic int executeCountIncrement(String id, Date lastExecuteTime, Date nextExecuteTime){\n\t\tif(nextExecuteTime == null){\n\t\t\treturn sfMapper.executeCountIncrement(id, lastExecuteTime);\n\t\t}\n\t\treturn sfMapper.executeCountIncrementAndExecuteTime(id, lastExecuteTime, nextExecuteTime);\n\n\t}\n\n\t/**\n\t * 重置定时任务\n\t * @param id 爬虫的ID\n\t * @param cron 定时器\n\t */\n\tpublic void resetCornExpression(String id, String cron){\n\t\tCronTrigger trigger = TriggerBuilder.newTrigger()\n\t\t\t\t.withIdentity(\"Caclulate Next Execute Date\")\n\t\t\t\t.withSchedule(CronScheduleBuilder.cronSchedule(cron))\n\t\t\t\t.build();\n\t\tsfMapper.resetCornExpression(id, cron, trigger.getFireTimeAfter(null));\n\t\tspiderJobManager.remove(id);\n\t\tSpiderFlow spiderFlow = getById(id);\n\t\tif(\"1\".equals(spiderFlow.getEnabled()) && StringUtils.isNotEmpty(spiderFlow.getCron())){\n\t\t\tspiderJobManager.addJob(spiderFlow);\n\t\t}\n\t}\n\n\t@Override\n\tpublic boolean save(SpiderFlow spiderFlow){\n\t\t//解析corn,获取并设置任务的开始时间\n\t\tif(StringUtils.isNotEmpty(spiderFlow.getCron())){\n\t\t\tCronTrigger trigger = TriggerBuilder.newTrigger()\n\t\t\t\t\t\t\t.withIdentity(\"Caclulate Next Execute Date\")\n\t\t\t\t\t\t\t.withSchedule(CronScheduleBuilder.cronSchedule(spiderFlow.getCron()))\n\t\t\t\t\t\t\t.build();\n\t\t\tspiderFlow.setNextExecuteTime(trigger.getStartTime());\n\t\t}\n\t\tif(StringUtils.isNotEmpty(spiderFlow.getId())){\t//update 任务\n\t\t\tsfMapper.updateSpiderFlow(spiderFlow.getId(), spiderFlow.getName(), spiderFlow.getXml());\n\t\t\tspiderJobManager.remove(spiderFlow.getId());\n\t\t\tspiderFlow = getById(spiderFlow.getId());\n\t\t\tif(\"1\".equals(spiderFlow.getEnabled()) && StringUtils.isNotEmpty(spiderFlow.getCron())){\n\t\t\t\tspiderJobManager.addJob(spiderFlow);\n\t\t\t}\n\t\t}else{//insert 任务\n\t\t\tString id = UUID.randomUUID().toString().replace(\"-\", \"\");\n\t\t\tsfMapper.insertSpiderFlow(id, spiderFlow.getName(), spiderFlow.getXml());\n\t\t\tspiderFlow.setId(id);\n\t\t}\n\t\tFile file = new File(workspace,spiderFlow.getId() + File.separator + \"xmls\" + File.separator + System.currentTimeMillis() + \".xml\");\n\t\ttry {\n\t\t\tFileUtils.write(file,spiderFlow.getXml(),\"UTF-8\");\n\t\t} catch (IOException e) {\n\t\t\tlogger.error(\"保存历史记录出错\",e);\n\t\t}\n\t\treturn true;\n\t}\n\n\tpublic void stop(String id){\n\t\tsfMapper.resetSpiderStatus(id,\"0\");\n\t\tsfMapper.resetNextExecuteTime(id);\n\t\tspiderJobManager.remove(id);\n\t}\n\n\tpublic void copy(String id){\n\t\t// 复制ID\n\t\tSpiderFlow spiderFlow = sfMapper.selectById(id);\n\t\tString new_id = UUID.randomUUID().toString().replace(\"-\", \"\");\n\t\tsfMapper.insertSpiderFlow(new_id, spiderFlow.getName() + \"-copy\", spiderFlow.getXml());\n\t}\n\n\tpublic void start(String id){\n\t\tspiderJobManager.remove(id);\n\t\tSpiderFlow spiderFlow = getById(id);\n\t\tDate nextExecuteTime = spiderJobManager.addJob(spiderFlow);\n\t\tif (nextExecuteTime != null) {\n\t\t\tspiderFlow.setNextExecuteTime(nextExecuteTime);\n\t\t\tsfMapper.updateById(spiderFlow);\n\t\t\tsfMapper.resetSpiderStatus(id, \"1\");\n\t\t}\n\t}\n\n\tpublic void run(String id){\n\t\tspiderJobManager.run(id);\n\t}\n\n\tpublic void resetExecuteCount(String id){\n\t\tsfMapper.resetExecuteCount(id);\n\t}\n\tpublic void remove(String id){\n\t\tsfMapper.deleteById(id);\n\t\tspiderJobManager.remove(id);\n\t\tflowNoticeMapper.deleteById(id);\n\t}\n\n\tpublic List<SpiderFlow> selectOtherFlows(String id){\n\t\treturn sfMapper.selectOtherFlows(id);\n\t}\n\n\tpublic List<SpiderFlow> selectFlows(){\n\t\treturn sfMapper.selectFlows();\n\t}\n\n    /**\n     * 根据表达式获取最近几次运行时间\n\t * @param cron 表达式\n\t * @param numTimes 几次\n\t * @return\n     */\n\tpublic List<String> getRecentTriggerTime(String cron,int numTimes) {\n\t\tList<String> list = new ArrayList<>();\n\t\tCronTrigger trigger;\n\t\ttry {\n\t\t\ttrigger = TriggerBuilder.newTrigger()\n\t\t\t\t\t.withSchedule(CronScheduleBuilder.cronSchedule(cron))\n\t\t\t\t\t.build();\n\t\t}catch (Exception e) {\n\t\t\tlist.add(\"cron表达式 \"+cron+\" 有误：\" + e.getCause());\n\t\t\treturn list;\n\t\t}\n\t\tList<Date> dates = TriggerUtils.computeFireTimes((OperableTrigger) trigger, null, numTimes);\n\t\tSimpleDateFormat dateFormat = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n\t\tfor (Date date : dates) {\n\t\t\tlist.add(dateFormat.format(date));\n\t\t}\n\t\treturn list;\n\t}\n\n\tpublic List<Long> historyList(String id){\n\t\tFile directory = new File(workspace, id + File.separator + \"xmls\");\n\t\tif(directory.exists() && directory.isDirectory()){\n\t\t\tFile[] files = directory.listFiles((dir, name) -> name.endsWith(\".xml\"));\n\t\t\tif(files != null && files.length > 0){\n\t\t\t\treturn Arrays.stream(files).map(f-> Long.parseLong(f.getName().replace(\".xml\",\"\"))).sorted().collect(Collectors.toList());\n\t\t\t}\n\t\t}\n\t\treturn Collections.emptyList();\n\t}\n\n\tpublic String readHistory(String id,String timestamp){\n\t\tFile file = new File(workspace, id + File.separator + \"xmls\" + File.separator + timestamp + \".xml\");\n\t\tif(file.exists()){\n\t\t\ttry {\n\t\t\t\treturn FileUtils.readFileToString(file,\"UTF-8\");\n\t\t\t} catch (IOException e) {\n\t\t\t\tlogger.error(\"读取历史版本出错\",e);\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tpublic Integer getFlowMaxTaskId(String flowId){\n\t\treturn sfMapper.getFlowMaxTaskId(flowId);\n\t}\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/service/TaskService.java",
    "content": "package org.spiderflow.core.service;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.spiderflow.core.mapper.TaskMapper;\nimport org.spiderflow.core.model.Task;\nimport org.springframework.stereotype.Service;\n\n@Service\npublic class TaskService extends ServiceImpl<TaskMapper, Task> {\n\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/service/VariableService.java",
    "content": "package org.spiderflow.core.service;\n\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.spiderflow.core.expression.ExpressionGlobalVariables;\nimport org.spiderflow.core.mapper.VariableMapper;\nimport org.spiderflow.core.model.Variable;\nimport org.springframework.stereotype.Service;\n\nimport javax.annotation.PostConstruct;\nimport java.io.Serializable;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\n@Service\npublic class VariableService extends ServiceImpl<VariableMapper, Variable> {\n\n\t@Override\n\tpublic boolean removeById(Serializable id) {\n\t\tboolean ret = super.removeById(id);\n\t\tthis.resetGlobalVariables();\n\t\treturn  ret;\n\t}\n\n\t@Override\n\tpublic boolean saveOrUpdate(Variable entity) {\n\t\tboolean ret =  super.saveOrUpdate(entity);\n\t\tthis.resetGlobalVariables();\n\t\treturn ret;\n\t}\n\n\t@PostConstruct\n\tprivate void resetGlobalVariables(){\n\t\tMap<String, String> variables = this.list().stream().collect(Collectors.toMap(Variable::getName, Variable::getValue));\n\t\tExpressionGlobalVariables.reset(variables);\n\t}\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/utils/DataSourceUtils.java",
    "content": "package org.spiderflow.core.utils;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\n\r\nimport javax.sql.DataSource;\r\n\r\nimport org.spiderflow.core.service.DataSourceService;\r\nimport org.springframework.beans.factory.annotation.Autowired;\r\nimport org.springframework.stereotype.Component;\r\n\r\nimport com.alibaba.druid.pool.DruidDataSource;\r\n\r\n/**\r\n * 数据库连接工具类\r\n * @author jmxd\r\n *\r\n */\r\n@Component\r\npublic class DataSourceUtils {\r\n\t\r\n\tprivate static final Map<String,DataSource> datasources = new HashMap<>();\r\n\t\r\n\tprivate static DataSourceService dataSourceService;\r\n\t\r\n\tpublic static DataSource createDataSource(String className,String url,String username,String password){\r\n\t\tDruidDataSource datasource = new DruidDataSource();\r\n\t\tdatasource.setDriverClassName(className);\r\n\t\tdatasource.setUrl(url);\r\n\t\tdatasource.setUsername(username);\r\n\t\tdatasource.setPassword(password);\r\n\t\tdatasource.setDefaultAutoCommit(true);\r\n\t\tdatasource.setMinIdle(1);\r\n\t\tdatasource.setInitialSize(2);\r\n\t\treturn datasource;\r\n\t}\r\n\t\r\n\tpublic static void remove(String dataSourceId){\r\n\t\tDataSource dataSource = datasources.get(dataSourceId);\r\n\t\tif(dataSource != null){\r\n\t\t\tDruidDataSource ds = (DruidDataSource) dataSource;\r\n\t\t\tds.close();\r\n\t\t\tdatasources.remove(dataSourceId);\r\n\t\t}\r\n\t}\r\n\t\r\n\tpublic synchronized static DataSource getDataSource(String dataSourceId){\r\n\t\tDataSource dataSource = datasources.get(dataSourceId);\r\n\t\tif(dataSource == null){\r\n\t\t\torg.spiderflow.core.model.DataSource ds = dataSourceService.getById(dataSourceId);\r\n\t\t\tif(ds != null){\r\n\t\t\t\tdataSource = createDataSource(ds.getDriverClassName(), ds.getJdbcUrl(), ds.getUsername(), ds.getPassword());\r\n\t\t\t\tdatasources.put(dataSourceId, dataSource);\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn dataSource;\r\n\t}\r\n\r\n\t@Autowired\r\n\tpublic void setDataSourceService(DataSourceService dataSourceService) {\r\n\t\tDataSourceUtils.dataSourceService = dataSourceService;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/utils/EmailUtils.java",
    "content": "package org.spiderflow.core.utils;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.mail.SimpleMailMessage;\nimport org.springframework.mail.javamail.JavaMailSender;\nimport org.springframework.stereotype.Component;\n\n/**\n * 邮件发送工具类\n * \n * @author BillDowney\n * @date 2020年4月4日 上午12:31:09\n */\n@Component\npublic class EmailUtils {\n\n\t// 发送邮件服务\n\t@Autowired\n\tprivate JavaMailSender javaMailSender;\n\t// 发送者\n\t@Value(\"${spring.mail.username}\")\n\tprivate String from;\n\n\t/**\n\t * 发送简单文本邮件\n\t * \n\t * @param subject 主题\n\t * @param content 内容\n\t * @param to      收件人列表\n\t * @author BillDowney\n\t * @date 2020年4月4日 上午12:40:42\n\t */\n\tpublic void sendSimpleMail(String subject, String content, String... to) {\n\t\tSimpleMailMessage message = new SimpleMailMessage();\n\t\tmessage.setFrom(from);\n\t\tmessage.setSubject(subject);\n\t\tmessage.setText(content);\n\t\tmessage.setTo(to);\n\t\tjavaMailSender.send(message);\n\t}\n\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/utils/ExecutorsUtils.java",
    "content": "package org.spiderflow.core.utils;\n\nimport org.spiderflow.executor.ShapeExecutor;\nimport org.spiderflow.model.Shape;\nimport org.springframework.beans.BeansException;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.ApplicationContextAware;\nimport org.springframework.stereotype.Component;\n\nimport javax.annotation.PostConstruct;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\n/**\n * Created on 2020-03-11\n *\n * @author Octopus\n */\n@Component\npublic class ExecutorsUtils implements ApplicationContextAware {\n\n    /**\n     * 节点执行器列表 当前爬虫的全部流程\n     */\n    private static List<ShapeExecutor> executors;\n\n    private static Map<String, ShapeExecutor> executorMap;\n\n    private static ApplicationContext applicationContext;\n\n    @Autowired\n    ExecutorsUtils(List<ShapeExecutor> executors){\n        ExecutorsUtils.executors = executors;\n    }\n\n    @PostConstruct\n    private void init() {\n        executorMap = executors.stream().collect(Collectors.toMap(ShapeExecutor::supportShape, v -> v));\n    }\n\n    @Override\n    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {\n        ExecutorsUtils.applicationContext = applicationContext;\n    }\n\n    public static List<Shape> shapes(){\n        return executors.stream().filter(e-> e.shape() !=null).map(executor -> executor.shape()).collect(Collectors.toList());\n    }\n\n    public static ShapeExecutor get(String shape){\n        return executorMap.get(shape);\n    }\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/utils/ExpressionUtils.java",
    "content": "package org.spiderflow.core.utils;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.spiderflow.ExpressionEngine;\nimport org.spiderflow.model.SpiderNode;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * Created on 2020-03-11\n *\n * @author Octopus\n */\n@Component\npublic class ExpressionUtils {\n\n    private static Logger logger = LoggerFactory.getLogger(ExpressionUtils.class);\n\n    /**\n     * 选择器\n     */\n    private static ExpressionEngine engine;\n\n    @Autowired\n    private ExpressionUtils(ExpressionEngine engine){\n        ExpressionUtils.engine = engine;\n    }\n\n    public static boolean executeCondition(SpiderNode fromNode, SpiderNode node, Map<String, Object> variables) {\n        if (fromNode != null) {\n            String condition = node.getCondition(fromNode.getNodeId());\n            if (StringUtils.isNotBlank(condition)) { // 判断是否有条件\n                Object result = null;\n                try {\n                    result = engine.execute(condition, variables);\n                } catch (Exception e) {\n                    logger.error(\"判断{}出错,异常信息：{}\", condition, e);\n                }\n                if (result != null) {\n                    boolean isContinue = \"true\".equals(result) || Objects.equals(result, true);\n                    logger.debug(\"判断{}={}\", condition, isContinue);\n                    return isContinue;\n                }\n                return false;\n            }\n        }\n        return true;\n    }\n\n    public static Object execute(String expression, Map<String, Object> variables) {\n        return engine.execute(expression, variables);\n    }\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/utils/ExtractUtils.java",
    "content": "package org.spiderflow.core.utils;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.HashMap;\r\nimport java.util.List;\r\nimport java.util.Map;\r\nimport java.util.regex.Matcher;\r\nimport java.util.regex.Pattern;\r\n\r\nimport org.jsoup.nodes.Element;\r\nimport org.jsoup.select.Elements;\r\n\r\nimport com.alibaba.fastjson.JSONPath;\r\nimport us.codecraft.xsoup.Xsoup;\r\n\r\n/**\r\n * 抽取数据工具类\r\n * @author jmxd\r\n *\r\n */\r\npublic class ExtractUtils {\r\n\t\r\n\tprivate static Map<String,Pattern> patterns = new HashMap<>();\r\n\t\r\n\tprivate static Pattern compile(String regx){\r\n\t\tPattern pattern = patterns.get(regx);\r\n\t\tif(pattern == null){\r\n\t\t\tpattern = Pattern.compile(regx,Pattern.DOTALL);\r\n\t\t\tpatterns.put(regx, pattern);\r\n\t\t}\r\n\t\treturn pattern;\r\n\t}\r\n\t\r\n\tpublic static List<String> getMatchers(String content,String regx,boolean isGroup){\r\n\t\treturn getMatchers(content,regx,isGroup ? 1: 0);\r\n\t}\r\n\t\r\n\tpublic static List<String> getMatchers(String content,String regx,int groupIndex){\r\n\t\tMatcher matcher = compile(regx).matcher(content);\r\n\t\tList<String> results = new ArrayList<>();\r\n\t\twhile(matcher.find()){\r\n\t\t\tresults.add(matcher.group(groupIndex));\r\n\t\t}\r\n\t\treturn results;\r\n\t}\r\n\t\r\n\tpublic static List<List<String>> getMatchers(String content,String regx,List<Integer> groups){\r\n\t\tMatcher matcher = compile(regx).matcher(content);\r\n\t\tList<List<String>> results = new ArrayList<>();\r\n\t\twhile(matcher.find()){\r\n\t\t\tList<String> matches = new ArrayList<>();\r\n\t\t\tfor (Integer groupIndex : groups) {\r\n\t\t\t\tmatches.add(matcher.group(groupIndex));\r\n\t\t\t}\r\n\t\t\tresults.add(matches);\r\n\t\t}\r\n\t\treturn results;\r\n\t}\r\n\t\r\n\tpublic static String getFirstMatcher(String content,String regx,boolean isGroup){\r\n\t\t\r\n\t\treturn getFirstMatcher(content,regx,isGroup ? 1 : 0);\r\n\t}\r\n\t\r\n\tpublic static String getFirstMatcher(String content,String regx,int groupIndex){\r\n\t\tMatcher matcher = compile(regx).matcher(content);\r\n\t\tif(matcher.find()){\r\n\t\t\treturn matcher.group(groupIndex);\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\t\r\n\tpublic static List<String> getFirstMatcher(String content,String regx,List<Integer> groups){\r\n\t\tMatcher matcher = compile(regx).matcher(content);\r\n\t\tList<String> matches = new ArrayList<>();\r\n\t\tif(matcher.find()){\r\n\t\t\tfor (Integer groupIndex : groups) {\r\n\t\t\t\tmatches.add(matcher.group(groupIndex));\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn matches;\r\n\t}\r\n\t\r\n\tpublic static String getHostFromURL(String url){\r\n\t\treturn getFirstMatcher(url, \"(?<=//|)((\\\\w)+\\\\.)+\\\\w+\", false);\r\n\t}\r\n\t\r\n\tpublic static String getFirstHTMLBySelector(Element element,String selector){\r\n\t\telement = getFirstElement(element,selector);\r\n\t\treturn element == null ? null : element.html();\r\n\t}\r\n\r\n\tpublic static String getFirstOuterHTMLBySelector(Element element,String selector){\r\n\t\telement = getFirstElement(element,selector);\r\n\t\treturn element == null ? null : element.outerHtml();\r\n\t}\r\n\t\r\n\tpublic static String getFirstTextBySelector(Element element,String selector){\r\n\t\telement = getFirstElement(element,selector);\r\n\t\treturn element == null ? null : element.text();\r\n\t}\r\n\t\r\n\tpublic static String getFirstAttrBySelector(Element element,String selector,String attr){\r\n\t\telement = getFirstElement(element,selector);\r\n\t\treturn element == null ? null : element.attr(attr);\r\n\t}\r\n\t\r\n\tpublic static Element getFirstElement(Element element,String selector){\r\n\t\treturn element.selectFirst(selector);\r\n\t}\r\n\t\r\n\tpublic static List<Element> getElements(Element element,String selector){\r\n\t\treturn element.select(selector);\r\n\t}\r\n\t\r\n\tpublic static List<String> getHTMLBySelector(Element element,String selector){\r\n\t\tElements elements = element.select(selector);\r\n\t\tList<String> result = new ArrayList<>();\r\n\t\tfor (Element elem : elements) {\r\n\t\t\tresult.add(elem.html());\r\n\t\t}\r\n\t\treturn result;\r\n\t}\r\n\t\r\n\tpublic static List<String> getOuterHTMLBySelector(Element element,String selector){\r\n\t\tElements elements = element.select(selector);\r\n\t\tList<String> result = new ArrayList<>();\r\n\t\tfor (Element elem : elements) {\r\n\t\t\tresult.add(elem.outerHtml());\r\n\t\t}\r\n\t\treturn result;\r\n\t}\r\n\t\r\n\tpublic static List<String> getTextBySelector(Element element,String selector){\r\n\t\tElements elements = element.select(selector);\r\n\t\tList<String> result = new ArrayList<>();\r\n\t\tfor (Element elem : elements) {\r\n\t\t\tresult.add(elem.text());\r\n\t\t}\r\n\t\treturn result;\r\n\t}\r\n\t\r\n\tpublic static List<String> getAttrBySelector(Element element,String selector,String attr){\r\n\t\tElements elements = element.select(selector);\r\n\t\tList<String> result = new ArrayList<>();\r\n\t\tfor (Element elem : elements) {\r\n\t\t\tresult.add(elem.attr(attr));\r\n\t\t}\r\n\t\treturn result;\r\n\t}\r\n\t\r\n\tpublic static Object getValueByJsonPath(Object root,String jsonPath){\r\n\t\treturn JSONPath.eval(root, jsonPath);\r\n\t}\r\n\t\r\n\tpublic static List<String> getValuesByXPath(Element element,String xpath){\r\n\t\treturn Xsoup.select(element,xpath).list();\r\n\t}\r\n\r\n\tpublic static List<String> getValuesByXPath(Elements elements,String xpath){\r\n\t\treturn Xsoup.select(elements.html(),xpath).list();\r\n\t}\r\n\t\r\n\tpublic static String getValueByXPath(Element element,String xpath){\r\n\t\treturn Xsoup.select(element,xpath).get();\r\n\t}\r\n\r\n\tpublic static String getValueByXPath(Elements elements,String xpath){\r\n\t\treturn Xsoup.select(elements.html(),xpath).get();\r\n\t}\r\n\t\r\n\tpublic static String getElementByXPath(Element element,String xpath){\r\n\t\treturn Xsoup.select(element,xpath).get();\r\n\t}\r\n\t\r\n\tpublic static boolean isNumber(String str) {\r\n        return compile(\"^(\\\\-|\\\\+)?\\\\d+(\\\\.\\\\d+)?$\").matcher(str).matches();  \r\n\t}\r\n\t\r\n}\r\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/utils/FileUtils.java",
    "content": "package org.spiderflow.core.utils;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javax.servlet.http.HttpServletRequest;\nimport java.io.*;\nimport java.net.*;\n\n/**\n * 文件处理工具类\n * \n * @author ruoyi\n */\npublic class FileUtils\n{\n    private static Logger logger = LoggerFactory.getLogger(FileUtils.class);\n\n    public static String FILENAME_PATTERN = \"[a-zA-Z0-9_\\\\-\\\\|\\\\.\\\\u4e00-\\\\u9fa5]+\";\n\n    /**\n     * 输出指定文件的byte数组\n     * \n     * @param filePath 文件路径\n     * @param os 输出流\n     * @return\n     */\n    public static void writeBytes(String filePath, OutputStream os) throws IOException\n    {\n        FileInputStream fis = null;\n        try\n        {\n            File file = new File(filePath);\n            if (!file.exists())\n            {\n                throw new FileNotFoundException(filePath);\n            }\n            fis = new FileInputStream(file);\n            byte[] b = new byte[1024];\n            int length;\n            while ((length = fis.read(b)) > 0)\n            {\n                os.write(b, 0, length);\n            }\n        }\n        catch (IOException e)\n        {\n            throw e;\n        }\n        finally\n        {\n            if (os != null)\n            {\n                try\n                {\n                    os.close();\n                }\n                catch (IOException e1)\n                {\n                    e1.printStackTrace();\n                }\n            }\n            if (fis != null)\n            {\n                try\n                {\n                    fis.close();\n                }\n                catch (IOException e1)\n                {\n                    e1.printStackTrace();\n                }\n            }\n        }\n    }\n\n    /**\n     * 删除文件\n     * \n     * @param filePath 文件\n     * @return\n     */\n    public static boolean deleteFile(String filePath)\n    {\n        boolean flag = false;\n        File file = new File(filePath);\n        // 路径为文件且不为空则进行删除\n        if (file.isFile() && file.exists())\n        {\n            file.delete();\n            flag = true;\n        }\n        return flag;\n    }\n\n    /**\n     * 文件名称验证\n     * \n     * @param filename 文件名称\n     * @return true 正常 false 非法\n     */\n    public static boolean isValidFilename(String filename)\n    {\n        return filename.matches(FILENAME_PATTERN);\n    }\n\n    /**\n     * 下载文件名重新编码\n     * \n     * @param request 请求对象\n     * @param fileName 文件名\n     * @return 编码后的文件名\n     */\n    public static String setFileDownloadHeader(HttpServletRequest request, String fileName)\n            throws UnsupportedEncodingException\n    {\n        final String agent = request.getHeader(\"USER-AGENT\");\n        String filename = fileName;\n        if (agent.contains(\"MSIE\"))\n        {\n            // IE浏览器\n            filename = URLEncoder.encode(filename, \"utf-8\");\n            filename = filename.replace(\"+\", \" \");\n        }\n        else if (agent.contains(\"Firefox\"))\n        {\n            // 火狐浏览器\n            filename = new String(fileName.getBytes(), \"ISO8859-1\");\n        }\n        else if (agent.contains(\"Chrome\"))\n        {\n            // google浏览器\n            filename = URLEncoder.encode(filename, \"utf-8\");\n        }\n        else\n        {\n            // 其它浏览器\n            filename = URLEncoder.encode(filename, \"utf-8\");\n        }\n        return filename;\n    }\n\n    /**\n     * 文件下载状态\n     */\n    public enum DownloadStatus {\n        URL_ERROR(1, \"URL错误\"),\n        FILE_EXIST(2,\"文件存在\"),\n        TIME_OUT(3,\"连接超时\"),\n        DOWNLOAD_FAIL(4,\"下载失败\"),\n        DOWNLOAD_SUCCESS(5,\"下载成功\");\n\n        private int code;\n\n        private String name;\n\n        DownloadStatus(int code, String name){\n            this.code = code;\n            this.name = name;\n        }\n\n        public int getCode() {\n            return code;\n        }\n\n        public void setCode(int code) {\n            this.code = code;\n        }\n\n        public String getName() {\n            return name;\n        }\n\n        public void setName(String name) {\n            this.name = name;\n        }\n    }\n\n    public static DownloadStatus downloadFile(String savePath, String fileUrl, boolean downNew) {\n        URL urlfile = null;\n        HttpURLConnection httpUrl = null;\n        BufferedInputStream bis = null;\n        BufferedOutputStream bos = null;\n        if (fileUrl.startsWith(\"//\")) {\n            fileUrl = \"http:\" + fileUrl;\n        }\n        String fileName;\n        try {\n            urlfile = new URL(fileUrl);\n            String urlPath = urlfile.getPath();\n            fileName = urlPath.substring(urlPath.lastIndexOf(\"/\") + 1);\n        } catch (MalformedURLException e) {\n            logger.error(\"URL异常\", e);\n            return DownloadStatus.URL_ERROR;\n        }\n        File path = new File(savePath);\n        if (!path.exists()) {\n            path.mkdirs();\n        }\n        File file = new File(savePath + File.separator + fileName);\n        if (file.exists()) {\n            if (downNew) {\n                file.delete();\n            } else {\n                logger.info(\"文件已存在不重新下载！\");\n                return DownloadStatus.FILE_EXIST;\n            }\n        }\n        try {\n            httpUrl = (HttpURLConnection) urlfile.openConnection();\n            httpUrl.setRequestProperty(\"User-Agent\",\"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0\");\n            //读取超时时间\n            httpUrl.setReadTimeout(60000);\n            //连接超时时间\n            httpUrl.setConnectTimeout(60000);\n            httpUrl.connect();\n            bis = new BufferedInputStream(httpUrl.getInputStream());\n            bos = new BufferedOutputStream(new FileOutputStream(file));\n            int len = 2048;\n            byte[] b = new byte[len];\n            long readLen = 0;\n            while ((len = bis.read(b)) != -1) {\n                bos.write(b, 0, len);\n            }\n            logger.info(\"远程文件下载成功:\" + fileUrl);\n            bos.flush();\n            bis.close();\n            httpUrl.disconnect();\n            return DownloadStatus.DOWNLOAD_SUCCESS;\n        } catch (SocketTimeoutException e) {\n            logger.error(\"读取文件超时\", e);\n            return DownloadStatus.TIME_OUT;\n        } catch (Exception e) {\n            logger.error(\"远程文件下载失败\", e);\n            return DownloadStatus.DOWNLOAD_FAIL;\n        } finally {\n            try {\n                if (bis != null) {\n                    bis.close();\n                }\n                if (bos != null) {\n                    bos.close();\n                }\n            } catch (Exception e) {\n                logger.error(\"下载出错\", e);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "spider-flow-core/src/main/java/org/spiderflow/core/utils/SpiderFlowUtils.java",
    "content": "package org.spiderflow.core.utils;\r\n\r\nimport java.util.Collections;\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\nimport java.util.Map.Entry;\r\nimport java.util.Set;\r\n\r\nimport org.jsoup.Jsoup;\r\nimport org.jsoup.nodes.Document;\r\nimport org.jsoup.nodes.Element;\r\nimport org.jsoup.select.Elements;\r\nimport org.spiderflow.model.SpiderNode;\r\nimport org.springframework.util.CollectionUtils;\r\n\r\nimport com.alibaba.fastjson.JSON;\r\n\r\n/**\r\n * 爬虫流程图工具类\r\n * @author jmxd\r\n *\r\n */\r\npublic class SpiderFlowUtils {\r\n\t\r\n\t/**\r\n\t * 加载流程图\r\n\t * @param xmlString string类型保存的XML流程图\r\n\t * @return SpiderNode 爬虫的开始节点\r\n\t */\r\n\tpublic static SpiderNode loadXMLFromString(String xmlString){\r\n\t\tDocument document = Jsoup.parse(xmlString);\r\n\t\tElements cells = document.getElementsByTag(\"mxCell\");\r\n\t\tMap<String,SpiderNode> nodeMap = new HashMap<>();\r\n\t\tSpiderNode root = null;\r\n\t\tSpiderNode firstNode = null;\r\n\t\tMap<String,Map<String,String>> edgeMap = new HashMap<>();\r\n\t\tfor (Element element : cells) {\r\n\t\t\tMap<String, Object> jsonProperty = getSpiderFlowJsonProperty(element);\r\n\t\t\tSpiderNode node = new SpiderNode();\r\n\t\t\tnode.setJsonProperty(jsonProperty);\r\n\t\t\tString nodeId = element.attr(\"id\");\r\n\t\t\tnode.setNodeName(element.attr(\"value\"));\r\n\t\t\tnode.setNodeId(nodeId);\r\n\t\t\tnodeMap.put(nodeId, node);\r\n\t\t\tif(element.hasAttr(\"edge\")){\t//判断是否是连线\r\n\t\t\t\tedgeMap.put(nodeId, Collections.singletonMap(element.attr(\"source\"), element.attr(\"target\")));\r\n\t\t\t} else if (jsonProperty != null && node.getStringJsonValue(\"shape\") != null) {\r\n\t\t\t\tif (\"start\".equals(node.getStringJsonValue(\"shape\"))) {\r\n\t\t\t\t\troot = node;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif(\"0\".equals(nodeId)){\r\n\t\t\t\tfirstNode = node;\r\n\t\t\t}\r\n\t\t}\r\n\t\t//处理连线\r\n\t\tSet<String> edges = edgeMap.keySet();\r\n\t\tfor (String edgeId : edges) {\r\n\t\t\tSet<Entry<String, String>> entries = edgeMap.get(edgeId).entrySet();\r\n\t\t\tSpiderNode edgeNode = nodeMap.get(edgeId);\r\n\t\t\tfor (Entry<String, String> edge : entries) {\r\n\t\t\t\tSpiderNode sourceNode = nodeMap.get(edge.getKey());\r\n\t\t\t\tSpiderNode targetNode = nodeMap.get(edge.getValue());\r\n\t\t\t\t//设置流转条件\r\n\t\t\t\ttargetNode.setCondition(sourceNode.getNodeId(),edgeNode.getStringJsonValue(\"condition\"));\r\n\t\t\t\t//设置流转特性\r\n\t\t\t\ttargetNode.setExceptionFlow(sourceNode.getNodeId(),edgeNode.getStringJsonValue(\"exception-flow\"));\r\n\t\t\t\ttargetNode.setTransmitVariable(sourceNode.getNodeId(),edgeNode.getStringJsonValue(\"transmit-variable\"));\r\n\t\t\t\tsourceNode.addNextNode(targetNode);\r\n\t\t\t}\r\n\t\t}\r\n\t\tfirstNode.addNextNode(root);\r\n\t\treturn firstNode;\r\n\t}\r\n\t\r\n\t/**\r\n\t * 提取配置的json属性\r\n\t */\r\n\t@SuppressWarnings(\"unchecked\")\r\n\tprivate static Map<String,Object> getSpiderFlowJsonProperty(Element element){\r\n\t\tElements elements = element.getElementsByTag(\"JsonProperty\");\r\n\t\tif(!CollectionUtils.isEmpty(elements)){\r\n\t\t\treturn JSON.parseObject(elements.get(0).html(),Map.class);\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "spider-flow-web/pom.xml",
    "content": "<?xml version=\"1.0\"?>\n<project xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\" xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n\t<modelVersion>4.0.0</modelVersion>\n\t<parent>\n\t\t<groupId>org.spiderflow</groupId>\n\t\t<artifactId>spider-flow</artifactId>\n\t\t<version>0.5.0</version>\n\t</parent>\n\t<artifactId>spider-flow-web</artifactId>\n\t<name>spider-flow-web</name>\n\t<url>https://gitee.com/jmxd/spider-flow/tree/master/spider-flow-web</url>\n\t<properties>\n\t\t<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n\t</properties>\n\t<dependencies>\n\t\t<dependency>\n\t\t\t<groupId>org.spiderflow</groupId>\n\t\t\t<artifactId>spider-flow-core</artifactId>\n\t\t</dependency>\n\t</dependencies>\n\t<build>\n\t\t<plugins>\n\t\t\t<plugin>\n\t\t\t\t<groupId>org.springframework.boot</groupId>\n\t\t\t\t<artifactId>spring-boot-maven-plugin</artifactId>\n\t\t\t\t<configuration>\n\t\t\t\t\t<finalName>spider-flow</finalName>\n\t\t\t\t\t<mainClass>org.spiderflow.SpiderApplication</mainClass>\n\t\t\t\t</configuration>\n\t\t\t</plugin>\n\t\t</plugins>\n\t</build>\n</project>\n"
  },
  {
    "path": "spider-flow-web/src/main/java/org/spiderflow/SpiderApplication.java",
    "content": "package org.spiderflow;\r\n\r\nimport java.io.IOException;\r\n\r\nimport javax.servlet.ServletContext;\r\nimport javax.servlet.ServletException;\r\n\r\nimport org.mybatis.spring.annotation.MapperScan;\r\nimport org.springframework.boot.SpringApplication;\r\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\r\nimport org.springframework.boot.web.servlet.ServletContextInitializer;\r\nimport org.springframework.context.annotation.Bean;\r\nimport org.springframework.scheduling.annotation.EnableScheduling;\r\n\r\nimport com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;\r\n\r\n@SpringBootApplication\r\n@EnableScheduling\r\n@MapperScan(\"org.spiderflow.*.mapper\")\r\npublic class SpiderApplication implements ServletContextInitializer{\r\n\t\r\n\tpublic static void main(String[] args) throws IOException {\r\n\t\t\r\n\t\tSpringApplication.run(SpiderApplication.class, args);\r\n\t}\r\n\r\n\t@Override\r\n\tpublic void onStartup(ServletContext servletContext) throws ServletException {\r\n\t\t//设置文本缓存1M\r\n\t\tservletContext.setInitParameter(\"org.apache.tomcat.websocket.textBufferSize\", Integer.toString((1024 * 1024)));\r\n\t}\r\n\t\r\n\t@Bean\r\n    public PaginationInterceptor paginationInterceptor() {\r\n        return new PaginationInterceptor();\r\n    }\r\n\t\r\n}\r\n"
  },
  {
    "path": "spider-flow-web/src/main/java/org/spiderflow/configuration/ResourcesConfiguration.java",
    "content": "package org.spiderflow.configuration;\n\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.servlet.config.annotation.CorsRegistry;\nimport org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\n\n/**\n * 配置放行静态资源文件\n * @author Administrator\n *\n */\n@Configuration\npublic class ResourcesConfiguration implements WebMvcConfigurer{\n\n\t@Override\n\tpublic void addResourceHandlers(ResourceHandlerRegistry registry) {\n\t\tregistry.addResourceHandler(\"/static/**\").addResourceLocations(\"classpath:/static/\");\n\t}\n\n\t@Override\n\tpublic void addCorsMappings(CorsRegistry registry) {\n\t\tregistry.addMapping(\"/**\")\n\t\t\t\t.allowedOrigins(\"*\")\n\t\t\t\t.allowedMethods(\"GET\",\"POST\",\"OPTIONS\")\n\t\t\t\t.allowCredentials(true);\n\t}\n}\n"
  },
  {
    "path": "spider-flow-web/src/main/java/org/spiderflow/configuration/WebSocketConfiguration.java",
    "content": "package org.spiderflow.configuration;\r\n\r\nimport org.spiderflow.core.Spider;\r\nimport org.spiderflow.websocket.WebSocketEditorServer;\r\nimport org.springframework.beans.factory.annotation.Autowired;\r\nimport org.springframework.context.annotation.Bean;\r\nimport org.springframework.context.annotation.Configuration;\r\nimport org.springframework.web.socket.server.standard.ServerEndpointExporter;\r\n\r\n/**\r\n * 配置WebSocket\r\n * @author Administrator\r\n *\r\n */\r\n@Configuration\r\npublic class WebSocketConfiguration {\r\n\t\r\n\t@Bean\r\n\tpublic ServerEndpointExporter endpointExporter(){\r\n\t\treturn new ServerEndpointExporter();\r\n\t}\r\n\t\r\n\t@Autowired\r\n\tpublic void setSpider(Spider spider) {\r\n\t\tWebSocketEditorServer.spider = spider;\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "spider-flow-web/src/main/java/org/spiderflow/controller/DataSourceController.java",
    "content": "package org.spiderflow.controller;\r\n\r\nimport java.sql.Connection;\r\nimport java.sql.DriverManager;\r\nimport java.util.List;\r\n\r\nimport org.apache.commons.lang3.StringUtils;\r\nimport org.spiderflow.core.model.DataSource;\r\nimport org.spiderflow.core.service.DataSourceService;\r\nimport org.spiderflow.core.utils.DataSourceUtils;\r\nimport org.spiderflow.model.JsonBean;\r\nimport org.springframework.beans.factory.annotation.Autowired;\r\nimport org.springframework.web.bind.annotation.RequestMapping;\r\nimport org.springframework.web.bind.annotation.RequestParam;\r\nimport org.springframework.web.bind.annotation.RestController;\r\n\r\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\r\nimport com.baomidou.mybatisplus.core.metadata.IPage;\r\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\r\n\r\n@RestController\r\n@RequestMapping(\"/datasource\")\r\npublic class DataSourceController {\r\n\t\r\n\t@Autowired\r\n\tprivate DataSourceService dataSourceService;\r\n\t\r\n\t@RequestMapping(\"/list\")\r\n\tpublic IPage<DataSource> list(@RequestParam(name = \"page\",defaultValue = \"1\")Integer page, @RequestParam(name = \"limit\",defaultValue = \"1\")Integer size) {\r\n\t\treturn dataSourceService.page(new Page<DataSource>(page, size), new QueryWrapper<DataSource>().select(\"id\", \"name\", \"driver_class_name\", \"create_date\").orderByDesc(\"create_date\"));\r\n\t}\r\n\t\r\n\t@RequestMapping(\"/all\")\r\n\tpublic List<DataSource> all(){\r\n\t\treturn dataSourceService.list();\r\n\t}\r\n\t\r\n\t@RequestMapping(\"/save\")\r\n\tpublic String save(DataSource dataSource){\r\n\t\tif(StringUtils.isNotBlank(dataSource.getId())){\r\n\t\t\tDataSourceUtils.remove(dataSource.getId());\r\n\t\t}\r\n\t\tdataSourceService.saveOrUpdate(dataSource);\r\n\t\treturn dataSource.getId();\r\n\t}\r\n\t\r\n\t@RequestMapping(\"/get\")\r\n\tpublic DataSource get(String id){\r\n\t\tDataSource dataSource = dataSourceService.getById(id);\r\n\t\tdataSource.setPassword(null);\r\n\t\treturn dataSource;\r\n\t}\r\n\t\r\n\t@RequestMapping(\"/remove\")\r\n\tpublic void remove(String id){\r\n\t\tDataSourceUtils.remove(id);\r\n\t\tdataSourceService.removeById(id);\r\n\t}\r\n\t\r\n\t@RequestMapping(\"/test\")\r\n\tpublic JsonBean<Void> test(DataSource dataSource){\r\n\t\tif(StringUtils.isBlank(dataSource.getDriverClassName())){\r\n\t\t\treturn new JsonBean<>(0, \"DriverClassName不能为空！\");\r\n\t\t}\r\n\t\tif(StringUtils.isBlank(dataSource.getJdbcUrl())){\r\n\t\t\treturn new JsonBean<>(0, \"jdbcUrl不能为空！\");\r\n\t\t}\r\n\t\tConnection connection = null;\r\n\t\ttry {\r\n\t\t\tClass.forName(dataSource.getDriverClassName());\r\n\t\t\tString url = dataSource.getJdbcUrl();\r\n\t\t\tString username = dataSource.getUsername();\r\n\t\t\tString password = dataSource.getPassword();\r\n\t\t\tif(StringUtils.isNotBlank(username)){\r\n\t\t\t\tconnection = DriverManager.getConnection(url,username,password);\r\n\t\t\t}else{\r\n\t\t\t\tconnection = DriverManager.getConnection(url);\r\n\t\t\t}\r\n\t\t\treturn new JsonBean<>(1, \"测试连接成功\");\r\n\t\t} catch (ClassNotFoundException e) {\r\n\t\t\treturn new JsonBean<>(0, \"找不到驱动包：\" + dataSource.getDriverClassName());\r\n\t\t} catch (Exception e){\r\n\t\t\treturn new JsonBean<>(0, \"连接失败，\"+ e.getMessage());\r\n\t\t} finally{\r\n\t\t\tif(connection != null){\r\n\t\t\t\ttry {\r\n\t\t\t\t\tconnection.close();\r\n\t\t\t\t} catch (Exception e) {\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n}\r\n"
  },
  {
    "path": "spider-flow-web/src/main/java/org/spiderflow/controller/FlowNoticeController.java",
    "content": "package org.spiderflow.controller;\n\nimport java.util.Map;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.spiderflow.core.model.FlowNotice;\nimport org.spiderflow.core.service.FlowNoticeService;\nimport org.spiderflow.enums.FlowNoticeWay;\nimport org.spiderflow.model.JsonBean;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.util.StringUtils;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\n@RequestMapping(\"/flowNotice\")\npublic class FlowNoticeController {\n\n\tprivate static final Logger logger = LoggerFactory.getLogger(FlowNoticeController.class);\n\t@Autowired\n\tprivate FlowNoticeService flowNoticeService;\n\n\t@RequestMapping(\"/save\")\n\tpublic JsonBean<FlowNotice> save(FlowNotice entity) {\n\t\tif (StringUtils.isEmpty(entity.getId())) {\n\t\t\treturn new JsonBean<FlowNotice>(0, \"流程id不能为空\");\n\t\t}\n\t\ttry {\n\t\t\tflowNoticeService.saveOrUpdate(entity);\n\t\t} catch (RuntimeException e) {\n\t\t\tlogger.error(e.getMessage(), e);\n\t\t\treturn new JsonBean<FlowNotice>(0, e.getMessage() == null ? \"发生错误\" : e.getMessage());\n\t\t}\n\t\treturn new JsonBean<FlowNotice>(entity);\n\t}\n\n\t@RequestMapping(\"/find\")\n\tpublic JsonBean<FlowNotice> find(String id) {\n\t\tFlowNotice data = flowNoticeService.getById(id);\n\t\tif (data == null) {\n\t\t\tdata = new FlowNotice();\n\t\t\tdata.setId(id);\n\t\t}\n\t\treturn new JsonBean<FlowNotice>(data);\n\t}\n\n\t@RequestMapping(\"/getNoticeWay\")\n\tpublic JsonBean<Map<String, String>> getNoticeWay(String id) {\n\t\treturn new JsonBean<Map<String, String>>(FlowNoticeWay.getMap());\n\t}\n\n}\n"
  },
  {
    "path": "spider-flow-web/src/main/java/org/spiderflow/controller/FunctionController.java",
    "content": "package org.spiderflow.controller;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport org.apache.commons.lang3.StringUtils;\nimport org.spiderflow.core.model.DataSource;\nimport org.spiderflow.core.model.Function;\nimport org.spiderflow.core.service.FunctionService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\n@RequestMapping(\"/function\")\npublic class FunctionController {\n\n    @Autowired\n    private FunctionService functionService;\n\n    @RequestMapping(\"/list\")\n    public IPage<Function> list(@RequestParam(name = \"page\",defaultValue = \"1\")Integer page, @RequestParam(name = \"limit\",defaultValue = \"1\")Integer size,String name) {\n        QueryWrapper<Function> select = new QueryWrapper<Function>().select(\"id\", \"name\", \"parameter\", \"create_date\");\n        if(StringUtils.isNotBlank(name)){\n            select.like(\"name\",name);\n        }\n        select.orderByDesc(\"create_date\");\n        return functionService.page(new Page<Function>(page, size), select);\n    }\n\n    @RequestMapping(\"/save\")\n    public String save(Function function){\n        return functionService.saveFunction(function);\n    }\n\n    @RequestMapping(\"/get\")\n    public Function get(String id){\n        return functionService.getById(id);\n    }\n\n    @RequestMapping(\"/remove\")\n    public void remove(String id){\n        functionService.removeById(id);\n    }\n}\n"
  },
  {
    "path": "spider-flow-web/src/main/java/org/spiderflow/controller/SpiderFlowController.java",
    "content": "package org.spiderflow.controller;\n\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.math.NumberUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.spiderflow.Grammerable;\nimport org.spiderflow.annotation.Comment;\nimport org.spiderflow.core.model.SpiderFlow;\nimport org.spiderflow.core.service.SpiderFlowService;\nimport org.spiderflow.core.utils.ExecutorsUtils;\nimport org.spiderflow.executor.FunctionExecutor;\nimport org.spiderflow.executor.FunctionExtension;\nimport org.spiderflow.executor.PluginConfig;\nimport org.spiderflow.io.Line;\nimport org.spiderflow.io.RandomAccessFileReader;\nimport org.spiderflow.model.Grammer;\nimport org.spiderflow.model.JsonBean;\nimport org.spiderflow.model.Plugin;\nimport org.spiderflow.model.Shape;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.core.io.FileSystemResource;\nimport org.springframework.http.MediaType;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport javax.annotation.PostConstruct;\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.RandomAccessFile;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * 爬虫Controller\n * @author Administrator\n *\n */\n@RestController\n@RequestMapping(\"/spider\")\npublic class SpiderFlowController {\n\n\t@Autowired\n\tprivate List<FunctionExecutor> functionExecutors;\n\n\t@Autowired\n\tprivate List<FunctionExtension> functionExtensions;\n\n\t@Autowired\n\tprivate List<Grammerable> grammerables;\n\n\t@Autowired\n\tprivate SpiderFlowService spiderFlowService;\n\n\t@Autowired(required = false)\n\tprivate List<PluginConfig> pluginConfigs;\n\n\t@Value(\"${spider.workspace}\")\n\tprivate String workspace;\n\n\tprivate final List<Grammer> grammers = new ArrayList<Grammer>();\n\n\tprivate static Logger logger = LoggerFactory.getLogger(SpiderFlowController.class);\n\n\t@PostConstruct\n\tprivate void init(){\n\t\tfor (FunctionExecutor executor : functionExecutors) {\n\t\t\tString function = executor.getFunctionPrefix();\n\t\t\tgrammers.addAll(Grammer.findGrammers(executor.getClass(),function,function,true));\n\t\t\tComment comment = executor.getClass().getAnnotation(Comment.class);\n\t\t\tGrammer grammer = new Grammer();\n\t\t\tif(comment!= null){\n\t\t\t\tgrammer.setComment(comment.value());\n\t\t\t}\n\t\t\tgrammer.setFunction(function);\n\t\t\tgrammers.add(grammer);\n\t\t}\n\n\t\tfor (FunctionExtension extension : functionExtensions) {\n\t\t\tString owner = extension.support().getSimpleName();\n\t\t\tgrammers.addAll(Grammer.findGrammers(extension.getClass(),null,owner,true));\n\t\t}\n\t\tfor (Grammerable grammerable : grammerables) {\n\t\t\tgrammers.addAll(grammerable.grammers());\n\t\t}\n\t}\n\n\t/**\n\t * 爬虫列表\n\t * @param page 页数\n\t * @param size 每页显示条数\n\t * @return Page<SpiderFlow> 所有爬虫的列表页\n\t */\n\t@RequestMapping(\"/list\")\n\tpublic IPage<SpiderFlow> list(@RequestParam(name = \"page\", defaultValue = \"1\") Integer page, @RequestParam(name = \"limit\", defaultValue = \"1\") Integer size, @RequestParam(name = \"name\", defaultValue = \"\") String name) {\n\t\treturn spiderFlowService.selectSpiderPage(new Page<>(page, size), name);\n\t}\n\n\t@RequestMapping(\"/save\")\n\tpublic String save(SpiderFlow spiderFlow){\n\t\tspiderFlowService.save(spiderFlow);\n\t\treturn spiderFlow.getId();\n\t}\n\n\t@RequestMapping(\"/history\")\n\tpublic JsonBean<?> history(String id,String timestamp){\n\t\tif(StringUtils.isNotBlank(timestamp)){\n\t\t\treturn new JsonBean<>(spiderFlowService.readHistory(id,timestamp));\n\t\t}else{\n\t\t\treturn new JsonBean<>(spiderFlowService.historyList(id));\n\t\t}\n\t}\n\n\t@RequestMapping(\"/get\")\n\tpublic SpiderFlow get(String id){\n\t\treturn spiderFlowService.getById(id);\n\t}\n\n\t@RequestMapping(\"/other\")\n\tpublic List<SpiderFlow> other(String id){\n\t\tif(StringUtils.isBlank(id)){\n\t\t\treturn spiderFlowService.selectFlows();\n\t\t}\n\t\treturn spiderFlowService.selectOtherFlows(id);\n\t}\n\n\t@RequestMapping(\"/remove\")\n\tpublic void remove(String id){\n\t\tspiderFlowService.remove(id);\n\t}\n\n\t@RequestMapping(\"/start\")\n\tpublic void start(String id){\n\t\tspiderFlowService.start(id);\n\t}\n\n\t@RequestMapping(\"/stop\")\n\tpublic void stop(String id){\n\t\tspiderFlowService.stop(id);\n\t}\n\n\t@RequestMapping(\"/copy\")\n\tpublic void copy(String id){\n\t\tspiderFlowService.copy(id);\n\t}\n\n\t@RequestMapping(\"/run\")\n\tpublic void run(String id){\n\t\tspiderFlowService.run(id);\n\t}\n\n\t@RequestMapping(\"/cron\")\n\tpublic void cron(String id,String cron){\n\t\tspiderFlowService.resetCornExpression(id, cron);\n\t}\n\n\t@RequestMapping(\"/xml\")\n\tpublic String xml(String id){\n\t\treturn spiderFlowService.getById(id).getXml();\n\t}\n\n\t@RequestMapping(\"/log/download\")\n\tpublic ResponseEntity<FileSystemResource> download(String id, String taskId)  {\n\t\tif (StringUtils.isBlank(taskId) || NumberUtils.toInt(taskId,0) == 0) {\n\t\t\tInteger maxId = spiderFlowService.getFlowMaxTaskId(id);\n\t\t\ttaskId = maxId == null ? \"\" : maxId.toString();\n\t\t}\n\t\tFile file = new File(workspace, id + File.separator + \"logs\" + File.separator + taskId + \".log\");\n\t\treturn ResponseEntity.ok()\n\t\t\t\t.header(\"Content-Disposition\",\"attachment; filename=spider.log\")\n\t\t\t\t.contentType(MediaType.parseMediaType(\"application/octet-stream\"))\n\t\t\t\t.body(new FileSystemResource(file));\n\t}\n\n\t@RequestMapping(\"/log\")\n\tpublic JsonBean<List<Line>> log(String id, String taskId, String keywords, Long index, Integer count, Boolean reversed, Boolean matchcase, Boolean regx) {\n\t\tif (StringUtils.isBlank(taskId)) {\n\t\t\tInteger maxId = spiderFlowService.getFlowMaxTaskId(id);\n\t\t\ttaskId = maxId == null ? \"\" : maxId.toString();\n\t\t}\n\t\tFile logFile = new File(workspace, id + File.separator + \"logs\" + File.separator + taskId + \".log\");\n\t\ttry (RandomAccessFileReader reader = new RandomAccessFileReader(new RandomAccessFile(logFile,\"r\"), index == null ? -1 : index, reversed == null || reversed)){\n\t\t\treturn new JsonBean<>(reader.readLine(count == null ? 10 : count,keywords,matchcase != null && matchcase,regx != null && regx));\n\t\t} catch(FileNotFoundException e){\n\t\t\treturn new JsonBean<>(0,\"日志文件不存在\");\n\t\t} catch (IOException e) {\n\t\t\tlogger.error(\"读取日志文件出错\",e);\n\t\t\treturn new JsonBean<>(-1,\"读取日志文件出错\");\n\t\t}\n\t}\n\n\t@RequestMapping(\"/shapes\")\n\tpublic List<Shape> shapes(){\n\t\treturn ExecutorsUtils.shapes();\n\t}\n\n\t@RequestMapping(\"/pluginConfigs\")\n\tpublic List<Plugin> pluginConfigs(){\n\t\treturn null == pluginConfigs ? Collections.emptyList() : pluginConfigs.stream().filter(e-> e.plugin() != null).map(plugin -> plugin.plugin()).collect(Collectors.toList());\n\t}\n\n\t@RequestMapping(\"/grammers\")\n\tpublic JsonBean<List<Grammer>> grammers(){\n\t\treturn new JsonBean<>(this.grammers);\n\t}\n\n\t@GetMapping(\"/recent5TriggerTime\")\n\tpublic List<String> getRecent5TriggerTime(String cron){\n\t\treturn spiderFlowService.getRecentTriggerTime(cron,5);\n\t}\n}\n"
  },
  {
    "path": "spider-flow-web/src/main/java/org/spiderflow/controller/SpiderRestController.java",
    "content": "package org.spiderflow.controller;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.spiderflow.context.SpiderContext;\r\nimport org.spiderflow.core.Spider;\r\nimport org.spiderflow.core.job.SpiderJob;\r\nimport org.spiderflow.core.job.SpiderJobContext;\r\nimport org.spiderflow.core.model.SpiderFlow;\r\nimport org.spiderflow.core.model.Task;\r\nimport org.spiderflow.core.service.SpiderFlowService;\r\nimport org.spiderflow.core.service.TaskService;\r\nimport org.spiderflow.model.JsonBean;\r\nimport org.spiderflow.model.SpiderOutput;\r\nimport org.springframework.beans.factory.annotation.Autowired;\r\nimport org.springframework.beans.factory.annotation.Value;\r\nimport org.springframework.web.bind.annotation.PathVariable;\r\nimport org.springframework.web.bind.annotation.RequestBody;\r\nimport org.springframework.web.bind.annotation.RequestMapping;\r\nimport org.springframework.web.bind.annotation.RestController;\r\n\r\nimport java.util.Date;\r\nimport java.util.List;\r\nimport java.util.Map;\r\n\r\n@RestController\r\n@RequestMapping(\"/rest\")\r\npublic class SpiderRestController {\r\n\t\r\n\tprivate static Logger logger = LoggerFactory.getLogger(SpiderRestController.class);\r\n\t\r\n\t@Autowired\r\n\tprivate SpiderFlowService spiderFlowService;\r\n\t\r\n\t@Autowired\r\n\tprivate Spider spider;\r\n\t\r\n\t@Value(\"${spider.workspace}\")\r\n\tprivate String workspace;\r\n\r\n\t@Autowired\r\n\tprivate SpiderJob spiderJob;\r\n\r\n\t@Autowired\r\n\tprivate TaskService taskService;\r\n\r\n\t/**\r\n\t * 异步运行\r\n\t * @param id\r\n\t * @return\r\n\t */\r\n\t@RequestMapping(\"/runAsync/{id}\")\r\n\tpublic JsonBean<Integer> runAsync(@PathVariable(\"id\")String id){\r\n\t\tSpiderFlow flow = spiderFlowService.getById(id);\r\n\t\tif(flow == null){\r\n\t\t\treturn new JsonBean<>(0, \"找不到此爬虫信息\");\r\n\t\t}\r\n\t\tTask task = new Task();\r\n\t\ttask.setFlowId(flow.getId());\r\n\t\ttask.setBeginTime(new Date());\r\n\t\ttaskService.save(task);\r\n\t\tSpider.executorInstance.submit(()->{\r\n\t\t\tspiderJob.run(flow,task,null);\r\n\t\t});\r\n\t\treturn new JsonBean<>(task.getId());\r\n\t}\r\n\r\n\t/**\r\n\t * 停止运行任务\r\n\t * @param taskId\r\n\t */\r\n\t@RequestMapping(\"/stop/{taskId}\")\r\n\tpublic JsonBean<Void> stop(@PathVariable(\"taskId\")Integer taskId){\r\n\t\tSpiderContext context = SpiderJob.getSpiderContext(taskId);\r\n\t\tif(context == null){\r\n\t\t\treturn new JsonBean<>(0,\"任务不存在！\");\r\n\t\t}\r\n\t\tcontext.setRunning(false);\r\n\t\treturn new JsonBean<>(1,\"停止成功！\");\r\n\r\n\t}\r\n\r\n\t/**\r\n\t * 查询任务状态\r\n\t * @param taskId\r\n\t */\r\n\t@RequestMapping(\"/status/{taskId}\")\r\n\tpublic JsonBean<Integer> status(@PathVariable(\"taskId\")Integer taskId){\r\n\t\tSpiderContext context = SpiderJob.getSpiderContext(taskId);\r\n\t\tif(context == null){\r\n\t\t\treturn new JsonBean<>(0);\t//\r\n\t\t}\r\n\t\treturn new JsonBean<>(1);\t//正在运行中\r\n\t}\r\n\r\n\t/**\r\n\t * 同步运行\r\n\t * @param id\r\n\t * @param params\r\n\t * @return\r\n\t */\r\n\t@RequestMapping(\"/run/{id}\")\r\n\tpublic JsonBean<List<SpiderOutput>> run(@PathVariable(\"id\")String id,@RequestBody(required = false)Map<String,Object> params){\r\n\t\tSpiderFlow flow = spiderFlowService.getById(id);\r\n\t\tif(flow == null){\r\n\t\t\treturn new JsonBean<>(0, \"找不到此爬虫信息\");\r\n\t\t}\r\n\t\tList<SpiderOutput> outputs;\r\n\t\tInteger maxId = spiderFlowService.getFlowMaxTaskId(id);\r\n\t\tSpiderJobContext context = SpiderJobContext.create(workspace, id,maxId,true);\r\n\t\ttry{\r\n\t\t\toutputs = spider.run(flow,context, params);\t\r\n\t\t}catch(Exception e){\r\n\t\t\tlogger.error(\"执行爬虫失败\",e);\r\n\t\t\treturn new JsonBean<>(-1, \"执行失败\");\r\n\t\t} finally{\r\n\t\t\tcontext.close();\r\n\t\t}\r\n\t\treturn new JsonBean<>(outputs);\r\n\t}\r\n\t\r\n}\r\n"
  },
  {
    "path": "spider-flow-web/src/main/java/org/spiderflow/controller/TaskController.java",
    "content": "package org.spiderflow.controller;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport org.spiderflow.context.SpiderContext;\nimport org.spiderflow.core.job.SpiderJob;\nimport org.spiderflow.core.model.Task;\nimport org.spiderflow.core.service.TaskService;\nimport org.spiderflow.model.JsonBean;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\n@RequestMapping(\"/task\")\npublic class TaskController {\n\n\t@Autowired\n\tprivate TaskService taskService;\n\n\t@RequestMapping(\"/list\")\n\tpublic IPage<Task> list(@RequestParam(name = \"page\", defaultValue = \"1\") Integer page, @RequestParam(name = \"limit\", defaultValue = \"1\") Integer size,String flowId){\n\t\treturn taskService.page(new Page<>(page,size),new QueryWrapper<Task>().eq(\"flow_id\",flowId).last(\"order by isnull(end_time) desc,end_time desc\"));\n\t}\n\n\t/**\n\t * 停止执行任务\n\t * @param id\n\t * @return\n\t */\n\t@RequestMapping(\"/stop\")\n\tpublic JsonBean<Boolean> stop(Integer id){\n\t\tSpiderContext context = SpiderJob.getSpiderContext(id);\n\t\tif(context != null){\n\t\t\tcontext.setRunning(false);\n\t\t}\n\t\treturn new JsonBean<>(context != null);\n\t}\n\n\t@RequestMapping(\"/remove\")\n\tpublic JsonBean<Boolean> remove(Integer id){\n\t\t//删除任务记录之前先停止\n\t\tSpiderContext context = SpiderJob.getSpiderContext(id);\n\t\tif(context != null){\n\t\t\tcontext.setRunning(false);\n\t\t}\n\t\treturn new JsonBean<>(taskService.removeById(id));\n\t}\n}\n"
  },
  {
    "path": "spider-flow-web/src/main/java/org/spiderflow/controller/VariableController.java",
    "content": "package org.spiderflow.controller;\n\nimport org.spiderflow.common.CURDController;\nimport org.spiderflow.core.mapper.VariableMapper;\nimport org.spiderflow.core.model.Variable;\nimport org.spiderflow.core.service.VariableService;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\n@RequestMapping(\"/variable\")\npublic class VariableController extends CURDController<VariableService, VariableMapper, Variable> {\n}\n"
  },
  {
    "path": "spider-flow-web/src/main/java/org/spiderflow/logback/SpiderFlowFileAppender.java",
    "content": "package org.spiderflow.logback;\n\nimport ch.qos.logback.classic.spi.ILoggingEvent;\nimport ch.qos.logback.core.FileAppender;\nimport ch.qos.logback.core.spi.DeferredProcessingAware;\nimport ch.qos.logback.core.status.ErrorStatus;\nimport org.spiderflow.context.SpiderContext;\nimport org.spiderflow.context.SpiderContextHolder;\nimport org.spiderflow.core.job.SpiderJobContext;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\npublic class SpiderFlowFileAppender extends FileAppender<ILoggingEvent> {\n\n    @Override\n    protected void subAppend(ILoggingEvent event) {\n        SpiderContext context = SpiderContextHolder.get();\n        OutputStream os = getOutputStream();\n        if (context instanceof SpiderJobContext) {\n            SpiderJobContext jobContext = (SpiderJobContext) context;\n            os = jobContext.getOutputstream();\n        }\n        try {\n            if (event instanceof DeferredProcessingAware) {\n                ((DeferredProcessingAware) event).prepareForDeferredProcessing();\n            }\n            byte[] byteArray = this.encoder.encode(event);\n            writeBytes(os, byteArray);\n\n        } catch (IOException ioe) {\n            this.started = false;\n            addStatus(new ErrorStatus(\"IO failure in appender\", this, ioe));\n        }\n    }\n\n    private void writeBytes(OutputStream os, byte[] byteArray) throws IOException {\n        if (byteArray == null || byteArray.length == 0)\n            return;\n\n        lock.lock();\n        try {\n            os.write(byteArray);\n            if (isImmediateFlush()) {\n                os.flush();\n            }\n        } finally {\n            lock.unlock();\n        }\n    }\n\n}\n"
  },
  {
    "path": "spider-flow-web/src/main/java/org/spiderflow/logback/SpiderFlowWebSocketAppender.java",
    "content": "package org.spiderflow.logback;\n\nimport ch.qos.logback.classic.spi.ILoggingEvent;\nimport ch.qos.logback.classic.spi.ThrowableProxy;\nimport ch.qos.logback.core.UnsynchronizedAppenderBase;\nimport org.spiderflow.context.SpiderContext;\nimport org.spiderflow.context.SpiderContextHolder;\nimport org.spiderflow.model.SpiderLog;\nimport org.spiderflow.model.SpiderWebSocketContext;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\npublic class SpiderFlowWebSocketAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {\n\n\t@Override\n\tprotected void append(ILoggingEvent event) {\n\t\tSpiderContext context = SpiderContextHolder.get();\n\t\tif(context instanceof SpiderWebSocketContext){\n\t\t\tSpiderWebSocketContext socketContext = (SpiderWebSocketContext) context;\n\t\t\tObject[] argumentArray = event.getArgumentArray();\n\t\t\tList<Object> arguments = argumentArray == null ? Collections.emptyList()  : new ArrayList<>(Arrays.asList(argumentArray));\n\t\t\tThrowableProxy throwableProxy = (ThrowableProxy) event.getThrowableProxy();\n\t\t\tif(throwableProxy != null){\n\t\t\t\targuments.add(throwableProxy.getThrowable());\n\t\t\t}\n\t\t\tsocketContext.log(new SpiderLog(event.getLevel().levelStr.toLowerCase(),event.getMessage(),arguments));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "spider-flow-web/src/main/java/org/spiderflow/model/SpiderWebSocketContext.java",
    "content": "package org.spiderflow.model;\n\nimport com.alibaba.fastjson.JSON;\nimport org.apache.commons.lang3.time.DateFormatUtils;\nimport org.spiderflow.context.SpiderContext;\nimport org.spiderflow.core.serializer.FastJsonSerializer;\n\nimport javax.websocket.Session;\nimport java.util.Date;\n\n/**\n * WebSocket通讯中爬虫的上下文域\n *\n * @author Administrator\n */\npublic class SpiderWebSocketContext extends SpiderContext {\n\n    private static final long serialVersionUID = -1205530535069540245L;\n\n    private Session session;\n\n    private boolean debug;\n\n    private Object lock = new Object();\n\n    public SpiderWebSocketContext(Session session) {\n        this.session = session;\n    }\n\n    public boolean isDebug() {\n        return debug;\n    }\n\n    public void setDebug(boolean debug) {\n        this.debug = debug;\n    }\n\n    @Override\n    public void addOutput(SpiderOutput output) {\n        this.write(new WebSocketEvent<>(\"output\", output));\n    }\n\n    public void log(SpiderLog log) {\n        write(new WebSocketEvent<>(\"log\", DateFormatUtils.format(new Date(), \"yyyy-MM-dd HH:mm:ss.SSS\"), log));\n    }\n\n    public <T> void write(WebSocketEvent<T> event) {\n        try {\n            String message = JSON.toJSONString(event, FastJsonSerializer.serializeConfig);\n            if(session.isOpen()){\n                synchronized (session){\n                    session.getBasicRemote().sendText(message);\n                }\n            }\n        } catch (Throwable ignored) {\n        }\n    }\n\n    @Override\n    public void pause(String nodeId, String event, String key, Object value) {\n        if(this.debug && this.isRunning()) {\n            synchronized (this) {\n                if(this.debug && this.isRunning()) {\n                    synchronized (lock) {\n                        try {\n                            write(new WebSocketEvent<>(\"debug\", new DebugInfo(nodeId, event, key, value)));\n                            lock.wait();\n                        } catch (InterruptedException ignored) {\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    @Override\n    public void resume() {\n        if(this.debug){\n            synchronized (lock){\n                lock.notify();\n            }\n        }\n    }\n\n    @Override\n    public void stop() {\n        if(this.debug){\n            synchronized (lock){\n                lock.notifyAll();\n            }\n        }\n    }\n\n    class DebugInfo{\n\n        private String nodeId;\n\n        private String event;\n\n        private String key;\n\n        private Object value;\n\n        public DebugInfo(String nodeId, String event, String key, Object value) {\n            this.nodeId = nodeId;\n            this.event = event;\n            this.key = key;\n            this.value = value;\n        }\n\n        public String getNodeId() {\n            return nodeId;\n        }\n\n        public void setNodeId(String nodeId) {\n            this.nodeId = nodeId;\n        }\n\n        public String getEvent() {\n            return event;\n        }\n\n        public void setEvent(String event) {\n            this.event = event;\n        }\n\n        public String getKey() {\n            return key;\n        }\n\n        public void setKey(String key) {\n            this.key = key;\n        }\n\n        public Object getValue() {\n            return value;\n        }\n\n        public void setValue(Object value) {\n            this.value = value;\n        }\n    }\n}\n"
  },
  {
    "path": "spider-flow-web/src/main/java/org/spiderflow/model/WebSocketEvent.java",
    "content": "package org.spiderflow.model;\n\n/**\n * WebSocket事件\n * @author Administrator\n *\n * @param <T>\n */\npublic class WebSocketEvent<T> {\n\t\n\tprivate String eventType;\n\t\n\tprivate String timestamp;\n\t\n\tprivate T message;\n\t\n\tpublic String getTimestamp() {\n\t\treturn timestamp;\n\t}\n\n\tpublic void setTimestamp(String timestamp) {\n\t\tthis.timestamp = timestamp;\n\t}\n\n\tpublic WebSocketEvent(String eventType, T message) {\n\t\tthis.eventType = eventType;\n\t\tthis.message = message;\n\t}\n\t\n\tpublic WebSocketEvent(String eventType, String timestamp, T message) {\n\t\tthis.eventType = eventType;\n\t\tthis.timestamp = timestamp;\n\t\tthis.message = message;\n\t}\n\n\tpublic String getEventType() {\n\t\treturn eventType;\n\t}\n\n\tpublic void setEventType(String eventType) {\n\t\tthis.eventType = eventType;\n\t}\n\n\tpublic T getMessage() {\n\t\treturn message;\n\t}\n\n\tpublic void setMessage(T message) {\n\t\tthis.message = message;\n\t}\n}\n"
  },
  {
    "path": "spider-flow-web/src/main/java/org/spiderflow/websocket/WebSocketEditorServer.java",
    "content": "package org.spiderflow.websocket;\r\n\r\nimport com.alibaba.fastjson.JSON;\r\nimport com.alibaba.fastjson.JSONObject;\r\nimport org.spiderflow.core.Spider;\r\nimport org.spiderflow.core.utils.SpiderFlowUtils;\r\nimport org.spiderflow.model.SpiderWebSocketContext;\r\nimport org.spiderflow.model.WebSocketEvent;\r\nimport org.springframework.stereotype.Component;\r\n\r\nimport javax.websocket.OnClose;\r\nimport javax.websocket.OnMessage;\r\nimport javax.websocket.Session;\r\nimport javax.websocket.server.ServerEndpoint;\r\n\r\n/**\r\n * WebSocket通讯编辑服务\r\n *\r\n * @author Administrator\r\n */\r\n@ServerEndpoint(\"/ws\")\r\n@Component\r\npublic class WebSocketEditorServer {\r\n\r\n    public static Spider spider;\r\n\r\n    private SpiderWebSocketContext context;\r\n\r\n    @OnMessage\r\n    public void onMessage(String message, Session session) {\r\n        JSONObject event = JSON.parseObject(message);\r\n        String eventType = event.getString(\"eventType\");\r\n        boolean isDebug = \"debug\".equalsIgnoreCase(eventType);\r\n        if (\"test\".equalsIgnoreCase(eventType) || isDebug) {\r\n            context = new SpiderWebSocketContext(session);\r\n            context.setDebug(isDebug);\r\n            context.setRunning(true);\r\n            new Thread(() -> {\r\n                String xml = event.getString(\"message\");\r\n                if (xml != null) {\r\n                    spider.runWithTest(SpiderFlowUtils.loadXMLFromString(xml), context);\r\n                    context.write(new WebSocketEvent<>(\"finish\", null));\r\n                } else {\r\n                    context.write(new WebSocketEvent<>(\"error\", \"xml不正确！\"));\r\n                }\r\n                context.setRunning(false);\r\n            }).start();\r\n        } else if (\"stop\".equals(eventType) && context != null) {\r\n            context.setRunning(false);\r\n            context.stop();\r\n        } else if(\"resume\".equalsIgnoreCase(eventType) && context != null){\r\n            context.resume();\r\n        }\r\n    }\r\n\r\n    @OnClose\r\n    public void onClose(Session session) {\r\n        context.setRunning(false);\r\n        context.stop();\r\n    }\r\n}\r\n"
  },
  {
    "path": "spider-flow-web/src/main/resources/application.properties",
    "content": "server.port=8088\n\nlogging.level.root=INFO\n#logging.level.org.spiderflow=DEBUG\n#平台最大线程数\nspider.thread.max=64\n#单任务默认最大线程数\nspider.thread.default=8\n#设置为true时定时任务才生效\nspider.job.enable=false\n#爬虫任务的工作空间\nspider.workspace=/data/spider\n#布隆过滤器默认容量\nspider.bloomfilter.capacity=1000000\n#布隆过滤器默认容错率\nspider.bloomfilter.error-rate=0.0001\n\n#死循环检测(节点执行次数超过该值时认为是死循环)默认值为5000\n#spider.detect.dead-cycle=5000\n\nspring.jackson.date-format=yyyy-MM-dd HH:mm:ss\nspring.jackson.time-zone=GMT+8\nspring.jackson.serialization.fail_on_empty_beans=false\n\nspring.mvc.favicon.enabled=false\n\nspring.datasource.driver-class-name=com.mysql.jdbc.Driver\nspring.datasource.username=root\nspring.datasource.password=123456789\nspring.datasource.url=jdbc:mysql://localhost:3306/spiderflow?useSSL=false&useUnicode=true&characterEncoding=UTF8&autoReconnect=true\n\n#JavaMailSender 邮件发送的配置\nspring.mail.protocol=smtp\nspring.mail.host=smtp.qq.com\nspring.mail.port=465\nspring.mail.username=xxxx@qq.com\nspring.mail.password=xxxx\nspring.mail.default-encoding=UTF-8\nspring.mail.properties.mail.smtp.auth=true\nspring.mail.properties.mail.smtp.starttls.enable=true\nspring.mail.properties.mail.smtp.starttls.required=true\nspring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory\nspring.mail.properties.mail.smtp.socketFactory.port=465\nspring.mail.properties.mail.smtp.socketFactory.fallback=false\n\nspring.autoconfigure.exclude=org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration\n\n#selenium 插件配置\n\n#设置chrome的WebDriver驱动路径，下载地址：http://npm.taobao.org/mirrors/chromedriver/，注意版本问题\nselenium.driver.chrome=E:/driver/chromedriver.exe\n#设置fireFox的WebDriver驱动路径，下载地址：https://github.com/mozilla/geckodriver/releases\nselenium.driver.firefox=E:/driver/geckodriver.exe\n\n#爬虫通知相关内容配置,可使用SpiderFlow中的变量名和以下变量名:currentDate:当前发送时间\nspider.notice.subject=spider-flow流程通知\nspider.notice.content.start=流程开始执行：{name}，开始时间：{currentDate}\nspider.notice.content.end=流程执行完毕：{name}，结束时间：{currentDate}\nspider.notice.content.exception=流程发生异常：{name}，异常时间：{currentDate}"
  },
  {
    "path": "spider-flow-web/src/main/resources/logback-spring.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration>\n    <springProperty scope=\"context\" name=\"LOG_LEVEL\" source=\"logging.level.root\" defaultValue=\"DEBUG\"/>\n    <springProperty scope=\"context\" name=\"WORKSPACE\" source=\"spider.workspace\" defaultValue=\"/data/spider/logs\"/>\n    <!-- 控制台输出 -->\n    <appender name=\"Stdout\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder class=\"ch.qos.logback.classic.encoder.PatternLayoutEncoder\">\n            <!--格式化输出：%d表示日期，%thread表示线程名，%-5level：级别从左显示5个字符宽度%msg：日志消息，%n是换行符-->\n            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg  %n</pattern>\n        </encoder>\n    </appender>\n    <!-- 输出日志文件 -->\n    <appender name=\"File\"  class=\"org.spiderflow.logback.SpiderFlowFileAppender\">\n        <file>${WORKSPACE}/logs/spider-flow.log</file>\n        <append>true</append>\n        <encoder class=\"ch.qos.logback.classic.encoder.PatternLayoutEncoder\">\n            <!--格式化输出：%d表示日期，%thread表示线程名，%-5level：级别从左显示5个字符宽度%msg：日志消息，%n是换行符-->\n            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{50} - %msg%n</pattern>\n        </encoder>\n    </appender>\n    <!-- WebSocket输出日志 -->\n    <appender name=\"WebSocket\" class=\"org.spiderflow.logback.SpiderFlowWebSocketAppender\"/>\n    <!-- 日志输出级别 -->\n    <root level=\"${LOG_LEVEL}\">\n        <appender-ref ref=\"Stdout\" />\n        <appender-ref ref=\"File\" />\n        <appender-ref ref=\"WebSocket\" />\n    </root>\n</configuration>"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/css/easyui.css",
    "content": ".panel{overflow:hidden;text-align:left}.panel-header,.panel-body{border-width:1px;border-style:solid}.panel-header{padding:5px;position:relative}.panel-title{background:url('images/blank.gif') no-repeat}.panel-header-noborder{border-width:0 0 1px 0}.panel-body{overflow:auto;border-top-width:0}.panel-body-noheader{border-top-width:1px}.panel-body-noborder{border-width:0}.panel-with-icon{padding-left:18px}.panel-icon,.panel-tool{position:absolute;top:50%;margin-top:-8px;height:16px;overflow:hidden}.panel-icon{left:5px;width:16px}.panel-tool{right:5px;width:auto}.panel-tool a{display:inline-block;width:16px;height:16px;opacity:.6;filter:alpha(opacity=60);margin:0 0 0 2px;vertical-align:top}.panel-tool a:hover{opacity:1;filter:alpha(opacity=100);background-color:#e6e6e6;-moz-border-radius:3px 3px 3px 3px;-webkit-border-radius:3px 3px 3px 3px;border-radius:3px 3px 3px 3px}.panel-loading{padding:11px 0 10px 30px}.panel-noscroll{overflow:hidden}.panel-fit,.panel-fit body{height:100%;margin:0;padding:0;border:0;overflow:hidden}.panel-loading{background:url('images/loading.gif') no-repeat 10px 10px}.panel-tool-close{background:url('images/panel_tools.png') no-repeat -16px 0}.panel-tool-min{background:url('images/panel_tools.png') no-repeat 0 0}.panel-tool-max{background:url('images/panel_tools.png') no-repeat 0 -16px}.panel-tool-restore{background:url('images/panel_tools.png') no-repeat -16px -16px}.panel-tool-collapse{background:url('images/panel_tools.png') no-repeat -32px 0}.panel-tool-expand{background:url('images/panel_tools.png') no-repeat -32px -16px}.panel-header,.panel-body{border-color:#d4d4d4}.panel-header{background-color:#f2f2f2;background:-webkit-linear-gradient(top,#fff 0,#f2f2f2 100%);background:-moz-linear-gradient(top,#fff 0,#f2f2f2 100%);background:-o-linear-gradient(top,#fff 0,#f2f2f2 100%);background:linear-gradient(to bottom,#fff 0,#f2f2f2 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffffff,endColorstr=#F2F2F2,GradientType=0)}.panel-title{font-size:12px;font-weight:bold;color:#777;height:16px;line-height:16px}.panel-body{background-color:#fff;color:#333;font-size:12px;padding:10px 8px 8px 8px}.panel-body .line{height:25px;margin:3px}.panel-body .imp{padding-left:25px}.panel-body .col{width:60px}.panel-body ul{list-style:none;padding-left:10px}.panel-body li{height:20px}.accordion{overflow:hidden;border-width:1px;border-style:solid}.accordion .accordion-header{border-width:0 0 1px;cursor:pointer}.accordion .accordion-body{border-width:0 0 1px}.accordion-noborder{border-width:0}.accordion-noborder .accordion-header{border-width:0 0 1px}.accordion-noborder .accordion-body{border-width:0 0 1px}.accordion-collapse{background:url('images/accordion_arrows.png') no-repeat 0 0}.accordion-expand{background:url('images/accordion_arrows.png') no-repeat -16px 0}.accordion{background:#fff;border-color:#d4d4d4}.accordion .accordion-header{background:#f2f2f2;filter:none}.accordion .accordion-header-selected{background:#0081c2}.accordion .accordion-header-selected .panel-title{color:#fff}.window{overflow:hidden;padding:5px;border-width:1px;border-style:solid}.window .window-header{background:transparent;padding:0 0 6px 0}.window .window-body{border-width:1px;border-style:solid;border-top-width:0}.window .window-body-noheader{border-top-width:1px}.window .window-header .panel-icon,.window .window-header .panel-tool{top:50%;margin-top:-11px}.window .window-header .panel-icon{left:1px}.window .window-header .panel-tool{right:1px}.window .window-header .panel-with-icon{padding-left:18px}.window-proxy{position:absolute;overflow:hidden}.window-proxy-mask{position:absolute;filter:alpha(opacity=5);opacity:.05}.window-mask{position:absolute;left:0;top:0;width:100%;height:100%;filter:alpha(opacity=40);opacity:.40;font-size:1px;*zoom:1;overflow:hidden}.window,.window-shadow{position:absolute;-moz-border-radius:5px 5px 5px 5px;-webkit-border-radius:5px 5px 5px 5px;border-radius:5px 5px 5px 5px}.window-shadow{background:#ccc;-moz-box-shadow:2px 2px 3px #ccc;-webkit-box-shadow:2px 2px 3px #ccc;box-shadow:2px 2px 3px #ccc;filter:progid:DXImageTransform.Microsoft.Blur(pixelRadius=2,MakeShadow=false,ShadowOpacity=0.2)}.window,.window .window-body{border-color:#d4d4d4}.window{background-color:#f2f2f2;background:-webkit-linear-gradient(top,#fff 0,#f2f2f2 20%);background:-moz-linear-gradient(top,#fff 0,#f2f2f2 20%);background:-o-linear-gradient(top,#fff 0,#f2f2f2 20%);background:linear-gradient(to bottom,#fff 0,#f2f2f2 20%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffffff,endColorstr=#F2F2F2,GradientType=0)}.window-proxy{border:1px dashed #d4d4d4}.window-proxy-mask,.window-mask{background:#ccc}.dialog-content{overflow:auto}.dialog-toolbar{padding:2px 5px}.dialog-tool-separator{float:left;height:24px;border-left:1px solid #ccc;border-right:1px solid #fff;margin:2px 1px}.dialog-button{padding:5px;text-align:right}.dialog-button .l-btn{margin-left:5px}.dialog-toolbar,.dialog-button{background:#f5f5f5}.dialog-toolbar{border-bottom:1px solid #e6e6e6}.dialog-button{border-top:1px solid #e6e6e6}.combo{display:inline-block;white-space:nowrap;margin:0;padding:0;border-width:1px;border-style:solid;overflow:hidden;vertical-align:middle}.combo .combo-text{font-size:12px;border:0;line-height:20px;height:20px;margin:0;padding:0 2px;*margin-top:-1px;*height:18px;*line-height:18px;_height:18px;_line-height:18px;vertical-align:baseline}.combo-arrow{width:18px;height:20px;overflow:hidden;display:inline-block;vertical-align:top;cursor:pointer;opacity:.6;filter:alpha(opacity=60)}.combo-arrow-hover{opacity:1.0;filter:alpha(opacity=100)}.combo-panel{overflow:auto}.combo-arrow{background:url('images/combo_arrow.png') no-repeat center center}.combo,.combo-panel{background-color:#fff}.combo{border-color:#d4d4d4;background-color:#fff}.combo-arrow{background-color:#f2f2f2}.combo-arrow-hover{background-color:#e6e6e6}.combobox-item{padding:2px;font-size:12px;padding:3px;padding-right:0}.combobox-item-hover{background-color:#e6e6e6;color:#00438a}.combobox-item-selected{background-color:#0081c2;color:#fff}.layout{position:relative;overflow:hidden;margin:0;padding:0;z-index:0}.layout-panel{position:absolute;overflow:hidden}.layout-panel-east,.layout-panel-west{z-index:2}.layout-panel-north,.layout-panel-south{z-index:3}.layout-expand{position:absolute;padding:0;font-size:1px;cursor:pointer;z-index:1}.layout-expand .panel-header,.layout-expand .panel-body{background:transparent;filter:none;overflow:hidden}.layout-expand .panel-header{border-bottom-width:0}.layout-split-proxy-h,.layout-split-proxy-v{position:absolute;font-size:1px;display:none;z-index:5}.layout-split-proxy-h{width:5px;cursor:e-resize}.layout-split-proxy-v{height:5px;cursor:n-resize}.layout-mask{position:absolute;background:#fafafa;filter:alpha(opacity=10);opacity:.10;z-index:4}.layout-button-up{background:url('images/layout_arrows.png') no-repeat -16px -16px}.layout-button-down{background:url('images/layout_arrows.png') no-repeat -16px 0}.layout-button-left{background:url('images/layout_arrows.png') no-repeat 0 0}.layout-button-right{background:url('images/layout_arrows.png') no-repeat 0 -16px}.layout-split-proxy-h,.layout-split-proxy-v{background-color:#bbb}.layout-split-north{border-bottom:5px solid #eee}.layout-split-south{border-top:5px solid #eee}.layout-split-east{border-left:5px solid #eee}.layout-split-west{border-right:5px solid #eee}.layout-expand{background-color:#f2f2f2}.layout-expand-over{background-color:#f2f2f2}.tabs-container{overflow:hidden}.tabs-header{border-width:1px;border-style:solid;border-bottom-width:0;position:relative;padding:0;padding-top:2px;overflow:hidden}.tabs-header-plain{border:0;background:transparent}.tabs-scroller-left,.tabs-scroller-right{position:absolute;top:auto;bottom:0;width:18px;height:28px!important;height:30px;font-size:1px;display:none;cursor:pointer;border-width:1px;border-style:solid}.tabs-scroller-left{left:0}.tabs-scroller-right{right:0}.tabs-header-plain .tabs-scroller-left,.tabs-header-plain .tabs-scroller-right{height:25px!important;height:27px}.tabs-tool{position:absolute;bottom:0;padding:1px;overflow:hidden;border-width:1px;border-style:solid}.tabs-header-plain .tabs-tool{padding:0 1px}.tabs-wrap{position:relative;left:0;overflow:hidden;width:100%;margin:0;padding:0}.tabs-scrolling{margin-left:18px;margin-right:18px}.tabs-disabled{opacity:.3;filter:alpha(opacity=30)}.tabs{list-style-type:none;height:26px;margin:0;padding:0;padding-left:4px;width:5000px;border-style:solid;border-width:0 0 1px 0}.tabs li{float:left;display:inline-block;margin:0 4px -1px 0;padding:0;position:relative;border:0}.tabs li a.tabs-inner{display:inline-block;text-decoration:none;margin:0;padding:0 10px;height:25px;line-height:25px;text-align:center;white-space:nowrap;border-width:1px;border-style:solid;-moz-border-radius:5px 5px 0 0;-webkit-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.tabs li.tabs-selected a.tabs-inner{font-weight:bold;outline:0}.tabs li.tabs-selected a:hover.tabs-inner{cursor:default;pointer:default}.tabs li a.tabs-close,.tabs-p-tool{position:absolute;font-size:1px;display:block;height:12px;padding:0;top:50%;margin-top:-6px;overflow:hidden}.tabs li a.tabs-close{width:12px;right:5px;opacity:.6;filter:alpha(opacity=60)}.tabs-p-tool{right:16px}.tabs-p-tool a{display:inline-block;font-size:1px;width:12px;height:12px;margin:0;opacity:.6;filter:alpha(opacity=60)}.tabs li a:hover.tabs-close,.tabs-p-tool a:hover{opacity:1;filter:alpha(opacity=100);cursor:hand;cursor:pointer}.tabs-with-icon{padding-left:18px}.tabs-icon{position:absolute;width:16px;height:16px;left:10px;top:50%;margin-top:-8px}.tabs-title{font-size:12px}.tabs-closable{padding-right:8px}.tabs-panels{margin:0;padding:0;border-width:1px;border-style:solid;border-top-width:0;overflow:hidden}.tabs-header-bottom{border-width:0 1px 1px 1px;padding:0 0 2px 0}.tabs-header-bottom .tabs{border-width:1px 0 0 0}.tabs-header-bottom .tabs li{margin:-1px 4px 0 0}.tabs-header-bottom .tabs li a.tabs-inner{-moz-border-radius:0 0 5px 5px;-webkit-border-radius:0 0 5px 5px;border-radius:0 0 5px 5px}.tabs-header-bottom .tabs-tool{top:0}.tabs-header-bottom .tabs-scroller-left,.tabs-header-bottom .tabs-scroller-right{top:0;bottom:auto}.tabs-panels-top{border-width:1px 1px 0 1px}.tabs-header-left{float:left;border-width:1px 0 1px 1px;padding:0}.tabs-header-right{float:right;border-width:1px 1px 1px 0;padding:0}.tabs-header-left .tabs-wrap,.tabs-header-right .tabs-wrap{height:100%}.tabs-header-left .tabs{height:100%;padding:4px 0 0 4px;border-width:0 1px 0 0}.tabs-header-right .tabs{height:100%;padding:4px 4px 0 0;border-width:0 0 0 1px}.tabs-header-left .tabs li,.tabs-header-right .tabs li{display:block;width:100%;position:relative}.tabs-header-left .tabs li{left:auto;right:0;margin:0 -1px 4px 0;float:right}.tabs-header-right .tabs li{left:0;right:auto;margin:0 0 4px -1px;float:left}.tabs-header-left .tabs li a.tabs-inner{display:block;text-align:left;-moz-border-radius:5px 0 0 5px;-webkit-border-radius:5px 0 0 5px;border-radius:5px 0 0 5px}.tabs-header-right .tabs li a.tabs-inner{display:block;text-align:left;-moz-border-radius:0 5px 5px 0;-webkit-border-radius:0 5px 5px 0;border-radius:0 5px 5px 0}.tabs-panels-right{float:right;border-width:1px 1px 1px 0}.tabs-panels-left{float:left;border-width:1px 0 1px 1px}.tabs-header-noborder,.tabs-panels-noborder{border:0}.tabs-header-plain{border:0;background:transparent}.tabs-scroller-left{background:#f2f2f2 url('images/tabs_icons.png') no-repeat 1px center}.tabs-scroller-right{background:#f2f2f2 url('images/tabs_icons.png') no-repeat -15px center}.tabs li a.tabs-close{background:url('images/tabs_icons.png') no-repeat -34px center}.tabs li a.tabs-inner:hover{background:#e6e6e6;color:#00438a;filter:none}.tabs li.tabs-selected a.tabs-inner{background-color:#fff;color:#777;background:-webkit-linear-gradient(top,#fff 0,#fff 100%);background:-moz-linear-gradient(top,#fff 0,#fff 100%);background:-o-linear-gradient(top,#fff 0,#fff 100%);background:linear-gradient(to bottom,#fff 0,#fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffffff,endColorstr=#ffffff,GradientType=0)}.tabs-header-bottom .tabs li.tabs-selected a.tabs-inner{background:-webkit-linear-gradient(top,#fff 0,#fff 100%);background:-moz-linear-gradient(top,#fff 0,#fff 100%);background:-o-linear-gradient(top,#fff 0,#fff 100%);background:linear-gradient(to bottom,#fff 0,#fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffffff,endColorstr=#ffffff,GradientType=0)}.tabs-header-left .tabs li.tabs-selected a.tabs-inner{background:-webkit-linear-gradient(left,#fff 0,#fff 100%);background:-moz-linear-gradient(left,#fff 0,#fff 100%);background:-o-linear-gradient(left,#fff 0,#fff 100%);background:linear-gradient(to right,#fff 0,#fff 100%);background-repeat:repeat-y;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffffff,endColorstr=#ffffff,GradientType=1)}.tabs-header-right .tabs li.tabs-selected a.tabs-inner{background:-webkit-linear-gradient(left,#fff 0,#fff 100%);background:-moz-linear-gradient(left,#fff 0,#fff 100%);background:-o-linear-gradient(left,#fff 0,#fff 100%);background:linear-gradient(to right,#fff 0,#fff 100%);background-repeat:repeat-y;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffffff,endColorstr=#ffffff,GradientType=1)}.tabs li a.tabs-inner{color:#777;background-color:#f2f2f2;background:-webkit-linear-gradient(top,#fff 0,#f2f2f2 100%);background:-moz-linear-gradient(top,#fff 0,#f2f2f2 100%);background:-o-linear-gradient(top,#fff 0,#f2f2f2 100%);background:linear-gradient(to bottom,#fff 0,#f2f2f2 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffffff,endColorstr=#F2F2F2,GradientType=0)}.tabs-header,.tabs-tool{background-color:#f2f2f2}.tabs-header-plain{background:transparent}.tabs-header,.tabs-scroller-left,.tabs-scroller-right,.tabs-tool,.tabs,.tabs-panels,.tabs li a.tabs-inner,.tabs li.tabs-selected a.tabs-inner,.tabs-header-bottom .tabs li.tabs-selected a.tabs-inner,.tabs-header-left .tabs li.tabs-selected a.tabs-inner,.tabs-header-right .tabs li.tabs-selected a.tabs-inner{border-color:#d4d4d4}.tabs-p-tool a:hover,.tabs li a:hover.tabs-close,.tabs-scroller-over{background-color:#e6e6e6}.tabs li.tabs-selected a.tabs-inner{border-bottom:1px solid #fff}.tabs-header-bottom .tabs li.tabs-selected a.tabs-inner{border-top:1px solid #fff}.tabs-header-left .tabs li.tabs-selected a.tabs-inner{border-right:1px solid #fff}.tabs-header-right .tabs li.tabs-selected a.tabs-inner{border-left:1px solid #fff}a.l-btn{background-position:right 0;text-decoration:none;display:inline-block;zoom:1;height:24px;padding-right:18px;cursor:pointer;outline:0}a.l-btn-plain{padding-right:5px;border:0;padding:1px 6px 1px 1px}a.l-btn-disabled{color:#ccc;opacity:.5;filter:alpha(opacity=50);cursor:default}a.l-btn span.l-btn-left{display:inline-block;background-position:0 -48px;padding:4px 0 4px 18px;line-height:16px;height:16px}a.l-btn-plain span.l-btn-left{padding-left:5px}a.l-btn span span.l-btn-text{display:inline-block;vertical-align:baseline;width:auto;height:16px;line-height:16px;font-size:12px;padding:0;margin:0}a.l-btn span span.l-btn-icon-left{padding:0 0 0 20px;background-position:left center}a.l-btn span span.l-btn-icon-right{padding:0 20px 0 0;background-position:right center}a.l-btn span span span.l-btn-empty{display:inline-block;margin:0;padding:0;width:16px}a:hover.l-btn{background-position:right -24px;outline:0;text-decoration:none}a:hover.l-btn span.l-btn-left{background-position:0 bottom}a:hover.l-btn-plain{padding:0 5px 0 0}a:hover.l-btn-disabled{background-position:right 0}a:hover.l-btn-disabled span.l-btn-left{background-position:0 -48px}a.l-btn .l-btn-focus{outline:#00f dotted thin}a.l-btn{color:#444;background-image:url('images/linkbutton_bg.png');background-repeat:no-repeat;background:#f5f5f5;background-repeat:repeat-x;border:1px solid #bbb;background:-webkit-linear-gradient(top,#fff 0,#e6e6e6 100%);background:-moz-linear-gradient(top,#fff 0,#e6e6e6 100%);background:-o-linear-gradient(top,#fff 0,#e6e6e6 100%);background:linear-gradient(to bottom,#fff 0,#e6e6e6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffffff,endColorstr=#e6e6e6,GradientType=0);-moz-border-radius:5px 5px 5px 5px;-webkit-border-radius:5px 5px 5px 5px;border-radius:5px 5px 5px 5px}a.l-btn span.l-btn-left{background-image:url('images/linkbutton_bg.png');background-repeat:no-repeat;background-image:none}a:hover.l-btn{background:#e6e6e6;color:#00438a;border:1px solid #ddd;filter:none}a.l-btn-plain,a.l-btn-plain span.l-btn-left{background:transparent;border:0;filter:none}a:hover.l-btn-plain{background:#e6e6e6;color:#00438a;border:1px solid #ddd;-moz-border-radius:5px 5px 5px 5px;-webkit-border-radius:5px 5px 5px 5px;border-radius:5px 5px 5px 5px}a.l-btn-disabled,a:hover.l-btn-disabled{color:#444;filter:alpha(opacity=50);background:#f5f5f5;color:#444;background:-webkit-linear-gradient(top,#fff 0,#e6e6e6 100%);background:-moz-linear-gradient(top,#fff 0,#e6e6e6 100%);background:-o-linear-gradient(top,#fff 0,#e6e6e6 100%);background:linear-gradient(to bottom,#fff 0,#e6e6e6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffffff,endColorstr=#e6e6e6,GradientType=0);filter:alpha(opacity=50) progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffffff,endColorstr=#e6e6e6,GradientType=0)}a.l-btn-plain-disabled,a:hover.l-btn-plain-disabled{background:transparent;filter:alpha(opacity=50)}a.l-btn-selected,a:hover.l-btn-selected{background-position:right -24px;background:#ddd;filter:none}a.l-btn-selected span.l-btn-left,a:hover.l-btn-selected span.l-btn-left{background-position:0 bottom;background-image:none}a.l-btn-plain-selected,a:hover.l-btn-plain-selected{background:#ddd}.datagrid .panel-body{overflow:hidden;position:relative}.datagrid-view{position:relative;overflow:hidden}.datagrid-view1,.datagrid-view2{position:absolute;overflow:hidden;top:0}.datagrid-view1{left:0}.datagrid-view2{right:0}.datagrid-mask{position:absolute;left:0;top:0;width:100%;height:100%;opacity:.3;filter:alpha(opacity=30);display:none}.datagrid-mask-msg{position:absolute;top:50%;margin-top:-20px;padding:12px 5px 10px 30px;width:auto;height:16px;border-width:2px;border-style:solid;display:none}.datagrid-sort-icon{padding:0}.datagrid-toolbar{height:auto;padding:1px 2px;border-width:0 0 1px 0;border-style:solid}.datagrid-btn-separator{float:left;height:24px;border-left:1px solid #ccc;border-right:1px solid #fff;margin:2px 1px}.datagrid .datagrid-pager{margin:0;border-width:1px 0 0 0;border-style:solid}.datagrid .datagrid-pager-top{border-width:0 0 1px 0}.datagrid-header{overflow:hidden;cursor:default;border-width:0 0 1px 0;border-style:solid}.datagrid-header-inner{float:left;width:10000px}.datagrid-header-row,.datagrid-row{height:25px}.datagrid-header td,.datagrid-body td,.datagrid-footer td{border-width:0 1px 1px 0;border-style:dotted;margin:0;padding:0}.datagrid-cell,.datagrid-cell-group,.datagrid-header-rownumber,.datagrid-cell-rownumber{margin:0;padding:0 4px;white-space:nowrap;word-wrap:normal;overflow:hidden;height:18px;line-height:18px;font-weight:normal;font-size:12px}.datagrid-header .datagrid-cell{height:auto}.datagrid-header .datagrid-cell span{font-size:12px}.datagrid-cell-group{text-align:center}.datagrid-header-rownumber,.datagrid-cell-rownumber{width:25px;text-align:center;margin:0;padding:0}.datagrid-body{margin:0;padding:0;overflow:auto;zoom:1}.datagrid-view1 .datagrid-body-inner{padding-bottom:20px}.datagrid-view1 .datagrid-body{overflow:hidden}.datagrid-footer{overflow:hidden}.datagrid-footer-inner{border-width:1px 0 0 0;border-style:solid;width:10000px;float:left}.datagrid-row-editing .datagrid-cell{height:auto}.datagrid-header-check,.datagrid-cell-check{padding:0;width:27px;height:18px;font-size:1px;text-align:center;overflow:hidden}.datagrid-header-check input,.datagrid-cell-check input{margin:0;padding:0;width:15px;height:18px}.datagrid-resize-proxy{position:absolute;width:1px;height:10000px;top:0;cursor:e-resize;display:none}.datagrid-body .datagrid-editable{margin:0;padding:0}.datagrid-body .datagrid-editable table{width:100%;height:100%}.datagrid-body .datagrid-editable td{border:0;margin:0;padding:0}.datagrid-body .datagrid-editable .datagrid-editable-input{margin:0;padding:2px;border-width:1px;border-style:solid}.datagrid-sort-desc .datagrid-sort-icon{padding:0 13px 0 0;background:url('images/datagrid_icons.png') no-repeat -16px center}.datagrid-sort-asc .datagrid-sort-icon{padding:0 13px 0 0;background:url('images/datagrid_icons.png') no-repeat 0 center}.datagrid-row-collapse{background:url('images/datagrid_icons.png') no-repeat -48px center}.datagrid-row-expand{background:url('images/datagrid_icons.png') no-repeat -32px center}.datagrid-mask-msg{background:#fff url('images/loading.gif') no-repeat scroll 5px center}.datagrid-header,.datagrid-td-rownumber{background-color:#f2f2f2;background:-webkit-linear-gradient(top,#fff 0,#f2f2f2 100%);background:-moz-linear-gradient(top,#fff 0,#f2f2f2 100%);background:-o-linear-gradient(top,#fff 0,#f2f2f2 100%);background:linear-gradient(to bottom,#fff 0,#f2f2f2 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffffff,endColorstr=#F2F2F2,GradientType=0)}.datagrid-cell-rownumber{color:#333}.datagrid-resize-proxy{background:#bbb}.datagrid-mask{background:#ccc}.datagrid-mask-msg{border-color:#d4d4d4}.datagrid-toolbar,.datagrid-pager{background:#f5f5f5}.datagrid-header,.datagrid-toolbar,.datagrid-pager,.datagrid-footer-inner{border-color:#e6e6e6}.datagrid-header td,.datagrid-body td,.datagrid-footer td{border-color:#ccc}.datagrid-htable,.datagrid-btable,.datagrid-ftable{color:#333}.datagrid-row-alt{background:#f5f5f5}.datagrid-row-over,.datagrid-header td.datagrid-header-over{background:#e6e6e6;color:#00438a;cursor:default}.datagrid-row-selected{background:#0081c2;color:#fff}.datagrid-body .datagrid-editable .datagrid-editable-input{border-color:#d4d4d4}.propertygrid .datagrid-view1 .datagrid-body td{padding-bottom:1px;border-width:0 1px 0 0}.propertygrid .datagrid-group{height:21px;overflow:hidden;border-width:0 0 1px 0;border-style:solid}.propertygrid .datagrid-group span{font-weight:bold}.propertygrid .datagrid-view1 .datagrid-body td{border-color:#e6e6e6}.propertygrid .datagrid-view1 .datagrid-group{border-color:#f2f2f2}.propertygrid .datagrid-view2 .datagrid-group{border-color:#e6e6e6}.propertygrid .datagrid-group,.propertygrid .datagrid-view1 .datagrid-body,.propertygrid .datagrid-view1 .datagrid-row-over,.propertygrid .datagrid-view1 .datagrid-row-selected{background:#f2f2f2}.pagination{zoom:1}.pagination table{float:left;height:30px}.pagination td{border:0}.pagination-btn-separator{float:left;height:24px;border-left:1px solid #ccc;border-right:1px solid #fff;margin:3px 1px}.pagination .pagination-num{border-width:1px;border-style:solid;margin:0 2px;padding:2px;width:2em;height:auto}.pagination-page-list{margin:0 6px;padding:1px 2px;width:auto;height:auto;border-width:1px;border-style:solid}.pagination-info{float:right;margin:0 6px 0 0;padding:0;height:30px;line-height:30px;font-size:12px}.pagination span{font-size:12px}.pagination-first{background:url('images/pagination_icons.png') no-repeat 0 0}.pagination-prev{background:url('images/pagination_icons.png') no-repeat -16px 0}.pagination-next{background:url('images/pagination_icons.png') no-repeat -32px 0}.pagination-last{background:url('images/pagination_icons.png') no-repeat -48px 0}.pagination-load{background:url('images/pagination_icons.png') no-repeat -64px 0}.pagination-loading{background:url('images/loading.gif') no-repeat}.pagination-page-list,.pagination .pagination-num{border-color:#d4d4d4}.calendar{border-width:1px;border-style:solid;padding:1px;overflow:hidden}.calendar table{border-collapse:separate;font-size:12px;width:100%;height:100%}.calendar table td,.calendar table th{font-size:12px}.calendar-noborder{border:0}.calendar-header{position:relative;height:22px}.calendar-title{text-align:center;height:22px}.calendar-title span{position:relative;display:inline-block;top:2px;padding:0 3px;height:18px;line-height:18px;font-size:12px;cursor:pointer;-moz-border-radius:5px 5px 5px 5px;-webkit-border-radius:5px 5px 5px 5px;border-radius:5px 5px 5px 5px}.calendar-prevmonth,.calendar-nextmonth,.calendar-prevyear,.calendar-nextyear{position:absolute;top:50%;margin-top:-7px;width:14px;height:14px;cursor:pointer;font-size:1px;-moz-border-radius:5px 5px 5px 5px;-webkit-border-radius:5px 5px 5px 5px;border-radius:5px 5px 5px 5px}.calendar-prevmonth{left:20px;background:url('images/calendar_arrows.png') no-repeat -18px -2px}.calendar-nextmonth{right:20px;background:url('images/calendar_arrows.png') no-repeat -34px -2px}.calendar-prevyear{left:3px;background:url('images/calendar_arrows.png') no-repeat -1px -2px}.calendar-nextyear{right:3px;background:url('images/calendar_arrows.png') no-repeat -49px -2px}.calendar-body{position:relative}.calendar-body th,.calendar-body td{text-align:center}.calendar-day{border:0;padding:1px;cursor:pointer;-moz-border-radius:5px 5px 5px 5px;-webkit-border-radius:5px 5px 5px 5px;border-radius:5px 5px 5px 5px}.calendar-other-month{opacity:.3;filter:alpha(opacity=30)}.calendar-menu{position:absolute;top:0;left:0;width:180px;height:150px;padding:5px;font-size:12px;display:none;overflow:hidden}.calendar-menu-year-inner{text-align:center;padding-bottom:5px}.calendar-menu-year{width:40px;text-align:center;border-width:1px;border-style:solid;margin:0;padding:2px;font-weight:bold;font-size:12px}.calendar-menu-prev,.calendar-menu-next{display:inline-block;width:21px;height:21px;vertical-align:top;cursor:pointer;-moz-border-radius:5px 5px 5px 5px;-webkit-border-radius:5px 5px 5px 5px;border-radius:5px 5px 5px 5px}.calendar-menu-prev{margin-right:10px;background:url('images/calendar_arrows.png') no-repeat 2px 2px}.calendar-menu-next{margin-left:10px;background:url('images/calendar_arrows.png') no-repeat -45px 2px}.calendar-menu-month{text-align:center;cursor:pointer;font-weight:bold;-moz-border-radius:5px 5px 5px 5px;-webkit-border-radius:5px 5px 5px 5px;border-radius:5px 5px 5px 5px}.calendar-body th,.calendar-menu-month{color:#808080}.calendar-day{color:#333}.calendar-sunday{color:#c22}.calendar-saturday{color:#0e0}.calendar-today{color:#00f}.calendar-menu-year{border-color:#d4d4d4}.calendar{border-color:#d4d4d4}.calendar-header{background:#f2f2f2}.calendar-body,.calendar-menu{background:#fff}.calendar-body th{background:#f5f5f5}.calendar-hover,.calendar-nav-hover,.calendar-menu-hover{background-color:#e6e6e6;color:#00438a}.calendar-hover{border:1px solid #ddd;padding:0}.calendar-selected{background-color:#0081c2;color:#fff;border:1px solid #0070a9;padding:0}.datebox-calendar-inner{height:180px}.datebox-button{height:18px;padding:2px 5px;text-align:center}.datebox-button a{font-size:12px}.datebox-current,.datebox-close,.datebox-ok{text-decoration:none;font-weight:bold;opacity:.6;filter:alpha(opacity=60)}.datebox-current,.datebox-close{float:left}.datebox-close{float:right}.datebox-button-hover{opacity:1.0;filter:alpha(opacity=100)}.datebox .combo-arrow{background-image:url('images/datebox_arrow.png');background-position:center center}.datebox-button{background-color:#f5f5f5}.datebox-current,.datebox-close,.datebox-ok{color:#444}.spinner{display:inline-block;white-space:nowrap;margin:0 5px;padding:0;border-width:1px;border-style:solid;overflow:hidden;vertical-align:middle}.spinner .spinner-text{font-size:12px;border:0;line-height:20px;height:20px;margin:0;padding:0 2px;*margin-top:-1px;*height:18px;*line-height:18px;_height:18px;_line-height:18px;vertical-align:baseline}.spinner-arrow{display:inline-block;overflow:hidden;vertical-align:top;margin:0;padding:0}.spinner-arrow-up,.spinner-arrow-down{opacity:.6;filter:alpha(opacity=60);display:block;font-size:1px;width:18px;height:10px}.spinner-arrow-hover{opacity:1.0;filter:alpha(opacity=100)}.spinner-arrow-up{background:url('images/spinner_arrows.png') no-repeat 1px center}.spinner-arrow-down{background:url('images/spinner_arrows.png') no-repeat -15px center}.spinner{border-color:#d4d4d4}.spinner-arrow{background-color:#f2f2f2}.spinner-arrow-hover{background-color:#e6e6e6}.progressbar{border-width:1px;border-style:solid;-moz-border-radius:5px 5px 5px 5px;-webkit-border-radius:5px 5px 5px 5px;border-radius:5px 5px 5px 5px;overflow:hidden}.progressbar-text{text-align:center;position:absolute}.progressbar-value{position:relative;overflow:hidden;width:0;-moz-border-radius:5px 0 0 5px;-webkit-border-radius:5px 0 0 5px;border-radius:5px 0 0 5px}.progressbar{border-color:#d4d4d4}.progressbar-text{color:#333;font-size:12px}.progressbar-value .progressbar-text{background-color:#0081c2;color:#fff}.searchbox{display:inline-block;white-space:nowrap;margin:0;padding:0;border-width:1px;border-style:solid;overflow:hidden}.searchbox .searchbox-text{font-size:12px;border:0;margin:0;padding:0;line-height:20px;height:20px;*margin-top:-1px;*height:18px;*line-height:18px;_height:18px;_line-height:18px;vertical-align:baseline}.searchbox .searchbox-prompt{font-size:12px;color:#ccc}.searchbox-button{width:18px;height:20px;overflow:hidden;display:inline-block;vertical-align:top;cursor:pointer;opacity:.6;filter:alpha(opacity=60)}\r\n.searchbox-button-hover{opacity:1.0;filter:alpha(opacity=100)}.searchbox a.l-btn-plain{height:20px;border:0;padding:0 6px 0 0;vertical-align:top;-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;opacity:.6;filter:alpha(opacity=60)}.searchbox a.l-btn .l-btn-left{padding:2px 0 2px 4px}.searchbox a.l-btn-plain:hover{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;border:0;padding:0 6px 0 0;opacity:1.0;filter:alpha(opacity=100)}.searchbox a.m-btn-plain-active{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0}.searchbox-button{background:url('images/searchbox_button.png') no-repeat center center}.searchbox{border-color:#d4d4d4;background-color:#fff}.searchbox a.l-btn-plain{background:#f2f2f2}.slider-disabled{opacity:.5;filter:alpha(opacity=50)}.slider-h{height:22px}.slider-v{width:22px}.slider-inner{position:relative;height:6px;top:7px;border-width:1px;border-style:solid;border-radius:5px}.slider-handle{position:absolute;display:block;outline:0;width:20px;height:20px;top:-7px;margin-left:-10px}.slider-tip{position:absolute;display:inline-block;line-height:12px;font-size:12px;white-space:nowrap;top:-22px}.slider-rule{position:relative;top:15px}.slider-rule span{position:absolute;display:inline-block;font-size:0;height:5px;border-width:0 0 0 1px;border-style:solid}.slider-rulelabel{position:relative;top:20px}.slider-rulelabel span{position:absolute;display:inline-block;font-size:12px}.slider-v .slider-inner{width:6px;left:7px;top:0;float:left}.slider-v .slider-handle{left:3px;margin-top:-10px}.slider-v .slider-tip{left:-10px;margin-top:-6px}.slider-v .slider-rule{float:left;top:0;left:16px}.slider-v .slider-rule span{width:5px;height:'auto';border-left:0;border-width:1px 0 0 0;border-style:solid}.slider-v .slider-rulelabel{float:left;top:0;left:23px}.slider-handle{background:url('images/slider_handle.png') no-repeat}.slider-inner{border-color:#d4d4d4;background:#f2f2f2}.slider-rule span{border-color:#d4d4d4}.slider-rulelabel span{color:#333}.menu{position:absolute;margin:0;padding:2px;border-width:1px;border-style:solid;overflow:hidden}.menu-item{position:relative;margin:0;padding:0;overflow:hidden;white-space:nowrap;cursor:pointer;border-width:1px;border-style:solid}.menu-text{height:20px;line-height:20px;float:left;padding-left:28px}.menu-icon{position:absolute;width:16px;height:16px;left:2px;top:50%;margin-top:-8px}.menu-rightarrow{position:absolute;width:16px;height:16px;right:0;top:50%;margin-top:-8px}.menu-line{position:absolute;left:26px;top:0;height:2000px;font-size:1px}.menu-sep{margin:3px 0 3px 25px;font-size:1px}.menu-active{-moz-border-radius:5px 5px 5px 5px;-webkit-border-radius:5px 5px 5px 5px;border-radius:5px 5px 5px 5px}.menu-item-disabled{opacity:.5;filter:alpha(opacity=50);cursor:default}.menu-text,.menu-text span{font-size:12px}.menu-shadow{position:absolute;-moz-border-radius:5px 5px 5px 5px;-webkit-border-radius:5px 5px 5px 5px;border-radius:5px 5px 5px 5px;background:#ccc;-moz-box-shadow:2px 2px 3px #ccc;-webkit-box-shadow:2px 2px 3px #ccc;box-shadow:2px 2px 3px #ccc;filter:progid:DXImageTransform.Microsoft.Blur(pixelRadius=2,MakeShadow=false,ShadowOpacity=0.2)}.menu-rightarrow{background:url('images/menu_arrows.png') no-repeat -32px center}.menu-line{border-left:1px solid #ccc;border-right:1px solid #fff}.menu-sep{border-top:1px solid #ccc;border-bottom:1px solid #fff}.menu{background-color:#fff;border-color:#e6e6e6;color:#333}.menu-content{background:#fff}.menu-item{border-color:transparent;_border-color:#fff}.menu-active{border-color:#ddd;color:#00438a;background:#e6e6e6}.menu-active-disabled{border-color:transparent;background:transparent;color:#333}.m-btn-downarrow{display:inline-block;width:16px;height:16px;line-height:16px;font-size:12px;_vertical-align:middle}a.m-btn-active{background-position:bottom right}a.m-btn-active span.l-btn-left{background-position:bottom left}a.m-btn-plain-active{background:transparent;padding:0 5px 0 0;border-width:1px;border-style:solid;-moz-border-radius:5px 5px 5px 5px;-webkit-border-radius:5px 5px 5px 5px;border-radius:5px 5px 5px 5px}.m-btn-downarrow{background:url('images/menu_arrows.png') no-repeat 2px center}a.m-btn-plain-active{border-color:#ddd;background-color:#e6e6e6;color:#00438a}.s-btn-downarrow{display:inline-block;margin:0 0 0 4px;padding:0 0 0 1px;width:14px;height:16px;line-height:16px;border-width:0;border-style:solid;font-size:12px;_vertical-align:middle}a.s-btn-active{background-position:bottom right}a.s-btn-active span.l-btn-left{background-position:bottom left}a.s-btn-plain-active{background:transparent;padding:0 5px 0 0;border-width:1px;border-style:solid;-moz-border-radius:5px 5px 5px 5px;-webkit-border-radius:5px 5px 5px 5px;border-radius:5px 5px 5px 5px}.s-btn-downarrow{background:url('images/menu_arrows.png') no-repeat 2px center;border-color:#bbb}a:hover.l-btn .s-btn-downarrow,a.s-btn-active .s-btn-downarrow,a.s-btn-plain-active .s-btn-downarrow{background-position:1px center;padding:0;border-width:0 0 0 1px}a.s-btn-plain-active{border-color:#ddd;background-color:#e6e6e6;color:#00438a}.messager-body{padding:10px;overflow:hidden}.messager-button{text-align:center;padding-top:10px}.messager-icon{float:left;width:32px;height:32px;margin:0 10px 10px 0}.messager-error{background:url('images/messager_icons.png') no-repeat scroll -64px 0}.messager-info{background:url('images/messager_icons.png') no-repeat scroll 0 0}.messager-question{background:url('images/messager_icons.png') no-repeat scroll -32px 0}.messager-warning{background:url('images/messager_icons.png') no-repeat scroll -96px 0}.messager-progress{padding:10px}.messager-p-msg{margin-bottom:5px}.messager-body .messager-input{width:100%;padding:1px 0;border:1px solid #d4d4d4}.tree{margin:0;padding:0;list-style-type:none}.tree li{white-space:nowrap}.tree li ul{list-style-type:none;margin:0;padding:0}.tree-node{height:18px;white-space:nowrap;cursor:pointer}.tree-hit{cursor:pointer}.tree-expanded,.tree-collapsed,.tree-folder,.tree-file,.tree-checkbox,.tree-indent{display:inline-block;width:16px;height:18px;vertical-align:top;overflow:hidden}.tree-expanded{background:url('images/tree_icons.png') no-repeat -18px 0}.tree-expanded-hover{background:url('images/tree_icons.png') no-repeat -50px 0}.tree-collapsed{background:url('images/tree_icons.png') no-repeat 0 0}.tree-collapsed-hover{background:url('images/tree_icons.png') no-repeat -32px 0}.tree-lines .tree-expanded,.tree-lines .tree-root-first .tree-expanded{background:url('images/tree_icons.png') no-repeat -144px 0}.tree-lines .tree-collapsed,.tree-lines .tree-root-first .tree-collapsed{background:url('images/tree_icons.png') no-repeat -128px 0}.tree-lines .tree-node-last .tree-expanded,.tree-lines .tree-root-one .tree-expanded{background:url('images/tree_icons.png') no-repeat -80px 0}.tree-lines .tree-node-last .tree-collapsed,.tree-lines .tree-root-one .tree-collapsed{background:url('images/tree_icons.png') no-repeat -64px 0}.tree-line{background:url('images/tree_icons.png') no-repeat -176px 0}.tree-join{background:url('images/tree_icons.png') no-repeat -192px 0}.tree-joinbottom{background:url('images/tree_icons.png') no-repeat -160px 0}.tree-folder{background:url('images/tree_icons.png') no-repeat -208px 0}.tree-folder-open{background:url('images/tree_icons.png') no-repeat -224px 0}.tree-file{background:url('images/tree_icons.png') no-repeat -240px 0}.tree-loading{background:url('images/loading.gif') no-repeat center center}.tree-checkbox0{background:url('images/tree_icons.png') no-repeat -208px -18px}.tree-checkbox1{background:url('images/tree_icons.png') no-repeat -224px -18px}.tree-checkbox2{background:url('images/tree_icons.png') no-repeat -240px -18px}.tree-title{font-size:12px;display:inline-block;text-decoration:none;vertical-align:top;white-space:nowrap;padding:0 2px;height:18px;line-height:18px}.tree-node-proxy{font-size:12px;line-height:20px;padding:0 2px 0 20px;border-width:1px;border-style:solid;z-index:9900000}.tree-dnd-icon{display:inline-block;position:absolute;width:16px;height:18px;left:2px;top:50%;margin-top:-9px}.tree-dnd-yes{background:url('images/tree_icons.png') no-repeat -256px 0}.tree-dnd-no{background:url('images/tree_icons.png') no-repeat -256px -18px}.tree-node-top{border-top:1px dotted red}.tree-node-bottom{border-bottom:1px dotted red}.tree-node-append .tree-title{border:1px dotted red}.tree-editor{border:1px solid #ccc;font-size:12px;height:14px!important;height:18px;line-height:14px;padding:1px 2px;width:80px;position:absolute;top:0}.tree-node-proxy{background-color:#fff;color:#333;border-color:#d4d4d4}.tree-node-hover{background:#e6e6e6;color:#00438a}.tree-node-selected{background:#0081c2;color:#fff}.validatebox-invalid{background-image:url('images/validatebox_warning.png');background-repeat:no-repeat;background-position:right center;border-color:#ffa8a8;background-color:#fff3f3;color:#000}.tooltip{position:absolute;display:none;z-index:9900000;outline:0;padding:5px;border-width:1px;border-style:solid;border-radius:5px;-moz-border-radius:5px 5px 5px 5px;-webkit-border-radius:5px 5px 5px 5px;border-radius:5px 5px 5px 5px}.tooltip-content{font-size:12px}.tooltip-arrow-outer,.tooltip-arrow{position:absolute;width:0;height:0;line-height:0;font-size:0;border-style:solid;border-width:6px;border-color:transparent;_border-color:tomato;_filter:chroma(color=tomato)}.tooltip-right .tooltip-arrow-outer{left:0;top:50%;margin:-6px 0 0 -13px}.tooltip-right .tooltip-arrow{left:0;top:50%;margin:-6px 0 0 -12px}.tooltip-left .tooltip-arrow-outer{right:0;top:50%;margin:-6px -13px 0 0}.tooltip-left .tooltip-arrow{right:0;top:50%;margin:-6px -12px 0 0}.tooltip-top .tooltip-arrow-outer{bottom:0;left:50%;margin:0 0 -13px -6px}.tooltip-top .tooltip-arrow{bottom:0;left:50%;margin:0 0 -12px -6px}.tooltip-bottom .tooltip-arrow-outer{top:0;left:50%;margin:-13px 0 0 -6px}.tooltip-bottom .tooltip-arrow{top:0;left:50%;margin:-12px 0 0 -6px}.tooltip{background-color:#fff;border-color:#d4d4d4;color:#333}.tooltip-right .tooltip-arrow-outer{border-right-color:#d4d4d4}.tooltip-right .tooltip-arrow{border-right-color:#fff}.tooltip-left .tooltip-arrow-outer{border-left-color:#d4d4d4}.tooltip-left .tooltip-arrow{border-left-color:#fff}.tooltip-top .tooltip-arrow-outer{border-top-color:#d4d4d4}.tooltip-top .tooltip-arrow{border-top-color:#fff}.tooltip-bottom .tooltip-arrow-outer{border-bottom-color:#d4d4d4}.tooltip-bottom .tooltip-arrow{border-bottom-color:#fff}.tabs-panels{border-color:transparent}.tabs li a.tabs-inner{border-color:transparent;background:transparent;filter:none;color:#08c}.menu-active{background-color:#0081c2;border-color:#0081c2;color:#fff}.menu-active-disabled{border-color:transparent;background:transparent;color:#333}"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/css/editor.css",
    "content": "*{\r\n\tmargin : 0;\r\n\tpadding : 0;\r\n}\r\nhtml,body{\r\n\twidth : 100%;\r\n\theight : 100%;\r\n\toverflow: hidden;\r\n\tfont-family: Microsoft YaHei;\r\n}\r\n.text-center{\r\n\ttext-align: center;\r\n}\r\n.main-container{\r\n\twidth : 100%;\r\n\theight : 100%;\r\n}\r\n.main-container .sidebar-container{\r\n\tposition: absolute;\r\n\twidth : 46px;\r\n\tleft : 0px;\r\n\ttop : 30px;\r\n\tbottom:200px;\r\n\tmax-height: 100%;\r\n\toverflow: auto;\r\n\tbackground: #F6F6F6;\r\n\tz-index: 1;\r\n\tborder-radius: 2px;\r\n\toverflow-x: hidden;\r\n\twriting-mode: vertical-lr;\r\n\t-webkit-writing-mode: vertical-lr;\r\n\t-ms-writing-mode: vertical-lr;\r\n}\r\n.main-container .sidebar-container::-webkit-scrollbar {\r\n \twidth: 3px;\r\n}\r\n.main-container .sidebar-container::-webkit-scrollbar-track {\r\n\tbackground-color:#ccc;\r\n\t-webkit-border-radius: 2em;\r\n\t-moz-border-radius: 2em;\r\n\tborder-radius:2em;\r\n}\r\n.main-container .sidebar-container::-webkit-scrollbar-thumb {\r\n\tbackground-color:#999;\r\n\t-webkit-border-radius: 2em;\r\n\t-moz-border-radius: 2em;\r\n\tborder-radius:2em;\r\n}\r\n.main-container .sidebar-container img{\r\n\tpadding: 5px;\r\n\tmargin : 2px;\r\n\tborder-radius: 3px;\r\n\twidth : 32px;\r\n\theight : 32px;\r\n}\r\n.main-container .sidebar-container img:hover{\r\n\tbackground:#ccc;\r\n}\r\n.main-container .resize-container{\r\n\tcursor: n-resize;\r\n\tposition: absolute;\r\n\tbottom : 190px;\r\n\twidth : 100%;\r\n\theight : 20px;\r\n\tbackground: transparent;\r\n}\r\n.editor-container,.xml-container{\r\n\tposition: absolute;\r\n\tleft : 38px;\r\n\twidth : 100%;\r\n\ttop : 30px;\r\n\tbottom : 200px;\r\n\tbackground-image: linear-gradient(90deg, rgba(153, 153, 153, 0.3) 1px, rgba(0, 0, 0, 0) 1px),linear-gradient(rgba(153, 153, 153, 0.3) 1px, rgba(0, 0, 0, 0) 1px);\r\n    background-size: 8px 8px;\r\n    overflow: auto;\r\n}\r\n.properties-container{\r\n\tposition: absolute;\r\n\twidth : 100%;\r\n\tbottom : 0px;\r\n\theight : 200px;\r\n\tbox-shadow: -2px 1px 3px #eee;\r\n\toverflow: auto;\r\n}\r\n.properties-container .layui-form-selectup dl{\r\n\ttop:auto;\r\n\tbottom:auto;\r\n\r\n}\r\n.properties-container .layui-table-cell{\r\n\theight:32px;\r\n\tline-height: 32px;\r\n\toverflow: visible !important;\r\n}\r\n.properties-container .layui-table-box,.properties-container .layui-table-body {\r\n\toverflow: visible;\r\n}\r\n.properties-container .layui-table-cell{\r\n\tpadding : 0 5px;\r\n}\r\n.properties-container .layui-table .layui-input-block{\r\n\tmargin-left:0px;\r\n\tmin-height: 32px;\r\n\theight: 32px;\r\n}\r\n.properties-container .layui-table,.properties-container .layui-table-view{\r\n\tmargin-top:0px;\r\n}\r\n.properties-container .layui-table .layui-input,.properties-container .layui-table .layui-select,.properties-container .layui-table .layui-textarea{\r\n\theight:32px;\r\n}\r\n.properties-container .editor-form-node .layui-form-relative{\r\n\tposition: relative;\r\n}\r\n.properties-container .editor-form-node input,.properties-container .editor-form-node textarea{\r\n\tfont-family: 'Consolas';\r\n\tfont-size : 14px;\r\n}\r\n.properties-container .editor-form-node .layui-form-relative .layui-icon-close{\r\n\tposition: absolute;\r\n\tleft : 10px;\r\n\tfont-size:28px;\r\n\ttop : 30px;\r\n\tcolor : #333;\r\n\tcursor: pointer;\r\n\tz-index: 1;\r\n}\r\n.properties-container .editor-form-node .layui-form-relative .layui-icon-close.function-remove{\r\n\ttop : 5px;\r\n}\r\n.properties-container .editor-form-node .layui-form-relative .layui-icon-close.cmd-remove{\r\n\ttop : 5px;\r\n}\r\n\r\n.properties-container .layui-tab{\r\n\tmargin : 0px;\r\n}\r\n\r\n.toolbar-container ul li{\r\n\tfloat : left;\r\n\tpadding : 0 10px;\r\n\tcursor: pointer;\r\n\tmargin-top:5px;\r\n\t/*margin-right: 5px;*/\r\n\tcolor:#333;\r\n}\r\n.toolbar-container ul span{\r\n\tfloat: left;\r\n\tline-height: 30px;\r\n\tfont-size: large;\r\n\tmargin:0 5px 0 5px;\r\n\tcolor: #ccc;\r\n}\r\n.toolbar-container ul li:hover{\r\n\tcolor : #ff4d02;\r\n\tbackground-color: rgb(204,204,204);\r\n\r\n}\r\n.toolbar-container ul li:not(:first-child){\r\n\t/*border-left: 1px solid #ccc;*/\r\n}\r\n\r\n.test-window-container .output-container{\r\n\t/* height: 320px; */\r\n}\r\n.test-window-container .log-container{\r\n\tborder: 1px solid #ccc;\r\n}\r\n.test-window-container .log-container textarea{\r\n\twidth : 100%;\r\n\theight : 100%;\r\n}\r\n.xml-container{\r\n\tdisplay: none;\r\n}\r\n.xml-container textarea{\r\n    width: 100%;\r\n    height: 99.6%;\r\n\tborder:none;\r\n    padding: 10px 20px;\r\n    box-sizing: border-box;\r\n}\r\n.xml-container textarea::-webkit-scrollbar {\r\n \twidth: 5px;\r\n}\r\n.xml-container textarea::-webkit-scrollbar-track {\r\n\tbackground-color:#ccc;\r\n\t-webkit-border-radius: 2em;\r\n\t-moz-border-radius: 2em;\r\n\tborder-radius:2em;\r\n}\r\n.xml-container textarea::-webkit-scrollbar-thumb {\r\n\tbackground-color:#999;\r\n\t-webkit-border-radius: 2em;\r\n\t-moz-border-radius: 2em;\r\n\tborder-radius:2em;\r\n}\r\n.log-container span{\r\n\tdisplay:inline-block;\r\n\tvertical-align: middle;\r\n\tfont-weight: bold;\r\n}\r\n.log-container .test-log > span{\r\n\theight:24px;\r\n\tline-height:24px;\r\n\tfloat : left;\r\n\tfont-family: 'Consolas';\r\n}\r\n.log-container .test-log{\r\n\theight:24px;\r\n\tline-height:24px;\r\n\twidth:100%;\r\n\tclear:both;\r\n}\r\n.log-container .test-log.log-error{\r\n\tcolor : red;\r\n}\r\n.log-container span.level{\r\n\ttext-align:center;\r\n\twidth:60px;\r\n}\r\n.log-container span.timestamp{\r\n\tmargin-right:5px;\r\n}\r\n.log-container span.variable{\r\n\tmargin:0 2px;\r\n\tmax-width: 230px;\r\n\twhite-space: nowrap;\r\n\toverflow: hidden;\r\n\ttext-overflow: ellipsis;\r\n\tcursor:pointer;\r\n\tcolor: #025900;\r\n}\r\n.log-container span.variable-object,.log-container span.variable-array{\r\n\tcolor : #2a00ff;\r\n}\r\n.log-container span.variable.variable-boolean{\r\n\tcolor : #600100;\r\n}\r\n.log-container span.variable.variable-number{\r\n\tcolor : #000E59;\r\n}\r\n.jsontree_value-wrapper{\r\n\toverflow: unset !important;\r\n}\r\n.jsontree_value_string,.jsontree_node{\r\n\twhite-space: nowrap;\r\n}\r\n#test-window{\r\n\tpadding:0px 0px;\r\n\toverflow: hidden;\r\n}\r\n.layui-layer.codemirror .layui-input-block{\r\n\tmargin-left:0px;\r\n}\r\n\r\n.hint-grammer{\r\n\twidth : 100%;\r\n\tcolor : #333;\r\n}\r\n.hint-grammer:not(:first-child){\r\n\tborder-top:1px solid #ccc;\r\n\tmargin-top:5px;\r\n\tpadding-top:5px;\r\n}\r\n.hint-grammer .hint-owner span{\r\n\tcolor : #600100\r\n}\r\n.hint-grammer .hint-return span{\r\n\tcolor : #0000C0\r\n}\r\n.hint-grammer .hint-example{\r\n\tpadding:2px 5px;\r\n\tcolor : #000;\r\n}\r\n.layui-layer-tips{\r\n\tword-break: break-all;\r\n}\r\n\r\n.toolbar-container ul li {\r\n\tbackground-repeat: no-repeat;\r\n\tbackground-size: 18px 18px;\r\n\tbackground-position: center center;\r\n\twidth: 20px;\r\n\theight: 20px;\r\n\tpadding : 0 4px;\r\n}\r\n.toolbar-container ul li.btn-save{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABAUlEQVRYR+2XwRHCIBBF/3ZBCdqFHXjSOvDi1ViB1KE3G9Au9OjJoQscMyFDIggkgUxmkmOym33z9wMLYeSHRq6PGoAJdQOwSgj0ArCXnC5mDRNAJSxu/nprQvwASE6NtjChSrD2ex9sO48JdQawqfJqiGwA38I2iKwANojsAA0IwnsUgAqi9NYMMB0FfOve9d21f+h9wq/ASRUgHDoBKBzljgpbbjBAp8IBSTPAdBRgfUyovWAxY7ACOjDAV39DXMe8fxl2nAc0jWueiFYgdiBJBsCEegBYeFrylJyW5qk3WAsCAa6S0zoJQKwZZw8MrsDsgeQmjC3QNz7n5dTGev8AcqN8CWX2vpEAAAAASUVORK5CYII=\");\r\n}\r\n.toolbar-container ul li.btn-return{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACF0lEQVRYR8WWvWtTURjGf09UcGtBNAEdMjmpdOjm4qj4QZcWN8Gl6OK9QdAt6eLg0HsXwep/0A4iFbWTbjrpUARxcNDivRHBj8FBwn3lxiRc0zTJTZPTs95zzvO7z/txXrHHS3usT1+AUmirwFrsaW1SoDsCtMTngQXnAK7EU1e3OeBSfBtAR9xYyhtziZ8GW4UGm19u6v2w5zsOlEJL450m3TjWa4yNBqx88xX1u/C/EGQg0sxfyEMyHdj0ASjvFycMThtclvhNwsPYV22nu3rlQNuJ3BBZkcOBzRREVTAHPI09ne8F0bMMd+NEt0g7r8x4UPe12P29Xx8YixOpYCmwGqKKsdQdjkGdcH5cTagU2mPEbCNhNpuYzt6CUmBnEC+6XXAG0AxFaKsyjke+Ztq54BrgOnDPEk7VK9rs2Yrz1H7evWlp7hNvsw+cUwfSZnVQfBdUIk+BcwdaeWDZRHTqwLFlO9oosKWEa1FF95070C5FJZyLKnruHKAY2m1B9dAPpt7V9Mc5QCm0Z8DX2NMV532gGJgnERhcrHt64hSgGNoFwTpiPb6hS9n+MfEqaNd+K95nI08bzgCOBDZXEI+agsat2NfdoeeBvG02u7+4bCcpcFXgYXwAFmNfLwdORM3BYcRlYgooC9KXrvzvp1kpwJ3I06ehZsLO5DIaxC/gM+IjCW8kXnXHe6ADo+nu7tTEq2AQ3l/lPMsh86zkIgAAAABJRU5ErkJggg==\");\r\n}\r\n.toolbar-container ul li.btn-selectAll{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAClklEQVRYR82XsYsTURDGv1krK8HCbBqx1r/AszCK3IGoYKEgnHAWHlhI3p5coSCXAwsb3V0rBQsb4cRGLEywMYWgB6IWdygiaqG3LwhaiFoc5pONSdjdZDcvyeLdtu/NfL83M2/mrWCDP9lgfWw+ANtliYIrAuwbJzpaSc/hbJcVCBaia7FN9g3uRhOrAJZB1EYGEKxqJfej9h1xEIvakUpnLQZQ9HibwLRFTKw58nJkgIRhmni4LR4Bn3UQTa3k4P8Q7wEoeHxqAb8CJZN5AGSdvG8KbI/PAXzTSg6PC2Ai3psCjy8gWNNlOTYOgKl4D0DR5eum4GNDyfFRAYYR7xeBFRBvtCMnwsWWM2BZO1I1AbJ9LoCoJK9alm38Fnh8C+CVVnKqBfDP4SUQU9qRepajNPH2Iepp9kmA9wSeNZSc7oi1e8Pklj849OWCvOsHkXVy2yOzIhIHcPkJgidayZmuUIVWYRuqItjOdZQa8/Iz1uEGhH0ogILLz5aFalCWs1GRosedBGogPmhHjnTWTHI+FIDtMwDxQCs5lwz1Dp8TFlETYClQMmsi3qqjSApslzMQ7I9GOFkDXwEsaSXn++b6Ok/Cwr0wp+FUM6n2BEA4DUOAA2nD6HsTuNNQ4qRVfNHlHAXXTMT7RCAbwPb4A4KbuizzmVfOZWnQtezWSTwFAwB8/hbCC5RcNGk8JnuGSoHtcZ3A1YaSyybOTfYMC5DZNEwEk3uGA/D5EMRRIaYDR+6OIhhrUu03oDQxFczJ4/agSr8F7apdAbBnXPGufeQNaATQnoIzAHblABGbpMYAOQj3dWF7fARga2oj6slfniSC8J25N/NZ3h3BPmdJ3MpTH2j9bywm/xc2369Zzqce6G7DI/AXxriYMP/Ruk8AAAAASUVORK5CYII=\");\r\n}\r\n.toolbar-container ul li.btn-console-xml{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACmUlEQVRYR+2XTUgVURTHf2cQI6NPwjdai2iRkWCBUhRBukkkqEUQRItqGYaO0aaVti/fSBhEiwxaGbUoiKCFRiuxIiHDkMg+vRVoazFP3Ekfj+nNOPN85qa7Gobz8Ztzz9z/ucIKL1nh/MQCVHXrYXXoAvanBVX4KTCk0PnNk6Eo/1gA19fnwE5gOC0AsAbYBYwZTxpSA1T6WufACHDVeHKxCABcX28BZ0RomGyXF4ViRFbAzWojwgDKZdMhdhtSLzerXQidKE2mQwb/AySqQFWP1uscaxX2iJAF+lBup66/dRBO2x5QpUPglVPOu6+t8ik/Vq4HMr7uE+gF6otKltRJeFj2i3OfL8iXP4zzy/X1MXAAuIfyIXgtHBJlxP7TSeOHvm6DCrtRngbhhB0KJ4H7xpPjYQAFBo0nTcUkS+rj+joANBpPgo/Pr0AOwM3qEYSbwDjKNYRehJemXVqSJoqySwRgnatvaMXGSWZHu2TGPpdNserjJZn+ZwBLTbTkCpQKoLZfy0dPyExesy/eA9Z4a7eunnV4DWyPgHmb974mBviB8eRYaoBSVSAcJ3ETLjhW+dqsUIcG8pp42bNDhdHvnjzJd0oFkFOzxGkLGIbUNDFA9RXdPFfGD+AuyiNgIhWHQwXKeaAFh0rTJjaWnRGSNeFyzQORABlfpwXGjSd7A9JlGkhcX98Dm4wn68NHsRWjg/Mlt2K0LZDT0kxEVs4nYsUoRo77jCdnU+3/vHHBJo6S49xvFx5ISlCBRAPJXwfGMvVAOE/kVFzta80cjAHXjSetxWxBpkfviHIKh1rTJm8KxYi/mGR1HGFLMBfAVEqIhYvJcNyQs+jd0PW1H2gG1qUBCK5mwjPTLkfj/BYFSJO0GNsVB/gNWbh3MAOKB3oAAAAASUVORK5CYII=\");\r\n}\r\n.toolbar-container ul li.btn-graphical-xml{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABXklEQVRYR+2Xr0/DUBSFv1sLAgFbg0KDQaOYRCDBMIMjEKDVCEayhASzjgRIUAhmkDjU9lcQCAZHsYQExyGvsGU/UKt4E72uL733fu/0tj3P8BzmuT+TBRAm2gCOgZLBvUQ9je01j0phQwsKqJtYM3iSOEpj63Rr9hQoNbUSiAdguq9hJ42skgsgURtYHagRsJQe2KNb6wGEia6AHWCTgA7fXABOkdwhcW1wimX1zmS03g+tOgzgGu4iKk6iclO3JrZyd/8tcJlGtlduat/EOXCTRrY9DODo7oAP4BlYBKYcUC4Iwz2CL+AFYxYxb6L6FltrAMBdhA3VsGwIXXwi1vsHZhyQuYaWA6NtMJPli5M0ttrIEHYXwkT678Zxmvdq9m0sjWzg1R/5DhQAhQKFAoUChQKFAl4V8Po7/jOk/gxJmMi7JfNrSr3b8swT+jyY5PF8eXIn62yYZyfj5v4A3Ho4MJVAO+EAAAAASUVORK5CYII=\");\r\n}\r\n.toolbar-container ul li.btn-edit-xml{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAC/0lEQVRYR82XTWgTURDHf7NNrRelgtJFWhHtqYLQYkXwYIsHrR4UrRf1IHgR1HaTHrzWm1U0acWzBT9Ai3oSBQWrCOIXiIgnkWqUblH8qAc9NDvyNh+mcbNJa2r6YCHsznvzy7w3/5knlBj2kLaQ4hDQhdBcyj7z/REQdx0ZKWUvYQZ2XPsRosDiUgsFfleOu1HpD5tbFKBhUDeLcjdv8gjK65IgQjewJmunEJ9wJFZsXlEAe1BPo6QnKp1uVEZLOgeWn9WNXoqHBbYPXEc2Bc0vDpDQa8AuYNR1pLMc58Zm6YAuitQxGWD/1HVkfeH7MIB7QAdl7GPhonZCNRBYuO/2Skf+t/8LkPZ823WkKwtRDQDj+7rryG7zo1oA5mBvc6Nya64AzgEtBefABlYCC/3EEi5N9Mr+OQEIFbeEJoHGbHZVAyCdXZn0nj8Avu5PT1CjXL4OlCtCObsarro9EijbdkL/joCd0PPADuAj8EEhKYrZq6RaJCNTJFM1flFqBdqAWpTvCBHwn1rAygdV2DDhyOMAkQoEyL409kbzzWk1z1tVxlEGxPLrQlbFjE0TsDpjP03dwupHsQjkA/xAOIoyrMpeEYZQ9lgw6QnPffHwaFeLYb/qWXTg+dDTR5ECVg6AkacLKE0oNxFOmWroeXyVmrQjEfahnDQAIqxT5VllAQDXEckVFaUTi+0ogrJAhTaBBUC7Z9FsebypKIAofWq6IOEncCIT5iuexRYrxRKEYwq/BHZaUyzzInyqHIDyWaFHhMsCWxUuYtFt9jmltEYsDqv6veEr4Ij7jVq7njsZgD+H8R/OgNnnFcAqkwXAO79kmW1IZ8ha4GXOYUYnRGhUOJiLxAwBjA4cmLHghEyYkQ6YdaqqhEF/JJevs2jJSlTDeVqMimp2FSMwq7Y8LPymZa+p471AfbYvrPjFJAygIa6OCHHfRjjj9kpf5a9mxQiEdH+RHl/w6HJj8mRuL6fBMGPiERuPyY10IEqMWV7PA0oDL4ySijLoRmUsa/Ab6oa/MD/ej0IAAAAASUVORK5CYII=\");\r\n}\r\n.toolbar-container ul li.btn-copy{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACcElEQVRYR82XPYgTURSFz4kWdqZw1wluIbiVKyK4CoLgLhY21lY2guBWZkYLrWRtFzQTQQRRWFCrRWwstHIrQVBQMRZuky4vKiu4YOFPjkyS2fxN8t6sA2a6zDv33u/+vMx7xH9+6BK/ECoQcAzAhIveovkA4bEJuBrprACFshYk3MkgcK+LHGbMRX60AnglrYGYzhpAxKN6kWftAKHUDl6FMG8CVrcK45U0B+JF237V+JxPA9A02Grw2M7rJDQIUAh1vCHMdgchcQpqzYqAZy4AOeJrzefDJO1QAC/UGpBpr39AuGECXusGSQTwynoF4ahLdik1343PnVaA3aG+Eci3hRUJ91IG2pSTOAngdPzC+OyZs8QKdAP0G2wFZJS/8QTwQkV7cy7KNq5AvqT8DuJJ/N5SiYqAq3WfTyNdkr+R2zDJYDLUwRzwLkULVozPM5kBNB2VtAjihA1Cwqd6wAtdWQ5UNHUFbEFHrWfSgrEAcG0BgC9x/zObgUJZhyW8dq2EhLvxHGTSgqmb2vM7h+cAZhwg1iFcNgGXM6uAQ9ChkkwqMBYAE7c1ve0npmwwDWL9s8/3Sf8D0Smq86XCEQBL7d+dA0lSybxb2o8GKrbgm+vCdRNwsX8GhtoTS6bIK81PZRJAM/tfiA4pbk86gGXj81zkeChAEyyqwh9M2giYw0atyDfWFrQE1e6D7UgAW+Bh66N2Qb/NAACJ2e5s0kLsKqmwnXgJYG9kazvgxADRzWchbTAHfcX4PDBK1wLovTA4+HWTELhf83neChAPHBu4JGCfm/uWSsAhAm97bIgNNrBSC/jA5st6M7I5+Nf1v3QzjDCiAQFdAAAAAElFTkSuQmCC\");\r\n}\r\n.toolbar-container ul li.btn-paste{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACkElEQVRYR+2Xv08TYRjHP0+BkBg1JAZ7YMKEizoYcdBJnAy7Pwpuarqo5Yp/ALBr7trEgX/AARYXQ+JCFwdM1EkHo3EpchVjTJCwwD3mmvZyLde7XltMSLz1nud5P8/3ed/nfV4h4TdS0Al1OeG5SYrtzVl5lzBEg7kkcR619JIrNCyYUia+5+V9kjhB27YA0pYuiZCNXERZdPKykBQkFsDPWlmMDC7cc0wZ6wrAsHQKKCKMHwikXHfyUgpbwLB0AWE+5N/q/gC5rYfypRWYr8BpW9MpcIAVlE9NO2WedgCaVRJuAmOOKSdjAepZOKY0lGV0SY+5u+y0Ie1Hx5QLQTvD0kmEtSh4f7FWAF5Aw9ZbKOdjINadvKweCsB4UQf/7HO1DRUOmtQUCP7oV9bLc7Jb7SX1H60USNt6B3gmcKYjgHCnHRWylVl5EQtg2PoUeOLVsWcAghcTx5TL7QCs1Yx7BhBU+z/A0VLAKOo5XJYhtieE7td6k+tqD1SdO/kEr1OuVBtb7e7wgI5WCTpJPOr2TKzAkKVDg8KawMUImJJjSmTP6KoEhq3eJhyOuF6/bpryIEqtrgB6UYZ/BjBi6V2Fs6HQwnziPZAke8NWjbGvDjCHcgxHCppVZal5uqoONrCM8KGvj8zGI/nsA0Q4Jb4Nw2aLwOJvNUWm8li+NQwktdGrlWyxR6tpFKtOyX7rrWWu8KY/RWYjJ+W6fcMAOvxcx1N7TInLKT+gcC3pPBBUwM8cSi5kfphSCcLGPkwMWzsuAXDbq7kKrwf2mS7Pya/mjXnYAN7U+WrvONM/78t22KmIBUjbmhMoAKGvooijNqnwcsBlpj4BdwTgPS5EuKFwJUkfALac38ywIHtRfn8Bx9qgMJt6UN8AAAAASUVORK5CYII=\");\r\n}\r\n.toolbar-container ul li.btn-delete{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACBElEQVRYR+2XsWsUQRTGfy+msNMqmZgDIyJYiDYWYnX5C6wEsRADtuosiq2nrfFuEms1doKdtZBUgpUoiEVAUyjM+Q9oIfdk1t3l7ty9nbjYyE6zw843M9987z3ee0LEWNzQ63PKJYXzEXAEXo+E58Ob8qgOL3WABaen5+Bdhtupw2fr3fAdwZlvVt7P2lNLwDi9BawruKGVJIbAotOBgAVueysPmxEYaA/hLso9n0gvhoDZxx4xTp8CVyMOjpU/Pyo1Q83YCQQ+Ayt1yH+0viedvi7/FE4UFwjbQW5gvy+ezTGYEbooqzlwXtmdcEIz0C4ZgVh7xypjnG4HAt7KxJ0tgSgFzECDk4ZI2fKJ7OWyG6cXw9xbeVH8q8b+vQnGQnXLW1kLl6WkhBBBoBzLiZVhU3wTH8g3h8jwVlIvLhz2N4FVn0gaNWXYlkCrQKtAq0CrwH+gQEWRaZxqlg2LrFpVkDZKRnnyyRNOnnqXN7UT5l9vyJfxyigkqmlsYwKxpVcVLorAkXU9OZrnI/DBWznV9NKiSNnQNZQnwBtv5dz4uRMV0UpPD/44zC7QUbg2tPK4KYmlvp5V4RXCobLm5o/WbMlpotDPKp3obmia6MKmHj8w4r7C5WztgbdyZxpX2hsap8+AK01fP7b/pbdyoey8yuY0C6ejDbqm7yifVHg7y5S/AHMyMO8AdUhTAAAAAElFTkSuQmCC\");\r\n}\r\n.toolbar-container ul li.btn-cut{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEFElEQVRYR+2WXYhUZRjHf8/sLn1BbNrunFkjAlGEwDYTytxALyo30FpNQYoipfWiZefMIlZIZV1YYuw5sxK1ZUkQIipKaUJdtNInIUkiRKGlRe6cUXMJoRLW848zzsjs7MzuzAh50wvn5jxfP56v9zWu8rGrHJ8JAZwBtQS9duZKIB1PG4KUbajkY0KARFrfZ5LWXi9AIq1uhbTVDeD4Og4cC1zrrBUi3q/FFuMjxMt1AyTS6pLYA2wLXFtVLUSiX3MV45Bgb9a1pRPZTdqEUQ0xXhJszLq2fjKIqf2a1hTjMHD+YsiiM30WZbHimRQgsoz72mPQhdETJO2Nit4kS6Q5LGg3Y2kmaXsnA64KoG2LZoUX2Q9MN7Esk7KoLOOO4+sA0DlZ3YsNqwKIDBxfy4GdgEIx/3TKvil2lPC1VbC6mrrXBZCD8PQKxguCUyY6gpSdLP4P/KyQrmyfHZ0s9QV51RnIGQypMX6EXQaPAIemttFxLsMqiTcjsYzHsknbXm3wSK82AKBtQHeGIbty/WB8KPFw3tFrGdeeryV4XQD5fngCeP9yMPFxo1j+e5/9/Z8AtKQ1owE+CJJ2t+PpoBmfZlzbWGvwujIwZUA3NoXsNri/OKDE09mUba0VouYeSPjaLliZD/Q50AzMzjVhyJJsn+2rBaImAMfXFqAnH2AkNspMGpkawo+Ff4LOrGvfVgtRESDh60GJecSYgxhBnIjuhILjmOgYTtlX+T2wDmNTXnYcy92C04BGxP6GJvad6rE/ykGVBYh7GjSjG9hhcBJxs4x5wO25VIvV2ZS9V+ww7utLg/n5f98BRxG/YixC3ASsCVJ2sBRiHIDj6wugA7Gw2KDZU/M1xpDBtMC11lJHhVvTjDWZpL1dLE94cmV4obi3dIWPAUj06wHF+CQGs4Zd+6lcyhxfQ8APgWvPFMsdT8eAwSBlr5e18zSA4QSurSiWjwFwPK3FuCdw7dFKTZQYkKuQpwLX7ijoOJ46MfaMXqD17LN2vgJApHMgNkrL8Fo7W9AZAxD3tdcuPcHWVQJw0noIsTtw7fqCTtyTa8bjgWtzK9lFJbzWGAljtJ/utSNlARxPm/JperJiBtLqDsWLWdduuZyBS1f1YODalIrgnm7DOKGQ2cW35dge8LVMsD52HR3Da+yvsqlM613EzMC1+wryNk9zQuNrM3pLG7CoTAswhi78Q/PIc/Zn2QzkZvpSkxG4trBSp8tYmU3ajjGd7utViegBWnbc8vtiQekojhvDuK8lBtuADPCWhUSLpUXGYmD5RM8tx9dOIr2Qd0L4rAFuEMwADpbbARFU2UXUOqDpFrLZLLd4bgXOIX4JxbrSOR6XJV/RGN6V/6Iy/gZsDlyL3hDjTk13QaUGu5L//wP8C7qXoDBLpnUnAAAAAElFTkSuQmCC\");\r\n}\r\n.toolbar-container ul li.btn-undo{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACrklEQVRYR8WWTUhUURTHf2cccxEtMmPeiAmFi8iVQRREkBBFXxBBGZFEUBBIzRtBo2gT0cIPZt4IGRUECZG6KyIwNwURBEEthHBtOi+ClhUoc+I9dZrvN+9BM297/ud/f++ec++5QoAvktLzkmHJjsvbAOl5KeLXwLB0ABhG6a45QMTSpIDpQtcawEjpFMrZ7I7VCiAyohsJMyPC/rxy1QJga0I7GkK8AbYX9cr/BoiO6QHNMAs0lWzU/wkQsbRHYNLjhExJhtF0v3zye5Jy9UXHMJpUU4WkD9OvwHiokRdLfbLgI8+V5gEYSR1CGPRrkqOfFLifNuV9tR5ZACOlEyi91SaW1Ql/JMPNdFysarykfVw3Ly8zrcqhahKq1ajw7HtMLnjpxbB0Duj0EgaMz9um7KyUK62j2pIJMw10B1zEK+2nbcqWcqJ/PWCpc+x6vNyCxCuVo/AUjCFcC7KIZ44wYMdktFBXfA9YelvhrqdhMMFF25SJiheRE4yk9LIojz3W+Ay0A2XrW5ivsCghuuzr8mM9VvZBErX0iMJLYENJkPVZ8FAbo7/oUNiLcAY4VhFcuGHHZNgTwBEYY7qLDDNAW5FpmWFkWLoHXJA4EC4BMx/O0PWtX347Mc8nWcuQbgo34bz9dueZeUzDiKUnBJ4CzSUg+mxTxqsCWE82UvoK5XjWrIpx3JbQ5pUQzrQsfE98tE3Z5wvALYmlD4CrLkQVAFl4S18DR3N3okHpWozLF88SFG5fxNJbAvf8AKzBPwEu5fgN2qaM+AZwDKJJ7VVY8PssNyx9Dpxb2/rZtCmHAwG4f5TUg34BIkndIeI29DbHY0VpDQwQ7CKEqKVXFB6tthEnaw6w1g+rg0+5Ux+AhHYS4h3Ch7oAuPMmoTEJcapuAGulmKsrQDSpp/8CHSTrKGEN0hMAAAAASUVORK5CYII=\");\r\n}\r\n.toolbar-container ul li.btn-redo{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACv0lEQVRYR7WWTWgTURSFzxksqEtROmntWgQXCoIggtnUboqCi1oQBBcqKiQzsT8LEV2IYoVmohQERZRKoRZEFBeKUkGFgmA3VlwUBRXnDf5sxEVNmisTkxjSzPTNpHnL5Jxzv7lz33tDrMAys5IUAx1emhNR4xjV0EjvA4CYBjCkLF6OkrnSABDA8SzauhArDlAqTNxVaR7QgWgNAAARvEIBPd4gf4eBtAygXPTjYhF7vmU4HwTRagC/7gINdLspvmgE0TRAYlS2i4EBAKHvXIB+z+JkPUQsgI4x6SrmsQ/ACQCbdYatNJsC27Xp1OojASQc2SXASQD9ukWX6AQjyuZw5XdtgERWLDFwEYLVsYv/rzqu0jz0b8dqrPac3KHgoIZUW0LiaVsb+pYFMB15D2CTdnI04VwogOnIDwDromVqq6eNQkgHWtH2GrRJZbE0yA07YOZkAIJIt5r2cwuuKpupwF1gOuJP523twAhCAmdci+cDzwHzimyQImYJdEbI9efkE4BtYR4hjnhp3gg9Cc2cDEFwKSzI3z4CTLCIGXct5nGM+ZoPkkbWPwT2uhYfh94FG0dlTcHAbOCWI276hb00n9UHhQB8gYEeleK7oIeqDqHpiH+ujzUQ/oTglLJ5KygkAOBNYQHJ78P8FdrRyp+mIzMAdtSJXwLIKIuvw0KWABCPVJq9OnNU6kBnVrYustT+2jUleRxe7ovGN9QBXFMWj+sUr54DpiODAEZqTFPKYp9uSAVAgNOexQu6vipAwpEnAnSXjZGKVzpAoMu1OR6leAlgfVYSq4ivZeNnESQ9mx+iBPkdUDafR/FUtGx3pJfAw3I7jroWr8cJiuuhmZVzIM4CqF4QccPi+Gjm5AEEO1HEbpXhXJyQZjw0HZmWIu57GeaaCYrr9QHeKotb4gY062MiK/tdm/eaDYrr/wsm9/oZYCxm5gAAAABJRU5ErkJggg==\");\r\n}\r\n.toolbar-container ul li.btn-debug{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACDElEQVRYR+2XvW7TUBTHf/++AhJxXgJegCEMiIGlDEgMgICFIiFiV7AgJFoJiQWwqy7tUIH4WFhg6dKFDrwAfQnbPAPyQY6S4hTfe50QFITwmHs+fj7nf65PRODpZ5ZU4lQ51OOQbfO8t2VPVfG9SLTh85PvsJfabYm92sYgLWOtd4HoZfZSkIxsjU0fhBMgyuwK8KGZ0GC3jLXmhc5sR3BnysYD4a1AGwTG2yLRjTaIKLU3iOsnzh4UsV64oL0AtVMrBHwB+kBvHLgEcuBcM5FgLY+1O7cGJo4OCK8cZFzLE70PaSZYgXEVai3Umuj8mHhUDvUs5BAEiDKbOfkkqYmDcqiLc7cgSm0D8ST0Ft4EFXfzde04RRilNvAkGfxO8obvYWscY1NjgM8LSjRbGOP8XwPQ1mdX+dvL+fPdu/vVLWir2enUVlfER1ffXHe7T7SVcflbok8nY7YCeNXvudfn8Vs+gGcMu/dyuq6d/YpYy52CIpaWOoZNgKWM4agFjsXC/Q3456bAUYGbiFeLvIgwbhWJXv9yEf0fw2V+jkNjWLfrjy4kzjE83oYXsJIhnhdDPXSuZKEVpp/ZgcGFkJ3jvCxiRXMvpceVyOwesD0ThLFdJLof8gmu5ZMAvS27Khv95wvp4lCwl8d6F0pen3cGaOhiwAqXDM7IOFv/buKr4IiK/SJRaGWb4voBFz7zud8HAWwAAAAASUVORK5CYII=\");\r\n}\r\n.toolbar-container ul li.btn-debug.disabled{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAB+klEQVRYR+2XvWpUURDHZ/YVUqgvYV4gxfnfRSxskiKQQkVtTCAICaaRQLIQSONHkcYUQclHY2OaNIG9cwpfwLyE5B3CHTlhL1zXc87sXlauoKedr9/Omf/ZuUzG8d5vqOocgG3Lt2kXkb2qqm76/f5uLo5zxrIsXzDzUfBh5g/Ouc1JILz371V1I/hWVTXIQSQBvPfLqvplrOAhgNUchIh8JKKXTZ8cRLYDMQhVPSmK4mkMoizLY2Z+MmZ7DeBdCjoLEIISnfhGRPeI6M4o8TUR/SCihbFCqwAOW89AHZiAsMbhMYAzy8nsQEggImEWlq1kTTszv3HO7VsxJkCb4nVRZr50zj1sfQXD4XC31+vtWL/CsK8BCMqIHhYRR0SpIsE2i+MTSQY1gMyiSosc+GsAYleQan+qnXUDpokbRFUgIotE9DXW0tyzagztEoDz8ZxRgFyitgCpuO4BMjKc5i6bnZ04DkC3Kgj1O5VhE6ATGd5eQUxq/5YKYh0QkWdE9GnGD9FzAJ9/e4j+y3DUgU7+ji0Zhuv6owtJUob1oMxoJXsLYCu5kllbjPf+UlUfWH4J+zWAu62X0jpQRNaJ6GAaCFU9KIrilRVjruUNiJXRN581F2FjOgJwahUP9okBGiAB4BEz31fV+dskzN9V9YqILgBYK9svXD8BsfwbJJRBMOgAAAAASUVORK5CYII=\");\r\n}\r\n.toolbar-container ul li.btn-stop{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAA7UlEQVRYR+2XMQ4BQRSG/zcnUGklknUBbkGosCqTLWk4AU6g0gqVDRXhFhyASSRalRPME8ui06yd5m31spm8/59vm/0IX8/Jb5eJKGcZ2e/3Sc2KcGXmSyGc7+KdFA/G13sAxaTCfuw5eOGs9DgTFTAtvQSjnlL4M4aw8hazBhk/qAJ2nWr4O0zVyDR1D4SxkwKMPh19PVTAwEUBC4ykgBAQAkJACAgBISAEhIAQcE/AtHQHjImLv2IQuhT5IGjrogCDK081S9cL47tGfviR0zT98OWFbzn9GHJQBdu8JWT+8UkU4wZSZy+cbuL9d70ElVavuQ4NAAAAAElFTkSuQmCC\");\r\n}\r\n.toolbar-container ul li.btn-stop.disabled{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAA60lEQVRYR+2XIQ7CQBBFZ6YXQGFJuAL4ZmshoOAQYOAEwAlQXAIUBGQ7rYcrkGBRXKCzpA1L6jCla6Zq0mzm/301fQiVJ0mSARF18jxvV9/XNQdB8BSRRxRFF7cT3cDMVwDo1RX2Y8/NGNMvzpQFmHkPAJOGwl3MwRgzxSzLRiJybDi8jCOiMTLzAgC2PgoAwBLjOF4T0cpHARHZaAEloASUgBJQAkpACSgBJeCfQJqmM2vtzsdfMSLOsfBBRDz7KGCtHTo1a9IL3V1LP6zKaZN+WHrhV05dpY8ndkWk9Y9PQkQvIrqHYXhy+9/sjtdIJLkrLAAAAABJRU5ErkJggg==\");\r\n}\r\n.toolbar-container ul li.btn-test{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABSUlEQVRYR+2XoU7DUBSGv1MUT8D2NmgUCkGCxbH0PgFDoNjNhkBiZ0hAAQ4cFgwgQbE2kDCJ4pDL1oSQbu26tndi1W3+7/z3/uecCp4f8azPEmDxHWh2dUfhIzJyXcV9mepAo6tthH0nLHCHYgdGzssEmQ7Q00tg46+gQn8loPPWkvsyQLIAboH1FKEvVewqdF6NDOcBKQqQaD4r2DiU06IQ8wIkulfOkdjIzawgZQGMdIUTDbDxnrzkBSkXYKQ6AGwUis0DUQXA2Ix8sa0MIKnexfZbOXo38pDmSOUATlRhKMp2WjetBeC3cuUgMtL+70JdAI8EbEUteaodwN0BCThMEx/PmMlhafR0UivOTFje4VXFEXjsAx47obdZ4G0a1rMPrPX0QmDT30bkeyd0lTePdVeVzyiUs8zwF3hh8f8LChQ10ydLB34An+qlIUX4b60AAAAASUVORK5CYII=\");\r\n}\r\n.toolbar-container ul li.btn-test.disabled{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABRElEQVRYR+2XoU/EMBTG39fu71rb1KFQCBIs/waHQHGTSCyGBBScauewYACJxJBwErOV9MKSC1m63a5bT1z1lu/Xb+977w2U+CCxPu0Bdt8BY8wJ5/xLSrkYo16CDhhjZoyxMy8M4AlAIYS4iwkSBLDWPgA4WBd0zt1wzudCiOcYIEGAsixLIpItQj9EVBDRXCm13AZkKECj+e5BlFLXQyG2BVjpAnisqqrQWttNQaIArIleMcZ8oX70BYkN4N34dM75z+JrpPNEB2gU+8Z2NIAGxMcWwKVS6qXNjtEB/kSXAI7buulUAFTX9bnWevbfhakAXjnnR3mev00O4Gsgy7KLNvFVDwnlJNCKu+PVc3hFB0jdB9J0wpSzINk0nGwfuCeiw2QbUfKd0N/cWnvKGPuWUt52hn/AA7v/XzDgUhu9snfgFw99pCHdhM6eAAAAAElFTkSuQmCC\");\r\n}\r\n.toolbar-container ul li.btn-resume{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACYAAAAgCAYAAAB+ZAqzAAACIElEQVRYR83Yz2vTYBzH8fcnivgXaI/eBcHjQGSCRw/C8OBhZ0GkTdUJCo0TxSkIrSJ4ENGDFxl4UWSi4A/cScSLguJBFG1SGYrDH1DtvpKu3WZZ0zZr0+QU8jxJXvnky/M8PALYVLS9jjgGjBjMGpz64upB2NbuyJRsH7BTMO27ehrVN06bMkUbRTxquXnOgR1lV28jYO+BLUDNwKu4OhsH0O4eZS7aeaye1n+HQbHi6nAEzFra7kl4fk4v+gFUpmRhWqOrPOxx4GpXD7Cw67xBoeLq0lpx/YY1PdMOFKJKoRN8ULDwveVG7V3rhFitfZCw5vuur3PwPmf1qRdgEjAE7xYWa+9Wt7hEYCswlzf8xvt4XN86AZOGgXhpVh/37kbhkoc1NBLn/I14HNCfYRV/VDDPqOEFR9Q68zC0xJa0wmR4vqszK79g+LBlzX2gELh6Hl5KEyz0/BD19Ippg9XzsxojqYTVjO3pgxkHg7yupAcmPlTFtq9Zzaem+CWm/JxOpGm4CFjP7uCQXreOwkP7lQY3K67G200Lw4DNSYz7Oc2kaRK/Hbga67TkSbL4vwsmfFdXu0ElAxN3rMr+yoR+dosaNOwX4mSQ04VeQM2+gyr+Gecv+fJRvYmDGkRiVTMmK3lNxQUtJ9anLQKJh1pgspzX7FpRi4nF31R5BWwNVymNWjrdD9BSYuFJnG2ozSXbIxjD4UaQ1ZN+osJn/QMBvnXeD1WGKgAAAABJRU5ErkJggg==\");\r\n}\r\n.toolbar-container ul li.btn-resume.disabled{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACYAAAAgCAYAAAB+ZAqzAAAC5UlEQVRYR7WYy2oUQRSG/9N5AN/DhRsfYOpUWpGgSPCKBGMUFYJ4JagoiToQogQSEVGiGDFIUIxIIEiYpKqYLIKKILh34yqoIEIWCp0jPc6EcZjO1NRMatX0ufxfna4+XV1UKBS2RFF0IYqi/QC+AZhJkmQ6juMfyBjOuQMAxkXkg4jktdYfs3xD75MxpouI5moS9DHz06yk1loLQJXtqwDyzHw7FKJeHFlrJwEcqzHOMHNawbrDWmsAcI1xIYqifC6XK7YDMAWrnn0lp2PmWuF1PWttAUBcd6ZEeaXUYKtwQWDOuXkR2ZklTkTLIjLIzAuhgEFg1tq3AHY1EhWRMa31xUZ+WWus6UdpjJkjoi5Pwc9JkpyL4zjV8R5BFXPOzYrIHm8VAEQ0oZQ67RsTBGatfQNgr69IxY+IvqQdQCm11Cg2FOw1gO5GybPsIjKltT66UXwo2CsA+0LBynErRNStlFpu2+J3zr0QkYMtgpXCRWRSa328NldoxaYBHG4HWDnH6tramu7s7Hy/vh5DOr9z7rmIHGkjWCkVET1SSp0qXYeAWWunAPS0G6ySj5kpFCzdefRuFljavIPAnHNPRKRvs8A6Ojq2B4FZax8DOLEZYETUr5R6EARmjJkgopPtBCOiryKyjZl/trL4HwLw/u41mgAR3VFKXa72C6qYc+6+iPQ3EvSwfwewg5k/tavB3gNwxkM404WIXiqlDmU5BFXMGHOXiM6GgBHRLxHpZeZ0h5INHthgxwCcDwCbY+bdPnFBFbPWjgK45CNQ9vkN4Aozj/vGBIEZY+4Q0YCnyEKSJD1xHK94+v/7bgY+yhEA/73e9URFZEhrfasZoIpvEJgxZpiIrm4guCQiA1rrdyFQrVQsD+B6RpWGtdbXQoGqK9b0EYFz7mb6Q1stLiLpJm9Qaz3fKlSpYiGHKouLizeiKBqqAhhlZt+XwYubQo6hisXi1iRJnhHRHyIayeVys15qTTj9BSbJmzEMQ5JCAAAAAElFTkSuQmCC\");\r\n}\r\n.toolbar-container ul li.btn-history{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADWUlEQVRYR8WXW2gUZxTHf2c2iRQvRFEz24JgKwoKKih9qOCNolgUH4qhor55AZXsRBRpXxpBFBGzk6CC2gdfpHhB0AdbEGl8qL644AWVSr2gmJ0YqVRtITW7p3zDLG6WmZ1xkGSeFr7zne/3nXO+8z8rjPAnI3w+oQC2q78ZMM+RJdWAnx3W6aVBVgErUT5FyILvo0+gT+F8qZEL/dvkz6QXiwLQAMBfz3bqMs2wHmVDQscXVejuy8mVOPu6AOUSczMZdiusDRz1C1wG7pfhZkkp8B9vR40mW3rnR8REZhXCdGMvwk/FnGyqB1EXAOgHJgUOjpYtOl+0ycO4W2W7dLMqm4F5wG3PkTlRe+IAzL6LKHnfQYYXXpvciwOorNuungHWVKezdm8SgCF7RJhfzEkhMUReOxB+BH7xHPkmKcARYGbIIfcGlb0v26WYFMAv4i7dp8r3YTWRug9k87pOLV55ObmUBMZ29RYwW4Wvq19HKgA7r4sRTK/oqe0VUTB2XnciHDQ15TmyumI3bACTXW2xhPso47GYVSnmYQMwN7ZdPQd8i7LHa5cOv1ckyV+tTZoUGB8tnfqdWPwscK3oyIJhB7C7dSZl7gIPPUemDQGIEqCwCKWNwIRuHddU5m/grefI2FqAN8CYwQHGvdwt5nfklxZg4gEd2zCK11EARkK/yDQw4/l2eZAUAOVqHdser116KuuBnP8RmoKsq78rfIWypHpTTArq13BVtfuvIOgfoUVYaZfAIc+RnXGvw3Z1K8rkGLshEch26QlVNoY+w6yryxV+RXngtcuMOIA067arj4CpoY0oqNA7wBQRthRzcjzNIVF7ghnhWN1W7IcVjBIWPEfmf0wA29UbZkCJFSPbVaNuK4CzniOtHwPiw+S4QxvsZt75B9dUcRoY+/1AUrA+YWHvFvm32k/4RNSlrSin/U4l7C/m5IdUh1eNZFYjU3q3ybNaP5Fi1JLXpSJUxuqCCMeTFmbNUOqJsDJqjKuvhms0Yy9gP7ArIH9soLTMqUwjvQP/4I9mUWM5cLKpiR1Pt8qrqAgmkmO7U1uxfJDPE6biSVA/J+PsEwEYJ815bW6CxSIssuBLhRYj8cEB/l8zhKtGGwYsrv/VJkZ0Yr/EALGeUhqMOMD/LOV+MJQ5P0UAAAAASUVORK5CYII=\");\r\n}\r\n.toolbar-container ul li.btn-dock-right{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAqElEQVRYR+2XwQlCMRAFZ1tQhJTzLUILsAFzsQo/qG3YhJYTFGwhEvDg10BEAnt5OSabzWRyeTGchzmfjwCqBsIp78lsgFmnJ7oD1xRt/dnvCyAc8xlYdTp42sYY09Z275M1gBuwKMR/QAyNPY8Ubd4CyK+Comz5K0Q45AHj0qpP0SaXrhkQgAzIgAzIgAzIgAy4G3CPZL6htGQ611jeCpW91/U1czfwBJRbxiFeN0yUAAAAAElFTkSuQmCC\");\r\n}\r\n.toolbar-container ul li.btn-dock-bottom{\r\n\tbackground-image : url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAA1UlEQVRYR+2XwQnCQBBF37SgKClHi9ACbMA9mLN41kNswAK0CC1nMZgWVhaDmE0gKCO5TI5LmHnzFnb/CgN/MnB/DKDTQFaEMzADJkpbVAI372SZ1msBZMewJ7BRatwsIxz8WvLPxTZAER7AKBIrQ0SjlXcy7gMI9Q9R2VwDIivCtd5SvJPG0F0GDMAMmAEzYAbeBgjsNI5ihO0vR7FK77TIN3eBAZiBwQzcFcNoOkTpnUz7IllMxIu/jA+XNBl3x/JXMl7V4VSDpUI4pYk4FraXkRl4Aos4pSGe+LbHAAAAAElFTkSuQmCC\");\r\n}\r\n.spiderflow-debug-tooltip{\r\n\tposition: absolute;\r\n\tz-index: 2147483647;\r\n\tbackground: #fefefe;\r\n\tborder-radius: 2px;\r\n\tpadding: 5px;\r\n\tborder: 1px solid #eee;\r\n\tbox-shadow : 2px 0px 5px 1px rgba(0, 0, 0, 0.6)\r\n}\r\n.spiderflow-debug-tooltip .content{\r\n\tmax-height: 500px;\r\n\toverflow: auto;\r\n\tmax-width: 500px;\r\n}\r\n.spiderflow-debug-tooltip .content::-webkit-scrollbar {\r\n\twidth: 5px;\r\n\theight: 5px;\r\n}\r\n.spiderflow-debug-tooltip .content::-webkit-scrollbar-track {\r\n\tbackground-color:#ccc;\r\n\t-webkit-border-radius: 2em;\r\n\t-moz-border-radius: 2em;\r\n\tborder-radius:2em;\r\n}\r\n.spiderflow-debug-tooltip .content::-webkit-scrollbar-thumb {\r\n\tbackground-color:#999;\r\n\t-webkit-border-radius: 2em;\r\n\t-moz-border-radius: 2em;\r\n\tborder-radius:2em;\r\n}\r\n.spiderflow-debug-tooltip::before{\r\n\tcontent: ' ';\r\n\twidth: 0px;\r\n\theight: 0px;\r\n\tposition: absolute;\r\n\tbottom: -17px;\r\n\tleft: 50%;\r\n\tborder-width: 8px;\r\n\tborder-style: solid;\r\n\tborder-color: #ccc transparent transparent transparent;\r\n\tmargin-left: -4px;\r\n}\r\n.spiderflow-debug-tooltip::after{\r\n\tcontent: ' ';\r\n\twidth: 0px;\r\n\theight: 0px;\r\n\tposition: absolute;\r\n\tbottom: -16px;\r\n\tleft: 50%;\r\n\tborder-width: 8px;\r\n\tborder-style: solid;\r\n\tborder-color: #fefefe transparent transparent transparent;\r\n\tmargin-left: -4px;\r\n}\r\n\r\n/*样式调整*/\r\n.properties-container .editor-form-node input, .properties-container .editor-form-node textarea{\r\n\tfont-size : 12px;\r\n}\r\n.properties-container .editor-form-node .layui-input,.properties-container .editor-form-node  .layui-select,.properties-container .editor-form-node  .layui-textarea{\r\n\theight : 24px;\r\n}\r\n.layui-form-label{\r\n\tpadding : 0 5px;\r\n\tline-height: 24px;\r\n}\r\n.layui-input-block{\r\n\tmin-height: 24px;\r\n\tline-height: 24px;\r\n}\r\n.layui-input-block{\r\n\tmargin-left : 100px;\r\n}\r\n\r\n.layui-tab-title li{\r\n\tfont-size:12px;\r\n\tline-height:24px;\r\n}\r\n.layui-tab-title{\r\n\theight :24px;\r\n}\r\n.layui-tab-title .layui-this:after{\r\n\theight : 25px;\r\n}\r\n.layui-form-select dl{\r\n\ttop : 26px;\r\n}\r\n.layui-form-select dl dd, .layui-form-select dl dt{\r\n\tline-height: 24px;\r\n}\r\n.layui-table td, .layui-table th{\r\n\tfont-size : 12px;\r\n\tpadding : 0;\r\n}\r\n.properties-container .layui-table-cell{\r\n\theight : 24px;\r\n\tline-height: 24px;\r\n}\r\n.properties-container .layui-table .layui-input, .properties-container .layui-table .layui-select, .properties-container .layui-table .layui-textarea{\r\n\theight : 24px;\r\n}\r\n.properties-container .layui-table .layui-input-block{\r\n\theight : 24px;\r\n\tmin-height: 24px;\r\n}\r\n.layui-table-view .layui-table td, .layui-table-view .layui-table th{\r\n\tpadding : 2px 0;\r\n}\r\n.layui-form-item{\r\n\tmargin-bottom : 5px;\r\n}\r\n.CodeMirror{\r\n\tpadding-top : 0px !important;\r\n\tfont-size : 12px;\r\n}\r\n.CodeMirror-lines{\r\n\tpadding : 0px !important;\r\n}\r\n.CodeMirror-cursor{\r\n\theight : 16px !important;\r\n\tmargin-top: 3px;\r\n}\r\n.layui-form-item .layui-form-checkbox[lay-skin=primary]{\r\n\tmargin-top : -2px;\r\n}\r\n.layui-colorpicker{\r\n\twidth : 16px;\r\n\theight : 16px;\r\n\tpadding : 4px;\r\n}\r\n.layui-icon-down:before{\r\n\tposition: absolute;\r\n\ttop : 2px;\r\n\tleft : 6.5px;\r\n}\r\n.properties-container button.layui-btn{\r\n\theight: 24px;\r\n\tline-height: 24px;\r\n\tpadding: 0px 5px;\r\n}\r\n.properties-container .layui-form-item .layui-input-inline{\r\n\twidth : auto;\r\n}\r\n\r\n.layer-test .layui-layer-title{\r\n\theight: 24px;\r\n\tline-height: 24px;\r\n}\r\n.layer-test .layui-layer-setwin{\r\n\ttop : 6px;\r\n}\r\n.layer-test .layui-tab{\r\n\tmargin : 0px;\r\n}\r\n.layer-test .layui-tab-content{\r\n\tpadding : 2px;\r\n}\r\n.layer-test .layui-layer-btn{\r\n\tpadding-top:2px !important;\r\n\tmargin-top:-5px;\r\n}\r\n.layer-test .layui-layer-btn .layui-inline{\r\n\tmargin-top:5px;\r\n}\r\n.layer-test .layui-layer-btn .layui-inline input{\r\n\tfont-size:12px;\r\n\theight : 26px;\r\n}\r\n.layer-test .layui-layer-btn a{\r\n\theight: 24px;\r\n\tline-height: 24px;\r\n\tmargin: 5px 5px 0;\r\n\tpadding: 0px 10px;\r\n\tfont-size:12px;\r\n}\r\n.layer-test .layui-layer-max{\r\n\tdisplay: none;\r\n}\r\n.layer-test .layui-layer-max.layui-layer-maxmin{\r\n\tdisplay: inline-block;\r\n}\r\n#test-window{\r\n\theight:340px !important;\r\n}\r\n\r\n.history-version{\r\n\tlist-style: disc;\r\n\tpadding : 2px 5px;\r\n\tmax-height: 200px;\r\n\toverflow: auto;\r\n}\r\n.history-version::-webkit-scrollbar {\r\n\twidth: 4px;\r\n}\r\n.history-version::-webkit-scrollbar-track {\r\n\tbackground-color:#ccc;\r\n\t-webkit-border-radius: 2em;\r\n\t-moz-border-radius: 2em;\r\n\tborder-radius:2em;\r\n}\r\n.history-version::-webkit-scrollbar-thumb {\r\n\tbackground-color:#999;\r\n\t-webkit-border-radius: 2em;\r\n\t-moz-border-radius: 2em;\r\n\tborder-radius:2em;\r\n}\r\n.history-version li{\r\n\tcolor: #333;\r\n\tcursor: pointer;\r\n\tfont-size: 12px;\r\n\theight:22px;\r\n\tline-height: 22px;\r\n\tborder-bottom: 1px solid #eee;\r\n\tlist-style: disc inside;\r\n}\r\n.history-version li:nth-last-child(1){\r\n\tborder-bottom: none;\r\n}\r\n.history-version li:hover{\r\n\tcolor : #1890FF;\r\n}\r\n\r\n.main-container.right .sidebar-container{\r\n\tbottom: 0px!important;\r\n\twidth: 50px!important;\r\n}\r\n.main-container.right .editor-container{\r\n\tbottom:0px!important;\r\n\tleft:38px!important;\r\n}\r\n.main-container.right .properties-container{\r\n\tright: 0px;\r\n\theight: 100%!important;\r\n\tbackground: white;\r\n}\r\n.main-container.right .layui-col-md2,\r\n.main-container.right .layui-col-md3,\r\n.main-container.right .layui-col-md4,\r\n.main-container.right .layui-col-md10{\r\n\twidth: 100%!important;\r\n}\r\n.main-container.right .resize-container{\r\n\theight:100%!important;\r\n\tright:40%!important;\r\n\twidth: 20px;\r\n\tbottom:0px;\r\n\tcursor: e-resize;\r\n}\r\n\r\n.layui-table-body.layui-table-main .layui-table,\r\n.layui-table-body.layui-table-main .layui-table input,\r\n.layui-table-box .layui-table-header .layui-table{\r\n\twidth: 100%;\r\n}"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/css/index.css",
    "content": ".layui-body .layui-tab{margin:0px;height:100%;}\r\n.layui-body .layui-tab .layui-tab-content{position: absolute;top: 40px;bottom: 0px;width: 100%;padding:0px;overflow: hidden;}\r\n.layui-body .layui-tab .layui-tab-content .layui-tab-item{height:100%;}\r\n/***********************/\r\n\r\n.layui-layout-admin .layui-search-menu{\r\n\twidth:200px;\r\n\tpadding:2px 5px;\r\n\tborder-bottom:1px solid #eee;\r\n\tbox-sizing: border-box;\r\n}\r\n.layui-layout-admin .layui-search-menu .layui-input{\r\n\tmargin:0;\r\n\tpadding-left:10px;\r\n}\r\n.layui-layout-admin .layui-search-menu .layui-form-select  .layui-anim{\r\n\tmax-width: 195px;\r\n\tmax-height: 360px;\r\n}\r\n.layui-layout-right .layui-nav-item a{\r\n\tpadding : 0 10px;\r\n}\r\n.layui-layout-right .layui-nav-item a img{\r\n\tbackground : #fff;\r\n\tborder-radius : 4px;\r\n}\r\na{\r\n\tcursor:pointer;\r\n}"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/css/layui-black-gray.css",
    "content": ".menu-list,\n.layui-nav.layui-nav-tree,\n.layui-layout-admin .layui-logo{\n    background: #304156;\n}\n.layui-nav-tree .layui-this,\n.layui-nav-tree .layui-this>a{\n    background: transparent;\n    color:#1890ff!important;\n}\n.layui-nav-tree .layui-nav-item.layui-nav-itemed > a:hover{\n    background: #263445;\n}\n.layui-nav-bar{\n    display: none;\n}\n.layui-layout-admin .layui-logo{\n    color:#fff!important;\n    font-size: 18px;\n    font-weight: 600;\n}\n.layui-layout-admin .layui-header{\n    background: #fff;\n    box-shadow: 0 1px 4px rgba(0,21,41,.08);\n}\n.layui-nav .layui-nav-item a{\n    color:black;\n}\n.version-no{\n    font-size: 12px;\n    text-indent: 5px;\n    display: inline-block;\n}\n.layui-layout-right .layui-nav-item a:hover{\n    color:black!important;\n}\n.layui-nav .layui-nav-more{\n    border-color: black transparent transparent;\n}\n.layui-nav .layui-nav-mored{\n    transform: rotate(180deg);\n}\n"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/css/layui-blue.css",
    "content": "/* start */\r\niframe{\r\n\tbackground:#fff;\r\n}\r\n.layui-layout-admin .layui-header,.layui-laypage .layui-laypage-curr .layui-laypage-em,.layui-form-checked[lay-skin=primary] i{\r\n\tbackground-color:#1890FF;\r\n}\r\n.layui-layout-admin .layui-layout-right.layui-nav .layui-nav-child dd.layui-this a,\r\n.layui-layout-admin .layui-layout-right.layui-nav .layui-nav-child dd.layui-this{\r\n\tbackground-color:#1890FF;\r\n}\r\n.layui-laydate li.layui-this,.layui-laydate td.layui-this{\r\n\tbackground-color:#1890FF !important;\r\n\tcolor : #fff !important;\r\n}\r\n.layui-laydate .layui-laydate-header i:hover, .layui-laydate .layui-laydate-header span:hover,.layui-laydate .layui-laydate-footer span:hover{\r\n\tcolor : #1890FF;\r\n}\r\n.layui-btn.layui-btn-normal,.layui-form-select dl dd.layui-this{\r\n\tbackground-color:#1890FF;\r\n\tcolor : #fff;\r\n}\r\n.layui-btn{\r\n    background-color: transparent;\r\n    color : #1890FF\r\n}\r\n.layui-btn-danger{\r\n    background-color: #FF5722;\r\n    color : #fff\r\n}\r\n.layui-btn-common{\r\n    background-color: #009688;\r\n    color : #fff\r\n}\r\n.layui-btn.layui-btn-normal:hover{\r\n\tcolor : #fff;\r\n}\r\n.layui-btn-sm{\r\n\tpadding:0;\r\n}\r\n.layui-btn-sm.layui-btn-normal{\r\n\tpadding:0 10px;\r\n}\r\n.layui-table-cell .layui-btn:not(:last-child){\r\n\tmargin-right:3px;\r\n}\r\n.layui-table tbody tr:hover, .layui-table-click, .layui-table-header, .layui-table-hover, .layui-table-mend, .layui-table-patch, .layui-table-tool, .layui-table-total, .layui-table-total tr, .layui-table[lay-even] tr:nth-child(even){\r\n\tbackground-color: rgb(230, 247, 255);\r\n}\r\n.layui-table thead tr{\r\n\tbackground-color:#fafafa !important;\r\n}\r\n.layui-btn-primary:hover,.layui-form-checked[lay-skin=primary] i,.layui-form-checkbox[lay-skin=primary]:hover i{\r\n\tborder-color:#1890FF;\r\n}\r\n.layui-input:hover,.layui-textarea:hover,.layui-laypage input:focus, .layui-laypage select:focus{\r\n\tborder-color:#40a9ff !important;\r\n}\r\n.layui-input:hover,.layui-textarea{\r\n    -webkit-box-shadow: 0 0 0 2px rgba(24,144,255,.2);\r\n    box-shadow: 0 0 0 2px rgba(24,144,255,.2);\r\n    border-right-width: 1px!important;\r\n}\r\n.layui-table-cell .layui-btn:not(:last-child)::after{\r\n\tdisplay: inline-block;\r\n\tcontent : ' ';\r\n\tposition: absolute;\r\n\ttop:5px;\r\n\tpadding-right:5px;\r\n\tbottom:5px;\r\n\tborder-right:1px solid #e8e8e8;\r\n}\r\n.layui-btn:hover,.layui-laypage a:hover,.layui-tab-brief>.layui-tab-title .layui-this{\r\n\tcolor : #40a9ff;\r\n}\r\n.layui-nav .layui-nav-item a,.layui-layout-admin .layui-logo{\r\n\tcolor : #fff;\r\n}\r\n.layui-nav .layui-nav-item.layui-this{\r\n\tbackground-color: rgba(255,255,255,.2);\r\n}\r\n.layui-nav .layui-this:after, .layui-nav-bar, .layui-nav-tree .layui-nav-itemed:after{\r\n\tdisplay: none;\r\n}\r\n.layui-tab-title li{\r\n\tpadding: 0 5px;\r\n}\r\n.layui-tab-title li .layui-tab-close{\r\n\tvisibility: hidden;\r\n}\r\n.layui-tab-title li:hover,.layui-tab-title .layui-this,.layui-form-radio>i:hover, .layui-form-radioed>i{\r\n\tcolor : #1890FF;\r\n}\r\n.layui-treeSelect .ztree li a.curSelectedNode{\r\n\tcolor : #1890FF !important;\r\n}\r\n.layui-tab-title li .layui-tab-close:hover{\r\n\tbackground: transparent;\r\n\tcolor:#231f1f;\r\n}\r\n.layui-nav .layui-nav-child a{\r\n\tcolor:rgba(0, 0, 0, 0.65);\r\n}\r\n.layui-body .layui-tab .layui-tab-content{\r\n\ttop:41px !important;\r\n\tleft:1px;\r\n}\r\n.layui-layout-admin .layui-body,.layui-layout-admin .layui-footer{\r\n\tbackground-color: #f0f2f5;\r\n}\r\n.layui-layout-admin .layui-tab-title{\r\n\tbackground-color: #fff;\r\n}\r\n.layui-tab-title li:hover .layui-tab-close{\r\n\tvisibility: visible;\r\n}\r\n.layui-nav{\r\n\tbackground:#fff;\r\n}\r\n.layui-nav-itemed>.layui-nav-child{\r\n\tbackground-color : transparent !important;\r\n\tmargin-left:15px;\r\n}\r\n.layui-nav-tree .layui-nav-item a{\r\n\tcolor : rgba(0, 0, 0, 0.65) !important;\r\n}\r\n.layui-nav-tree .layui-nav-item a:hover{\r\n\tbackground-color: transparent !important;\r\n\tcolor : #1890FF !important;\r\n}\r\n.layui-nav .layui-nav-more{\r\n\tborder-color : rgba(0, 0, 0, 0.3) transparent transparent;\r\n}\r\n.layui-nav-tree .layui-nav-child dd.layui-this, .layui-nav-tree .layui-nav-child dd.layui-this a, .layui-nav-tree .layui-this, .layui-nav-tree .layui-this>a, .layui-nav-tree .layui-this>a:hover{\r\n\tcolor : #1890FF !important;\r\n\tbackground-color: #e6f7ff;\r\n\tborder-right: 2px solid #1890FF;\r\n}\r\n.layui-nav .layui-nav-mored, .layui-nav-itemed>a .layui-nav-more{\r\n\tborder-color : transparent transparent rgba(0, 0, 0, 0.3);\r\n}\r\n.layui-nav-tree .layui-nav-bar{\r\n\tdisplay: none;\r\n}\r\n.menu-list{\r\n\tborder-right: 1px solid #e8e8e8;\r\n}\r\n.layui-tab-brief>.layui-tab-more li.layui-this:after, .layui-tab-brief>.layui-tab-title .layui-this:after{\r\n\tborder-bottom-color: #1890FF;\r\n}\r\n\r\n.layui-tab-title .layui-tab-bar{\r\n\tdisplay: none;\r\n}\r\n.layui-tab .layui-tab-title{\r\n\toverflow-x: auto;\r\n\toverflow-y: hidden;\r\n}\r\n.layui-tab[overflow]>.layui-tab-title{\r\n\toverflow : auto;\r\n\toverflow-y: hidden;\r\n}\r\n.layui-tab .layui-tab-title::-webkit-scrollbar{\r\n\twidth: 3px;\r\n\theight: 2px;\r\n}\r\n.layui-tab .layui-tab-title::-webkit-scrollbar-thumb{\r\n\tborder-radius: 10px;\r\n\t-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);\r\n\tbackground: #1890ff;\r\n}\r\n.layui-tab .layui-tab-title::-webkit-scrollbar-track{\r\n\t-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);\r\n   \tborder-radius: 10px;\r\n\tbackground: #EDEDED;\r\n}"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/datasource-edit.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>DataSource</title>\n\t<link rel=\"stylesheet\" href=\"js/layui/css/layui.css\" />\n\t<link rel=\"stylesheet\" href=\"css/layui-blue.css\" />\n\t<script type=\"text/javascript\" src=\"js/layui/layui.all.js\" ></script>\n\t<script type=\"text/javascript\" src=\"js/common.js\" ></script>\n\t<style type=\"text/css\">\n\t\thtml,body{\n\t\t\twidth:100%;\n\t\t}\n\t\t.layui-form{\n\t\t\twidth : 700px;\n\t\t\tmargin-top:10px;\n\t\t}\n\t\t.layui-form-label{\n\t\t\twidth : 140px;\n\t\t}\n\t\t.layui-input-block{\n\t\t\tmargin-left : 170px;\n\t\t}\n\t\t.btns-submit{\n\t\t\ttext-align : center;\n\t\t}\n\t</style>\n</head>\n<body>\n\t<form class=\"layui-form\" autocomplete=\"off\" lay-filter=\"form\">\n\t\t<div class=\"layui-form-item\">\n   \t\t\t<label class=\"layui-form-label\">数据源名称</label>\n   \t\t\t<div class=\"layui-input-block\">\n   \t\t\t\t<input type=\"text\" name=\"name\" placeholder=\"请输入数据源名称\" autocomplete=\"off\" class=\"layui-input\" lay-verify=\"required\"/>\n   \t\t\t</div>\n   \t\t</div>\n\t\t<div class=\"layui-form-item\">\n   \t\t\t<label class=\"layui-form-label\">DriverClassName</label>\n   \t\t\t<div class=\"layui-input-block\">\n   \t\t\t\t<input type=\"text\" name=\"driverClassName\" placeholder=\"请输入DriverClassName\" autocomplete=\"off\" class=\"layui-input\" lay-verify=\"required\"/>\n   \t\t\t</div>\n   \t\t</div>\n\t\t<div class=\"layui-form-item\">\n   \t\t\t<label class=\"layui-form-label\">数据库连接</label>\n   \t\t\t<div class=\"layui-input-block\">\n   \t\t\t\t<input type=\"text\" name=\"jdbcUrl\" placeholder=\"请输入数据库连接\" autocomplete=\"off\" class=\"layui-input\"  lay-verify=\"required\"/>\n   \t\t\t</div>\n   \t\t</div>\n   \t\t<div class=\"layui-form-item\">\n   \t\t\t<label class=\"layui-form-label\">用户名</label>\n   \t\t\t<div class=\"layui-input-block\">\n   \t\t\t\t<input type=\"text\" name=\"username\" placeholder=\"请输入用户名\" autocomplete=\"off\" class=\"layui-input\" />\n   \t\t\t</div>\n   \t\t</div>\n   \t\t<div class=\"layui-form-item\">\n   \t\t\t<label class=\"layui-form-label\">密码</label>\n   \t\t\t<div class=\"layui-input-block\">\n   \t\t\t\t<input type=\"password\" name=\"password\" placeholder=\"请输入密码\" autocomplete=\"off\" class=\"layui-input\" lay-verify=\"required\"/>\n   \t\t\t</div>\n   \t\t</div>\n   \t\t<div class=\"btns-submit\">\n\t\t\t<button class=\"layui-btn layui-btn-normal\" lay-submit lay-filter=\"save\">保存</button>\n\t\t\t<button class=\"layui-btn layui-btn-normal\" lay-submit lay-filter=\"test\" type=\"button\">测试连接</button>\n\t\t\t<button class=\"layui-btn layui-btn-primary btn-return\" type=\"button\" onclick=\"history.go(-1);\">返回</button>\n\t\t</div>\n\t</form>\n\t<script type=\"text/javascript\">\n\t\tvar $ = layui.$;\n\t\tvar dsId = getQueryString('id');\n\t\tif(dsId){\n\t\t\t$.ajax({\n\t\t\t\turl : 'datasource/get',\n\t\t\t\tdata : {\n\t\t\t\t\tid : dsId\n\t\t\t\t},\n\t\t\t\tsuccess : function(data) {\n\t\t\t\t\tlayui.form.val('form', data);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\tlayui.form.on('submit(save)',function(){\n\t\t\t$.ajax({\n\t\t\t\turl : 'datasource/save',\n\t\t\t\ttype : 'post',\n\t\t\t\tdata : {\n\t\t\t\t\tid : dsId,\n\t\t\t\t\tname : $(\"input[name=name]\").val(),\n\t\t\t\t\tdriverClassName : $(\"input[name=driverClassName]\").val(),\n\t\t\t\t\tjdbcUrl : $(\"input[name=jdbcUrl]\").val(),\n\t\t\t\t\tusername : $(\"input[name=username]\").val(),\n\t\t\t\t\tpassword : $(\"input[name=password]\").val()\n\t\t\t\t},\n\t\t\t\tsuccess : function(json){\n\t\t\t\t\tlayui.layer.msg('保存成功',{\n\t\t\t\t\t\ttime : 800\n\t\t\t\t\t},function(){\n\t\t\t\t\t\tlocation.href = 'datasources.html';\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\terror : function(){\n\t\t\t\t\tlayui.layer.msg('请求失败');\n\t\t\t\t}\n\t\t\t})\n\t\t\treturn false;\n\t\t})\n\t\tlayui.form.on('submit(test)', function () {\n\t\t\tsf.ajax({\n\t\t\t\turl : 'datasource/test',\n\t\t\t\ttype : 'post',\n\t\t\t\tdata : {\n\t\t\t\t\tdriverClassName : $(\"input[name=driverClassName]\").val(),\n\t\t\t\t\tjdbcUrl : $(\"input[name=jdbcUrl]\").val(),\n\t\t\t\t\tusername : $(\"input[name=username]\").val(),\n\t\t\t\t\tpassword : $(\"input[name=password]\").val()\n\t\t\t\t},\n\t\t\t\tsuccess : function(json){\n\t\t\t\t\tlayui.layer.msg(json.message);\n\t\t\t\t},\n\t\t\t\terror : function(){\n\t\t\t\t\tlayui.layer.msg('请求失败');\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn false;\n\t\t})\n\t</script>\n</body>\n</html>"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/datasources.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>DataSource</title>\n\t<link rel=\"stylesheet\" href=\"js/layui/css/layui.css\" />\n\t<link rel=\"stylesheet\" href=\"css/layui-blue.css\" />\n\t<script type=\"text/javascript\" src=\"js/layui/layui.all.js\" ></script>\n</head>\n<body style=\"padding:5px;\">\n<a class=\"layui-btn layui-btn-sm layui-btn-normal\" href=\"datasource-edit.html\"><i class=\"layui-icon\">&#xe654;</i> 添加数据源</a>\n<hr>\n<table class=\"layui-table\" id=\"table\" lay-filter=\"table\"></table>\n<script>\n\tvar $ = layui.$;\n\tvar $table = layui.table.render({\n\t\tid : 'table',\n\t\telem : '#table',\n\t\turl : 'datasource/list',\n\t\tpage : true,\n\t\tparseData : function(resp){\n\t\t\treturn {\n\t\t\t\tcode : 0,\n\t\t\t\tdata : resp.records,\n\t\t\t\tcount : resp.total\n\t\t\t}\n\t\t},\n\t\tcols : [[{\n\t\t\ttitle : '序号',\n\t\t\twidth : 60,\n\t\t\ttype : 'numbers',\n\t\t\talign : 'center'\n\t\t},{\n\t\t\ttitle : '数据源名称',\n\t\t\tfield : 'name'\n\t\t},{\n\t\t\ttitle : '驱动',\n\t\t\tfield : 'driverClassName',\n\t\t},{\n\t\t\ttitle : '创建时间',\n\t\t\twidth : 160,\n\t\t\tfield : 'createDate',\n\t\t\talign : 'center'\n\t\t},{\n\t\t\ttitle : '操作',\n\t\t\twidth : 120,\n\t\t\talign : 'center',\n\t\t\ttemplet : '#buttons'\n\t\t}]]\n\t})\n\t$(\"body\").on('click','.btn-remove',function(){\n\t\tvar id = $(this).data('id');\n\t\tlayui.layer.confirm('您确定要删除此数据源吗？',{\n\t\t\ttitle : '删除'\n\t\t},function(index){\n\t\t\t$table.reload();\n\t\t\t$.ajax({\n\t\t\t\turl : 'datasource/remove',\n\t\t\t\tdata : {\n\t\t\t\t\tid : id\n\t\t\t\t},\n\t\t\t\tsuccess : function(){\n\t\t\t\t\tlayui.layer.msg('删除成功',{time : 500},function(){\n\t\t\t\t\t\t$table.reload();\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\terror : function(){\n\t\t\t\t\tlayui.layer.msg('删除失败')\n\t\t\t\t}\n\t\t\t})\n\t\t\tlayui.layer.close(index);\n\t\t})\n\t}).on('click','.btn-edit',function(){\n\t\tlocation.href = 'datasource-edit.html?id=' + $(this).data('id');\n\t})\n</script>\n<script type=\"text/html\" id=\"buttons\">\n\t<a class=\"layui-btn layui-btn-sm btn-edit\" data-id=\"{{d.id}}\">编辑</a>\n\t<a class=\"layui-btn layui-btn-sm btn-remove\" data-id=\"{{d.id}}\">删除</a>\n</script>\n</body>\n</html>"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/editCron.html",
    "content": "﻿<!DOCTYPE html>\n<html>\n<head>\n<title>Cron表达式生成器</title>\n<link href=\"js/cron/easyui.min.css\" rel=\"stylesheet\" type=\"text/css\" />\n<style type=\"text/css\">\n.line {\n\theight: 25px;\n\tline-height: 25px;\n\tmargin: 3px\n}\n\n.imp {\n\tpadding-left: 25px\n}\n\n.col {\n\twidth: 95px\n}\n\nul {\n\tlist-style: none;\n\tpadding-left: 10px\n}\n\nli {\n\theight: 20px\n}\n.layui-btn {\n    display: inline-block;\n    height: 38px;\n    line-height: 38px;\n    padding: 0 18px;\n    background-color: #1E9FFF;\n    color: #fff;\n    white-space: nowrap;\n    text-align: center;\n    font-size: 14px;\n    border: none;\n    border-radius: 2px;\n    cursor: pointer;\n}\n</style>\n</head>\n<body>\n\t<center>\n\t\t<div class=\"easyui-layout\" style=\"width: 830px; height: 548px; border: 1px rgb(202, 196, 196) solid; border-radius: 5px;\">\n\t\t\t<div style=\"height: 100%;\">\n\t\t\t\t<div class=\"easyui-tabs\" data-options=\"fit:true,border:false\">\n\t\t\t\t\t<div title=\"秒\">\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" checked=\"checked\" name=\"second\"\n\t\t\t\t\t\t\t\tonclick=\"everyTime(this)\"> 每秒 允许的通配符[, - * /]\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"second\" onclick=\"cycle(this)\">\n\t\t\t\t\t\t\t周期从 <input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:1,max:58\" value=\"1\" id=\"secondStart_0\">\n\t\t\t\t\t\t\t- <input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:2,max:59\" value=\"2\" id=\"secondEnd_0\">\n\t\t\t\t\t\t\t秒\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"second\" onclick=\"startOn(this)\">\n\t\t\t\t\t\t\t从 <input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:0,max:59\" value=\"0\" id=\"secondStart_1\">\n\t\t\t\t\t\t\t秒开始,每 <input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:1,max:59\" value=\"1\" id=\"secondEnd_1\">\n\t\t\t\t\t\t\t秒执行一次\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"second\" id=\"sencond_appoint\">\n\t\t\t\t\t\t\t指定\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"imp secondList\">\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"0\">00 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"1\">01 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"2\">02 <input type=\"checkbox\" value=\"3\">03\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"4\">04 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"5\">05 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"6\">06 <input type=\"checkbox\" value=\"7\">07\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"8\">08 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"9\">09\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"imp secondList\">\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"10\">10 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"11\">11 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"12\">12 <input type=\"checkbox\" value=\"13\">13\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"14\">14 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"15\">15 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"16\">16 <input type=\"checkbox\" value=\"17\">17\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"18\">18 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"19\">19\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"imp secondList\">\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"20\">20 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"21\">21 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"22\">22 <input type=\"checkbox\" value=\"23\">23\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"24\">24 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"25\">25 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"26\">26 <input type=\"checkbox\" value=\"27\">27\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"28\">28 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"29\">29\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"imp secondList\">\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"30\">30 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"31\">31 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"32\">32 <input type=\"checkbox\" value=\"33\">33\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"34\">34 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"35\">35 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"36\">36 <input type=\"checkbox\" value=\"37\">37\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"38\">38 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"39\">39\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"imp secondList\">\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"40\">40 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"41\">41 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"42\">42 <input type=\"checkbox\" value=\"43\">43\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"44\">44 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"45\">45 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"46\">46 <input type=\"checkbox\" value=\"47\">47\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"48\">48 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"49\">49\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"imp secondList\">\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"50\">50 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"51\">51 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"52\">52 <input type=\"checkbox\" value=\"53\">53\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"54\">54 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"55\">55 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"56\">56 <input type=\"checkbox\" value=\"57\">57\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"58\">58 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"59\">59\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div title=\"分钟\">\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" checked=\"checked\" name=\"min\"\n\t\t\t\t\t\t\t\tonclick=\"everyTime(this)\"> 分钟 允许的通配符[, - * /]\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"min\" onclick=\"cycle(this)\"> 周期从\n\t\t\t\t\t\t\t<input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:1,max:58\" value=\"1\" id=\"minStart_0\"> -\n\t\t\t\t\t\t\t<input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:2,max:59\" value=\"2\" id=\"minEnd_0\"> 分钟\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"min\" onclick=\"startOn(this)\"> 从\n\t\t\t\t\t\t\t<input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:0,max:59\" value=\"0\" id=\"minStart_1\">\n\t\t\t\t\t\t\t分钟开始,每 <input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:1,max:59\" value=\"1\" id=\"minEnd_1\">\n\t\t\t\t\t\t\t分钟执行一次\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"min\" id=\"min_appoint\"> 指定\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"imp minList\">\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"0\">00 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"1\">01 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"2\">02 <input type=\"checkbox\" value=\"3\">03\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"4\">04 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"5\">05 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"6\">06 <input type=\"checkbox\" value=\"7\">07\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"8\">08 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"9\">09\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"imp minList\">\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"10\">10 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"11\">11 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"12\">12 <input type=\"checkbox\" value=\"13\">13\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"14\">14 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"15\">15 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"16\">16 <input type=\"checkbox\" value=\"17\">17\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"18\">18 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"19\">19\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"imp minList\">\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"20\">20 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"21\">21 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"22\">22 <input type=\"checkbox\" value=\"23\">23\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"24\">24 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"25\">25 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"26\">26 <input type=\"checkbox\" value=\"27\">27\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"28\">28 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"29\">29\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"imp minList\">\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"30\">30 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"31\">31 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"32\">32 <input type=\"checkbox\" value=\"33\">33\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"34\">34 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"35\">35 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"36\">36 <input type=\"checkbox\" value=\"37\">37\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"38\">38 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"39\">39\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"imp minList\">\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"40\">40 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"41\">41 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"42\">42 <input type=\"checkbox\" value=\"43\">43\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"44\">44 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"45\">45 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"46\">46 <input type=\"checkbox\" value=\"47\">47\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"48\">48 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"49\">49\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"imp minList\">\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"50\">50 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"51\">51 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"52\">52 <input type=\"checkbox\" value=\"53\">53\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"54\">54 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"55\">55 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"56\">56 <input type=\"checkbox\" value=\"57\">57\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"58\">58 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"59\">59\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div title=\"小时\">\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" checked=\"checked\" name=\"hour\"\n\t\t\t\t\t\t\t\tonclick=\"everyTime(this)\"> 小时 允许的通配符[, - * /]\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"hour\" onclick=\"cycle(this)\">\n\t\t\t\t\t\t\t周期从 <input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:0,max:23\" value=\"0\" id=\"hourStart_0\">\n\t\t\t\t\t\t\t- <input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:2,max:23\" value=\"2\" id=\"hourEnd_0\"> 小时\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"hour\" onclick=\"startOn(this)\">\n\t\t\t\t\t\t\t从 <input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:0,max:23\" value=\"0\" id=\"hourStart_1\">\n\t\t\t\t\t\t\t小时开始,每 <input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:1,max:23\" value=\"1\" id=\"hourEnd_1\">\n\t\t\t\t\t\t\t小时执行一次\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"hour\" id=\"hour_appoint\"> 指定\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"imp hourList\">\n\t\t\t\t\t\t\tAM: <input type=\"checkbox\" value=\"0\">00 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"1\">01 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"2\">02 <input type=\"checkbox\" value=\"3\">03\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"4\">04 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"5\">05 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"6\">06 <input type=\"checkbox\" value=\"7\">07\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"8\">08 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"9\">09 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"10\">10 <input type=\"checkbox\" value=\"11\">11\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"imp hourList\">\n\t\t\t\t\t\t\tPM: <input type=\"checkbox\" value=\"12\">12 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"13\">13 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"14\">14 <input type=\"checkbox\" value=\"15\">15\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"16\">16 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"17\">17 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"18\">18 <input type=\"checkbox\" value=\"19\">19\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"20\">20 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"21\">21 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"22\">22 <input type=\"checkbox\" value=\"23\">23\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div title=\"日\">\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" checked=\"checked\" name=\"day\"\n\t\t\t\t\t\t\t\tonclick=\"everyTime(this)\"> 日 允许的通配符[, - * / L W]\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"day\" onclick=\"unAppoint(this)\">\n\t\t\t\t\t\t\t不指定\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"day\" onclick=\"cycle(this)\"> 周期从\n\t\t\t\t\t\t\t<input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:1,max:31\" value=\"1\" id=\"dayStart_0\"> -\n\t\t\t\t\t\t\t<input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:2,max:31\" value=\"2\" id=\"dayEnd_0\"> 日\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"day\" onclick=\"startOn(this)\"> 从\n\t\t\t\t\t\t\t<input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:1,max:31\" value=\"1\" id=\"dayStart_1\">\n\t\t\t\t\t\t\t日开始,每 <input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:1,max:31\" value=\"1\" id=\"dayEnd_1\">\n\t\t\t\t\t\t\t天执行一次\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"day\" onclick=\"workDay(this)\">\n\t\t\t\t\t\t\t每月 <input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:1,max:31\" value=\"1\" id=\"dayStart_2\">\n\t\t\t\t\t\t\t号最近的那个工作日\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"day\" onclick=\"lastDay(this)\">\n\t\t\t\t\t\t\t每月最后一天\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"day\" id=\"day_appoint\"> 指定\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"imp dayList\">\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"1\">1 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"2\">2 <input type=\"checkbox\" value=\"3\">3 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"4\">4 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"5\">5 <input type=\"checkbox\" value=\"6\">6 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"7\">7 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"8\">8 <input type=\"checkbox\" value=\"9\">9 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"10\">10 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"11\">11 <input type=\"checkbox\" value=\"12\">12\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"13\">13 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"14\">14 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"15\">15 <input type=\"checkbox\" value=\"16\">16\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"imp dayList\">\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"17\">17 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"18\">18 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"19\">19 <input type=\"checkbox\" value=\"20\">20\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"21\">21 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"22\">22 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"23\">23 <input type=\"checkbox\" value=\"24\">24\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"25\">25 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"26\">26 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"27\">27 <input type=\"checkbox\" value=\"28\">28\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"29\">29 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"30\">30 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"31\">31\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div title=\"月\">\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" checked=\"checked\" name=\"mouth\"\n\t\t\t\t\t\t\t\tonclick=\"everyTime(this)\"> 月 允许的通配符[, - * /]\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"mouth\" onclick=\"unAppoint(this)\">\n\t\t\t\t\t\t\t不指定\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"mouth\" onclick=\"cycle(this)\">\n\t\t\t\t\t\t\t周期从 <input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:1,max:12\" value=\"1\" id=\"mouthStart_0\">\n\t\t\t\t\t\t\t- <input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:2,max:12\" value=\"2\" id=\"mouthEnd_0\"> 月\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"mouth\" onclick=\"startOn(this)\">\n\t\t\t\t\t\t\t从 <input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:1,max:12\" value=\"1\" id=\"mouthStart_1\">\n\t\t\t\t\t\t\t日开始,每 <input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:1,max:12\" value=\"1\" id=\"mouthEnd_1\">\n\t\t\t\t\t\t\t月执行一次\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"mouth\" id=\"mouth_appoint\"> 指定\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"imp mouthList\">\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"1\">1 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"2\">2 <input type=\"checkbox\" value=\"3\">3 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"4\">4 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"5\">5 <input type=\"checkbox\" value=\"6\">6 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"7\">7 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"8\">8 <input type=\"checkbox\" value=\"9\">9 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"10\">10 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"11\">11 <input type=\"checkbox\" value=\"12\">12\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div title=\"周\">\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" checked=\"checked\" name=\"week\"\n\t\t\t\t\t\t\t\tonclick=\"everyTime(this)\"> 周 允许的通配符[, - * / L #]\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"week\" onclick=\"unAppoint(this)\">\n\t\t\t\t\t\t\t不指定\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"week\" onclick=\"startOn(this)\">\n\t\t\t\t\t\t\t周期 从星期<input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:1,max:7\" id=\"weekStart_0\" value=\"1\"> -\n\t\t\t\t\t\t\t<input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:2,max:7\" value=\"2\" id=\"weekEnd_0\">\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"week\" onclick=\"weekOfDay(this)\">\n\t\t\t\t\t\t\t第<input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:1,max:4\" value=\"1\" id=\"weekStart_1\"> 周\n\t\t\t\t\t\t\t的星期<input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:1,max:7\" id=\"weekEnd_1\" value=\"1\">\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"week\" onclick=\"lastWeek(this)\">\n\t\t\t\t\t\t\t本月最后一个星期<input class=\"numberspinner\" style=\"width: 60px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:1,max:7\" id=\"weekStart_2\" value=\"1\">\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"week\" id=\"week_appoint\"> 指定\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"imp weekList\">\n\t\t\t\t\t\t\t<input type=\"checkbox\" value=\"1\">1 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"2\">2 <input type=\"checkbox\" value=\"3\">3 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"4\">4 <input type=\"checkbox\"\n\t\t\t\t\t\t\t\tvalue=\"5\">5 <input type=\"checkbox\" value=\"6\">6 <input\n\t\t\t\t\t\t\t\ttype=\"checkbox\" value=\"7\">7\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div title=\"年\">\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" checked=\"checked\" name=\"year\"\n\t\t\t\t\t\t\t\tonclick=\"unAppoint(this)\"> 不指定 允许的通配符[, - * /] 非必填\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"year\" onclick=\"everyTime(this)\">\n\t\t\t\t\t\t\t每年\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"line\">\n\t\t\t\t\t\t\t<input type=\"radio\" name=\"year\" onclick=\"cycle(this)\">周期\n\t\t\t\t\t\t\t从 <input class=\"numberspinner\" style=\"width: 90px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:2013,max:3000\" id=\"yearStart_0\" value=\"2013\">\n\t\t\t\t\t\t\t- <input class=\"numberspinner\" style=\"width: 90px;\"\n\t\t\t\t\t\t\t\tdata-options=\"min:2014,max:3000\" id=\"yearEnd_0\" value=\"2014\">\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div data-options=\"region:'south',border:false\" style=\"height: 270px\">\n\t\t\t\t<fieldset style=\"border-radius: 3px; height: 250px;\">\n\t\t\t\t\t<legend>表达式</legend>\n\t\t\t\t\t<table style=\"height: 100px;\">\n\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t<td></td>\n\t\t\t\t\t\t\t\t<td align=\"center\">秒</td>\n\t\t\t\t\t\t\t\t<td align=\"center\">分钟</td>\n\t\t\t\t\t\t\t\t<td align=\"center\">小时</td>\n\t\t\t\t\t\t\t\t<td align=\"center\">日</td>\n\t\t\t\t\t\t\t\t<td align=\"center\">月<br />\n\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t<td align=\"center\">星期</td>\n\t\t\t\t\t\t\t\t<td align=\"center\">年</td>\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t<td>表达式字段:</td>\n\t\t\t\t\t\t\t\t<td><input type=\"text\" name=\"v_second\" class=\"col\"\n\t\t\t\t\t\t\t\t\tvalue=\"*\" readonly=\"readonly\" /></td>\n\t\t\t\t\t\t\t\t<td><input type=\"text\" name=\"v_min\" class=\"col\" value=\"*\"\n\t\t\t\t\t\t\t\t\treadonly=\"readonly\" /></td>\n\t\t\t\t\t\t\t\t<td><input type=\"text\" name=\"v_hour\" class=\"col\" value=\"*\"\n\t\t\t\t\t\t\t\t\treadonly=\"readonly\" /></td>\n\t\t\t\t\t\t\t\t<td><input type=\"text\" name=\"v_day\" class=\"col\" value=\"*\"\n\t\t\t\t\t\t\t\t\treadonly=\"readonly\" /></td>\n\t\t\t\t\t\t\t\t<td><input type=\"text\" name=\"v_mouth\" class=\"col\" value=\"*\"\n\t\t\t\t\t\t\t\t\treadonly=\"readonly\" /></td>\n\t\t\t\t\t\t\t\t<td><input type=\"text\" name=\"v_week\" class=\"col\" value=\"?\"\n\t\t\t\t\t\t\t\t\treadonly=\"readonly\" /></td>\n\t\t\t\t\t\t\t\t<td><input type=\"text\" name=\"v_year\" class=\"col\"\n\t\t\t\t\t\t\t\t\treadonly=\"readonly\" /></td>\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t<td>Cron 表达式:</td>\n\t\t\t\t\t\t\t\t<td colspan=\"6\">\n\t\t\t\t\t\t\t\t\t<input type=\"text\" name=\"cron\" style=\"width: 100%;\" value=\"* * * * * ?\" id=\"cron\" />\n\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t<td>\n\t\t\t\t\t\t\t\t\t<input type=\"button\" value=\"反解析到UI \" style=\"margin-left: 10px;\" id=\"btnFan\" onclick=\"btnFan()\" />\n\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t<td colspan=\"8\">最近5次运行时间:</td>\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t<td colspan=\"8\" id=\"runTime\"></td>\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t</tbody>\n\t\t\t\t\t</table>\n\t\t\t\t</fieldset>\n\t\t\t</div>\n\t\t\t<div></div>\n\t\t</div>\n\t\t  <div>\n\t\t      <button id=\"transmit\" type=\"button\" class=\"layui-btn layui-btn-normal\" style=\"margin-top:10px;\">确定</button>\n\t\t  </div>\n\t</center>\n\t<script type=\"text/javascript\" src=\"js/cron/jquery-2.1.4.min.js\"></script>\n\t<script type=\"text/javascript\" src=\"js/cron/jquery.easyui.min.js\"></script>\n\t<script type=\"text/javascript\" src=\"js/cron/cron.js\"></script>\n\t<script type=\"text/javascript\" src=\"js/layui/layui.all.js\"></script>\n\t<script>\n\t var index = parent.layer.getFrameIndex(window.name); //获取窗口索引\n\t    var parentCron = getURLParameter(\"cron\");\n\t    var id = getURLParameter(\"id\");\n\t    window.onload = function () {\n\t        parent.layer.iframeAuto(index);\n\t        $(\"#cron\").val(parentCron);\n\t        $(\"#cron\").change();\n\t    };\n\t    function getURLParameter(name)\n\t    {\n\t        var query = decodeURI(window.location.search.substring(1));\n\t        var vars = query.split(\"&\");\n\t        for (var i=0;i<vars.length;i++) {\n\t            var pair = vars[i].split(\"=\");\n\t            if(pair[0] === name){return pair[1];}\n\t        }\n\t        return \"\";\n\t    }\n\t   \n\t\t $('#transmit').on('click', function(){\n\t\t\t   var value = $(\"#cron\").val();\n\t\t\t   if (value) {\n\t\t\t\t   $.ajax({\n\t\t\t            url : '/spider/cron',\n\t\t\t            data : {\n\t\t\t                id : id,\n\t\t\t                cron : value\n\t\t\t            },\n\t\t\t            success : function(){\n\t\t\t                var $parent = parent;\n\t\t\t                parent.layer.msg('修改成功',{time : 500},function(e){\n\t\t\t                    $parent.$table.reload();\n\t\t\t                })\n\t\t\t            },\n\t\t\t            error : function(){\n\t\t\t                parent.layer.msg('修改失败')\n\t\t\t            }\n\t\t\t        });\n\t\t\t        parent.layer.close(index);\n\t\t\t   }\n\t\t });\n\t</script>\n</body>\n</html>\n"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/editor.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n\t<head>\r\n\t\t<meta charset=\"utf-8\" />\r\n\t\t<title>SpiderFlow-Editor</title>\r\n\t\t<link rel=\"stylesheet\" href=\"js/layui/css/layui.css\" />\r\n\t\t<link rel=\"stylesheet\" href=\"css/layui-blue.css\" />\r\n\t\t<link rel=\"stylesheet\" href=\"css/editor.css\" />\r\n\t\t<link rel=\"stylesheet\" href=\"js/codemirror/codemirror.css\" />\r\n\t\t<link rel=\"stylesheet\" href=\"js/codemirror/idea.css\" />\r\n\t\t<link rel=\"stylesheet\" href=\"js/codemirror/show-hint.css\" />\r\n\t\t<link rel=\"stylesheet\" href=\"js/jsontree/jsontree.css\" />\r\n\t\t<script type=\"text/javascript\" src=\"js/layui/layui.all.js\" ></script>\r\n\t\t<script>mxBasePath = 'js/mxgraph';$=layui.$</script>\r\n\t\t<script type=\"text/javascript\" src=\"js/mxgraph/mxgraph.min.js\" ></script>\r\n\t\t<script type=\"text/javascript\" src=\"js/common.js\" ></script>\r\n\t\t<script type=\"text/javascript\" src=\"js/spider-editor.js\" ></script>\r\n\t\t<script type=\"text/javascript\" src=\"js/canvas-viewer.js\" ></script>\r\n\t\t<script type=\"text/javascript\" src=\"js/codemirror/codemirror.js\" ></script>\r\n\t\t<script type=\"text/javascript\" src=\"js/codemirror/spiderflow.js\" ></script>\r\n\t\t<script type=\"text/javascript\" src=\"js/codemirror/placeholder.js\" ></script>\r\n\t\t<script type=\"text/javascript\" src=\"js/codemirror/sql.js\" ></script>\r\n\t\t<script type=\"text/javascript\" src=\"js/codemirror/show-hint.js\" ></script>\r\n\t\t<script type=\"text/javascript\" src=\"js/codemirror/spiderflow-hint.js\" ></script>\r\n\t\t<script type=\"text/javascript\" src=\"js/jsontree/jsontree.js\" ></script>\r\n\t\t<script type=\"text/javascript\" src=\"js/editor.js\" ></script>\r\n\t</head>\r\n\t<body>\r\n\t\t<div class=\"main-container\">\r\n\t\t\t<div class=\"toolbar-container\">\r\n\t\t\t\t<ul>\r\n\t\t\t\t\t<li class=\"btn-return\" title=\"返回列表\"></li>\r\n\t\t\t\t\t<span>|</span>\r\n\t\t\t\t\t<li class=\"btn-save\" title=\"保存（Ctrl+S）\"></li>\r\n\t\t\t\t\t<li class=\"btn-test\" title=\"测试（Ctrl+Q）\"></li>\r\n\t\t\t\t\t<span>|</span>\r\n\t\t\t\t\t<li class=\"btn-undo\" title=\"撤销（Ctrl+Z）\"></li>\r\n\t\t\t\t\t<li class=\"btn-redo\" title=\"反撤销（Ctrl+Y）\"></li>\r\n\t\t\t\t\t<li class=\"btn-history\" title=\"历史版本\"></li>\r\n\t\t\t\t\t<span>|</span>\r\n\t\t\t\t\t<li class=\"btn-selectAll\" title=\"全选（Ctrl+A）\"></li>\r\n\t\t\t\t\t<li class=\"btn-cut\" title=\"剪切（Ctrl+X）\"></li>\r\n\t\t\t\t\t<li class=\"btn-copy\" title=\"复制（Ctrl+C）\"></li>\r\n\t\t\t\t\t<li class=\"btn-paste\" title=\"粘贴（Ctrl+V）\"></li>\r\n\t\t\t\t\t<li class=\"btn-delete\" title=\"删除（Delete）\"></li>\r\n\t\t\t\t\t<span>|</span>\r\n\t\t\t\t\t<li class=\"btn-edit-xml\" title=\"XML编辑\"></li>\r\n\t\t\t\t\t<li class=\"btn-console-xml\" title=\"打印XML\"></li>\r\n\t\t\t\t\t<span>|</span>\r\n\t\t\t\t\t<li class=\"btn-debug\" title=\"调试（Ctrl+Q）\"></li>\r\n\t\t\t\t\t<li class=\"btn-resume disabled\" title=\"下一步\"></li>\r\n\t\t\t\t\t<li class=\"btn-stop disabled\" title=\"停止\"></li>\r\n\t\t\t\t\t<span>|</span>\r\n\t\t\t\t\t<li class=\"btn-dock-right\" title=\"Dock to right\"></li>\r\n\t\t\t\t\t<li class=\"btn-dock-bottom\" title=\"Dock to bottom\"></li>\r\n\t\t\t\t</ul>\r\n\t\t\t</div>\r\n\t\t\t<div class=\"sidebar-container\"></div>\r\n\t\t\t<div class=\"xml-container\"><textarea></textarea></div>\r\n\t\t\t<div class=\"editor-container\"></div>\r\n\t\t\t<div class=\"resize-container\"></div>\r\n\t\t\t<div class=\"properties-container\"></div>\r\n\t\t</div>\r\n\t\t<script type=\"text/html\" id=\"parameter-name-tmpl\">\r\n\t\t\t<input type=\"text\" name=\"parameter-name\" placeholder=\"请输入参数名\" autocomplete=\"off\" class=\"layui-input array\" value=\"{{=d['parameter-name']}}\">\r\n\t\t</script>\r\n\t\t<script type=\"text/html\" id=\"parameter-description-tmpl\">\r\n\t\t\t<input type=\"text\" name=\"parameter-description\" placeholder=\"请输入参数描述\" autocomplete=\"off\" class=\"layui-input array\" value=\"{{=d['parameter-description']}}\">\r\n\t\t</script>\r\n\t\t<script type=\"text/html\" id=\"parameter-value-tmpl\">\r\n\t\t\t<div class=\"layui-input-block array\" placeholder=\"请输入参数值\" codemirror=\"parameter-value\" data-value=\"{{=d['parameter-value']}}\"></div>\r\n\t\t</script>\r\n\t\t<script type=\"text/html\" id=\"parameter-type-tmpl\">\r\n\t\t\t<select name=\"parameter-form-type\" class=\"array\">\r\n\t\t\t\t<option value=\"text\" {{d['parameter-form-type'] == 'text' ? 'selected': '' }}\">text</option>\r\n\t\t\t\t<option value=\"file\" {{d['parameter-form-type'] == 'file' ? 'selected': '' }}>file</option>\r\n\t\t\t</select>\r\n\t\t</script>\r\n\t\t<script type=\"text/html\" id=\"parameter-from-name-tmpl\">\r\n\t\t\t<input type=\"text\" name=\"parameter-form-name\" placeholder=\"请输入参数名\" autocomplete=\"off\" class=\"layui-input array\" value=\"{{=d['parameter-form-name']}}\">\r\n\t\t</script>\r\n\t\t<script type=\"text/html\" id=\"parameter-from-description-tmpl\">\r\n\t\t\t<input type=\"text\" name=\"parameter-form-description\" placeholder=\"请输入参数描述\" autocomplete=\"off\" class=\"layui-input array\" value=\"{{=d['parameter-form-description']}}\">\r\n\t\t</script>\r\n\t\t<script type=\"text/html\" id=\"parameter-from-value-tmpl\">\r\n\t\t\t<div class=\"layui-input-block array\" placeholder=\"请输入参数值\" codemirror=\"parameter-form-value\" data-value=\"{{=d['parameter-form-value']}}\"></div>\r\n\t\t</script>\r\n\t\t<script type=\"text/html\" id=\"parameter-from-type-tmpl\">\r\n\t\t\t<select name=\"parameter-form-type\" class=\"array\">\r\n\t\t\t\t<option value=\"text\" {{d['parameter-form-type'] == 'text' ? 'selected': '' }}\">text</option>\r\n\t\t\t\t<option value=\"file\" {{d['parameter-form-type'] == 'file' ? 'selected': '' }}>file</option>\r\n\t\t\t</select>\r\n\t\t</script>\r\n\t\t<script type=\"text/html\" id=\"parameter-from-filename-tmpl\">\r\n\t\t\t<div class=\"layui-input-block array\" placeholder=\"请输入文件名\" codemirror=\"parameter-form-filename\" data-value=\"{{=d['parameter-form-filename']}}\"></div>\r\n\t\t</script>\r\n\r\n\t\t<script type=\"text/html\" id=\"cookie-name-tmpl\">\r\n\t\t\t<input type=\"text\" name=\"cookie-name\" placeholder=\"请输入Cookie名\" autocomplete=\"off\" class=\"layui-input array\" value=\"{{=d['cookie-name']}}\">\r\n\t\t</script>\r\n\t\t<script type=\"text/html\" id=\"cookie-description-tmpl\">\r\n\t\t\t<input type=\"text\" name=\"cookie-description\" placeholder=\"请输入Cookie描述\" autocomplete=\"off\" class=\"layui-input array\" value=\"{{=d['cookie-description']}}\">\r\n\t\t</script>\r\n\t\t<script type=\"text/html\" id=\"cookie-value-tmpl\">\r\n\t\t\t<div class=\"layui-input-block array\" placeholder=\"请输入Cookie值\" codemirror=\"cookie-value\" data-value=\"{{=d['cookie-value']}}\"></div>\r\n\t\t</script>\r\n\r\n\t\t<script type=\"text/html\" id=\"header-name-tmpl\">\r\n\t\t\t<input type=\"text\" name=\"header-name\" placeholder=\"请输入Header名\" autocomplete=\"off\" class=\"layui-input array\" value=\"{{=d['header-name']}}\">\r\n\t\t</script>\r\n\t\t<script type=\"text/html\" id=\"header-description-tmpl\">\r\n\t\t\t<input type=\"text\" name=\"header-description\" placeholder=\"请输入Header描述\" autocomplete=\"off\" class=\"layui-input array\" value=\"{{=d['header-description']}}\">\r\n\t\t</script>\r\n\t\t<script type=\"text/html\" id=\"header-value-tmpl\">\r\n\t\t\t<div class=\"layui-input-block array\" placeholder=\"请输入Header值\" codemirror=\"header-value\" data-value=\"{{=d['header-value']}}\"></div>\r\n\t\t</script>\r\n\r\n\t\t<script type=\"text/html\" id=\"variable-name-tmpl\">\r\n\t\t\t<input type=\"text\" name=\"variable-name\" placeholder=\"请输入变量名\" autocomplete=\"off\" class=\"layui-input array\" value=\"{{=d['variable-name']}}\">\r\n\t\t</script>\r\n\t\t<script type=\"text/html\" id=\"variable-description-tmpl\">\r\n\t\t\t<input type=\"text\" name=\"variable-description\" placeholder=\"请输入变量描述\" autocomplete=\"off\" class=\"layui-input array\" value=\"{{=d['variable-description']}}\">\r\n\t\t</script>\r\n\t\t<script type=\"text/html\" id=\"variable-value-tmpl\">\r\n\t\t\t<div class=\"layui-input-block array\" placeholder=\"请输入变量值\" codemirror=\"variable-value\" data-value=\"{{=d['variable-value']}}\"></div>\r\n\t\t</script>\r\n\r\n\t\t<script type=\"text/html\" id=\"output-name-tmpl\">\r\n\t\t\t<input type=\"text\" name=\"output-name\" placeholder=\"输出项\" autocomplete=\"off\" class=\"layui-input array\" value=\"{{=d['output-name']}}\">\r\n\t\t</script>\r\n\t\t<script type=\"text/html\" id=\"output-value-tmpl\">\r\n\t\t\t<div class=\"layui-input-block array\" codemirror=\"output-value\" placeholder=\"输出值\" data-value=\"{{=d['output-value']}}\"></div>\r\n\t\t</script>\r\n\r\n\t\t<script type=\"text/html\" id=\"history-version-tmpl\">\r\n\t\t\t<ul class=\"history-version\">\r\n\t\t\t\t{{# layui.each(d,function(index,item){ }}\r\n\t\t\t\t\t<li data-timestamp=\"{{item.timestamp}}\">{{item.time}}</li>\r\n\t\t\t\t{{# });}}\r\n\t\t\t</ul>\r\n\t\t</script>\r\n\r\n\t\t<script type=\"text/html\" id=\"common-operation\">\r\n\t\t\t<a class=\"layui-btn layui-btn-sm table-row-up\">上移</a>\r\n\t\t\t<a class=\"layui-btn layui-btn-sm table-row-down\">下移</a>\r\n\t\t\t<a class=\"layui-btn layui-btn-sm\" lay-event=\"del\">删除</a>\r\n\t\t</script>\r\n\t</body>\r\n</html>\r\n"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/function-edit.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>DataSource</title>\n\t<link rel=\"stylesheet\" href=\"js/layui/css/layui.css\" />\n\t<link rel=\"stylesheet\" href=\"css/layui-blue.css\" />\n\t<link rel=\"stylesheet\" href=\"js/codemirror/codemirror.css\" />\n\t<link rel=\"stylesheet\" href=\"js/codemirror/dracula.css\" />\n\t<script type=\"text/javascript\" src=\"js/layui/layui.all.js\" ></script>\n\t<script type=\"text/javascript\" src=\"js/common.js\" ></script>\n\t<script type=\"text/javascript\" src=\"js/codemirror/codemirror.js\" ></script>\n\t<script type=\"text/javascript\" src=\"js/codemirror/javascript.js\" ></script>\n\t<style type=\"text/css\">\n\t\thtml,body{\n\t\t\twidth:100%;\n\t\t}\n\t\t.layui-form{\n\t\t\twidth : 700px;\n\t\t\tmargin-top:10px;\n\t\t}\n\t\t.layui-form-label{\n\t\t\twidth : 140px;\n\t\t}\n\t\t.layui-input-block{\n\t\t\tmargin-left : 170px;\n\t\t}\n\t\t.btns-submit{\n\t\t\ttext-align : center;\n\t\t}\n\t</style>\n</head>\n<body>\n\t<form class=\"layui-form\" autocomplete=\"off\" lay-filter=\"form\" style=\"width:auto !important;padding:20px;\">\n\t\t<div class=\"layui-row\">\n\t\t\t<div class=\"layui-col-md6\">\n\t\t\t\t<div class=\"layui-form-item\">\n\t\t\t\t\t<label class=\"layui-form-label\">函数名称</label>\n\t\t\t\t\t<div class=\"layui-input-block\">\n\t\t\t\t\t\t<input type=\"text\" name=\"name\" placeholder=\"请输入函数名\" autocomplete=\"off\" class=\"layui-input\" lay-verify=\"required\"/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div class=\"layui-col-md6\">\n\t\t\t\t<div class=\"layui-form-item\">\n\t\t\t\t\t<label class=\"layui-form-label\">函数入参</label>\n\t\t\t\t\t<div class=\"layui-input-block\">\n\t\t\t\t\t\t<input type=\"text\" name=\"parameter\" placeholder=\"请输入函数入参,多个用逗号隔开\" autocomplete=\"off\" class=\"layui-input\"/>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"layui-form-item\">\n\t\t\t<label class=\"layui-form-label\">脚本</label>\n\t\t\t<div class=\"layui-input-block\" style=\"height: 450px\">\n\t\t\t\t<textarea name=\"script\" id=\"script\"></textarea>\n\t\t\t</div>\n\t\t</div>\n   \t\t<div class=\"btns-submit\">\n\t\t\t<button class=\"layui-btn layui-btn-normal\" lay-submit lay-filter=\"save\">保存</button>\n\t\t\t<button class=\"layui-btn layui-btn-primary btn-return\" type=\"button\" onclick=\"history.go(-1);\">返回</button>\n\t\t</div>\n\t</form>\n\t<script type=\"text/javascript\">\n\t\tvar editor = CodeMirror.fromTextArea(document.getElementById(\"script\"), {\n\t\t\tlineNumbers: true,\n\t\t\tmatchBrackets: true,\n\t\t\tcontinueComments: \"Enter\"\n\t\t});\n\t\teditor.setOption('theme','dracula')\n\t\tvar $ = layui.$;\n\t\tvar fId = getQueryString('id');\n\t\tif(fId){\n\t\t\t$.ajax({\n\t\t\t\turl : 'function/get',\n\t\t\t\tdata : {\n\t\t\t\t\tid : fId\n\t\t\t\t},\n\t\t\t\tsuccess : function(data) {\n\t\t\t\t\tlayui.form.val('form', data);\n\t\t\t\t\tif(data.script){\n\t\t\t\t\t\teditor.setValue(data.script);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\tlayui.form.on('submit(save)',function(){\n\t\t\t$.ajax({\n\t\t\t\turl : 'function/save',\n\t\t\t\ttype : 'post',\n\t\t\t\tdata : {\n\t\t\t\t\tid : fId,\n\t\t\t\t\tname : $(\"input[name=name]\").val(),\n\t\t\t\t\tparameter : $(\"input[name=parameter]\").val(),\n\t\t\t\t\tscript : editor.getValue()\n\t\t\t\t},\n\t\t\t\tsuccess : function(data){\n\t\t\t\t\tif(data){\n\t\t\t\t\t\tvar message = data.replace(/\\n/g,'<br>').replace(/ /g,'&nbsp;').replace(/\\t/g,'&nbsp;&nbsp;&nbsp;&nbsp;');\n\t\t\t\t\t\tlayui.layer.alert('<div style=\"font-weight: bold;font-family:Consolas;font-size:12px;\">' + message + '</div>',{title : '保存出错',area:['800px','400px']});\n\t\t\t\t\t}else{\n\t\t\t\t\t\tlayui.layer.msg('保存成功',{\n\t\t\t\t\t\t\ttime : 800\n\t\t\t\t\t\t},function(){\n\t\t\t\t\t\t\tlocation.href = 'functions.html';\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\terror : function(){\n\t\t\t\t\tlayui.layer.msg('请求失败');\n\t\t\t\t}\n\t\t\t})\n\t\t\treturn false;\n\t\t})\n\t</script>\n</body>\n</html>"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/functions.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>DataSource</title>\n\t<link rel=\"stylesheet\" href=\"js/layui/css/layui.css\" />\n\t<link rel=\"stylesheet\" href=\"css/layui-blue.css\" />\n\t<script type=\"text/javascript\" src=\"js/layui/layui.all.js\" ></script>\n</head>\n<body style=\"padding:5px;\">\n<div class=\"layui-form-item\">\n\t<label class=\"layui-form-label\">函数名称</label>\n\t<div class=\"layui-input-inline\">\n\t\t<input type=\"text\" name=\"name\" required  lay-verify=\"required\" placeholder=\"请输入函数名称\" autocomplete=\"off\" class=\"layui-input\">\n\t</div>\n\t<div class=\"layui-input-inline\" style=\"margin-top:5px\">\n\t\t<a class=\"layui-btn layui-btn-sm layui-btn-normal btn-search\"><i class=\"layui-icon\">&#xe615;</i> 搜索</a>\n\t\t<a class=\"layui-btn layui-btn-sm layui-btn-normal\" href=\"function-edit.html\"><i class=\"layui-icon\">&#xe654;</i> 添加函数</a>\n\t</div>\n</div>\n<hr>\n<table class=\"layui-table\" id=\"table\" lay-filter=\"table\"></table>\n<script>\n\tvar $ = layui.$;\n\tvar $table = layui.table.render({\n\t\tid : 'table',\n\t\telem : '#table',\n\t\turl : 'function/list',\n\t\tpage : true,\n\t\tparseData : function(resp){\n\t\t\treturn {\n\t\t\t\tcode : 0,\n\t\t\t\tdata : resp.records,\n\t\t\t\tcount : resp.total\n\t\t\t}\n\t\t},\n\t\tcols : [[{\n\t\t\ttitle : '序号',\n\t\t\twidth : 60,\n\t\t\ttype : 'numbers',\n\t\t\talign : 'center'\n\t\t},{\n\t\t\ttitle : '函数名称',\n\t\t\tfield : 'name'\n\t\t},{\n\t\t\ttitle : '函数参数',\n\t\t\tfield : 'parameter',\n\t\t},{\n\t\t\ttitle : '操作',\n\t\t\twidth : 120,\n\t\t\talign : 'center',\n\t\t\ttemplet : '#buttons'\n\t\t}]]\n\t})\n\t$(\"body\").on('click','.btn-search',function(){\n\t\t$table.reload({\n\t\t\twhere : {\n\t\t\t\tname : $('input[name=name]').val()\n\t\t\t},\n\t\t\tpage : {\n\t\t\t\tcurr : 1\n\t\t\t}\n\t\t})\n\t}).on('click','.btn-remove',function(){\n\t\tvar id = $(this).data('id');\n\t\tlayui.layer.confirm('您确定要删除此函数吗？',{\n\t\t\ttitle : '删除'\n\t\t},function(index){\n\t\t\t$table.reload();\n\t\t\t$.ajax({\n\t\t\t\turl : 'function/remove',\n\t\t\t\tdata : {\n\t\t\t\t\tid : id\n\t\t\t\t},\n\t\t\t\tsuccess : function(){\n\t\t\t\t\tlayui.layer.msg('删除成功',{time : 500},function(){\n\t\t\t\t\t\t$table.reload();\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\terror : function(){\n\t\t\t\t\tlayui.layer.msg('删除失败')\n\t\t\t\t}\n\t\t\t})\n\t\t\tlayui.layer.close(index);\n\t\t})\n\t}).on('click','.btn-edit',function(){\n\t\tlocation.href = 'function-edit.html?id=' + $(this).data('id');\n\t})\n</script>\n<script type=\"text/html\" id=\"buttons\">\n\t<a class=\"layui-btn layui-btn-sm btn-edit\" data-id=\"{{d.id}}\">编辑</a>\n\t<a class=\"layui-btn layui-btn-sm btn-remove\" data-id=\"{{d.id}}\">删除</a>\n</script>\n</body>\n</html>"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/index.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n\t<head>\r\n\t\t<meta charset=\"UTF-8\">\r\n\t\t<title>SpiderFlow</title>\r\n\t\t<link rel=\"stylesheet\" href=\"js/layui/css/layui.css\" />\r\n\t\t<link rel=\"stylesheet\" id=\"theSkin\" />\r\n\t\t<link rel=\"stylesheet\" href=\"css/index.css\" />\r\n\t\t<script>SPIDER_FLOW_VERSION = '0.5.0'</script>\r\n\t\t<script type=\"text/javascript\" src=\"js/layui/layui.all.js\" ></script>\r\n\t\t<script type=\"text/javascript\" src=\"js/index.js\" ></script>\r\n\t\t<script type=\"text/javascript\" src=\"https://www.spiderflow.org/update-detection.js\"></script>\r\n\t</head>\r\n\t<body class=\"layui-layout layui-layout-admin\">\r\n\t\t<div class=\"layui-header\">\r\n\t\t\t<div class=\"layui-logo\">SpiderFlow平台</div>\r\n\t\t\t<ul class=\"layui-nav layui-layout-left\"></ul>\r\n\t\t\t<ul class=\"layui-nav layui-layout-right\">\r\n\t\t\t\t<li class=\"layui-nav-item\"><a target=\"_blank\" href=\"https://www.spiderflow.org\">帮助文档</a></li>\r\n\t\t\t\t<li class=\"layui-nav-item\"><a target=\"_blank\" href=\"https://shang.qq.com/wpa/qunwpa?idkey=10faa4cf9743e0aa379a72f2ad12a9e576c81462742143c8f3391b52e8c3ed8d\">加入QQ群</li>\r\n\t\t\t\t<li class=\"layui-nav-item\"><a target=\"_blank\" href='https://gitee.com/jmxd/spider-flow'><img src='https://gitee.com/jmxd/spider-flow/badge/star.svg?theme=white' alt='gitee star' /></a></li>\r\n\t\t\t\t<li class=\"layui-nav-item\"><a target=\"_blank\" href='https://github.com/javamxd/spider-flow'><img src='https://img.shields.io/github/stars/javamxd/spider-flow.svg?style=social' alt='github star' style=\"background: none\"/></a></li>\r\n\t\t\t\t<li class=\"layui-nav-item\">\r\n\t\t\t\t\t<a href=\"javascript:;\" style=\"padding: 0 20px;\">\r\n\t\t\t\t\t\t换肤\r\n\t\t\t\t\t</a>\r\n\t\t\t\t\t<dl class=\"layui-nav-child\">\r\n\t\t\t\t\t\t<dd><a data-value=\"layui-blue\">默认</a></dd>\r\n\t\t\t\t\t\t<dd><a data-value=\"layui-black-gray\">黑色</a></dd>\r\n\t\t\t\t\t</dl>\r\n\t\t\t\t</li>\r\n\t\t\t</ul>\r\n\t\t</div>\r\n\t\t<div class=\"layui-side\">\r\n\t\t\t<div class=\"layui-side-scroll menu-list\">\r\n\t\t\t\t<ul class=\"layui-nav layui-nav-tree\">\r\n\t\t\t\t\t<li class=\"layui-nav-item layui-nav-itemed layui-this\"><a data-link=\"spiderList.html\" title=\"爬虫列表\">爬虫列表</a></li>\r\n\t\t\t\t\t<li class=\"layui-nav-item layui-nav-itemed\"><a data-link=\"variables.html\" title=\"全局变量\">全局变量</a></li>\r\n\t\t\t\t\t<li class=\"layui-nav-item layui-nav-itemed\"><a data-link=\"functions.html\" title=\"自定义函数\">自定义函数</a></li>\r\n\t\t\t\t\t<li class=\"layui-nav-item layui-nav-itemed\"><a data-link=\"datasources.html\" title=\"数据源管理\">数据源管理</a></li>\r\n\t\t\t\t</ul>\r\n\t\t\t</div>\r\n\t\t</div>\r\n\t\t<div class=\"layui-body\">\r\n\t\t\t<div class=\"layui-tab\" lay-filter=\"admin-tab\" lay-allowClose=\"true\">\r\n\t\t    \t<ul id=\"layuiTab\" class=\"layui-tab-title admin-unselect\" unselectable=\"on\" lay-allowClose=\"false\">\r\n\t\t\t    \t<li class=\"layui-this layui-tab-hidden-close\" lay-id=\"welcome\">爬虫列表</li>\r\n\t\t    \t</ul>\r\n\t\t    \t<div class=\"layui-tab-content\">\r\n\t\t    \t\t<div class=\"layui-tab-item layui-show\">\r\n\t\t    \t\t\t<iframe src=\"spiderList.html\" width=\"100%\" height=\"100%\" scrolling=\"yes\" frameborder=\"0\"></iframe>\r\n\t\t    \t\t</div>\r\n\t\t    \t</div>\r\n\t\t    </div>\r\n\t\t</div>\r\n\t\t<script>\r\n\t\t\tvar title = $(\".layui-logo\");\r\n\t\t\ttitle.append('<span class=\"version-no\">v' + SPIDER_FLOW_VERSION + '</span>');\r\n\t\t</script>\r\n\t</body>\r\n</html>"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/canvas-viewer.js",
    "content": "window.requestAnimFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback,element){\r\n\twindow.setTimeout(callback, 1000 / 60);\r\n}\r\nwindow.cancelAnimationFrame=window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.msCancelAnimationFrame || window.oCancelAnimationFrame || function( id ){\r\n\twindow.clearTimeout( id );\r\n}\r\nvar canvas = document.getElementById('logviewer');\r\nfunction CanvasText(options){\r\n\toptions = options || {};\r\n\tthis.maxWidth = options.maxWidth || 2147483648;\r\n\tthis.color = options.color;\r\n\tthis.text = options.text === undefined || options.text === null ? '': options.text.toString();\r\n\tthis.click = options.click;\r\n\tthis.startX = 0;\r\n\tthis.endX = 0;\r\n}\r\nfunction CanvasViewer(options){\r\n\toptions = options || {};\r\n\tthis.canvas = options.element;\r\n\tthis.context = this.canvas.getContext('2d');\r\n\tthis.style = options.style || {};\r\n\tthis.context.font = this.style.font || 'bold 14px Consolas';\r\n\tthis.context.textBaseline = this.style.textBaseLine || 'middle';\r\n\tthis.lines = [];\r\n\tthis.sourceLines = [];\r\n\tthis.lineHeight = 24;\r\n\tthis.maxRows = Math.ceil(this.canvas.height  / this.lineHeight);\r\n\tthis.scrollHeight = this.canvas.height;\r\n\tthis.scrollTop = 0;\r\n\tvar _this = this;\r\n\tthis.mouseEvent = 0;\r\n\tthis.mouseDownX = 0;\r\n\tthis.mouseDownY = 0;\r\n\tthis.startIndex = 0;\r\n\tthis.startX = 5;\r\n\tthis.mouseX = 0;\r\n\tthis.mouseY = 0;\r\n\tthis.colsOffsetX = [0];\r\n\tthis.filterText = '';\r\n\tthis.maxWidth = this.canvas.width - 8;\r\n\tthis.onClick = options.onClick || function(){};\r\n\tthis.grid = options.grid;\r\n\tthis.header = options.header;\r\n\tthis.canvas.onmousemove = function(e){\r\n\t\tvar x = e.offsetX;\r\n\t\tvar y = e.offsetY;\r\n\t\t_this.mouseX = x;\r\n\t\t_this.mouseY = y;\r\n\t\tvar _hover = false;\r\n\t\tif(x < _this.canvas.width - 8 && y < _this.canvas.height - 8){\r\n\t\t\tvar row = _this.startIndex + parseInt(y / _this.lineHeight) + (y % _this.lineHeight == 0 ? 0 : 1) - 1;\r\n\t\t\tvar texts = _this.lines[row];\r\n\t\t\tif(texts){\r\n\t\t\t\tfor(var i =0,len = texts.length;i<len;i++){\r\n\t\t\t\t\tvar text = texts[i];\r\n\t\t\t\t\tif(text.click&&text.startX < x && text.endX > x){\r\n\t\t\t\t\t\t_hover = true;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tif(_hover){\r\n\t\t\t_this.canvas.style.cursor = 'pointer';\r\n\t\t}else{\r\n\t\t\t_this.canvas.style.cursor = 'default';\r\n\t\t}\r\n\t\t//鼠标按下且有纵向滚动条\r\n\t\tif(_this.mouseEvent == 1 && _this.hasScroll){\r\n\t\t\t_this.scrollTo(Math.max(Math.min(Math.floor((y / (_this.canvas.height - 8)) * _this.lines.length - _this.maxRows / 2),_this.lines.length - _this.maxRows),0));\r\n\t\t}\r\n\t\t//鼠标按下且有横向滚动条\r\n\t\tif(_this.mouseEvent == 2 && _this.hasXScroll){\r\n\t\t\tvar delta = e.offsetX - _this.mouseDownX;\r\n\t\t\t_this.mouseDownX = e.offsetX;\r\n\t\t\tvar canvasWidth = _this.canvas.width - 8;\r\n\t\t\t_this.startX = Math.max(Math.min(_this.startX - (canvasWidth / _this.slideWidth) * delta,_this.grid ? 5 : 0),canvasWidth - _this.maxWidth)\r\n\t\t}\r\n\t\t\r\n\t}\r\n\tthis.canvas.onmousewheel = function(e){\r\n\t\tif(e.wheelDelta > 0){\t//向上滚动\r\n\t\t\t_this.scrollTo(Math.max(_this.startIndex - 2,0));\r\n\t\t}else{\r\n\t\t\t_this.scrollTo(Math.max(Math.min(_this.startIndex + 1,_this.lines.length - _this.maxRows),0));\r\n\t\t}\r\n\t}\r\n\tthis.canvas.onmousedown = function(e){\r\n\t\tif(e.offsetX > _this.canvas.width - 8 && e.offsetY < _this.canvas.height - 8){\r\n\t\t\t_this.mouseEvent = 1;\r\n\t\t\t_this.mouseDownX = e.offsetX;\r\n\t\t\t_this.mouseDownY = e.offsetY;\r\n\t\t}\r\n\t\tif(e.offsetX < _this.canvas.width - 8 && e.offsetY > _this.canvas.height - 8){\r\n\t\t\t_this.mouseEvent = 2;\r\n\t\t\t_this.mouseDownX = e.offsetX;\r\n\t\t\t_this.mouseDownY = e.offsetY;\r\n\t\t}\r\n\t}\r\n\tthis.canvas.onmouseup = this.canvas.onmouseout = function(){\r\n\t\t_this.mouseEvent = 0;\r\n\t\t_this.mouseDownX = 0;\r\n\t\t_this.mouseDownY = 0;\r\n\t}\r\n\tthis.canvas.onclick = function(e){\r\n\t\tvar x = e.offsetX;\r\n\t\tvar y = e.offsetY;\r\n\t\tif(x < _this.canvas.width - 8 && y < _this.canvas.height - 8){\r\n\t\t\tvar row = _this.startIndex + parseInt(y / _this.lineHeight) + (y % _this.lineHeight == 0 ? 0 : 1) - 1;\r\n\t\t\tvar _hover = false;\r\n\t\t\tvar texts = _this.lines[row];\r\n\t\t\tif(texts){\r\n\t\t\t\tfor(var i =0,len = texts.length;i<len;i++){\r\n\t\t\t\t\tvar text = texts[i];\r\n\t\t\t\t\tif(text.click&&text.startX < x && text.endX > x){\r\n\t\t\t\t\t\t_this.onClick(text);\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t}\r\n\tvar animate = function(){\r\n\t\t_this.animateIndex = requestAnimFrame(animate);\r\n\t\t_this.redraw();\r\n\t}\r\n\tanimate();\r\n}\r\nCanvasViewer.prototype.destory = function(){\r\n\tcancelAnimationFrame(this.animateIndex);\r\n\tthis.texts = null;\r\n}\r\nCanvasViewer.prototype.append = function(texts){\r\n\tthis.sourceLines.push(texts);\r\n\tif(this.filterLine(texts)){\r\n\t\tthis.calcMaxWidth(texts);\r\n\t\tthis.lines.push(texts);\r\n\t}\r\n}\r\nCanvasViewer.prototype.calcMaxWidth = function(texts){\r\n\tvar width = texts.length * 10 - 10;\r\n\tfor(var i =0,len = texts.length;i<len;i++){\r\n\t\tvar text = texts[i];\r\n\t\tvar w = 0;\r\n\t\tif(text.maxWidth > 0){\r\n\t\t\tw = this._drawLongText(text.text,0,0,text.maxWidth,true);\r\n\t\t}else{\r\n\t\t\tw = this.context.measureText(content).width;\r\n\t\t}\r\n\t\tthis.colsOffsetX[i] = Math.max(this.colsOffsetX[i] || 0,w);\r\n\t\twidth += ((this.grid ? this.colsOffsetX[i] : 0) ||  w);\r\n\t}\r\n\tthis.maxWidth = Math.max(this.maxWidth,width);\r\n}\r\nCanvasViewer.prototype.filter = function(content){\r\n\tthis.filterText = content;\r\n\tvar nLines = [];\r\n\tfor(var i=0,len = this.sourceLines.length;i<len;i++){\r\n\t\tvar sourceLine = this.sourceLines[i];\r\n\t\tif(this.filterLine(this.sourceLines[i],content)){\r\n\t\t\tthis.calcMaxWidth(sourceLine);\r\n\t\t\tnLines.push(sourceLine);\r\n\t\t}\r\n\t}\r\n\tthis.lines = nLines;\r\n\tthis.scrollTo(-1);\r\n}\r\nCanvasViewer.prototype.filterLine = function(line){\r\n\tif(!this.filterText){\r\n\t\treturn true;\r\n\t}\r\n\tvar text = [];\r\n\tfor(var j=0,l = line.length;j<l;j++){\r\n\t\ttext.push(line[j].text);\r\n\t}\r\n\treturn text.join('').indexOf(this.filterText) > -1;\r\n}\r\nCanvasViewer.prototype.resize = function(){\r\n\tvar prevMaxRows = this.maxRows;\r\n\tthis.maxRows = Math.ceil(this.canvas.height  / this.lineHeight);\r\n\tthis.context = this.canvas.getContext('2d');\r\n\tthis.context.font = 'bold 14px Consolas';\r\n\tthis.context.textBaseline = 'middle';\r\n\tthis.scrollTo(this.startIndex + (prevMaxRows - this.maxRows));\r\n\tthis.redraw();\r\n}\r\nCanvasViewer.prototype._drawScroll = function(){\r\n\tvar surplus = this.lines.length - this.maxRows;\r\n\tthis.hasScroll = surplus > 0;\r\n\t\r\n\tthis.context.clearRect(x,canvasHeight,8,8);\r\n\tif(this.hasScroll){\r\n\t\tvar canvasHeight = this.canvas.height - 8;\r\n\t\tthis.scrollHeight = canvasHeight + this.lineHeight * surplus;\r\n\t\tthis.slideHeight = Math.max(canvasHeight * (canvasHeight / this.scrollHeight),10);\r\n\t\tthis.scrollTop = Math.min(this.startIndex / this.lines.length * canvasHeight,canvasHeight - this.slideHeight);\r\n\t\tthis.context.save(); \r\n\t\tthis.context.beginPath();\r\n\t\tvar x = this.canvas.width - 8;\r\n\t\tvar y = this.scrollTop;\r\n\t\tvar r = 4;\r\n\t\tvar width = 8;\r\n\t\tvar height = this.slideHeight;\r\n\t\tthis.context.fillStyle = '#f1f1f1';\r\n\t\tthis.context.fillRect(x,0,8,canvasHeight);\r\n\t\r\n\t\t\r\n\t\tthis.context.moveTo(x + r, y);\r\n\t\tthis.context.arcTo(x + width, y, x + width, y + r, r);  \r\n\t\tthis.context.arcTo(x + width, y + height, x + width - r, y + height, r); \r\n\t\tthis.context.arcTo(x, y + height, x, y + height - r, r);\r\n\t\tthis.context.arcTo(x, y, x + r, y, r);\r\n\t\tif(this.mouseEvent == 1){\r\n\t\t\tthis.context.fillStyle = '#787878';\r\n\t\t}else if(this.mouseX > x && this.mouseY > y &&this.mouseY < y + height){\r\n\t\t\tthis.context.fillStyle = '#a8a8a8';\r\n\t\t}else{\r\n\t\t\tthis.context.fillStyle = '#c1c1c1';\r\n\t\t}\r\n\t\tthis.context.fill();\r\n\t\tthis.context.restore(); \r\n\t}\r\n\t//\r\n\tthis.hasXScroll = this.maxWidth > this.canvas.width - 8;\r\n\tif(this.hasXScroll){\r\n\t\tvar canvasWidth = this.canvas.width - 8;\r\n\t\tthis.scrollWidth = this.maxWidth;\r\n\t\tthis.slideWidth = Math.max(canvasWidth * (canvasWidth / this.scrollWidth),10);\r\n\t\t\r\n\t\tthis.scrollLeft = Math.min(-this.startX / this.maxWidth * canvasWidth,canvasWidth - this.slideWidth);\r\n\t\tthis.context.save(); \r\n\t\tthis.context.beginPath();\r\n\t\tvar x = this.scrollLeft;\r\n\t\tvar y = this.canvas.height - 8;\r\n\t\tvar r = 4;\r\n\t\tvar width = this.slideWidth;\r\n\t\tvar height = 8;\r\n\t\tthis.context.fillStyle = '#f1f1f1';\r\n\t\tthis.context.fillRect(0,this.canvas.height - 8,canvasWidth,8);\r\n\t\t\r\n\t\tthis.context.moveTo(x + r, y);\r\n\t\tthis.context.arcTo(x + width, y, x + width, y + r, r);  \r\n\t\tthis.context.arcTo(x + width, y + height, x + width - r, y + height, r); \r\n\t\tthis.context.arcTo(x, y + height, x, y + height - r, r);\r\n\t\tthis.context.arcTo(x, y, x + r, y, r);\r\n\t\tif(this.mouseEvent == 2){\r\n\t\t\tthis.context.fillStyle = '#787878';\r\n\t\t}else if(this.mouseX > x && this.mouseY > y &&this.mouseX < x + width){\r\n\t\t\tthis.context.fillStyle = '#a8a8a8';\r\n\t\t}else{\r\n\t\t\tthis.context.fillStyle = '#c1c1c1';\r\n\t\t}\r\n\t\tthis.context.fill();\r\n\t\tthis.context.restore(); \r\n\t}\r\n}\r\nCanvasViewer.prototype.scrollTo = function(index){\r\n\tif(index < 0){\r\n\t\tindex = this.lines.length - 1;\r\n\t}\r\n\tthis.startIndex = Math.max(Math.min(Math.max(index,0),this.lines.length - this.maxRows),0);\r\n\tif(this.startIndex > 0){\r\n\t\tthis.startIndex = this.startIndex + 1;\r\n\t}\r\n}\r\nCanvasViewer.prototype.redraw = function(){\r\n\tvar lines = this.lines.slice(this.startIndex,this.startIndex + this.maxRows);\r\n\tthis.context.clearRect(0,0,this.canvas.width,this.canvas.height);\r\n\tthis.context.lineWidth = 1;\r\n\tthis.context.strokeStyle = '#e6e6e6';\r\n\tthis.context.font = this.style.font || 'bold 14px Consolas';\r\n\tthis.context.textBaseline = this.style.textBaseLine || 'middle';\r\n\tvar cols = [0];\r\n\tvar maxY = 0;\r\n\tvar maxX = 0;\r\n\tfor(var i=0,l =lines.length;i<l;i++){\r\n\t\tvar texts = i==0&&this.grid&&this.header ? this.lines[0]: lines[i];\r\n\t\tvar x = this.startX;\r\n\t\tvar y = i * this.lineHeight + this.lineHeight / 2;\r\n\t\tmaxY = y + this.lineHeight / 2;\r\n\t\tfor(var j =0,t = texts.length;j < t;j++){\r\n\t\t\tvar text = texts[j];\r\n\t\t\tvar content = text.text;\r\n\t\t\tthis.context.fillStyle = text.color || 'black';\r\n\t\t\tvar width = this.context.measureText(content).width;\r\n\t\t\tif(text.maxWidth > 0){\r\n\t\t\t\twidth = this._drawLongText(content,x,y,text.maxWidth);\r\n\t\t\t}else{\r\n\t\t\t\tthis.context.fillText(content,x,y);\r\n\t\t\t}\r\n\t\t\ttext.startX = x;\r\n\t\t\ttext.endX = x + width;\r\n\t\t\tx = x + ((this.grid ? this.colsOffsetX[j] : 0) ||  width) + 10;\r\n\t\t\tmaxX = x - 5;\r\n\t\t\tif(this.grid){\r\n\t\t\t\tcols[j + 1] = Math.max(cols[j + 1] || 0,maxX);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tif(this.grid && lines.length > 0){\r\n\t\tfor(var i=0;i<=lines.length;i++){\r\n\t\t\tif(this.grid){\r\n\t\t\t\tthis.context.save();\r\n\t\t\t\tthis.context.beginPath();\r\n\t\t\t\tthis.context.moveTo(2.5,i * 24 + 0.5);\r\n\t\t\t\tthis.context.lineTo(maxX + 0.5, i * 24 + 0.5);\r\n\t\t\t\tthis.context.stroke();\r\n\t\t\t\tthis.context.restore();\r\n\t\t\t}\r\n\t\t}\r\n\t\tfor(var i=0;i < cols.length;i++){\r\n\t\t\tvar x = cols[i];\r\n\t\t\tthis.context.save();\r\n\t\t\tthis.context.beginPath();\r\n\t\t\tthis.context.moveTo(x + 0.5,0.5);\r\n\t\t\tthis.context.lineTo(x + 0.5, maxY);\r\n\t\t\tthis.context.stroke();\r\n\t\t\tthis.context.restore();\r\n\t\t}\r\n\t}\r\n\tthis._drawScroll();\r\n}\r\nCanvasViewer.prototype._drawLongText = function(text,x,y,maxWidth,calcWidth){\r\n\tvar length = text.length;\r\n\tvar index = 0;\r\n\tvar width = 0;\r\n\twhile(index < length){\r\n\t\tvar str = text.substr(index,1);\r\n\t\tvar w = this.context.measureText(str).width;\r\n\t\twidth+= w;\r\n\t\tif(width > maxWidth){\r\n\t\t\twidth-=w;\r\n\t\t\tbreak;\r\n\t\t}\r\n\t\tif(calcWidth === undefined){\r\n\t\t\tthis.context.fillText(str,x + width - w,y);\r\n\t\t}\r\n\t\tindex++;\r\n\t}\r\n\tif(index < length){\r\n\t\tvar w = this.context.measureText('...').width;\r\n\t\twidth+=w;\r\n\t\tif(calcWidth === undefined){\r\n\t\t\tthis.context.fillText('...',x + width - w,y);\r\n\t\t}\r\n\t\t\r\n\t}\r\n\treturn width;\r\n}"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/codemirror/codemirror.css",
    "content": "/* BASICS */\n\n.CodeMirror {\n  /* Set height, width, borders, and global font properties here */\n  font-family: monospace;\n  color: black;\n  direction: ltr;\n  border: 1px solid #e6e6e6;\n  height: 100%;\n  padding-left: 7px;\n  padding-top: 4px;\n  box-sizing: border-box;\n}\n\n/* PADDING */\n\n.CodeMirror-lines {\n  padding: 8px 0; /* Vertical padding around content */\n}\n.layui-table .CodeMirror-lines {\n  padding: 0px 0; /* Vertical padding around content */\n}\n.CodeMirror pre.CodeMirror-line,\n.CodeMirror pre.CodeMirror-line-like {\n  padding: 0 4px; /* Horizontal padding of content */\n}\n\n.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {\n  background-color: white; /* The little square between H and V scrollbars */\n}\n\n/* GUTTER */\n\n.CodeMirror-gutters {\n  border-right: 1px solid #ddd;\n  background-color: #f7f7f7;\n  white-space: nowrap;\n}\n.CodeMirror-linenumbers {}\n.CodeMirror-linenumber {\n  padding: 0 3px 0 5px;\n  min-width: 20px;\n  text-align: right;\n  color: #999;\n  white-space: nowrap;\n}\n\n.CodeMirror-guttermarker { color: black; }\n.CodeMirror-guttermarker-subtle { color: #999; }\n\n/* CURSOR */\n\n.CodeMirror-cursor {\n  border-left: 1px solid black;\n  border-right: none;\n  width: 0;\n}\n/* Shown when moving in bi-directional text */\n.CodeMirror div.CodeMirror-secondarycursor {\n  border-left: 1px solid silver;\n}\n.cm-fat-cursor .CodeMirror-cursor {\n  width: auto;\n  border: 0 !important;\n  background: #7e7;\n}\n.cm-fat-cursor div.CodeMirror-cursors {\n  z-index: 1;\n}\n.cm-fat-cursor-mark {\n  background-color: rgba(20, 255, 20, 0.5);\n  -webkit-animation: blink 1.06s steps(1) infinite;\n  -moz-animation: blink 1.06s steps(1) infinite;\n  animation: blink 1.06s steps(1) infinite;\n}\n.cm-animate-fat-cursor {\n  width: auto;\n  border: 0;\n  -webkit-animation: blink 1.06s steps(1) infinite;\n  -moz-animation: blink 1.06s steps(1) infinite;\n  animation: blink 1.06s steps(1) infinite;\n  background-color: #7e7;\n}\n@-moz-keyframes blink {\n  0% {}\n  50% { background-color: transparent; }\n  100% {}\n}\n@-webkit-keyframes blink {\n  0% {}\n  50% { background-color: transparent; }\n  100% {}\n}\n@keyframes blink {\n  0% {}\n  50% { background-color: transparent; }\n  100% {}\n}\n\n/* Can style cursor different in overwrite (non-insert) mode */\n.CodeMirror-overwrite .CodeMirror-cursor {}\n\n.cm-tab { display: inline-block; text-decoration: inherit; }\n\n.CodeMirror-rulers {\n  position: absolute;\n  left: 0; right: 0; top: -50px; bottom: 0;\n  overflow: hidden;\n}\n.CodeMirror-ruler {\n  border-left: 1px solid #ccc;\n  top: 0; bottom: 0;\n  position: absolute;\n}\n\n/* DEFAULT THEME */\n\n.cm-s-default .cm-header {color: blue;}\n.cm-s-default .cm-quote {color: #090;}\n.cm-negative {color: #d44;}\n.cm-positive {color: #292;}\n.cm-header, .cm-strong {font-weight: bold;}\n.cm-em {font-style: italic;}\n.cm-link {text-decoration: underline;}\n.cm-strikethrough {text-decoration: line-through;}\n\n.cm-s-default .cm-keyword {color: #708;}\n.cm-s-default .cm-atom {color: #219;}\n.cm-s-default .cm-number {color: #164;}\n.cm-s-default .cm-def {color: #00f;}\n.cm-s-default .cm-variable,\n.cm-s-default .cm-punctuation,\n.cm-s-default .cm-property,\n.cm-s-default .cm-operator {}\n.cm-s-default .cm-variable-2 {color: #05a;}\n.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}\n.cm-s-default .cm-comment {color: #a50;}\n.cm-s-default .cm-string {color: #a11;}\n.cm-s-default .cm-string-2 {color: #f50;}\n.cm-s-default .cm-meta {color: #555;}\n.cm-s-default .cm-qualifier {color: #555;}\n.cm-s-default .cm-builtin {color: #30a;}\n.cm-s-default .cm-bracket {color: #997;}\n.cm-s-default .cm-tag {color: #170;}\n.cm-s-default .cm-attribute {color: #00c;}\n.cm-s-default .cm-hr {color: #999;}\n.cm-s-default .cm-link {color: #00c;}\n\n.cm-s-default .cm-error {color: #f00;}\n.cm-invalidchar {color: #f00;}\n\n.CodeMirror-composing { border-bottom: 2px solid; }\n\n/* Default styles for common addons */\n\ndiv.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}\ndiv.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}\n.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }\n.CodeMirror-activeline-background {background: #e8f2ff;}\n\n/* STOP */\n\n/* The rest of this file contains styles related to the mechanics of\n   the editor. You probably shouldn't touch them. */\n\n.CodeMirror {\n  position: relative;\n  overflow: hidden;\n  background: white;\n}\n\n.CodeMirror-scroll {\n  overflow: scroll !important; /* Things will break if this is overridden */\n  /* 30px is the magic margin used to hide the element's real scrollbars */\n  /* See overflow: hidden in .CodeMirror */\n  margin-bottom: -30px; margin-right: -30px;\n  padding-bottom: 30px;\n  height: 100%;\n  outline: none; /* Prevent dragging from highlighting the element */\n  position: relative;\n}\n.CodeMirror-sizer {\n  position: relative;\n  border-right: 30px solid transparent;\n}\n\n/* The fake, visible scrollbars. Used to force redraw during scrolling\n   before actual scrolling happens, thus preventing shaking and\n   flickering artifacts. */\n.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {\n  position: absolute;\n  z-index: 6;\n  display: none;\n}\n.CodeMirror-vscrollbar {\n  right: 0; top: 0;\n  overflow-x: hidden;\n  overflow-y: scroll;\n}\n.CodeMirror-hscrollbar {\n  bottom: 0; left: 0;\n  overflow-y: hidden;\n  overflow-x: scroll;\n}\n.CodeMirror-scrollbar-filler {\n  right: 0; bottom: 0;\n}\n.CodeMirror-gutter-filler {\n  left: 0; bottom: 0;\n}\n\n.CodeMirror-gutters {\n  position: absolute; left: 0; top: 0;\n  min-height: 100%;\n  z-index: 3;\n}\n.CodeMirror-gutter {\n  white-space: normal;\n  height: 100%;\n  display: inline-block;\n  vertical-align: top;\n  margin-bottom: -30px;\n}\n.CodeMirror-gutter-wrapper {\n  position: absolute;\n  z-index: 4;\n  background: none !important;\n  border: none !important;\n}\n.CodeMirror-gutter-background {\n  position: absolute;\n  top: 0; bottom: 0;\n  z-index: 4;\n}\n.CodeMirror-gutter-elt {\n  position: absolute;\n  cursor: default;\n  z-index: 4;\n}\n.CodeMirror-gutter-wrapper ::selection { background-color: transparent }\n.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }\n\n.CodeMirror-lines {\n  cursor: text;\n  min-height: 1px; /* prevents collapsing before first draw */\n}\n.CodeMirror pre.CodeMirror-line,\n.CodeMirror pre.CodeMirror-line-like {\n  /* Reset some styles that the rest of the page might have set */\n  -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;\n  border-width: 0;\n  background: transparent;\n  font-family: inherit;\n  font-size: inherit;\n  margin: 0;\n  white-space: pre;\n  word-wrap: normal;\n  line-height: inherit;\n  color: inherit;\n  z-index: 2;\n  position: relative;\n  overflow: visible;\n  -webkit-tap-highlight-color: transparent;\n  -webkit-font-variant-ligatures: contextual;\n  font-variant-ligatures: contextual;\n}\n.CodeMirror-wrap pre.CodeMirror-line,\n.CodeMirror-wrap pre.CodeMirror-line-like {\n  word-wrap: break-word;\n  white-space: pre-wrap;\n  word-break: normal;\n}\n\n.CodeMirror-linebackground {\n  position: absolute;\n  left: 0; right: 0; top: 0; bottom: 0;\n  z-index: 0;\n}\n\n.CodeMirror-linewidget {\n  position: relative;\n  z-index: 2;\n  padding: 0.1px; /* Force widget margins to stay inside of the container */\n}\n\n.CodeMirror-widget {}\n\n.CodeMirror-rtl pre { direction: rtl; }\n\n.CodeMirror-code {\n  outline: none;\n}\n\n/* Force content-box sizing for the elements where we expect it */\n.CodeMirror-scroll,\n.CodeMirror-sizer,\n.CodeMirror-gutter,\n.CodeMirror-gutters,\n.CodeMirror-linenumber {\n  -moz-box-sizing: content-box;\n  box-sizing: content-box;\n}\n\n.CodeMirror-measure {\n  position: absolute;\n  width: 100%;\n  height: 0;\n  overflow: hidden;\n  visibility: hidden;\n}\n\n.CodeMirror-cursor {\n  position: absolute;\n  pointer-events: none;\n}\n.CodeMirror-measure pre { position: static; }\n\ndiv.CodeMirror-cursors {\n  visibility: hidden;\n  position: relative;\n  z-index: 3;\n}\ndiv.CodeMirror-dragcursors {\n  visibility: visible;\n}\n\n.CodeMirror-focused div.CodeMirror-cursors {\n  visibility: visible;\n}\n\n.CodeMirror-selected { background: #d9d9d9; }\n.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }\n.CodeMirror-crosshair { cursor: crosshair; }\n.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }\n.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }\n\n.cm-searching {\n  background-color: #ffa;\n  background-color: rgba(255, 255, 0, .4);\n}\n\n/* Used to force a border model for a node */\n.cm-force-border { padding-right: .1px; }\n\n@media print {\n  /* Hide the cursor when printing */\n  .CodeMirror div.CodeMirror-cursors {\n    visibility: hidden;\n  }\n}\n\n/* See issue #2901 */\n.cm-tab-wrap-hack:after { content: ''; }\n\n/* Help users use markselection to safely style text background */\nspan.CodeMirror-selectedtext { background: none; }\n\n\n.CodeMirror pre.CodeMirror-placeholder { color: #666; }"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/codemirror/codemirror.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n// This is CodeMirror (https://codemirror.net), a code editor\n// implemented in JavaScript on top of the browser's DOM.\n//\n// You can find some technical background for some of the code below\n// at http://marijnhaverbeke.nl/blog/#cm-internals .\n\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n  typeof define === 'function' && define.amd ? define(factory) :\n  (global.CodeMirror = factory());\n}(this, (function () { 'use strict';\n\n  // Kludges for bugs and behavior differences that can't be feature\n  // detected are enabled based on userAgent etc sniffing.\n  var userAgent = navigator.userAgent;\n  var platform = navigator.platform;\n\n  var gecko = /gecko\\/\\d/i.test(userAgent);\n  var ie_upto10 = /MSIE \\d/.test(userAgent);\n  var ie_11up = /Trident\\/(?:[7-9]|\\d{2,})\\..*rv:(\\d+)/.exec(userAgent);\n  var edge = /Edge\\/(\\d+)/.exec(userAgent);\n  var ie = ie_upto10 || ie_11up || edge;\n  var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : +(edge || ie_11up)[1]);\n  var webkit = !edge && /WebKit\\//.test(userAgent);\n  var qtwebkit = webkit && /Qt\\/\\d+\\.\\d+/.test(userAgent);\n  var chrome = !edge && /Chrome\\//.test(userAgent);\n  var presto = /Opera\\//.test(userAgent);\n  var safari = /Apple Computer/.test(navigator.vendor);\n  var mac_geMountainLion = /Mac OS X 1\\d\\D([8-9]|\\d\\d)\\D/.test(userAgent);\n  var phantom = /PhantomJS/.test(userAgent);\n\n  var ios = !edge && /AppleWebKit/.test(userAgent) && /Mobile\\/\\w+/.test(userAgent);\n  var android = /Android/.test(userAgent);\n  // This is woefully incomplete. Suggestions for alternative methods welcome.\n  var mobile = ios || android || /webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent);\n  var mac = ios || /Mac/.test(platform);\n  var chromeOS = /\\bCrOS\\b/.test(userAgent);\n  var windows = /win/i.test(platform);\n\n  var presto_version = presto && userAgent.match(/Version\\/(\\d*\\.\\d*)/);\n  if (presto_version) { presto_version = Number(presto_version[1]); }\n  if (presto_version && presto_version >= 15) { presto = false; webkit = true; }\n  // Some browsers use the wrong event properties to signal cmd/ctrl on OS X\n  var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11));\n  var captureRightClick = gecko || (ie && ie_version >= 9);\n\n  function classTest(cls) { return new RegExp(\"(^|\\\\s)\" + cls + \"(?:$|\\\\s)\\\\s*\") }\n\n  var rmClass = function(node, cls) {\n    var current = node.className;\n    var match = classTest(cls).exec(current);\n    if (match) {\n      var after = current.slice(match.index + match[0].length);\n      node.className = current.slice(0, match.index) + (after ? match[1] + after : \"\");\n    }\n  };\n\n  function removeChildren(e) {\n    for (var count = e.childNodes.length; count > 0; --count)\n      { e.removeChild(e.firstChild); }\n    return e\n  }\n\n  function removeChildrenAndAdd(parent, e) {\n    return removeChildren(parent).appendChild(e)\n  }\n\n  function elt(tag, content, className, style) {\n    var e = document.createElement(tag);\n    if (className) { e.className = className; }\n    if (style) { e.style.cssText = style; }\n    if (typeof content == \"string\") { e.appendChild(document.createTextNode(content)); }\n    else if (content) { for (var i = 0; i < content.length; ++i) { e.appendChild(content[i]); } }\n    return e\n  }\n  // wrapper for elt, which removes the elt from the accessibility tree\n  function eltP(tag, content, className, style) {\n    var e = elt(tag, content, className, style);\n    e.setAttribute(\"role\", \"presentation\");\n    return e\n  }\n\n  var range;\n  if (document.createRange) { range = function(node, start, end, endNode) {\n    var r = document.createRange();\n    r.setEnd(endNode || node, end);\n    r.setStart(node, start);\n    return r\n  }; }\n  else { range = function(node, start, end) {\n    var r = document.body.createTextRange();\n    try { r.moveToElementText(node.parentNode); }\n    catch(e) { return r }\n    r.collapse(true);\n    r.moveEnd(\"character\", end);\n    r.moveStart(\"character\", start);\n    return r\n  }; }\n\n  function contains(parent, child) {\n    if (child.nodeType == 3) // Android browser always returns false when child is a textnode\n      { child = child.parentNode; }\n    if (parent.contains)\n      { return parent.contains(child) }\n    do {\n      if (child.nodeType == 11) { child = child.host; }\n      if (child == parent) { return true }\n    } while (child = child.parentNode)\n  }\n\n  function activeElt() {\n    // IE and Edge may throw an \"Unspecified Error\" when accessing document.activeElement.\n    // IE < 10 will throw when accessed while the page is loading or in an iframe.\n    // IE > 9 and Edge will throw when accessed in an iframe if document.body is unavailable.\n    var activeElement;\n    try {\n      activeElement = document.activeElement;\n    } catch(e) {\n      activeElement = document.body || null;\n    }\n    while (activeElement && activeElement.shadowRoot && activeElement.shadowRoot.activeElement)\n      { activeElement = activeElement.shadowRoot.activeElement; }\n    return activeElement\n  }\n\n  function addClass(node, cls) {\n    var current = node.className;\n    if (!classTest(cls).test(current)) { node.className += (current ? \" \" : \"\") + cls; }\n  }\n  function joinClasses(a, b) {\n    var as = a.split(\" \");\n    for (var i = 0; i < as.length; i++)\n      { if (as[i] && !classTest(as[i]).test(b)) { b += \" \" + as[i]; } }\n    return b\n  }\n\n  var selectInput = function(node) { node.select(); };\n  if (ios) // Mobile Safari apparently has a bug where select() is broken.\n    { selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; }; }\n  else if (ie) // Suppress mysterious IE10 errors\n    { selectInput = function(node) { try { node.select(); } catch(_e) {} }; }\n\n  function bind(f) {\n    var args = Array.prototype.slice.call(arguments, 1);\n    return function(){return f.apply(null, args)}\n  }\n\n  function copyObj(obj, target, overwrite) {\n    if (!target) { target = {}; }\n    for (var prop in obj)\n      { if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop)))\n        { target[prop] = obj[prop]; } }\n    return target\n  }\n\n  // Counts the column offset in a string, taking tabs into account.\n  // Used mostly to find indentation.\n  function countColumn(string, end, tabSize, startIndex, startValue) {\n    if (end == null) {\n      end = string.search(/[^\\s\\u00a0]/);\n      if (end == -1) { end = string.length; }\n    }\n    for (var i = startIndex || 0, n = startValue || 0;;) {\n      var nextTab = string.indexOf(\"\\t\", i);\n      if (nextTab < 0 || nextTab >= end)\n        { return n + (end - i) }\n      n += nextTab - i;\n      n += tabSize - (n % tabSize);\n      i = nextTab + 1;\n    }\n  }\n\n  var Delayed = function() {this.id = null;};\n  Delayed.prototype.set = function (ms, f) {\n    clearTimeout(this.id);\n    this.id = setTimeout(f, ms);\n  };\n\n  function indexOf(array, elt) {\n    for (var i = 0; i < array.length; ++i)\n      { if (array[i] == elt) { return i } }\n    return -1\n  }\n\n  // Number of pixels added to scroller and sizer to hide scrollbar\n  var scrollerGap = 30;\n\n  // Returned or thrown by various protocols to signal 'I'm not\n  // handling this'.\n  var Pass = {toString: function(){return \"CodeMirror.Pass\"}};\n\n  // Reused option objects for setSelection & friends\n  var sel_dontScroll = {scroll: false}, sel_mouse = {origin: \"*mouse\"}, sel_move = {origin: \"+move\"};\n\n  // The inverse of countColumn -- find the offset that corresponds to\n  // a particular column.\n  function findColumn(string, goal, tabSize) {\n    for (var pos = 0, col = 0;;) {\n      var nextTab = string.indexOf(\"\\t\", pos);\n      if (nextTab == -1) { nextTab = string.length; }\n      var skipped = nextTab - pos;\n      if (nextTab == string.length || col + skipped >= goal)\n        { return pos + Math.min(skipped, goal - col) }\n      col += nextTab - pos;\n      col += tabSize - (col % tabSize);\n      pos = nextTab + 1;\n      if (col >= goal) { return pos }\n    }\n  }\n\n  var spaceStrs = [\"\"];\n  function spaceStr(n) {\n    while (spaceStrs.length <= n)\n      { spaceStrs.push(lst(spaceStrs) + \" \"); }\n    return spaceStrs[n]\n  }\n\n  function lst(arr) { return arr[arr.length-1] }\n\n  function map(array, f) {\n    var out = [];\n    for (var i = 0; i < array.length; i++) { out[i] = f(array[i], i); }\n    return out\n  }\n\n  function insertSorted(array, value, score) {\n    var pos = 0, priority = score(value);\n    while (pos < array.length && score(array[pos]) <= priority) { pos++; }\n    array.splice(pos, 0, value);\n  }\n\n  function nothing() {}\n\n  function createObj(base, props) {\n    var inst;\n    if (Object.create) {\n      inst = Object.create(base);\n    } else {\n      nothing.prototype = base;\n      inst = new nothing();\n    }\n    if (props) { copyObj(props, inst); }\n    return inst\n  }\n\n  var nonASCIISingleCaseWordChar = /[\\u00df\\u0587\\u0590-\\u05f4\\u0600-\\u06ff\\u3040-\\u309f\\u30a0-\\u30ff\\u3400-\\u4db5\\u4e00-\\u9fcc\\uac00-\\ud7af]/;\n  function isWordCharBasic(ch) {\n    return /\\w/.test(ch) || ch > \"\\x80\" &&\n      (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch))\n  }\n  function isWordChar(ch, helper) {\n    if (!helper) { return isWordCharBasic(ch) }\n    if (helper.source.indexOf(\"\\\\w\") > -1 && isWordCharBasic(ch)) { return true }\n    return helper.test(ch)\n  }\n\n  function isEmpty(obj) {\n    for (var n in obj) { if (obj.hasOwnProperty(n) && obj[n]) { return false } }\n    return true\n  }\n\n  // Extending unicode characters. A series of a non-extending char +\n  // any number of extending chars is treated as a single unit as far\n  // as editing and measuring is concerned. This is not fully correct,\n  // since some scripts/fonts/browsers also treat other configurations\n  // of code points as a group.\n  var extendingChars = /[\\u0300-\\u036f\\u0483-\\u0489\\u0591-\\u05bd\\u05bf\\u05c1\\u05c2\\u05c4\\u05c5\\u05c7\\u0610-\\u061a\\u064b-\\u065e\\u0670\\u06d6-\\u06dc\\u06de-\\u06e4\\u06e7\\u06e8\\u06ea-\\u06ed\\u0711\\u0730-\\u074a\\u07a6-\\u07b0\\u07eb-\\u07f3\\u0816-\\u0819\\u081b-\\u0823\\u0825-\\u0827\\u0829-\\u082d\\u0900-\\u0902\\u093c\\u0941-\\u0948\\u094d\\u0951-\\u0955\\u0962\\u0963\\u0981\\u09bc\\u09be\\u09c1-\\u09c4\\u09cd\\u09d7\\u09e2\\u09e3\\u0a01\\u0a02\\u0a3c\\u0a41\\u0a42\\u0a47\\u0a48\\u0a4b-\\u0a4d\\u0a51\\u0a70\\u0a71\\u0a75\\u0a81\\u0a82\\u0abc\\u0ac1-\\u0ac5\\u0ac7\\u0ac8\\u0acd\\u0ae2\\u0ae3\\u0b01\\u0b3c\\u0b3e\\u0b3f\\u0b41-\\u0b44\\u0b4d\\u0b56\\u0b57\\u0b62\\u0b63\\u0b82\\u0bbe\\u0bc0\\u0bcd\\u0bd7\\u0c3e-\\u0c40\\u0c46-\\u0c48\\u0c4a-\\u0c4d\\u0c55\\u0c56\\u0c62\\u0c63\\u0cbc\\u0cbf\\u0cc2\\u0cc6\\u0ccc\\u0ccd\\u0cd5\\u0cd6\\u0ce2\\u0ce3\\u0d3e\\u0d41-\\u0d44\\u0d4d\\u0d57\\u0d62\\u0d63\\u0dca\\u0dcf\\u0dd2-\\u0dd4\\u0dd6\\u0ddf\\u0e31\\u0e34-\\u0e3a\\u0e47-\\u0e4e\\u0eb1\\u0eb4-\\u0eb9\\u0ebb\\u0ebc\\u0ec8-\\u0ecd\\u0f18\\u0f19\\u0f35\\u0f37\\u0f39\\u0f71-\\u0f7e\\u0f80-\\u0f84\\u0f86\\u0f87\\u0f90-\\u0f97\\u0f99-\\u0fbc\\u0fc6\\u102d-\\u1030\\u1032-\\u1037\\u1039\\u103a\\u103d\\u103e\\u1058\\u1059\\u105e-\\u1060\\u1071-\\u1074\\u1082\\u1085\\u1086\\u108d\\u109d\\u135f\\u1712-\\u1714\\u1732-\\u1734\\u1752\\u1753\\u1772\\u1773\\u17b7-\\u17bd\\u17c6\\u17c9-\\u17d3\\u17dd\\u180b-\\u180d\\u18a9\\u1920-\\u1922\\u1927\\u1928\\u1932\\u1939-\\u193b\\u1a17\\u1a18\\u1a56\\u1a58-\\u1a5e\\u1a60\\u1a62\\u1a65-\\u1a6c\\u1a73-\\u1a7c\\u1a7f\\u1b00-\\u1b03\\u1b34\\u1b36-\\u1b3a\\u1b3c\\u1b42\\u1b6b-\\u1b73\\u1b80\\u1b81\\u1ba2-\\u1ba5\\u1ba8\\u1ba9\\u1c2c-\\u1c33\\u1c36\\u1c37\\u1cd0-\\u1cd2\\u1cd4-\\u1ce0\\u1ce2-\\u1ce8\\u1ced\\u1dc0-\\u1de6\\u1dfd-\\u1dff\\u200c\\u200d\\u20d0-\\u20f0\\u2cef-\\u2cf1\\u2de0-\\u2dff\\u302a-\\u302f\\u3099\\u309a\\ua66f-\\ua672\\ua67c\\ua67d\\ua6f0\\ua6f1\\ua802\\ua806\\ua80b\\ua825\\ua826\\ua8c4\\ua8e0-\\ua8f1\\ua926-\\ua92d\\ua947-\\ua951\\ua980-\\ua982\\ua9b3\\ua9b6-\\ua9b9\\ua9bc\\uaa29-\\uaa2e\\uaa31\\uaa32\\uaa35\\uaa36\\uaa43\\uaa4c\\uaab0\\uaab2-\\uaab4\\uaab7\\uaab8\\uaabe\\uaabf\\uaac1\\uabe5\\uabe8\\uabed\\udc00-\\udfff\\ufb1e\\ufe00-\\ufe0f\\ufe20-\\ufe26\\uff9e\\uff9f]/;\n  function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch) }\n\n  // Returns a number from the range [`0`; `str.length`] unless `pos` is outside that range.\n  function skipExtendingChars(str, pos, dir) {\n    while ((dir < 0 ? pos > 0 : pos < str.length) && isExtendingChar(str.charAt(pos))) { pos += dir; }\n    return pos\n  }\n\n  // Returns the value from the range [`from`; `to`] that satisfies\n  // `pred` and is closest to `from`. Assumes that at least `to`\n  // satisfies `pred`. Supports `from` being greater than `to`.\n  function findFirst(pred, from, to) {\n    // At any point we are certain `to` satisfies `pred`, don't know\n    // whether `from` does.\n    var dir = from > to ? -1 : 1;\n    for (;;) {\n      if (from == to) { return from }\n      var midF = (from + to) / 2, mid = dir < 0 ? Math.ceil(midF) : Math.floor(midF);\n      if (mid == from) { return pred(mid) ? from : to }\n      if (pred(mid)) { to = mid; }\n      else { from = mid + dir; }\n    }\n  }\n\n  // BIDI HELPERS\n\n  function iterateBidiSections(order, from, to, f) {\n    if (!order) { return f(from, to, \"ltr\", 0) }\n    var found = false;\n    for (var i = 0; i < order.length; ++i) {\n      var part = order[i];\n      if (part.from < to && part.to > from || from == to && part.to == from) {\n        f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? \"rtl\" : \"ltr\", i);\n        found = true;\n      }\n    }\n    if (!found) { f(from, to, \"ltr\"); }\n  }\n\n  var bidiOther = null;\n  function getBidiPartAt(order, ch, sticky) {\n    var found;\n    bidiOther = null;\n    for (var i = 0; i < order.length; ++i) {\n      var cur = order[i];\n      if (cur.from < ch && cur.to > ch) { return i }\n      if (cur.to == ch) {\n        if (cur.from != cur.to && sticky == \"before\") { found = i; }\n        else { bidiOther = i; }\n      }\n      if (cur.from == ch) {\n        if (cur.from != cur.to && sticky != \"before\") { found = i; }\n        else { bidiOther = i; }\n      }\n    }\n    return found != null ? found : bidiOther\n  }\n\n  // Bidirectional ordering algorithm\n  // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm\n  // that this (partially) implements.\n\n  // One-char codes used for character types:\n  // L (L):   Left-to-Right\n  // R (R):   Right-to-Left\n  // r (AL):  Right-to-Left Arabic\n  // 1 (EN):  European Number\n  // + (ES):  European Number Separator\n  // % (ET):  European Number Terminator\n  // n (AN):  Arabic Number\n  // , (CS):  Common Number Separator\n  // m (NSM): Non-Spacing Mark\n  // b (BN):  Boundary Neutral\n  // s (B):   Paragraph Separator\n  // t (S):   Segment Separator\n  // w (WS):  Whitespace\n  // N (ON):  Other Neutrals\n\n  // Returns null if characters are ordered as they appear\n  // (left-to-right), or an array of sections ({from, to, level}\n  // objects) in the order in which they occur visually.\n  var bidiOrdering = (function() {\n    // Character types for codepoints 0 to 0xff\n    var lowTypes = \"bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN\";\n    // Character types for codepoints 0x600 to 0x6f9\n    var arabicTypes = \"nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111\";\n    function charType(code) {\n      if (code <= 0xf7) { return lowTypes.charAt(code) }\n      else if (0x590 <= code && code <= 0x5f4) { return \"R\" }\n      else if (0x600 <= code && code <= 0x6f9) { return arabicTypes.charAt(code - 0x600) }\n      else if (0x6ee <= code && code <= 0x8ac) { return \"r\" }\n      else if (0x2000 <= code && code <= 0x200b) { return \"w\" }\n      else if (code == 0x200c) { return \"b\" }\n      else { return \"L\" }\n    }\n\n    var bidiRE = /[\\u0590-\\u05f4\\u0600-\\u06ff\\u0700-\\u08ac]/;\n    var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/;\n\n    function BidiSpan(level, from, to) {\n      this.level = level;\n      this.from = from; this.to = to;\n    }\n\n    return function(str, direction) {\n      var outerType = direction == \"ltr\" ? \"L\" : \"R\";\n\n      if (str.length == 0 || direction == \"ltr\" && !bidiRE.test(str)) { return false }\n      var len = str.length, types = [];\n      for (var i = 0; i < len; ++i)\n        { types.push(charType(str.charCodeAt(i))); }\n\n      // W1. Examine each non-spacing mark (NSM) in the level run, and\n      // change the type of the NSM to the type of the previous\n      // character. If the NSM is at the start of the level run, it will\n      // get the type of sor.\n      for (var i$1 = 0, prev = outerType; i$1 < len; ++i$1) {\n        var type = types[i$1];\n        if (type == \"m\") { types[i$1] = prev; }\n        else { prev = type; }\n      }\n\n      // W2. Search backwards from each instance of a European number\n      // until the first strong type (R, L, AL, or sor) is found. If an\n      // AL is found, change the type of the European number to Arabic\n      // number.\n      // W3. Change all ALs to R.\n      for (var i$2 = 0, cur = outerType; i$2 < len; ++i$2) {\n        var type$1 = types[i$2];\n        if (type$1 == \"1\" && cur == \"r\") { types[i$2] = \"n\"; }\n        else if (isStrong.test(type$1)) { cur = type$1; if (type$1 == \"r\") { types[i$2] = \"R\"; } }\n      }\n\n      // W4. A single European separator between two European numbers\n      // changes to a European number. A single common separator between\n      // two numbers of the same type changes to that type.\n      for (var i$3 = 1, prev$1 = types[0]; i$3 < len - 1; ++i$3) {\n        var type$2 = types[i$3];\n        if (type$2 == \"+\" && prev$1 == \"1\" && types[i$3+1] == \"1\") { types[i$3] = \"1\"; }\n        else if (type$2 == \",\" && prev$1 == types[i$3+1] &&\n                 (prev$1 == \"1\" || prev$1 == \"n\")) { types[i$3] = prev$1; }\n        prev$1 = type$2;\n      }\n\n      // W5. A sequence of European terminators adjacent to European\n      // numbers changes to all European numbers.\n      // W6. Otherwise, separators and terminators change to Other\n      // Neutral.\n      for (var i$4 = 0; i$4 < len; ++i$4) {\n        var type$3 = types[i$4];\n        if (type$3 == \",\") { types[i$4] = \"N\"; }\n        else if (type$3 == \"%\") {\n          var end = (void 0);\n          for (end = i$4 + 1; end < len && types[end] == \"%\"; ++end) {}\n          var replace = (i$4 && types[i$4-1] == \"!\") || (end < len && types[end] == \"1\") ? \"1\" : \"N\";\n          for (var j = i$4; j < end; ++j) { types[j] = replace; }\n          i$4 = end - 1;\n        }\n      }\n\n      // W7. Search backwards from each instance of a European number\n      // until the first strong type (R, L, or sor) is found. If an L is\n      // found, then change the type of the European number to L.\n      for (var i$5 = 0, cur$1 = outerType; i$5 < len; ++i$5) {\n        var type$4 = types[i$5];\n        if (cur$1 == \"L\" && type$4 == \"1\") { types[i$5] = \"L\"; }\n        else if (isStrong.test(type$4)) { cur$1 = type$4; }\n      }\n\n      // N1. A sequence of neutrals takes the direction of the\n      // surrounding strong text if the text on both sides has the same\n      // direction. European and Arabic numbers act as if they were R in\n      // terms of their influence on neutrals. Start-of-level-run (sor)\n      // and end-of-level-run (eor) are used at level run boundaries.\n      // N2. Any remaining neutrals take the embedding direction.\n      for (var i$6 = 0; i$6 < len; ++i$6) {\n        if (isNeutral.test(types[i$6])) {\n          var end$1 = (void 0);\n          for (end$1 = i$6 + 1; end$1 < len && isNeutral.test(types[end$1]); ++end$1) {}\n          var before = (i$6 ? types[i$6-1] : outerType) == \"L\";\n          var after = (end$1 < len ? types[end$1] : outerType) == \"L\";\n          var replace$1 = before == after ? (before ? \"L\" : \"R\") : outerType;\n          for (var j$1 = i$6; j$1 < end$1; ++j$1) { types[j$1] = replace$1; }\n          i$6 = end$1 - 1;\n        }\n      }\n\n      // Here we depart from the documented algorithm, in order to avoid\n      // building up an actual levels array. Since there are only three\n      // levels (0, 1, 2) in an implementation that doesn't take\n      // explicit embedding into account, we can build up the order on\n      // the fly, without following the level-based algorithm.\n      var order = [], m;\n      for (var i$7 = 0; i$7 < len;) {\n        if (countsAsLeft.test(types[i$7])) {\n          var start = i$7;\n          for (++i$7; i$7 < len && countsAsLeft.test(types[i$7]); ++i$7) {}\n          order.push(new BidiSpan(0, start, i$7));\n        } else {\n          var pos = i$7, at = order.length;\n          for (++i$7; i$7 < len && types[i$7] != \"L\"; ++i$7) {}\n          for (var j$2 = pos; j$2 < i$7;) {\n            if (countsAsNum.test(types[j$2])) {\n              if (pos < j$2) { order.splice(at, 0, new BidiSpan(1, pos, j$2)); }\n              var nstart = j$2;\n              for (++j$2; j$2 < i$7 && countsAsNum.test(types[j$2]); ++j$2) {}\n              order.splice(at, 0, new BidiSpan(2, nstart, j$2));\n              pos = j$2;\n            } else { ++j$2; }\n          }\n          if (pos < i$7) { order.splice(at, 0, new BidiSpan(1, pos, i$7)); }\n        }\n      }\n      if (direction == \"ltr\") {\n        if (order[0].level == 1 && (m = str.match(/^\\s+/))) {\n          order[0].from = m[0].length;\n          order.unshift(new BidiSpan(0, 0, m[0].length));\n        }\n        if (lst(order).level == 1 && (m = str.match(/\\s+$/))) {\n          lst(order).to -= m[0].length;\n          order.push(new BidiSpan(0, len - m[0].length, len));\n        }\n      }\n\n      return direction == \"rtl\" ? order.reverse() : order\n    }\n  })();\n\n  // Get the bidi ordering for the given line (and cache it). Returns\n  // false for lines that are fully left-to-right, and an array of\n  // BidiSpan objects otherwise.\n  function getOrder(line, direction) {\n    var order = line.order;\n    if (order == null) { order = line.order = bidiOrdering(line.text, direction); }\n    return order\n  }\n\n  // EVENT HANDLING\n\n  // Lightweight event framework. on/off also work on DOM nodes,\n  // registering native DOM handlers.\n\n  var noHandlers = [];\n\n  var on = function(emitter, type, f) {\n    if (emitter.addEventListener) {\n      emitter.addEventListener(type, f, false);\n    } else if (emitter.attachEvent) {\n      emitter.attachEvent(\"on\" + type, f);\n    } else {\n      var map$$1 = emitter._handlers || (emitter._handlers = {});\n      map$$1[type] = (map$$1[type] || noHandlers).concat(f);\n    }\n  };\n\n  function getHandlers(emitter, type) {\n    return emitter._handlers && emitter._handlers[type] || noHandlers\n  }\n\n  function off(emitter, type, f) {\n    if (emitter.removeEventListener) {\n      emitter.removeEventListener(type, f, false);\n    } else if (emitter.detachEvent) {\n      emitter.detachEvent(\"on\" + type, f);\n    } else {\n      var map$$1 = emitter._handlers, arr = map$$1 && map$$1[type];\n      if (arr) {\n        var index = indexOf(arr, f);\n        if (index > -1)\n          { map$$1[type] = arr.slice(0, index).concat(arr.slice(index + 1)); }\n      }\n    }\n  }\n\n  function signal(emitter, type /*, values...*/) {\n    var handlers = getHandlers(emitter, type);\n    if (!handlers.length) { return }\n    var args = Array.prototype.slice.call(arguments, 2);\n    for (var i = 0; i < handlers.length; ++i) { handlers[i].apply(null, args); }\n  }\n\n  // The DOM events that CodeMirror handles can be overridden by\n  // registering a (non-DOM) handler on the editor for the event name,\n  // and preventDefault-ing the event in that handler.\n  function signalDOMEvent(cm, e, override) {\n    if (typeof e == \"string\")\n      { e = {type: e, preventDefault: function() { this.defaultPrevented = true; }}; }\n    signal(cm, override || e.type, cm, e);\n    return e_defaultPrevented(e) || e.codemirrorIgnore\n  }\n\n  function signalCursorActivity(cm) {\n    var arr = cm._handlers && cm._handlers.cursorActivity;\n    if (!arr) { return }\n    var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []);\n    for (var i = 0; i < arr.length; ++i) { if (indexOf(set, arr[i]) == -1)\n      { set.push(arr[i]); } }\n  }\n\n  function hasHandler(emitter, type) {\n    return getHandlers(emitter, type).length > 0\n  }\n\n  // Add on and off methods to a constructor's prototype, to make\n  // registering events on such objects more convenient.\n  function eventMixin(ctor) {\n    ctor.prototype.on = function(type, f) {on(this, type, f);};\n    ctor.prototype.off = function(type, f) {off(this, type, f);};\n  }\n\n  // Due to the fact that we still support jurassic IE versions, some\n  // compatibility wrappers are needed.\n\n  function e_preventDefault(e) {\n    if (e.preventDefault) { e.preventDefault(); }\n    else { e.returnValue = false; }\n  }\n  function e_stopPropagation(e) {\n    if (e.stopPropagation) { e.stopPropagation(); }\n    else { e.cancelBubble = true; }\n  }\n  function e_defaultPrevented(e) {\n    return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false\n  }\n  function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}\n\n  function e_target(e) {return e.target || e.srcElement}\n  function e_button(e) {\n    var b = e.which;\n    if (b == null) {\n      if (e.button & 1) { b = 1; }\n      else if (e.button & 2) { b = 3; }\n      else if (e.button & 4) { b = 2; }\n    }\n    if (mac && e.ctrlKey && b == 1) { b = 3; }\n    return b\n  }\n\n  // Detect drag-and-drop\n  var dragAndDrop = function() {\n    // There is *some* kind of drag-and-drop support in IE6-8, but I\n    // couldn't get it to work yet.\n    if (ie && ie_version < 9) { return false }\n    var div = elt('div');\n    return \"draggable\" in div || \"dragDrop\" in div\n  }();\n\n  var zwspSupported;\n  function zeroWidthElement(measure) {\n    if (zwspSupported == null) {\n      var test = elt(\"span\", \"\\u200b\");\n      removeChildrenAndAdd(measure, elt(\"span\", [test, document.createTextNode(\"x\")]));\n      if (measure.firstChild.offsetHeight != 0)\n        { zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8); }\n    }\n    var node = zwspSupported ? elt(\"span\", \"\\u200b\") :\n      elt(\"span\", \"\\u00a0\", null, \"display: inline-block; width: 1px; margin-right: -1px\");\n    node.setAttribute(\"cm-text\", \"\");\n    return node\n  }\n\n  // Feature-detect IE's crummy client rect reporting for bidi text\n  var badBidiRects;\n  function hasBadBidiRects(measure) {\n    if (badBidiRects != null) { return badBidiRects }\n    var txt = removeChildrenAndAdd(measure, document.createTextNode(\"A\\u062eA\"));\n    var r0 = range(txt, 0, 1).getBoundingClientRect();\n    var r1 = range(txt, 1, 2).getBoundingClientRect();\n    removeChildren(measure);\n    if (!r0 || r0.left == r0.right) { return false } // Safari returns null in some cases (#2780)\n    return badBidiRects = (r1.right - r0.right < 3)\n  }\n\n  // See if \"\".split is the broken IE version, if so, provide an\n  // alternative way to split lines.\n  var splitLinesAuto = \"\\n\\nb\".split(/\\n/).length != 3 ? function (string) {\n    var pos = 0, result = [], l = string.length;\n    while (pos <= l) {\n      var nl = string.indexOf(\"\\n\", pos);\n      if (nl == -1) { nl = string.length; }\n      var line = string.slice(pos, string.charAt(nl - 1) == \"\\r\" ? nl - 1 : nl);\n      var rt = line.indexOf(\"\\r\");\n      if (rt != -1) {\n        result.push(line.slice(0, rt));\n        pos += rt + 1;\n      } else {\n        result.push(line);\n        pos = nl + 1;\n      }\n    }\n    return result\n  } : function (string) { return string.split(/\\r\\n?|\\n/); };\n\n  var hasSelection = window.getSelection ? function (te) {\n    try { return te.selectionStart != te.selectionEnd }\n    catch(e) { return false }\n  } : function (te) {\n    var range$$1;\n    try {range$$1 = te.ownerDocument.selection.createRange();}\n    catch(e) {}\n    if (!range$$1 || range$$1.parentElement() != te) { return false }\n    return range$$1.compareEndPoints(\"StartToEnd\", range$$1) != 0\n  };\n\n  var hasCopyEvent = (function () {\n    var e = elt(\"div\");\n    if (\"oncopy\" in e) { return true }\n    e.setAttribute(\"oncopy\", \"return;\");\n    return typeof e.oncopy == \"function\"\n  })();\n\n  var badZoomedRects = null;\n  function hasBadZoomedRects(measure) {\n    if (badZoomedRects != null) { return badZoomedRects }\n    var node = removeChildrenAndAdd(measure, elt(\"span\", \"x\"));\n    var normal = node.getBoundingClientRect();\n    var fromRange = range(node, 0, 1).getBoundingClientRect();\n    return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1\n  }\n\n  // Known modes, by name and by MIME\n  var modes = {}, mimeModes = {};\n\n  // Extra arguments are stored as the mode's dependencies, which is\n  // used by (legacy) mechanisms like loadmode.js to automatically\n  // load a mode. (Preferred mechanism is the require/define calls.)\n  function defineMode(name, mode) {\n    if (arguments.length > 2)\n      { mode.dependencies = Array.prototype.slice.call(arguments, 2); }\n    modes[name] = mode;\n  }\n\n  function defineMIME(mime, spec) {\n    mimeModes[mime] = spec;\n  }\n\n  // Given a MIME type, a {name, ...options} config object, or a name\n  // string, return a mode config object.\n  function resolveMode(spec) {\n    if (typeof spec == \"string\" && mimeModes.hasOwnProperty(spec)) {\n      spec = mimeModes[spec];\n    } else if (spec && typeof spec.name == \"string\" && mimeModes.hasOwnProperty(spec.name)) {\n      var found = mimeModes[spec.name];\n      if (typeof found == \"string\") { found = {name: found}; }\n      spec = createObj(found, spec);\n      spec.name = found.name;\n    } else if (typeof spec == \"string\" && /^[\\w\\-]+\\/[\\w\\-]+\\+xml$/.test(spec)) {\n      return resolveMode(\"application/xml\")\n    } else if (typeof spec == \"string\" && /^[\\w\\-]+\\/[\\w\\-]+\\+json$/.test(spec)) {\n      return resolveMode(\"application/json\")\n    }\n    if (typeof spec == \"string\") { return {name: spec} }\n    else { return spec || {name: \"null\"} }\n  }\n\n  // Given a mode spec (anything that resolveMode accepts), find and\n  // initialize an actual mode object.\n  function getMode(options, spec) {\n    spec = resolveMode(spec);\n    var mfactory = modes[spec.name];\n    if (!mfactory) { return getMode(options, \"text/plain\") }\n    var modeObj = mfactory(options, spec);\n    if (modeExtensions.hasOwnProperty(spec.name)) {\n      var exts = modeExtensions[spec.name];\n      for (var prop in exts) {\n        if (!exts.hasOwnProperty(prop)) { continue }\n        if (modeObj.hasOwnProperty(prop)) { modeObj[\"_\" + prop] = modeObj[prop]; }\n        modeObj[prop] = exts[prop];\n      }\n    }\n    modeObj.name = spec.name;\n    if (spec.helperType) { modeObj.helperType = spec.helperType; }\n    if (spec.modeProps) { for (var prop$1 in spec.modeProps)\n      { modeObj[prop$1] = spec.modeProps[prop$1]; } }\n\n    return modeObj\n  }\n\n  // This can be used to attach properties to mode objects from\n  // outside the actual mode definition.\n  var modeExtensions = {};\n  function extendMode(mode, properties) {\n    var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {});\n    copyObj(properties, exts);\n  }\n\n  function copyState(mode, state) {\n    if (state === true) { return state }\n    if (mode.copyState) { return mode.copyState(state) }\n    var nstate = {};\n    for (var n in state) {\n      var val = state[n];\n      if (val instanceof Array) { val = val.concat([]); }\n      nstate[n] = val;\n    }\n    return nstate\n  }\n\n  // Given a mode and a state (for that mode), find the inner mode and\n  // state at the position that the state refers to.\n  function innerMode(mode, state) {\n    var info;\n    while (mode.innerMode) {\n      info = mode.innerMode(state);\n      if (!info || info.mode == mode) { break }\n      state = info.state;\n      mode = info.mode;\n    }\n    return info || {mode: mode, state: state}\n  }\n\n  function startState(mode, a1, a2) {\n    return mode.startState ? mode.startState(a1, a2) : true\n  }\n\n  // STRING STREAM\n\n  // Fed to the mode parsers, provides helper functions to make\n  // parsers more succinct.\n\n  var StringStream = function(string, tabSize, lineOracle) {\n    this.pos = this.start = 0;\n    this.string = string;\n    this.tabSize = tabSize || 8;\n    this.lastColumnPos = this.lastColumnValue = 0;\n    this.lineStart = 0;\n    this.lineOracle = lineOracle;\n  };\n\n  StringStream.prototype.eol = function () {return this.pos >= this.string.length};\n  StringStream.prototype.sol = function () {return this.pos == this.lineStart};\n  StringStream.prototype.peek = function () {return this.string.charAt(this.pos) || undefined};\n  StringStream.prototype.next = function () {\n    if (this.pos < this.string.length)\n      { return this.string.charAt(this.pos++) }\n  };\n  StringStream.prototype.eat = function (match) {\n    var ch = this.string.charAt(this.pos);\n    var ok;\n    if (typeof match == \"string\") { ok = ch == match; }\n    else { ok = ch && (match.test ? match.test(ch) : match(ch)); }\n    if (ok) {++this.pos; return ch}\n  };\n  StringStream.prototype.eatWhile = function (match) {\n    var start = this.pos;\n    while (this.eat(match)){}\n    return this.pos > start\n  };\n  StringStream.prototype.eatSpace = function () {\n      var this$1 = this;\n\n    var start = this.pos;\n    while (/[\\s\\u00a0]/.test(this.string.charAt(this.pos))) { ++this$1.pos; }\n    return this.pos > start\n  };\n  StringStream.prototype.skipToEnd = function () {this.pos = this.string.length;};\n  StringStream.prototype.skipTo = function (ch) {\n    var found = this.string.indexOf(ch, this.pos);\n    if (found > -1) {this.pos = found; return true}\n  };\n  StringStream.prototype.backUp = function (n) {this.pos -= n;};\n  StringStream.prototype.column = function () {\n    if (this.lastColumnPos < this.start) {\n      this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue);\n      this.lastColumnPos = this.start;\n    }\n    return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0)\n  };\n  StringStream.prototype.indentation = function () {\n    return countColumn(this.string, null, this.tabSize) -\n      (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0)\n  };\n  StringStream.prototype.match = function (pattern, consume, caseInsensitive) {\n    if (typeof pattern == \"string\") {\n      var cased = function (str) { return caseInsensitive ? str.toLowerCase() : str; };\n      var substr = this.string.substr(this.pos, pattern.length);\n      if (cased(substr) == cased(pattern)) {\n        if (consume !== false) { this.pos += pattern.length; }\n        return true\n      }\n    } else {\n      var match = this.string.slice(this.pos).match(pattern);\n      if (match && match.index > 0) { return null }\n      if (match && consume !== false) { this.pos += match[0].length; }\n      return match\n    }\n  };\n  StringStream.prototype.current = function (){return this.string.slice(this.start, this.pos)};\n  StringStream.prototype.hideFirstChars = function (n, inner) {\n    this.lineStart += n;\n    try { return inner() }\n    finally { this.lineStart -= n; }\n  };\n  StringStream.prototype.lookAhead = function (n) {\n    var oracle = this.lineOracle;\n    return oracle && oracle.lookAhead(n)\n  };\n  StringStream.prototype.baseToken = function () {\n    var oracle = this.lineOracle;\n    return oracle && oracle.baseToken(this.pos)\n  };\n\n  // Find the line object corresponding to the given line number.\n  function getLine(doc, n) {\n    n -= doc.first;\n    if (n < 0 || n >= doc.size) { throw new Error(\"There is no line \" + (n + doc.first) + \" in the document.\") }\n    var chunk = doc;\n    while (!chunk.lines) {\n      for (var i = 0;; ++i) {\n        var child = chunk.children[i], sz = child.chunkSize();\n        if (n < sz) { chunk = child; break }\n        n -= sz;\n      }\n    }\n    return chunk.lines[n]\n  }\n\n  // Get the part of a document between two positions, as an array of\n  // strings.\n  function getBetween(doc, start, end) {\n    var out = [], n = start.line;\n    doc.iter(start.line, end.line + 1, function (line) {\n      var text = line.text;\n      if (n == end.line) { text = text.slice(0, end.ch); }\n      if (n == start.line) { text = text.slice(start.ch); }\n      out.push(text);\n      ++n;\n    });\n    return out\n  }\n  // Get the lines between from and to, as array of strings.\n  function getLines(doc, from, to) {\n    var out = [];\n    doc.iter(from, to, function (line) { out.push(line.text); }); // iter aborts when callback returns truthy value\n    return out\n  }\n\n  // Update the height of a line, propagating the height change\n  // upwards to parent nodes.\n  function updateLineHeight(line, height) {\n    var diff = height - line.height;\n    if (diff) { for (var n = line; n; n = n.parent) { n.height += diff; } }\n  }\n\n  // Given a line object, find its line number by walking up through\n  // its parent links.\n  function lineNo(line) {\n    if (line.parent == null) { return null }\n    var cur = line.parent, no = indexOf(cur.lines, line);\n    for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {\n      for (var i = 0;; ++i) {\n        if (chunk.children[i] == cur) { break }\n        no += chunk.children[i].chunkSize();\n      }\n    }\n    return no + cur.first\n  }\n\n  // Find the line at the given vertical position, using the height\n  // information in the document tree.\n  function lineAtHeight(chunk, h) {\n    var n = chunk.first;\n    outer: do {\n      for (var i$1 = 0; i$1 < chunk.children.length; ++i$1) {\n        var child = chunk.children[i$1], ch = child.height;\n        if (h < ch) { chunk = child; continue outer }\n        h -= ch;\n        n += child.chunkSize();\n      }\n      return n\n    } while (!chunk.lines)\n    var i = 0;\n    for (; i < chunk.lines.length; ++i) {\n      var line = chunk.lines[i], lh = line.height;\n      if (h < lh) { break }\n      h -= lh;\n    }\n    return n + i\n  }\n\n  function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size}\n\n  function lineNumberFor(options, i) {\n    return String(options.lineNumberFormatter(i + options.firstLineNumber))\n  }\n\n  // A Pos instance represents a position within the text.\n  function Pos(line, ch, sticky) {\n    if ( sticky === void 0 ) sticky = null;\n\n    if (!(this instanceof Pos)) { return new Pos(line, ch, sticky) }\n    this.line = line;\n    this.ch = ch;\n    this.sticky = sticky;\n  }\n\n  // Compare two positions, return 0 if they are the same, a negative\n  // number when a is less, and a positive number otherwise.\n  function cmp(a, b) { return a.line - b.line || a.ch - b.ch }\n\n  function equalCursorPos(a, b) { return a.sticky == b.sticky && cmp(a, b) == 0 }\n\n  function copyPos(x) {return Pos(x.line, x.ch)}\n  function maxPos(a, b) { return cmp(a, b) < 0 ? b : a }\n  function minPos(a, b) { return cmp(a, b) < 0 ? a : b }\n\n  // Most of the external API clips given positions to make sure they\n  // actually exist within the document.\n  function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1))}\n  function clipPos(doc, pos) {\n    if (pos.line < doc.first) { return Pos(doc.first, 0) }\n    var last = doc.first + doc.size - 1;\n    if (pos.line > last) { return Pos(last, getLine(doc, last).text.length) }\n    return clipToLen(pos, getLine(doc, pos.line).text.length)\n  }\n  function clipToLen(pos, linelen) {\n    var ch = pos.ch;\n    if (ch == null || ch > linelen) { return Pos(pos.line, linelen) }\n    else if (ch < 0) { return Pos(pos.line, 0) }\n    else { return pos }\n  }\n  function clipPosArray(doc, array) {\n    var out = [];\n    for (var i = 0; i < array.length; i++) { out[i] = clipPos(doc, array[i]); }\n    return out\n  }\n\n  var SavedContext = function(state, lookAhead) {\n    this.state = state;\n    this.lookAhead = lookAhead;\n  };\n\n  var Context = function(doc, state, line, lookAhead) {\n    this.state = state;\n    this.doc = doc;\n    this.line = line;\n    this.maxLookAhead = lookAhead || 0;\n    this.baseTokens = null;\n    this.baseTokenPos = 1;\n  };\n\n  Context.prototype.lookAhead = function (n) {\n    var line = this.doc.getLine(this.line + n);\n    if (line != null && n > this.maxLookAhead) { this.maxLookAhead = n; }\n    return line\n  };\n\n  Context.prototype.baseToken = function (n) {\n      var this$1 = this;\n\n    if (!this.baseTokens) { return null }\n    while (this.baseTokens[this.baseTokenPos] <= n)\n      { this$1.baseTokenPos += 2; }\n    var type = this.baseTokens[this.baseTokenPos + 1];\n    return {type: type && type.replace(/( |^)overlay .*/, \"\"),\n            size: this.baseTokens[this.baseTokenPos] - n}\n  };\n\n  Context.prototype.nextLine = function () {\n    this.line++;\n    if (this.maxLookAhead > 0) { this.maxLookAhead--; }\n  };\n\n  Context.fromSaved = function (doc, saved, line) {\n    if (saved instanceof SavedContext)\n      { return new Context(doc, copyState(doc.mode, saved.state), line, saved.lookAhead) }\n    else\n      { return new Context(doc, copyState(doc.mode, saved), line) }\n  };\n\n  Context.prototype.save = function (copy) {\n    var state = copy !== false ? copyState(this.doc.mode, this.state) : this.state;\n    return this.maxLookAhead > 0 ? new SavedContext(state, this.maxLookAhead) : state\n  };\n\n\n  // Compute a style array (an array starting with a mode generation\n  // -- for invalidation -- followed by pairs of end positions and\n  // style strings), which is used to highlight the tokens on the\n  // line.\n  function highlightLine(cm, line, context, forceToEnd) {\n    // A styles array always starts with a number identifying the\n    // mode/overlays that it is based on (for easy invalidation).\n    var st = [cm.state.modeGen], lineClasses = {};\n    // Compute the base array of styles\n    runMode(cm, line.text, cm.doc.mode, context, function (end, style) { return st.push(end, style); },\n            lineClasses, forceToEnd);\n    var state = context.state;\n\n    // Run overlays, adjust style array.\n    var loop = function ( o ) {\n      context.baseTokens = st;\n      var overlay = cm.state.overlays[o], i = 1, at = 0;\n      context.state = true;\n      runMode(cm, line.text, overlay.mode, context, function (end, style) {\n        var start = i;\n        // Ensure there's a token end at the current position, and that i points at it\n        while (at < end) {\n          var i_end = st[i];\n          if (i_end > end)\n            { st.splice(i, 1, end, st[i+1], i_end); }\n          i += 2;\n          at = Math.min(end, i_end);\n        }\n        if (!style) { return }\n        if (overlay.opaque) {\n          st.splice(start, i - start, end, \"overlay \" + style);\n          i = start + 2;\n        } else {\n          for (; start < i; start += 2) {\n            var cur = st[start+1];\n            st[start+1] = (cur ? cur + \" \" : \"\") + \"overlay \" + style;\n          }\n        }\n      }, lineClasses);\n      context.state = state;\n      context.baseTokens = null;\n      context.baseTokenPos = 1;\n    };\n\n    for (var o = 0; o < cm.state.overlays.length; ++o) loop( o );\n\n    return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null}\n  }\n\n  function getLineStyles(cm, line, updateFrontier) {\n    if (!line.styles || line.styles[0] != cm.state.modeGen) {\n      var context = getContextBefore(cm, lineNo(line));\n      var resetState = line.text.length > cm.options.maxHighlightLength && copyState(cm.doc.mode, context.state);\n      var result = highlightLine(cm, line, context);\n      if (resetState) { context.state = resetState; }\n      line.stateAfter = context.save(!resetState);\n      line.styles = result.styles;\n      if (result.classes) { line.styleClasses = result.classes; }\n      else if (line.styleClasses) { line.styleClasses = null; }\n      if (updateFrontier === cm.doc.highlightFrontier)\n        { cm.doc.modeFrontier = Math.max(cm.doc.modeFrontier, ++cm.doc.highlightFrontier); }\n    }\n    return line.styles\n  }\n\n  function getContextBefore(cm, n, precise) {\n    var doc = cm.doc, display = cm.display;\n    if (!doc.mode.startState) { return new Context(doc, true, n) }\n    var start = findStartLine(cm, n, precise);\n    var saved = start > doc.first && getLine(doc, start - 1).stateAfter;\n    var context = saved ? Context.fromSaved(doc, saved, start) : new Context(doc, startState(doc.mode), start);\n\n    doc.iter(start, n, function (line) {\n      processLine(cm, line.text, context);\n      var pos = context.line;\n      line.stateAfter = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo ? context.save() : null;\n      context.nextLine();\n    });\n    if (precise) { doc.modeFrontier = context.line; }\n    return context\n  }\n\n  // Lightweight form of highlight -- proceed over this line and\n  // update state, but don't save a style array. Used for lines that\n  // aren't currently visible.\n  function processLine(cm, text, context, startAt) {\n    var mode = cm.doc.mode;\n    var stream = new StringStream(text, cm.options.tabSize, context);\n    stream.start = stream.pos = startAt || 0;\n    if (text == \"\") { callBlankLine(mode, context.state); }\n    while (!stream.eol()) {\n      readToken(mode, stream, context.state);\n      stream.start = stream.pos;\n    }\n  }\n\n  function callBlankLine(mode, state) {\n    if (mode.blankLine) { return mode.blankLine(state) }\n    if (!mode.innerMode) { return }\n    var inner = innerMode(mode, state);\n    if (inner.mode.blankLine) { return inner.mode.blankLine(inner.state) }\n  }\n\n  function readToken(mode, stream, state, inner) {\n    for (var i = 0; i < 10; i++) {\n      if (inner) { inner[0] = innerMode(mode, state).mode; }\n      var style = mode.token(stream, state);\n      if (stream.pos > stream.start) { return style }\n    }\n    throw new Error(\"Mode \" + mode.name + \" failed to advance stream.\")\n  }\n\n  var Token = function(stream, type, state) {\n    this.start = stream.start; this.end = stream.pos;\n    this.string = stream.current();\n    this.type = type || null;\n    this.state = state;\n  };\n\n  // Utility for getTokenAt and getLineTokens\n  function takeToken(cm, pos, precise, asArray) {\n    var doc = cm.doc, mode = doc.mode, style;\n    pos = clipPos(doc, pos);\n    var line = getLine(doc, pos.line), context = getContextBefore(cm, pos.line, precise);\n    var stream = new StringStream(line.text, cm.options.tabSize, context), tokens;\n    if (asArray) { tokens = []; }\n    while ((asArray || stream.pos < pos.ch) && !stream.eol()) {\n      stream.start = stream.pos;\n      style = readToken(mode, stream, context.state);\n      if (asArray) { tokens.push(new Token(stream, style, copyState(doc.mode, context.state))); }\n    }\n    return asArray ? tokens : new Token(stream, style, context.state)\n  }\n\n  function extractLineClasses(type, output) {\n    if (type) { for (;;) {\n      var lineClass = type.match(/(?:^|\\s+)line-(background-)?(\\S+)/);\n      if (!lineClass) { break }\n      type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length);\n      var prop = lineClass[1] ? \"bgClass\" : \"textClass\";\n      if (output[prop] == null)\n        { output[prop] = lineClass[2]; }\n      else if (!(new RegExp(\"(?:^|\\s)\" + lineClass[2] + \"(?:$|\\s)\")).test(output[prop]))\n        { output[prop] += \" \" + lineClass[2]; }\n    } }\n    return type\n  }\n\n  // Run the given mode's parser over a line, calling f for each token.\n  function runMode(cm, text, mode, context, f, lineClasses, forceToEnd) {\n    var flattenSpans = mode.flattenSpans;\n    if (flattenSpans == null) { flattenSpans = cm.options.flattenSpans; }\n    var curStart = 0, curStyle = null;\n    var stream = new StringStream(text, cm.options.tabSize, context), style;\n    var inner = cm.options.addModeClass && [null];\n    if (text == \"\") { extractLineClasses(callBlankLine(mode, context.state), lineClasses); }\n    while (!stream.eol()) {\n      if (stream.pos > cm.options.maxHighlightLength) {\n        flattenSpans = false;\n        if (forceToEnd) { processLine(cm, text, context, stream.pos); }\n        stream.pos = text.length;\n        style = null;\n      } else {\n        style = extractLineClasses(readToken(mode, stream, context.state, inner), lineClasses);\n      }\n      if (inner) {\n        var mName = inner[0].name;\n        if (mName) { style = \"m-\" + (style ? mName + \" \" + style : mName); }\n      }\n      if (!flattenSpans || curStyle != style) {\n        while (curStart < stream.start) {\n          curStart = Math.min(stream.start, curStart + 5000);\n          f(curStart, curStyle);\n        }\n        curStyle = style;\n      }\n      stream.start = stream.pos;\n    }\n    while (curStart < stream.pos) {\n      // Webkit seems to refuse to render text nodes longer than 57444\n      // characters, and returns inaccurate measurements in nodes\n      // starting around 5000 chars.\n      var pos = Math.min(stream.pos, curStart + 5000);\n      f(pos, curStyle);\n      curStart = pos;\n    }\n  }\n\n  // Finds the line to start with when starting a parse. Tries to\n  // find a line with a stateAfter, so that it can start with a\n  // valid state. If that fails, it returns the line with the\n  // smallest indentation, which tends to need the least context to\n  // parse correctly.\n  function findStartLine(cm, n, precise) {\n    var minindent, minline, doc = cm.doc;\n    var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100);\n    for (var search = n; search > lim; --search) {\n      if (search <= doc.first) { return doc.first }\n      var line = getLine(doc, search - 1), after = line.stateAfter;\n      if (after && (!precise || search + (after instanceof SavedContext ? after.lookAhead : 0) <= doc.modeFrontier))\n        { return search }\n      var indented = countColumn(line.text, null, cm.options.tabSize);\n      if (minline == null || minindent > indented) {\n        minline = search - 1;\n        minindent = indented;\n      }\n    }\n    return minline\n  }\n\n  function retreatFrontier(doc, n) {\n    doc.modeFrontier = Math.min(doc.modeFrontier, n);\n    if (doc.highlightFrontier < n - 10) { return }\n    var start = doc.first;\n    for (var line = n - 1; line > start; line--) {\n      var saved = getLine(doc, line).stateAfter;\n      // change is on 3\n      // state on line 1 looked ahead 2 -- so saw 3\n      // test 1 + 2 < 3 should cover this\n      if (saved && (!(saved instanceof SavedContext) || line + saved.lookAhead < n)) {\n        start = line + 1;\n        break\n      }\n    }\n    doc.highlightFrontier = Math.min(doc.highlightFrontier, start);\n  }\n\n  // Optimize some code when these features are not used.\n  var sawReadOnlySpans = false, sawCollapsedSpans = false;\n\n  function seeReadOnlySpans() {\n    sawReadOnlySpans = true;\n  }\n\n  function seeCollapsedSpans() {\n    sawCollapsedSpans = true;\n  }\n\n  // TEXTMARKER SPANS\n\n  function MarkedSpan(marker, from, to) {\n    this.marker = marker;\n    this.from = from; this.to = to;\n  }\n\n  // Search an array of spans for a span matching the given marker.\n  function getMarkedSpanFor(spans, marker) {\n    if (spans) { for (var i = 0; i < spans.length; ++i) {\n      var span = spans[i];\n      if (span.marker == marker) { return span }\n    } }\n  }\n  // Remove a span from an array, returning undefined if no spans are\n  // left (we don't store arrays for lines without spans).\n  function removeMarkedSpan(spans, span) {\n    var r;\n    for (var i = 0; i < spans.length; ++i)\n      { if (spans[i] != span) { (r || (r = [])).push(spans[i]); } }\n    return r\n  }\n  // Add a span to a line.\n  function addMarkedSpan(line, span) {\n    line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span];\n    span.marker.attachLine(line);\n  }\n\n  // Used for the algorithm that adjusts markers for a change in the\n  // document. These functions cut an array of spans at a given\n  // character position, returning an array of remaining chunks (or\n  // undefined if nothing remains).\n  function markedSpansBefore(old, startCh, isInsert) {\n    var nw;\n    if (old) { for (var i = 0; i < old.length; ++i) {\n      var span = old[i], marker = span.marker;\n      var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh);\n      if (startsBefore || span.from == startCh && marker.type == \"bookmark\" && (!isInsert || !span.marker.insertLeft)) {\n        var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh)\n        ;(nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to));\n      }\n    } }\n    return nw\n  }\n  function markedSpansAfter(old, endCh, isInsert) {\n    var nw;\n    if (old) { for (var i = 0; i < old.length; ++i) {\n      var span = old[i], marker = span.marker;\n      var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh);\n      if (endsAfter || span.from == endCh && marker.type == \"bookmark\" && (!isInsert || span.marker.insertLeft)) {\n        var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh)\n        ;(nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh,\n                                              span.to == null ? null : span.to - endCh));\n      }\n    } }\n    return nw\n  }\n\n  // Given a change object, compute the new set of marker spans that\n  // cover the line in which the change took place. Removes spans\n  // entirely within the change, reconnects spans belonging to the\n  // same marker that appear on both sides of the change, and cuts off\n  // spans partially within the change. Returns an array of span\n  // arrays with one element for each line in (after) the change.\n  function stretchSpansOverChange(doc, change) {\n    if (change.full) { return null }\n    var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans;\n    var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans;\n    if (!oldFirst && !oldLast) { return null }\n\n    var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0;\n    // Get the spans that 'stick out' on both sides\n    var first = markedSpansBefore(oldFirst, startCh, isInsert);\n    var last = markedSpansAfter(oldLast, endCh, isInsert);\n\n    // Next, merge those two ends\n    var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0);\n    if (first) {\n      // Fix up .to properties of first\n      for (var i = 0; i < first.length; ++i) {\n        var span = first[i];\n        if (span.to == null) {\n          var found = getMarkedSpanFor(last, span.marker);\n          if (!found) { span.to = startCh; }\n          else if (sameLine) { span.to = found.to == null ? null : found.to + offset; }\n        }\n      }\n    }\n    if (last) {\n      // Fix up .from in last (or move them into first in case of sameLine)\n      for (var i$1 = 0; i$1 < last.length; ++i$1) {\n        var span$1 = last[i$1];\n        if (span$1.to != null) { span$1.to += offset; }\n        if (span$1.from == null) {\n          var found$1 = getMarkedSpanFor(first, span$1.marker);\n          if (!found$1) {\n            span$1.from = offset;\n            if (sameLine) { (first || (first = [])).push(span$1); }\n          }\n        } else {\n          span$1.from += offset;\n          if (sameLine) { (first || (first = [])).push(span$1); }\n        }\n      }\n    }\n    // Make sure we didn't create any zero-length spans\n    if (first) { first = clearEmptySpans(first); }\n    if (last && last != first) { last = clearEmptySpans(last); }\n\n    var newMarkers = [first];\n    if (!sameLine) {\n      // Fill gap with whole-line-spans\n      var gap = change.text.length - 2, gapMarkers;\n      if (gap > 0 && first)\n        { for (var i$2 = 0; i$2 < first.length; ++i$2)\n          { if (first[i$2].to == null)\n            { (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i$2].marker, null, null)); } } }\n      for (var i$3 = 0; i$3 < gap; ++i$3)\n        { newMarkers.push(gapMarkers); }\n      newMarkers.push(last);\n    }\n    return newMarkers\n  }\n\n  // Remove spans that are empty and don't have a clearWhenEmpty\n  // option of false.\n  function clearEmptySpans(spans) {\n    for (var i = 0; i < spans.length; ++i) {\n      var span = spans[i];\n      if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false)\n        { spans.splice(i--, 1); }\n    }\n    if (!spans.length) { return null }\n    return spans\n  }\n\n  // Used to 'clip' out readOnly ranges when making a change.\n  function removeReadOnlyRanges(doc, from, to) {\n    var markers = null;\n    doc.iter(from.line, to.line + 1, function (line) {\n      if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) {\n        var mark = line.markedSpans[i].marker;\n        if (mark.readOnly && (!markers || indexOf(markers, mark) == -1))\n          { (markers || (markers = [])).push(mark); }\n      } }\n    });\n    if (!markers) { return null }\n    var parts = [{from: from, to: to}];\n    for (var i = 0; i < markers.length; ++i) {\n      var mk = markers[i], m = mk.find(0);\n      for (var j = 0; j < parts.length; ++j) {\n        var p = parts[j];\n        if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) { continue }\n        var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to);\n        if (dfrom < 0 || !mk.inclusiveLeft && !dfrom)\n          { newParts.push({from: p.from, to: m.from}); }\n        if (dto > 0 || !mk.inclusiveRight && !dto)\n          { newParts.push({from: m.to, to: p.to}); }\n        parts.splice.apply(parts, newParts);\n        j += newParts.length - 3;\n      }\n    }\n    return parts\n  }\n\n  // Connect or disconnect spans from a line.\n  function detachMarkedSpans(line) {\n    var spans = line.markedSpans;\n    if (!spans) { return }\n    for (var i = 0; i < spans.length; ++i)\n      { spans[i].marker.detachLine(line); }\n    line.markedSpans = null;\n  }\n  function attachMarkedSpans(line, spans) {\n    if (!spans) { return }\n    for (var i = 0; i < spans.length; ++i)\n      { spans[i].marker.attachLine(line); }\n    line.markedSpans = spans;\n  }\n\n  // Helpers used when computing which overlapping collapsed span\n  // counts as the larger one.\n  function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0 }\n  function extraRight(marker) { return marker.inclusiveRight ? 1 : 0 }\n\n  // Returns a number indicating which of two overlapping collapsed\n  // spans is larger (and thus includes the other). Falls back to\n  // comparing ids when the spans cover exactly the same range.\n  function compareCollapsedMarkers(a, b) {\n    var lenDiff = a.lines.length - b.lines.length;\n    if (lenDiff != 0) { return lenDiff }\n    var aPos = a.find(), bPos = b.find();\n    var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b);\n    if (fromCmp) { return -fromCmp }\n    var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b);\n    if (toCmp) { return toCmp }\n    return b.id - a.id\n  }\n\n  // Find out whether a line ends or starts in a collapsed span. If\n  // so, return the marker for that span.\n  function collapsedSpanAtSide(line, start) {\n    var sps = sawCollapsedSpans && line.markedSpans, found;\n    if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) {\n      sp = sps[i];\n      if (sp.marker.collapsed && (start ? sp.from : sp.to) == null &&\n          (!found || compareCollapsedMarkers(found, sp.marker) < 0))\n        { found = sp.marker; }\n    } }\n    return found\n  }\n  function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true) }\n  function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false) }\n\n  function collapsedSpanAround(line, ch) {\n    var sps = sawCollapsedSpans && line.markedSpans, found;\n    if (sps) { for (var i = 0; i < sps.length; ++i) {\n      var sp = sps[i];\n      if (sp.marker.collapsed && (sp.from == null || sp.from < ch) && (sp.to == null || sp.to > ch) &&\n          (!found || compareCollapsedMarkers(found, sp.marker) < 0)) { found = sp.marker; }\n    } }\n    return found\n  }\n\n  // Test whether there exists a collapsed span that partially\n  // overlaps (covers the start or end, but not both) of a new span.\n  // Such overlap is not allowed.\n  function conflictingCollapsedRange(doc, lineNo$$1, from, to, marker) {\n    var line = getLine(doc, lineNo$$1);\n    var sps = sawCollapsedSpans && line.markedSpans;\n    if (sps) { for (var i = 0; i < sps.length; ++i) {\n      var sp = sps[i];\n      if (!sp.marker.collapsed) { continue }\n      var found = sp.marker.find(0);\n      var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker);\n      var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker);\n      if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) { continue }\n      if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) ||\n          fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0))\n        { return true }\n    } }\n  }\n\n  // A visual line is a line as drawn on the screen. Folding, for\n  // example, can cause multiple logical lines to appear on the same\n  // visual line. This finds the start of the visual line that the\n  // given line is part of (usually that is the line itself).\n  function visualLine(line) {\n    var merged;\n    while (merged = collapsedSpanAtStart(line))\n      { line = merged.find(-1, true).line; }\n    return line\n  }\n\n  function visualLineEnd(line) {\n    var merged;\n    while (merged = collapsedSpanAtEnd(line))\n      { line = merged.find(1, true).line; }\n    return line\n  }\n\n  // Returns an array of logical lines that continue the visual line\n  // started by the argument, or undefined if there are no such lines.\n  function visualLineContinued(line) {\n    var merged, lines;\n    while (merged = collapsedSpanAtEnd(line)) {\n      line = merged.find(1, true).line\n      ;(lines || (lines = [])).push(line);\n    }\n    return lines\n  }\n\n  // Get the line number of the start of the visual line that the\n  // given line number is part of.\n  function visualLineNo(doc, lineN) {\n    var line = getLine(doc, lineN), vis = visualLine(line);\n    if (line == vis) { return lineN }\n    return lineNo(vis)\n  }\n\n  // Get the line number of the start of the next visual line after\n  // the given line.\n  function visualLineEndNo(doc, lineN) {\n    if (lineN > doc.lastLine()) { return lineN }\n    var line = getLine(doc, lineN), merged;\n    if (!lineIsHidden(doc, line)) { return lineN }\n    while (merged = collapsedSpanAtEnd(line))\n      { line = merged.find(1, true).line; }\n    return lineNo(line) + 1\n  }\n\n  // Compute whether a line is hidden. Lines count as hidden when they\n  // are part of a visual line that starts with another line, or when\n  // they are entirely covered by collapsed, non-widget span.\n  function lineIsHidden(doc, line) {\n    var sps = sawCollapsedSpans && line.markedSpans;\n    if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) {\n      sp = sps[i];\n      if (!sp.marker.collapsed) { continue }\n      if (sp.from == null) { return true }\n      if (sp.marker.widgetNode) { continue }\n      if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp))\n        { return true }\n    } }\n  }\n  function lineIsHiddenInner(doc, line, span) {\n    if (span.to == null) {\n      var end = span.marker.find(1, true);\n      return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker))\n    }\n    if (span.marker.inclusiveRight && span.to == line.text.length)\n      { return true }\n    for (var sp = (void 0), i = 0; i < line.markedSpans.length; ++i) {\n      sp = line.markedSpans[i];\n      if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to &&\n          (sp.to == null || sp.to != span.from) &&\n          (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&\n          lineIsHiddenInner(doc, line, sp)) { return true }\n    }\n  }\n\n  // Find the height above the given line.\n  function heightAtLine(lineObj) {\n    lineObj = visualLine(lineObj);\n\n    var h = 0, chunk = lineObj.parent;\n    for (var i = 0; i < chunk.lines.length; ++i) {\n      var line = chunk.lines[i];\n      if (line == lineObj) { break }\n      else { h += line.height; }\n    }\n    for (var p = chunk.parent; p; chunk = p, p = chunk.parent) {\n      for (var i$1 = 0; i$1 < p.children.length; ++i$1) {\n        var cur = p.children[i$1];\n        if (cur == chunk) { break }\n        else { h += cur.height; }\n      }\n    }\n    return h\n  }\n\n  // Compute the character length of a line, taking into account\n  // collapsed ranges (see markText) that might hide parts, and join\n  // other lines onto it.\n  function lineLength(line) {\n    if (line.height == 0) { return 0 }\n    var len = line.text.length, merged, cur = line;\n    while (merged = collapsedSpanAtStart(cur)) {\n      var found = merged.find(0, true);\n      cur = found.from.line;\n      len += found.from.ch - found.to.ch;\n    }\n    cur = line;\n    while (merged = collapsedSpanAtEnd(cur)) {\n      var found$1 = merged.find(0, true);\n      len -= cur.text.length - found$1.from.ch;\n      cur = found$1.to.line;\n      len += cur.text.length - found$1.to.ch;\n    }\n    return len\n  }\n\n  // Find the longest line in the document.\n  function findMaxLine(cm) {\n    var d = cm.display, doc = cm.doc;\n    d.maxLine = getLine(doc, doc.first);\n    d.maxLineLength = lineLength(d.maxLine);\n    d.maxLineChanged = true;\n    doc.iter(function (line) {\n      var len = lineLength(line);\n      if (len > d.maxLineLength) {\n        d.maxLineLength = len;\n        d.maxLine = line;\n      }\n    });\n  }\n\n  // LINE DATA STRUCTURE\n\n  // Line objects. These hold state related to a line, including\n  // highlighting info (the styles array).\n  var Line = function(text, markedSpans, estimateHeight) {\n    this.text = text;\n    attachMarkedSpans(this, markedSpans);\n    this.height = estimateHeight ? estimateHeight(this) : 1;\n  };\n\n  Line.prototype.lineNo = function () { return lineNo(this) };\n  eventMixin(Line);\n\n  // Change the content (text, markers) of a line. Automatically\n  // invalidates cached information and tries to re-estimate the\n  // line's height.\n  function updateLine(line, text, markedSpans, estimateHeight) {\n    line.text = text;\n    if (line.stateAfter) { line.stateAfter = null; }\n    if (line.styles) { line.styles = null; }\n    if (line.order != null) { line.order = null; }\n    detachMarkedSpans(line);\n    attachMarkedSpans(line, markedSpans);\n    var estHeight = estimateHeight ? estimateHeight(line) : 1;\n    if (estHeight != line.height) { updateLineHeight(line, estHeight); }\n  }\n\n  // Detach a line from the document tree and its markers.\n  function cleanUpLine(line) {\n    line.parent = null;\n    detachMarkedSpans(line);\n  }\n\n  // Convert a style as returned by a mode (either null, or a string\n  // containing one or more styles) to a CSS style. This is cached,\n  // and also looks for line-wide styles.\n  var styleToClassCache = {}, styleToClassCacheWithMode = {};\n  function interpretTokenStyle(style, options) {\n    if (!style || /^\\s*$/.test(style)) { return null }\n    var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache;\n    return cache[style] ||\n      (cache[style] = style.replace(/\\S+/g, \"cm-$&\"))\n  }\n\n  // Render the DOM representation of the text of a line. Also builds\n  // up a 'line map', which points at the DOM nodes that represent\n  // specific stretches of text, and is used by the measuring code.\n  // The returned object contains the DOM node, this map, and\n  // information about line-wide styles that were set by the mode.\n  function buildLineContent(cm, lineView) {\n    // The padding-right forces the element to have a 'border', which\n    // is needed on Webkit to be able to get line-level bounding\n    // rectangles for it (in measureChar).\n    var content = eltP(\"span\", null, null, webkit ? \"padding-right: .1px\" : null);\n    var builder = {pre: eltP(\"pre\", [content], \"CodeMirror-line\"), content: content,\n                   col: 0, pos: 0, cm: cm,\n                   trailingSpace: false,\n                   splitSpaces: cm.getOption(\"lineWrapping\")};\n    lineView.measure = {};\n\n    // Iterate over the logical lines that make up this visual line.\n    for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) {\n      var line = i ? lineView.rest[i - 1] : lineView.line, order = (void 0);\n      builder.pos = 0;\n      builder.addToken = buildToken;\n      // Optionally wire in some hacks into the token-rendering\n      // algorithm, to deal with browser quirks.\n      if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line, cm.doc.direction)))\n        { builder.addToken = buildTokenBadBidi(builder.addToken, order); }\n      builder.map = [];\n      var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line);\n      insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate));\n      if (line.styleClasses) {\n        if (line.styleClasses.bgClass)\n          { builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || \"\"); }\n        if (line.styleClasses.textClass)\n          { builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || \"\"); }\n      }\n\n      // Ensure at least a single node is present, for measuring.\n      if (builder.map.length == 0)\n        { builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))); }\n\n      // Store the map and a cache object for the current logical line\n      if (i == 0) {\n        lineView.measure.map = builder.map;\n        lineView.measure.cache = {};\n      } else {\n  (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map)\n        ;(lineView.measure.caches || (lineView.measure.caches = [])).push({});\n      }\n    }\n\n    // See issue #2901\n    if (webkit) {\n      var last = builder.content.lastChild;\n      if (/\\bcm-tab\\b/.test(last.className) || (last.querySelector && last.querySelector(\".cm-tab\")))\n        { builder.content.className = \"cm-tab-wrap-hack\"; }\n    }\n\n    signal(cm, \"renderLine\", cm, lineView.line, builder.pre);\n    if (builder.pre.className)\n      { builder.textClass = joinClasses(builder.pre.className, builder.textClass || \"\"); }\n\n    return builder\n  }\n\n  function defaultSpecialCharPlaceholder(ch) {\n    var token = elt(\"span\", \"\\u2022\", \"cm-invalidchar\");\n    token.title = \"\\\\u\" + ch.charCodeAt(0).toString(16);\n    token.setAttribute(\"aria-label\", token.title);\n    return token\n  }\n\n  // Build up the DOM representation for a single token, and add it to\n  // the line map. Takes care to render special characters separately.\n  function buildToken(builder, text, style, startStyle, endStyle, css, attributes) {\n    if (!text) { return }\n    var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text;\n    var special = builder.cm.state.specialChars, mustWrap = false;\n    var content;\n    if (!special.test(text)) {\n      builder.col += text.length;\n      content = document.createTextNode(displayText);\n      builder.map.push(builder.pos, builder.pos + text.length, content);\n      if (ie && ie_version < 9) { mustWrap = true; }\n      builder.pos += text.length;\n    } else {\n      content = document.createDocumentFragment();\n      var pos = 0;\n      while (true) {\n        special.lastIndex = pos;\n        var m = special.exec(text);\n        var skipped = m ? m.index - pos : text.length - pos;\n        if (skipped) {\n          var txt = document.createTextNode(displayText.slice(pos, pos + skipped));\n          if (ie && ie_version < 9) { content.appendChild(elt(\"span\", [txt])); }\n          else { content.appendChild(txt); }\n          builder.map.push(builder.pos, builder.pos + skipped, txt);\n          builder.col += skipped;\n          builder.pos += skipped;\n        }\n        if (!m) { break }\n        pos += skipped + 1;\n        var txt$1 = (void 0);\n        if (m[0] == \"\\t\") {\n          var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize;\n          txt$1 = content.appendChild(elt(\"span\", spaceStr(tabWidth), \"cm-tab\"));\n          txt$1.setAttribute(\"role\", \"presentation\");\n          txt$1.setAttribute(\"cm-text\", \"\\t\");\n          builder.col += tabWidth;\n        } else if (m[0] == \"\\r\" || m[0] == \"\\n\") {\n          txt$1 = content.appendChild(elt(\"span\", m[0] == \"\\r\" ? \"\\u240d\" : \"\\u2424\", \"cm-invalidchar\"));\n          txt$1.setAttribute(\"cm-text\", m[0]);\n          builder.col += 1;\n        } else {\n          txt$1 = builder.cm.options.specialCharPlaceholder(m[0]);\n          txt$1.setAttribute(\"cm-text\", m[0]);\n          if (ie && ie_version < 9) { content.appendChild(elt(\"span\", [txt$1])); }\n          else { content.appendChild(txt$1); }\n          builder.col += 1;\n        }\n        builder.map.push(builder.pos, builder.pos + 1, txt$1);\n        builder.pos++;\n      }\n    }\n    builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32;\n    if (style || startStyle || endStyle || mustWrap || css) {\n      var fullStyle = style || \"\";\n      if (startStyle) { fullStyle += startStyle; }\n      if (endStyle) { fullStyle += endStyle; }\n      var token = elt(\"span\", [content], fullStyle, css);\n      if (attributes) {\n        for (var attr in attributes) { if (attributes.hasOwnProperty(attr) && attr != \"style\" && attr != \"class\")\n          { token.setAttribute(attr, attributes[attr]); } }\n      }\n      return builder.content.appendChild(token)\n    }\n    builder.content.appendChild(content);\n  }\n\n  // Change some spaces to NBSP to prevent the browser from collapsing\n  // trailing spaces at the end of a line when rendering text (issue #1362).\n  function splitSpaces(text, trailingBefore) {\n    if (text.length > 1 && !/  /.test(text)) { return text }\n    var spaceBefore = trailingBefore, result = \"\";\n    for (var i = 0; i < text.length; i++) {\n      var ch = text.charAt(i);\n      if (ch == \" \" && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32))\n        { ch = \"\\u00a0\"; }\n      result += ch;\n      spaceBefore = ch == \" \";\n    }\n    return result\n  }\n\n  // Work around nonsense dimensions being reported for stretches of\n  // right-to-left text.\n  function buildTokenBadBidi(inner, order) {\n    return function (builder, text, style, startStyle, endStyle, css, attributes) {\n      style = style ? style + \" cm-force-border\" : \"cm-force-border\";\n      var start = builder.pos, end = start + text.length;\n      for (;;) {\n        // Find the part that overlaps with the start of this text\n        var part = (void 0);\n        for (var i = 0; i < order.length; i++) {\n          part = order[i];\n          if (part.to > start && part.from <= start) { break }\n        }\n        if (part.to >= end) { return inner(builder, text, style, startStyle, endStyle, css, attributes) }\n        inner(builder, text.slice(0, part.to - start), style, startStyle, null, css, attributes);\n        startStyle = null;\n        text = text.slice(part.to - start);\n        start = part.to;\n      }\n    }\n  }\n\n  function buildCollapsedSpan(builder, size, marker, ignoreWidget) {\n    var widget = !ignoreWidget && marker.widgetNode;\n    if (widget) { builder.map.push(builder.pos, builder.pos + size, widget); }\n    if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) {\n      if (!widget)\n        { widget = builder.content.appendChild(document.createElement(\"span\")); }\n      widget.setAttribute(\"cm-marker\", marker.id);\n    }\n    if (widget) {\n      builder.cm.display.input.setUneditable(widget);\n      builder.content.appendChild(widget);\n    }\n    builder.pos += size;\n    builder.trailingSpace = false;\n  }\n\n  // Outputs a number of spans to make up a line, taking highlighting\n  // and marked text into account.\n  function insertLineContent(line, builder, styles) {\n    var spans = line.markedSpans, allText = line.text, at = 0;\n    if (!spans) {\n      for (var i$1 = 1; i$1 < styles.length; i$1+=2)\n        { builder.addToken(builder, allText.slice(at, at = styles[i$1]), interpretTokenStyle(styles[i$1+1], builder.cm.options)); }\n      return\n    }\n\n    var len = allText.length, pos = 0, i = 1, text = \"\", style, css;\n    var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed, attributes;\n    for (;;) {\n      if (nextChange == pos) { // Update current marker set\n        spanStyle = spanEndStyle = spanStartStyle = css = \"\";\n        attributes = null;\n        collapsed = null; nextChange = Infinity;\n        var foundBookmarks = [], endStyles = (void 0);\n        for (var j = 0; j < spans.length; ++j) {\n          var sp = spans[j], m = sp.marker;\n          if (m.type == \"bookmark\" && sp.from == pos && m.widgetNode) {\n            foundBookmarks.push(m);\n          } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) {\n            if (sp.to != null && sp.to != pos && nextChange > sp.to) {\n              nextChange = sp.to;\n              spanEndStyle = \"\";\n            }\n            if (m.className) { spanStyle += \" \" + m.className; }\n            if (m.css) { css = (css ? css + \";\" : \"\") + m.css; }\n            if (m.startStyle && sp.from == pos) { spanStartStyle += \" \" + m.startStyle; }\n            if (m.endStyle && sp.to == nextChange) { (endStyles || (endStyles = [])).push(m.endStyle, sp.to); }\n            // support for the old title property\n            // https://github.com/codemirror/CodeMirror/pull/5673\n            if (m.title) { (attributes || (attributes = {})).title = m.title; }\n            if (m.attributes) {\n              for (var attr in m.attributes)\n                { (attributes || (attributes = {}))[attr] = m.attributes[attr]; }\n            }\n            if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0))\n              { collapsed = sp; }\n          } else if (sp.from > pos && nextChange > sp.from) {\n            nextChange = sp.from;\n          }\n        }\n        if (endStyles) { for (var j$1 = 0; j$1 < endStyles.length; j$1 += 2)\n          { if (endStyles[j$1 + 1] == nextChange) { spanEndStyle += \" \" + endStyles[j$1]; } } }\n\n        if (!collapsed || collapsed.from == pos) { for (var j$2 = 0; j$2 < foundBookmarks.length; ++j$2)\n          { buildCollapsedSpan(builder, 0, foundBookmarks[j$2]); } }\n        if (collapsed && (collapsed.from || 0) == pos) {\n          buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos,\n                             collapsed.marker, collapsed.from == null);\n          if (collapsed.to == null) { return }\n          if (collapsed.to == pos) { collapsed = false; }\n        }\n      }\n      if (pos >= len) { break }\n\n      var upto = Math.min(len, nextChange);\n      while (true) {\n        if (text) {\n          var end = pos + text.length;\n          if (!collapsed) {\n            var tokenText = end > upto ? text.slice(0, upto - pos) : text;\n            builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle,\n                             spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : \"\", css, attributes);\n          }\n          if (end >= upto) {text = text.slice(upto - pos); pos = upto; break}\n          pos = end;\n          spanStartStyle = \"\";\n        }\n        text = allText.slice(at, at = styles[i++]);\n        style = interpretTokenStyle(styles[i++], builder.cm.options);\n      }\n    }\n  }\n\n\n  // These objects are used to represent the visible (currently drawn)\n  // part of the document. A LineView may correspond to multiple\n  // logical lines, if those are connected by collapsed ranges.\n  function LineView(doc, line, lineN) {\n    // The starting line\n    this.line = line;\n    // Continuing lines, if any\n    this.rest = visualLineContinued(line);\n    // Number of logical lines in this visual line\n    this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1;\n    this.node = this.text = null;\n    this.hidden = lineIsHidden(doc, line);\n  }\n\n  // Create a range of LineView objects for the given lines.\n  function buildViewArray(cm, from, to) {\n    var array = [], nextPos;\n    for (var pos = from; pos < to; pos = nextPos) {\n      var view = new LineView(cm.doc, getLine(cm.doc, pos), pos);\n      nextPos = pos + view.size;\n      array.push(view);\n    }\n    return array\n  }\n\n  var operationGroup = null;\n\n  function pushOperation(op) {\n    if (operationGroup) {\n      operationGroup.ops.push(op);\n    } else {\n      op.ownsGroup = operationGroup = {\n        ops: [op],\n        delayedCallbacks: []\n      };\n    }\n  }\n\n  function fireCallbacksForOps(group) {\n    // Calls delayed callbacks and cursorActivity handlers until no\n    // new ones appear\n    var callbacks = group.delayedCallbacks, i = 0;\n    do {\n      for (; i < callbacks.length; i++)\n        { callbacks[i].call(null); }\n      for (var j = 0; j < group.ops.length; j++) {\n        var op = group.ops[j];\n        if (op.cursorActivityHandlers)\n          { while (op.cursorActivityCalled < op.cursorActivityHandlers.length)\n            { op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm); } }\n      }\n    } while (i < callbacks.length)\n  }\n\n  function finishOperation(op, endCb) {\n    var group = op.ownsGroup;\n    if (!group) { return }\n\n    try { fireCallbacksForOps(group); }\n    finally {\n      operationGroup = null;\n      endCb(group);\n    }\n  }\n\n  var orphanDelayedCallbacks = null;\n\n  // Often, we want to signal events at a point where we are in the\n  // middle of some work, but don't want the handler to start calling\n  // other methods on the editor, which might be in an inconsistent\n  // state or simply not expect any other events to happen.\n  // signalLater looks whether there are any handlers, and schedules\n  // them to be executed when the last operation ends, or, if no\n  // operation is active, when a timeout fires.\n  function signalLater(emitter, type /*, values...*/) {\n    var arr = getHandlers(emitter, type);\n    if (!arr.length) { return }\n    var args = Array.prototype.slice.call(arguments, 2), list;\n    if (operationGroup) {\n      list = operationGroup.delayedCallbacks;\n    } else if (orphanDelayedCallbacks) {\n      list = orphanDelayedCallbacks;\n    } else {\n      list = orphanDelayedCallbacks = [];\n      setTimeout(fireOrphanDelayed, 0);\n    }\n    var loop = function ( i ) {\n      list.push(function () { return arr[i].apply(null, args); });\n    };\n\n    for (var i = 0; i < arr.length; ++i)\n      loop( i );\n  }\n\n  function fireOrphanDelayed() {\n    var delayed = orphanDelayedCallbacks;\n    orphanDelayedCallbacks = null;\n    for (var i = 0; i < delayed.length; ++i) { delayed[i](); }\n  }\n\n  // When an aspect of a line changes, a string is added to\n  // lineView.changes. This updates the relevant part of the line's\n  // DOM structure.\n  function updateLineForChanges(cm, lineView, lineN, dims) {\n    for (var j = 0; j < lineView.changes.length; j++) {\n      var type = lineView.changes[j];\n      if (type == \"text\") { updateLineText(cm, lineView); }\n      else if (type == \"gutter\") { updateLineGutter(cm, lineView, lineN, dims); }\n      else if (type == \"class\") { updateLineClasses(cm, lineView); }\n      else if (type == \"widget\") { updateLineWidgets(cm, lineView, dims); }\n    }\n    lineView.changes = null;\n  }\n\n  // Lines with gutter elements, widgets or a background class need to\n  // be wrapped, and have the extra elements added to the wrapper div\n  function ensureLineWrapped(lineView) {\n    if (lineView.node == lineView.text) {\n      lineView.node = elt(\"div\", null, null, \"position: relative\");\n      if (lineView.text.parentNode)\n        { lineView.text.parentNode.replaceChild(lineView.node, lineView.text); }\n      lineView.node.appendChild(lineView.text);\n      if (ie && ie_version < 8) { lineView.node.style.zIndex = 2; }\n    }\n    return lineView.node\n  }\n\n  function updateLineBackground(cm, lineView) {\n    var cls = lineView.bgClass ? lineView.bgClass + \" \" + (lineView.line.bgClass || \"\") : lineView.line.bgClass;\n    if (cls) { cls += \" CodeMirror-linebackground\"; }\n    if (lineView.background) {\n      if (cls) { lineView.background.className = cls; }\n      else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; }\n    } else if (cls) {\n      var wrap = ensureLineWrapped(lineView);\n      lineView.background = wrap.insertBefore(elt(\"div\", null, cls), wrap.firstChild);\n      cm.display.input.setUneditable(lineView.background);\n    }\n  }\n\n  // Wrapper around buildLineContent which will reuse the structure\n  // in display.externalMeasured when possible.\n  function getLineContent(cm, lineView) {\n    var ext = cm.display.externalMeasured;\n    if (ext && ext.line == lineView.line) {\n      cm.display.externalMeasured = null;\n      lineView.measure = ext.measure;\n      return ext.built\n    }\n    return buildLineContent(cm, lineView)\n  }\n\n  // Redraw the line's text. Interacts with the background and text\n  // classes because the mode may output tokens that influence these\n  // classes.\n  function updateLineText(cm, lineView) {\n    var cls = lineView.text.className;\n    var built = getLineContent(cm, lineView);\n    if (lineView.text == lineView.node) { lineView.node = built.pre; }\n    lineView.text.parentNode.replaceChild(built.pre, lineView.text);\n    lineView.text = built.pre;\n    if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) {\n      lineView.bgClass = built.bgClass;\n      lineView.textClass = built.textClass;\n      updateLineClasses(cm, lineView);\n    } else if (cls) {\n      lineView.text.className = cls;\n    }\n  }\n\n  function updateLineClasses(cm, lineView) {\n    updateLineBackground(cm, lineView);\n    if (lineView.line.wrapClass)\n      { ensureLineWrapped(lineView).className = lineView.line.wrapClass; }\n    else if (lineView.node != lineView.text)\n      { lineView.node.className = \"\"; }\n    var textClass = lineView.textClass ? lineView.textClass + \" \" + (lineView.line.textClass || \"\") : lineView.line.textClass;\n    lineView.text.className = textClass || \"\";\n  }\n\n  function updateLineGutter(cm, lineView, lineN, dims) {\n    if (lineView.gutter) {\n      lineView.node.removeChild(lineView.gutter);\n      lineView.gutter = null;\n    }\n    if (lineView.gutterBackground) {\n      lineView.node.removeChild(lineView.gutterBackground);\n      lineView.gutterBackground = null;\n    }\n    if (lineView.line.gutterClass) {\n      var wrap = ensureLineWrapped(lineView);\n      lineView.gutterBackground = elt(\"div\", null, \"CodeMirror-gutter-background \" + lineView.line.gutterClass,\n                                      (\"left: \" + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + \"px; width: \" + (dims.gutterTotalWidth) + \"px\"));\n      cm.display.input.setUneditable(lineView.gutterBackground);\n      wrap.insertBefore(lineView.gutterBackground, lineView.text);\n    }\n    var markers = lineView.line.gutterMarkers;\n    if (cm.options.lineNumbers || markers) {\n      var wrap$1 = ensureLineWrapped(lineView);\n      var gutterWrap = lineView.gutter = elt(\"div\", null, \"CodeMirror-gutter-wrapper\", (\"left: \" + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + \"px\"));\n      cm.display.input.setUneditable(gutterWrap);\n      wrap$1.insertBefore(gutterWrap, lineView.text);\n      if (lineView.line.gutterClass)\n        { gutterWrap.className += \" \" + lineView.line.gutterClass; }\n      if (cm.options.lineNumbers && (!markers || !markers[\"CodeMirror-linenumbers\"]))\n        { lineView.lineNumber = gutterWrap.appendChild(\n          elt(\"div\", lineNumberFor(cm.options, lineN),\n              \"CodeMirror-linenumber CodeMirror-gutter-elt\",\n              (\"left: \" + (dims.gutterLeft[\"CodeMirror-linenumbers\"]) + \"px; width: \" + (cm.display.lineNumInnerWidth) + \"px\"))); }\n      if (markers) { for (var k = 0; k < cm.display.gutterSpecs.length; ++k) {\n        var id = cm.display.gutterSpecs[k].className, found = markers.hasOwnProperty(id) && markers[id];\n        if (found)\n          { gutterWrap.appendChild(elt(\"div\", [found], \"CodeMirror-gutter-elt\",\n                                     (\"left: \" + (dims.gutterLeft[id]) + \"px; width: \" + (dims.gutterWidth[id]) + \"px\"))); }\n      } }\n    }\n  }\n\n  function updateLineWidgets(cm, lineView, dims) {\n    if (lineView.alignable) { lineView.alignable = null; }\n    for (var node = lineView.node.firstChild, next = (void 0); node; node = next) {\n      next = node.nextSibling;\n      if (node.className == \"CodeMirror-linewidget\")\n        { lineView.node.removeChild(node); }\n    }\n    insertLineWidgets(cm, lineView, dims);\n  }\n\n  // Build a line's DOM representation from scratch\n  function buildLineElement(cm, lineView, lineN, dims) {\n    var built = getLineContent(cm, lineView);\n    lineView.text = lineView.node = built.pre;\n    if (built.bgClass) { lineView.bgClass = built.bgClass; }\n    if (built.textClass) { lineView.textClass = built.textClass; }\n\n    updateLineClasses(cm, lineView);\n    updateLineGutter(cm, lineView, lineN, dims);\n    insertLineWidgets(cm, lineView, dims);\n    return lineView.node\n  }\n\n  // A lineView may contain multiple logical lines (when merged by\n  // collapsed spans). The widgets for all of them need to be drawn.\n  function insertLineWidgets(cm, lineView, dims) {\n    insertLineWidgetsFor(cm, lineView.line, lineView, dims, true);\n    if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++)\n      { insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false); } }\n  }\n\n  function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) {\n    if (!line.widgets) { return }\n    var wrap = ensureLineWrapped(lineView);\n    for (var i = 0, ws = line.widgets; i < ws.length; ++i) {\n      var widget = ws[i], node = elt(\"div\", [widget.node], \"CodeMirror-linewidget\");\n      if (!widget.handleMouseEvents) { node.setAttribute(\"cm-ignore-events\", \"true\"); }\n      positionLineWidget(widget, node, lineView, dims);\n      cm.display.input.setUneditable(node);\n      if (allowAbove && widget.above)\n        { wrap.insertBefore(node, lineView.gutter || lineView.text); }\n      else\n        { wrap.appendChild(node); }\n      signalLater(widget, \"redraw\");\n    }\n  }\n\n  function positionLineWidget(widget, node, lineView, dims) {\n    if (widget.noHScroll) {\n  (lineView.alignable || (lineView.alignable = [])).push(node);\n      var width = dims.wrapperWidth;\n      node.style.left = dims.fixedPos + \"px\";\n      if (!widget.coverGutter) {\n        width -= dims.gutterTotalWidth;\n        node.style.paddingLeft = dims.gutterTotalWidth + \"px\";\n      }\n      node.style.width = width + \"px\";\n    }\n    if (widget.coverGutter) {\n      node.style.zIndex = 5;\n      node.style.position = \"relative\";\n      if (!widget.noHScroll) { node.style.marginLeft = -dims.gutterTotalWidth + \"px\"; }\n    }\n  }\n\n  function widgetHeight(widget) {\n    if (widget.height != null) { return widget.height }\n    var cm = widget.doc.cm;\n    if (!cm) { return 0 }\n    if (!contains(document.body, widget.node)) {\n      var parentStyle = \"position: relative;\";\n      if (widget.coverGutter)\n        { parentStyle += \"margin-left: -\" + cm.display.gutters.offsetWidth + \"px;\"; }\n      if (widget.noHScroll)\n        { parentStyle += \"width: \" + cm.display.wrapper.clientWidth + \"px;\"; }\n      removeChildrenAndAdd(cm.display.measure, elt(\"div\", [widget.node], null, parentStyle));\n    }\n    return widget.height = widget.node.parentNode.offsetHeight\n  }\n\n  // Return true when the given mouse event happened in a widget\n  function eventInWidget(display, e) {\n    for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {\n      if (!n || (n.nodeType == 1 && n.getAttribute(\"cm-ignore-events\") == \"true\") ||\n          (n.parentNode == display.sizer && n != display.mover))\n        { return true }\n    }\n  }\n\n  // POSITION MEASUREMENT\n\n  function paddingTop(display) {return display.lineSpace.offsetTop}\n  function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight}\n  function paddingH(display) {\n    if (display.cachedPaddingH) { return display.cachedPaddingH }\n    var e = removeChildrenAndAdd(display.measure, elt(\"pre\", \"x\", \"CodeMirror-line-like\"));\n    var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle;\n    var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)};\n    if (!isNaN(data.left) && !isNaN(data.right)) { display.cachedPaddingH = data; }\n    return data\n  }\n\n  function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth }\n  function displayWidth(cm) {\n    return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth\n  }\n  function displayHeight(cm) {\n    return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight\n  }\n\n  // Ensure the lineView.wrapping.heights array is populated. This is\n  // an array of bottom offsets for the lines that make up a drawn\n  // line. When lineWrapping is on, there might be more than one\n  // height.\n  function ensureLineHeights(cm, lineView, rect) {\n    var wrapping = cm.options.lineWrapping;\n    var curWidth = wrapping && displayWidth(cm);\n    if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) {\n      var heights = lineView.measure.heights = [];\n      if (wrapping) {\n        lineView.measure.width = curWidth;\n        var rects = lineView.text.firstChild.getClientRects();\n        for (var i = 0; i < rects.length - 1; i++) {\n          var cur = rects[i], next = rects[i + 1];\n          if (Math.abs(cur.bottom - next.bottom) > 2)\n            { heights.push((cur.bottom + next.top) / 2 - rect.top); }\n        }\n      }\n      heights.push(rect.bottom - rect.top);\n    }\n  }\n\n  // Find a line map (mapping character offsets to text nodes) and a\n  // measurement cache for the given line number. (A line view might\n  // contain multiple lines when collapsed ranges are present.)\n  function mapFromLineView(lineView, line, lineN) {\n    if (lineView.line == line)\n      { return {map: lineView.measure.map, cache: lineView.measure.cache} }\n    for (var i = 0; i < lineView.rest.length; i++)\n      { if (lineView.rest[i] == line)\n        { return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]} } }\n    for (var i$1 = 0; i$1 < lineView.rest.length; i$1++)\n      { if (lineNo(lineView.rest[i$1]) > lineN)\n        { return {map: lineView.measure.maps[i$1], cache: lineView.measure.caches[i$1], before: true} } }\n  }\n\n  // Render a line into the hidden node display.externalMeasured. Used\n  // when measurement is needed for a line that's not in the viewport.\n  function updateExternalMeasurement(cm, line) {\n    line = visualLine(line);\n    var lineN = lineNo(line);\n    var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN);\n    view.lineN = lineN;\n    var built = view.built = buildLineContent(cm, view);\n    view.text = built.pre;\n    removeChildrenAndAdd(cm.display.lineMeasure, built.pre);\n    return view\n  }\n\n  // Get a {top, bottom, left, right} box (in line-local coordinates)\n  // for a given character.\n  function measureChar(cm, line, ch, bias) {\n    return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias)\n  }\n\n  // Find a line view that corresponds to the given line number.\n  function findViewForLine(cm, lineN) {\n    if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo)\n      { return cm.display.view[findViewIndex(cm, lineN)] }\n    var ext = cm.display.externalMeasured;\n    if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size)\n      { return ext }\n  }\n\n  // Measurement can be split in two steps, the set-up work that\n  // applies to the whole line, and the measurement of the actual\n  // character. Functions like coordsChar, that need to do a lot of\n  // measurements in a row, can thus ensure that the set-up work is\n  // only done once.\n  function prepareMeasureForLine(cm, line) {\n    var lineN = lineNo(line);\n    var view = findViewForLine(cm, lineN);\n    if (view && !view.text) {\n      view = null;\n    } else if (view && view.changes) {\n      updateLineForChanges(cm, view, lineN, getDimensions(cm));\n      cm.curOp.forceUpdate = true;\n    }\n    if (!view)\n      { view = updateExternalMeasurement(cm, line); }\n\n    var info = mapFromLineView(view, line, lineN);\n    return {\n      line: line, view: view, rect: null,\n      map: info.map, cache: info.cache, before: info.before,\n      hasHeights: false\n    }\n  }\n\n  // Given a prepared measurement object, measures the position of an\n  // actual character (or fetches it from the cache).\n  function measureCharPrepared(cm, prepared, ch, bias, varHeight) {\n    if (prepared.before) { ch = -1; }\n    var key = ch + (bias || \"\"), found;\n    if (prepared.cache.hasOwnProperty(key)) {\n      found = prepared.cache[key];\n    } else {\n      if (!prepared.rect)\n        { prepared.rect = prepared.view.text.getBoundingClientRect(); }\n      if (!prepared.hasHeights) {\n        ensureLineHeights(cm, prepared.view, prepared.rect);\n        prepared.hasHeights = true;\n      }\n      found = measureCharInner(cm, prepared, ch, bias);\n      if (!found.bogus) { prepared.cache[key] = found; }\n    }\n    return {left: found.left, right: found.right,\n            top: varHeight ? found.rtop : found.top,\n            bottom: varHeight ? found.rbottom : found.bottom}\n  }\n\n  var nullRect = {left: 0, right: 0, top: 0, bottom: 0};\n\n  function nodeAndOffsetInLineMap(map$$1, ch, bias) {\n    var node, start, end, collapse, mStart, mEnd;\n    // First, search the line map for the text node corresponding to,\n    // or closest to, the target character.\n    for (var i = 0; i < map$$1.length; i += 3) {\n      mStart = map$$1[i];\n      mEnd = map$$1[i + 1];\n      if (ch < mStart) {\n        start = 0; end = 1;\n        collapse = \"left\";\n      } else if (ch < mEnd) {\n        start = ch - mStart;\n        end = start + 1;\n      } else if (i == map$$1.length - 3 || ch == mEnd && map$$1[i + 3] > ch) {\n        end = mEnd - mStart;\n        start = end - 1;\n        if (ch >= mEnd) { collapse = \"right\"; }\n      }\n      if (start != null) {\n        node = map$$1[i + 2];\n        if (mStart == mEnd && bias == (node.insertLeft ? \"left\" : \"right\"))\n          { collapse = bias; }\n        if (bias == \"left\" && start == 0)\n          { while (i && map$$1[i - 2] == map$$1[i - 3] && map$$1[i - 1].insertLeft) {\n            node = map$$1[(i -= 3) + 2];\n            collapse = \"left\";\n          } }\n        if (bias == \"right\" && start == mEnd - mStart)\n          { while (i < map$$1.length - 3 && map$$1[i + 3] == map$$1[i + 4] && !map$$1[i + 5].insertLeft) {\n            node = map$$1[(i += 3) + 2];\n            collapse = \"right\";\n          } }\n        break\n      }\n    }\n    return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd}\n  }\n\n  function getUsefulRect(rects, bias) {\n    var rect = nullRect;\n    if (bias == \"left\") { for (var i = 0; i < rects.length; i++) {\n      if ((rect = rects[i]).left != rect.right) { break }\n    } } else { for (var i$1 = rects.length - 1; i$1 >= 0; i$1--) {\n      if ((rect = rects[i$1]).left != rect.right) { break }\n    } }\n    return rect\n  }\n\n  function measureCharInner(cm, prepared, ch, bias) {\n    var place = nodeAndOffsetInLineMap(prepared.map, ch, bias);\n    var node = place.node, start = place.start, end = place.end, collapse = place.collapse;\n\n    var rect;\n    if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates.\n      for (var i$1 = 0; i$1 < 4; i$1++) { // Retry a maximum of 4 times when nonsense rectangles are returned\n        while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) { --start; }\n        while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) { ++end; }\n        if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart)\n          { rect = node.parentNode.getBoundingClientRect(); }\n        else\n          { rect = getUsefulRect(range(node, start, end).getClientRects(), bias); }\n        if (rect.left || rect.right || start == 0) { break }\n        end = start;\n        start = start - 1;\n        collapse = \"right\";\n      }\n      if (ie && ie_version < 11) { rect = maybeUpdateRectForZooming(cm.display.measure, rect); }\n    } else { // If it is a widget, simply get the box for the whole widget.\n      if (start > 0) { collapse = bias = \"right\"; }\n      var rects;\n      if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1)\n        { rect = rects[bias == \"right\" ? rects.length - 1 : 0]; }\n      else\n        { rect = node.getBoundingClientRect(); }\n    }\n    if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) {\n      var rSpan = node.parentNode.getClientRects()[0];\n      if (rSpan)\n        { rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom}; }\n      else\n        { rect = nullRect; }\n    }\n\n    var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top;\n    var mid = (rtop + rbot) / 2;\n    var heights = prepared.view.measure.heights;\n    var i = 0;\n    for (; i < heights.length - 1; i++)\n      { if (mid < heights[i]) { break } }\n    var top = i ? heights[i - 1] : 0, bot = heights[i];\n    var result = {left: (collapse == \"right\" ? rect.right : rect.left) - prepared.rect.left,\n                  right: (collapse == \"left\" ? rect.left : rect.right) - prepared.rect.left,\n                  top: top, bottom: bot};\n    if (!rect.left && !rect.right) { result.bogus = true; }\n    if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; }\n\n    return result\n  }\n\n  // Work around problem with bounding client rects on ranges being\n  // returned incorrectly when zoomed on IE10 and below.\n  function maybeUpdateRectForZooming(measure, rect) {\n    if (!window.screen || screen.logicalXDPI == null ||\n        screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure))\n      { return rect }\n    var scaleX = screen.logicalXDPI / screen.deviceXDPI;\n    var scaleY = screen.logicalYDPI / screen.deviceYDPI;\n    return {left: rect.left * scaleX, right: rect.right * scaleX,\n            top: rect.top * scaleY, bottom: rect.bottom * scaleY}\n  }\n\n  function clearLineMeasurementCacheFor(lineView) {\n    if (lineView.measure) {\n      lineView.measure.cache = {};\n      lineView.measure.heights = null;\n      if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++)\n        { lineView.measure.caches[i] = {}; } }\n    }\n  }\n\n  function clearLineMeasurementCache(cm) {\n    cm.display.externalMeasure = null;\n    removeChildren(cm.display.lineMeasure);\n    for (var i = 0; i < cm.display.view.length; i++)\n      { clearLineMeasurementCacheFor(cm.display.view[i]); }\n  }\n\n  function clearCaches(cm) {\n    clearLineMeasurementCache(cm);\n    cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null;\n    if (!cm.options.lineWrapping) { cm.display.maxLineChanged = true; }\n    cm.display.lineNumChars = null;\n  }\n\n  function pageScrollX() {\n    // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=489206\n    // which causes page_Offset and bounding client rects to use\n    // different reference viewports and invalidate our calculations.\n    if (chrome && android) { return -(document.body.getBoundingClientRect().left - parseInt(getComputedStyle(document.body).marginLeft)) }\n    return window.pageXOffset || (document.documentElement || document.body).scrollLeft\n  }\n  function pageScrollY() {\n    if (chrome && android) { return -(document.body.getBoundingClientRect().top - parseInt(getComputedStyle(document.body).marginTop)) }\n    return window.pageYOffset || (document.documentElement || document.body).scrollTop\n  }\n\n  function widgetTopHeight(lineObj) {\n    var height = 0;\n    if (lineObj.widgets) { for (var i = 0; i < lineObj.widgets.length; ++i) { if (lineObj.widgets[i].above)\n      { height += widgetHeight(lineObj.widgets[i]); } } }\n    return height\n  }\n\n  // Converts a {top, bottom, left, right} box from line-local\n  // coordinates into another coordinate system. Context may be one of\n  // \"line\", \"div\" (display.lineDiv), \"local\"./null (editor), \"window\",\n  // or \"page\".\n  function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) {\n    if (!includeWidgets) {\n      var height = widgetTopHeight(lineObj);\n      rect.top += height; rect.bottom += height;\n    }\n    if (context == \"line\") { return rect }\n    if (!context) { context = \"local\"; }\n    var yOff = heightAtLine(lineObj);\n    if (context == \"local\") { yOff += paddingTop(cm.display); }\n    else { yOff -= cm.display.viewOffset; }\n    if (context == \"page\" || context == \"window\") {\n      var lOff = cm.display.lineSpace.getBoundingClientRect();\n      yOff += lOff.top + (context == \"window\" ? 0 : pageScrollY());\n      var xOff = lOff.left + (context == \"window\" ? 0 : pageScrollX());\n      rect.left += xOff; rect.right += xOff;\n    }\n    rect.top += yOff; rect.bottom += yOff;\n    return rect\n  }\n\n  // Coverts a box from \"div\" coords to another coordinate system.\n  // Context may be \"window\", \"page\", \"div\", or \"local\"./null.\n  function fromCoordSystem(cm, coords, context) {\n    if (context == \"div\") { return coords }\n    var left = coords.left, top = coords.top;\n    // First move into \"page\" coordinate system\n    if (context == \"page\") {\n      left -= pageScrollX();\n      top -= pageScrollY();\n    } else if (context == \"local\" || !context) {\n      var localBox = cm.display.sizer.getBoundingClientRect();\n      left += localBox.left;\n      top += localBox.top;\n    }\n\n    var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect();\n    return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}\n  }\n\n  function charCoords(cm, pos, context, lineObj, bias) {\n    if (!lineObj) { lineObj = getLine(cm.doc, pos.line); }\n    return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context)\n  }\n\n  // Returns a box for a given cursor position, which may have an\n  // 'other' property containing the position of the secondary cursor\n  // on a bidi boundary.\n  // A cursor Pos(line, char, \"before\") is on the same visual line as `char - 1`\n  // and after `char - 1` in writing order of `char - 1`\n  // A cursor Pos(line, char, \"after\") is on the same visual line as `char`\n  // and before `char` in writing order of `char`\n  // Examples (upper-case letters are RTL, lower-case are LTR):\n  //     Pos(0, 1, ...)\n  //     before   after\n  // ab     a|b     a|b\n  // aB     a|B     aB|\n  // Ab     |Ab     A|b\n  // AB     B|A     B|A\n  // Every position after the last character on a line is considered to stick\n  // to the last character on the line.\n  function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) {\n    lineObj = lineObj || getLine(cm.doc, pos.line);\n    if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj); }\n    function get(ch, right) {\n      var m = measureCharPrepared(cm, preparedMeasure, ch, right ? \"right\" : \"left\", varHeight);\n      if (right) { m.left = m.right; } else { m.right = m.left; }\n      return intoCoordSystem(cm, lineObj, m, context)\n    }\n    var order = getOrder(lineObj, cm.doc.direction), ch = pos.ch, sticky = pos.sticky;\n    if (ch >= lineObj.text.length) {\n      ch = lineObj.text.length;\n      sticky = \"before\";\n    } else if (ch <= 0) {\n      ch = 0;\n      sticky = \"after\";\n    }\n    if (!order) { return get(sticky == \"before\" ? ch - 1 : ch, sticky == \"before\") }\n\n    function getBidi(ch, partPos, invert) {\n      var part = order[partPos], right = part.level == 1;\n      return get(invert ? ch - 1 : ch, right != invert)\n    }\n    var partPos = getBidiPartAt(order, ch, sticky);\n    var other = bidiOther;\n    var val = getBidi(ch, partPos, sticky == \"before\");\n    if (other != null) { val.other = getBidi(ch, other, sticky != \"before\"); }\n    return val\n  }\n\n  // Used to cheaply estimate the coordinates for a position. Used for\n  // intermediate scroll updates.\n  function estimateCoords(cm, pos) {\n    var left = 0;\n    pos = clipPos(cm.doc, pos);\n    if (!cm.options.lineWrapping) { left = charWidth(cm.display) * pos.ch; }\n    var lineObj = getLine(cm.doc, pos.line);\n    var top = heightAtLine(lineObj) + paddingTop(cm.display);\n    return {left: left, right: left, top: top, bottom: top + lineObj.height}\n  }\n\n  // Positions returned by coordsChar contain some extra information.\n  // xRel is the relative x position of the input coordinates compared\n  // to the found position (so xRel > 0 means the coordinates are to\n  // the right of the character position, for example). When outside\n  // is true, that means the coordinates lie outside the line's\n  // vertical range.\n  function PosWithInfo(line, ch, sticky, outside, xRel) {\n    var pos = Pos(line, ch, sticky);\n    pos.xRel = xRel;\n    if (outside) { pos.outside = outside; }\n    return pos\n  }\n\n  // Compute the character position closest to the given coordinates.\n  // Input must be lineSpace-local (\"div\" coordinate system).\n  function coordsChar(cm, x, y) {\n    var doc = cm.doc;\n    y += cm.display.viewOffset;\n    if (y < 0) { return PosWithInfo(doc.first, 0, null, -1, -1) }\n    var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1;\n    if (lineN > last)\n      { return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, 1, 1) }\n    if (x < 0) { x = 0; }\n\n    var lineObj = getLine(doc, lineN);\n    for (;;) {\n      var found = coordsCharInner(cm, lineObj, lineN, x, y);\n      var collapsed = collapsedSpanAround(lineObj, found.ch + (found.xRel > 0 || found.outside > 0 ? 1 : 0));\n      if (!collapsed) { return found }\n      var rangeEnd = collapsed.find(1);\n      if (rangeEnd.line == lineN) { return rangeEnd }\n      lineObj = getLine(doc, lineN = rangeEnd.line);\n    }\n  }\n\n  function wrappedLineExtent(cm, lineObj, preparedMeasure, y) {\n    y -= widgetTopHeight(lineObj);\n    var end = lineObj.text.length;\n    var begin = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch - 1).bottom <= y; }, end, 0);\n    end = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch).top > y; }, begin, end);\n    return {begin: begin, end: end}\n  }\n\n  function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) {\n    if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj); }\n    var targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), \"line\").top;\n    return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop)\n  }\n\n  // Returns true if the given side of a box is after the given\n  // coordinates, in top-to-bottom, left-to-right order.\n  function boxIsAfter(box, x, y, left) {\n    return box.bottom <= y ? false : box.top > y ? true : (left ? box.left : box.right) > x\n  }\n\n  function coordsCharInner(cm, lineObj, lineNo$$1, x, y) {\n    // Move y into line-local coordinate space\n    y -= heightAtLine(lineObj);\n    var preparedMeasure = prepareMeasureForLine(cm, lineObj);\n    // When directly calling `measureCharPrepared`, we have to adjust\n    // for the widgets at this line.\n    var widgetHeight$$1 = widgetTopHeight(lineObj);\n    var begin = 0, end = lineObj.text.length, ltr = true;\n\n    var order = getOrder(lineObj, cm.doc.direction);\n    // If the line isn't plain left-to-right text, first figure out\n    // which bidi section the coordinates fall into.\n    if (order) {\n      var part = (cm.options.lineWrapping ? coordsBidiPartWrapped : coordsBidiPart)\n                   (cm, lineObj, lineNo$$1, preparedMeasure, order, x, y);\n      ltr = part.level != 1;\n      // The awkward -1 offsets are needed because findFirst (called\n      // on these below) will treat its first bound as inclusive,\n      // second as exclusive, but we want to actually address the\n      // characters in the part's range\n      begin = ltr ? part.from : part.to - 1;\n      end = ltr ? part.to : part.from - 1;\n    }\n\n    // A binary search to find the first character whose bounding box\n    // starts after the coordinates. If we run across any whose box wrap\n    // the coordinates, store that.\n    var chAround = null, boxAround = null;\n    var ch = findFirst(function (ch) {\n      var box = measureCharPrepared(cm, preparedMeasure, ch);\n      box.top += widgetHeight$$1; box.bottom += widgetHeight$$1;\n      if (!boxIsAfter(box, x, y, false)) { return false }\n      if (box.top <= y && box.left <= x) {\n        chAround = ch;\n        boxAround = box;\n      }\n      return true\n    }, begin, end);\n\n    var baseX, sticky, outside = false;\n    // If a box around the coordinates was found, use that\n    if (boxAround) {\n      // Distinguish coordinates nearer to the left or right side of the box\n      var atLeft = x - boxAround.left < boxAround.right - x, atStart = atLeft == ltr;\n      ch = chAround + (atStart ? 0 : 1);\n      sticky = atStart ? \"after\" : \"before\";\n      baseX = atLeft ? boxAround.left : boxAround.right;\n    } else {\n      // (Adjust for extended bound, if necessary.)\n      if (!ltr && (ch == end || ch == begin)) { ch++; }\n      // To determine which side to associate with, get the box to the\n      // left of the character and compare it's vertical position to the\n      // coordinates\n      sticky = ch == 0 ? \"after\" : ch == lineObj.text.length ? \"before\" :\n        (measureCharPrepared(cm, preparedMeasure, ch - (ltr ? 1 : 0)).bottom + widgetHeight$$1 <= y) == ltr ?\n        \"after\" : \"before\";\n      // Now get accurate coordinates for this place, in order to get a\n      // base X position\n      var coords = cursorCoords(cm, Pos(lineNo$$1, ch, sticky), \"line\", lineObj, preparedMeasure);\n      baseX = coords.left;\n      outside = y < coords.top ? -1 : y >= coords.bottom ? 1 : 0;\n    }\n\n    ch = skipExtendingChars(lineObj.text, ch, 1);\n    return PosWithInfo(lineNo$$1, ch, sticky, outside, x - baseX)\n  }\n\n  function coordsBidiPart(cm, lineObj, lineNo$$1, preparedMeasure, order, x, y) {\n    // Bidi parts are sorted left-to-right, and in a non-line-wrapping\n    // situation, we can take this ordering to correspond to the visual\n    // ordering. This finds the first part whose end is after the given\n    // coordinates.\n    var index = findFirst(function (i) {\n      var part = order[i], ltr = part.level != 1;\n      return boxIsAfter(cursorCoords(cm, Pos(lineNo$$1, ltr ? part.to : part.from, ltr ? \"before\" : \"after\"),\n                                     \"line\", lineObj, preparedMeasure), x, y, true)\n    }, 0, order.length - 1);\n    var part = order[index];\n    // If this isn't the first part, the part's start is also after\n    // the coordinates, and the coordinates aren't on the same line as\n    // that start, move one part back.\n    if (index > 0) {\n      var ltr = part.level != 1;\n      var start = cursorCoords(cm, Pos(lineNo$$1, ltr ? part.from : part.to, ltr ? \"after\" : \"before\"),\n                               \"line\", lineObj, preparedMeasure);\n      if (boxIsAfter(start, x, y, true) && start.top > y)\n        { part = order[index - 1]; }\n    }\n    return part\n  }\n\n  function coordsBidiPartWrapped(cm, lineObj, _lineNo, preparedMeasure, order, x, y) {\n    // In a wrapped line, rtl text on wrapping boundaries can do things\n    // that don't correspond to the ordering in our `order` array at\n    // all, so a binary search doesn't work, and we want to return a\n    // part that only spans one line so that the binary search in\n    // coordsCharInner is safe. As such, we first find the extent of the\n    // wrapped line, and then do a flat search in which we discard any\n    // spans that aren't on the line.\n    var ref = wrappedLineExtent(cm, lineObj, preparedMeasure, y);\n    var begin = ref.begin;\n    var end = ref.end;\n    if (/\\s/.test(lineObj.text.charAt(end - 1))) { end--; }\n    var part = null, closestDist = null;\n    for (var i = 0; i < order.length; i++) {\n      var p = order[i];\n      if (p.from >= end || p.to <= begin) { continue }\n      var ltr = p.level != 1;\n      var endX = measureCharPrepared(cm, preparedMeasure, ltr ? Math.min(end, p.to) - 1 : Math.max(begin, p.from)).right;\n      // Weigh against spans ending before this, so that they are only\n      // picked if nothing ends after\n      var dist = endX < x ? x - endX + 1e9 : endX - x;\n      if (!part || closestDist > dist) {\n        part = p;\n        closestDist = dist;\n      }\n    }\n    if (!part) { part = order[order.length - 1]; }\n    // Clip the part to the wrapped line.\n    if (part.from < begin) { part = {from: begin, to: part.to, level: part.level}; }\n    if (part.to > end) { part = {from: part.from, to: end, level: part.level}; }\n    return part\n  }\n\n  var measureText;\n  // Compute the default text height.\n  function textHeight(display) {\n    if (display.cachedTextHeight != null) { return display.cachedTextHeight }\n    if (measureText == null) {\n      measureText = elt(\"pre\", null, \"CodeMirror-line-like\");\n      // Measure a bunch of lines, for browsers that compute\n      // fractional heights.\n      for (var i = 0; i < 49; ++i) {\n        measureText.appendChild(document.createTextNode(\"x\"));\n        measureText.appendChild(elt(\"br\"));\n      }\n      measureText.appendChild(document.createTextNode(\"x\"));\n    }\n    removeChildrenAndAdd(display.measure, measureText);\n    var height = measureText.offsetHeight / 50;\n    if (height > 3) { display.cachedTextHeight = height; }\n    removeChildren(display.measure);\n    return height || 1\n  }\n\n  // Compute the default character width.\n  function charWidth(display) {\n    if (display.cachedCharWidth != null) { return display.cachedCharWidth }\n    var anchor = elt(\"span\", \"xxxxxxxxxx\");\n    var pre = elt(\"pre\", [anchor], \"CodeMirror-line-like\");\n    removeChildrenAndAdd(display.measure, pre);\n    var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10;\n    if (width > 2) { display.cachedCharWidth = width; }\n    return width || 10\n  }\n\n  // Do a bulk-read of the DOM positions and sizes needed to draw the\n  // view, so that we don't interleave reading and writing to the DOM.\n  function getDimensions(cm) {\n    var d = cm.display, left = {}, width = {};\n    var gutterLeft = d.gutters.clientLeft;\n    for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {\n      var id = cm.display.gutterSpecs[i].className;\n      left[id] = n.offsetLeft + n.clientLeft + gutterLeft;\n      width[id] = n.clientWidth;\n    }\n    return {fixedPos: compensateForHScroll(d),\n            gutterTotalWidth: d.gutters.offsetWidth,\n            gutterLeft: left,\n            gutterWidth: width,\n            wrapperWidth: d.wrapper.clientWidth}\n  }\n\n  // Computes display.scroller.scrollLeft + display.gutters.offsetWidth,\n  // but using getBoundingClientRect to get a sub-pixel-accurate\n  // result.\n  function compensateForHScroll(display) {\n    return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left\n  }\n\n  // Returns a function that estimates the height of a line, to use as\n  // first approximation until the line becomes visible (and is thus\n  // properly measurable).\n  function estimateHeight(cm) {\n    var th = textHeight(cm.display), wrapping = cm.options.lineWrapping;\n    var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3);\n    return function (line) {\n      if (lineIsHidden(cm.doc, line)) { return 0 }\n\n      var widgetsHeight = 0;\n      if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) {\n        if (line.widgets[i].height) { widgetsHeight += line.widgets[i].height; }\n      } }\n\n      if (wrapping)\n        { return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th }\n      else\n        { return widgetsHeight + th }\n    }\n  }\n\n  function estimateLineHeights(cm) {\n    var doc = cm.doc, est = estimateHeight(cm);\n    doc.iter(function (line) {\n      var estHeight = est(line);\n      if (estHeight != line.height) { updateLineHeight(line, estHeight); }\n    });\n  }\n\n  // Given a mouse event, find the corresponding position. If liberal\n  // is false, it checks whether a gutter or scrollbar was clicked,\n  // and returns null if it was. forRect is used by rectangular\n  // selections, and tries to estimate a character position even for\n  // coordinates beyond the right of the text.\n  function posFromMouse(cm, e, liberal, forRect) {\n    var display = cm.display;\n    if (!liberal && e_target(e).getAttribute(\"cm-not-content\") == \"true\") { return null }\n\n    var x, y, space = display.lineSpace.getBoundingClientRect();\n    // Fails unpredictably on IE[67] when mouse is dragged around quickly.\n    try { x = e.clientX - space.left; y = e.clientY - space.top; }\n    catch (e) { return null }\n    var coords = coordsChar(cm, x, y), line;\n    if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) {\n      var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length;\n      coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff));\n    }\n    return coords\n  }\n\n  // Find the view element corresponding to a given line. Return null\n  // when the line isn't visible.\n  function findViewIndex(cm, n) {\n    if (n >= cm.display.viewTo) { return null }\n    n -= cm.display.viewFrom;\n    if (n < 0) { return null }\n    var view = cm.display.view;\n    for (var i = 0; i < view.length; i++) {\n      n -= view[i].size;\n      if (n < 0) { return i }\n    }\n  }\n\n  // Updates the display.view data structure for a given change to the\n  // document. From and to are in pre-change coordinates. Lendiff is\n  // the amount of lines added or subtracted by the change. This is\n  // used for changes that span multiple lines, or change the way\n  // lines are divided into visual lines. regLineChange (below)\n  // registers single-line changes.\n  function regChange(cm, from, to, lendiff) {\n    if (from == null) { from = cm.doc.first; }\n    if (to == null) { to = cm.doc.first + cm.doc.size; }\n    if (!lendiff) { lendiff = 0; }\n\n    var display = cm.display;\n    if (lendiff && to < display.viewTo &&\n        (display.updateLineNumbers == null || display.updateLineNumbers > from))\n      { display.updateLineNumbers = from; }\n\n    cm.curOp.viewChanged = true;\n\n    if (from >= display.viewTo) { // Change after\n      if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo)\n        { resetView(cm); }\n    } else if (to <= display.viewFrom) { // Change before\n      if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) {\n        resetView(cm);\n      } else {\n        display.viewFrom += lendiff;\n        display.viewTo += lendiff;\n      }\n    } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap\n      resetView(cm);\n    } else if (from <= display.viewFrom) { // Top overlap\n      var cut = viewCuttingPoint(cm, to, to + lendiff, 1);\n      if (cut) {\n        display.view = display.view.slice(cut.index);\n        display.viewFrom = cut.lineN;\n        display.viewTo += lendiff;\n      } else {\n        resetView(cm);\n      }\n    } else if (to >= display.viewTo) { // Bottom overlap\n      var cut$1 = viewCuttingPoint(cm, from, from, -1);\n      if (cut$1) {\n        display.view = display.view.slice(0, cut$1.index);\n        display.viewTo = cut$1.lineN;\n      } else {\n        resetView(cm);\n      }\n    } else { // Gap in the middle\n      var cutTop = viewCuttingPoint(cm, from, from, -1);\n      var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1);\n      if (cutTop && cutBot) {\n        display.view = display.view.slice(0, cutTop.index)\n          .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN))\n          .concat(display.view.slice(cutBot.index));\n        display.viewTo += lendiff;\n      } else {\n        resetView(cm);\n      }\n    }\n\n    var ext = display.externalMeasured;\n    if (ext) {\n      if (to < ext.lineN)\n        { ext.lineN += lendiff; }\n      else if (from < ext.lineN + ext.size)\n        { display.externalMeasured = null; }\n    }\n  }\n\n  // Register a change to a single line. Type must be one of \"text\",\n  // \"gutter\", \"class\", \"widget\"\n  function regLineChange(cm, line, type) {\n    cm.curOp.viewChanged = true;\n    var display = cm.display, ext = cm.display.externalMeasured;\n    if (ext && line >= ext.lineN && line < ext.lineN + ext.size)\n      { display.externalMeasured = null; }\n\n    if (line < display.viewFrom || line >= display.viewTo) { return }\n    var lineView = display.view[findViewIndex(cm, line)];\n    if (lineView.node == null) { return }\n    var arr = lineView.changes || (lineView.changes = []);\n    if (indexOf(arr, type) == -1) { arr.push(type); }\n  }\n\n  // Clear the view.\n  function resetView(cm) {\n    cm.display.viewFrom = cm.display.viewTo = cm.doc.first;\n    cm.display.view = [];\n    cm.display.viewOffset = 0;\n  }\n\n  function viewCuttingPoint(cm, oldN, newN, dir) {\n    var index = findViewIndex(cm, oldN), diff, view = cm.display.view;\n    if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size)\n      { return {index: index, lineN: newN} }\n    var n = cm.display.viewFrom;\n    for (var i = 0; i < index; i++)\n      { n += view[i].size; }\n    if (n != oldN) {\n      if (dir > 0) {\n        if (index == view.length - 1) { return null }\n        diff = (n + view[index].size) - oldN;\n        index++;\n      } else {\n        diff = n - oldN;\n      }\n      oldN += diff; newN += diff;\n    }\n    while (visualLineNo(cm.doc, newN) != newN) {\n      if (index == (dir < 0 ? 0 : view.length - 1)) { return null }\n      newN += dir * view[index - (dir < 0 ? 1 : 0)].size;\n      index += dir;\n    }\n    return {index: index, lineN: newN}\n  }\n\n  // Force the view to cover a given range, adding empty view element\n  // or clipping off existing ones as needed.\n  function adjustView(cm, from, to) {\n    var display = cm.display, view = display.view;\n    if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) {\n      display.view = buildViewArray(cm, from, to);\n      display.viewFrom = from;\n    } else {\n      if (display.viewFrom > from)\n        { display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view); }\n      else if (display.viewFrom < from)\n        { display.view = display.view.slice(findViewIndex(cm, from)); }\n      display.viewFrom = from;\n      if (display.viewTo < to)\n        { display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)); }\n      else if (display.viewTo > to)\n        { display.view = display.view.slice(0, findViewIndex(cm, to)); }\n    }\n    display.viewTo = to;\n  }\n\n  // Count the number of lines in the view whose DOM representation is\n  // out of date (or nonexistent).\n  function countDirtyView(cm) {\n    var view = cm.display.view, dirty = 0;\n    for (var i = 0; i < view.length; i++) {\n      var lineView = view[i];\n      if (!lineView.hidden && (!lineView.node || lineView.changes)) { ++dirty; }\n    }\n    return dirty\n  }\n\n  function updateSelection(cm) {\n    cm.display.input.showSelection(cm.display.input.prepareSelection());\n  }\n\n  function prepareSelection(cm, primary) {\n    if ( primary === void 0 ) primary = true;\n\n    var doc = cm.doc, result = {};\n    var curFragment = result.cursors = document.createDocumentFragment();\n    var selFragment = result.selection = document.createDocumentFragment();\n\n    for (var i = 0; i < doc.sel.ranges.length; i++) {\n      if (!primary && i == doc.sel.primIndex) { continue }\n      var range$$1 = doc.sel.ranges[i];\n      if (range$$1.from().line >= cm.display.viewTo || range$$1.to().line < cm.display.viewFrom) { continue }\n      var collapsed = range$$1.empty();\n      if (collapsed || cm.options.showCursorWhenSelecting)\n        { drawSelectionCursor(cm, range$$1.head, curFragment); }\n      if (!collapsed)\n        { drawSelectionRange(cm, range$$1, selFragment); }\n    }\n    return result\n  }\n\n  // Draws a cursor for the given range\n  function drawSelectionCursor(cm, head, output) {\n    var pos = cursorCoords(cm, head, \"div\", null, null, !cm.options.singleCursorHeightPerLine);\n\n    var cursor = output.appendChild(elt(\"div\", \"\\u00a0\", \"CodeMirror-cursor\"));\n    cursor.style.left = pos.left + \"px\";\n    cursor.style.top = pos.top + \"px\";\n    cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + \"px\";\n\n    if (pos.other) {\n      // Secondary cursor, shown when on a 'jump' in bi-directional text\n      var otherCursor = output.appendChild(elt(\"div\", \"\\u00a0\", \"CodeMirror-cursor CodeMirror-secondarycursor\"));\n      otherCursor.style.display = \"\";\n      otherCursor.style.left = pos.other.left + \"px\";\n      otherCursor.style.top = pos.other.top + \"px\";\n      otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + \"px\";\n    }\n  }\n\n  function cmpCoords(a, b) { return a.top - b.top || a.left - b.left }\n\n  // Draws the given range as a highlighted selection\n  function drawSelectionRange(cm, range$$1, output) {\n    var display = cm.display, doc = cm.doc;\n    var fragment = document.createDocumentFragment();\n    var padding = paddingH(cm.display), leftSide = padding.left;\n    var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right;\n    var docLTR = doc.direction == \"ltr\";\n\n    function add(left, top, width, bottom) {\n      if (top < 0) { top = 0; }\n      top = Math.round(top);\n      bottom = Math.round(bottom);\n      fragment.appendChild(elt(\"div\", null, \"CodeMirror-selected\", (\"position: absolute; left: \" + left + \"px;\\n                             top: \" + top + \"px; width: \" + (width == null ? rightSide - left : width) + \"px;\\n                             height: \" + (bottom - top) + \"px\")));\n    }\n\n    function drawForLine(line, fromArg, toArg) {\n      var lineObj = getLine(doc, line);\n      var lineLen = lineObj.text.length;\n      var start, end;\n      function coords(ch, bias) {\n        return charCoords(cm, Pos(line, ch), \"div\", lineObj, bias)\n      }\n\n      function wrapX(pos, dir, side) {\n        var extent = wrappedLineExtentChar(cm, lineObj, null, pos);\n        var prop = (dir == \"ltr\") == (side == \"after\") ? \"left\" : \"right\";\n        var ch = side == \"after\" ? extent.begin : extent.end - (/\\s/.test(lineObj.text.charAt(extent.end - 1)) ? 2 : 1);\n        return coords(ch, prop)[prop]\n      }\n\n      var order = getOrder(lineObj, doc.direction);\n      iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir, i) {\n        var ltr = dir == \"ltr\";\n        var fromPos = coords(from, ltr ? \"left\" : \"right\");\n        var toPos = coords(to - 1, ltr ? \"right\" : \"left\");\n\n        var openStart = fromArg == null && from == 0, openEnd = toArg == null && to == lineLen;\n        var first = i == 0, last = !order || i == order.length - 1;\n        if (toPos.top - fromPos.top <= 3) { // Single line\n          var openLeft = (docLTR ? openStart : openEnd) && first;\n          var openRight = (docLTR ? openEnd : openStart) && last;\n          var left = openLeft ? leftSide : (ltr ? fromPos : toPos).left;\n          var right = openRight ? rightSide : (ltr ? toPos : fromPos).right;\n          add(left, fromPos.top, right - left, fromPos.bottom);\n        } else { // Multiple lines\n          var topLeft, topRight, botLeft, botRight;\n          if (ltr) {\n            topLeft = docLTR && openStart && first ? leftSide : fromPos.left;\n            topRight = docLTR ? rightSide : wrapX(from, dir, \"before\");\n            botLeft = docLTR ? leftSide : wrapX(to, dir, \"after\");\n            botRight = docLTR && openEnd && last ? rightSide : toPos.right;\n          } else {\n            topLeft = !docLTR ? leftSide : wrapX(from, dir, \"before\");\n            topRight = !docLTR && openStart && first ? rightSide : fromPos.right;\n            botLeft = !docLTR && openEnd && last ? leftSide : toPos.left;\n            botRight = !docLTR ? rightSide : wrapX(to, dir, \"after\");\n          }\n          add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom);\n          if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top); }\n          add(botLeft, toPos.top, botRight - botLeft, toPos.bottom);\n        }\n\n        if (!start || cmpCoords(fromPos, start) < 0) { start = fromPos; }\n        if (cmpCoords(toPos, start) < 0) { start = toPos; }\n        if (!end || cmpCoords(fromPos, end) < 0) { end = fromPos; }\n        if (cmpCoords(toPos, end) < 0) { end = toPos; }\n      });\n      return {start: start, end: end}\n    }\n\n    var sFrom = range$$1.from(), sTo = range$$1.to();\n    if (sFrom.line == sTo.line) {\n      drawForLine(sFrom.line, sFrom.ch, sTo.ch);\n    } else {\n      var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line);\n      var singleVLine = visualLine(fromLine) == visualLine(toLine);\n      var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end;\n      var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start;\n      if (singleVLine) {\n        if (leftEnd.top < rightStart.top - 2) {\n          add(leftEnd.right, leftEnd.top, null, leftEnd.bottom);\n          add(leftSide, rightStart.top, rightStart.left, rightStart.bottom);\n        } else {\n          add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom);\n        }\n      }\n      if (leftEnd.bottom < rightStart.top)\n        { add(leftSide, leftEnd.bottom, null, rightStart.top); }\n    }\n\n    output.appendChild(fragment);\n  }\n\n  // Cursor-blinking\n  function restartBlink(cm) {\n    if (!cm.state.focused) { return }\n    var display = cm.display;\n    clearInterval(display.blinker);\n    var on = true;\n    display.cursorDiv.style.visibility = \"\";\n    if (cm.options.cursorBlinkRate > 0)\n      { display.blinker = setInterval(function () { return display.cursorDiv.style.visibility = (on = !on) ? \"\" : \"hidden\"; },\n        cm.options.cursorBlinkRate); }\n    else if (cm.options.cursorBlinkRate < 0)\n      { display.cursorDiv.style.visibility = \"hidden\"; }\n  }\n\n  function ensureFocus(cm) {\n    if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); }\n  }\n\n  function delayBlurEvent(cm) {\n    cm.state.delayingBlurEvent = true;\n    setTimeout(function () { if (cm.state.delayingBlurEvent) {\n      cm.state.delayingBlurEvent = false;\n      onBlur(cm);\n    } }, 100);\n  }\n\n  function onFocus(cm, e) {\n    if (cm.state.delayingBlurEvent) { cm.state.delayingBlurEvent = false; }\n\n    if (cm.options.readOnly == \"nocursor\") { return }\n    if (!cm.state.focused) {\n      signal(cm, \"focus\", cm, e);\n      cm.state.focused = true;\n      addClass(cm.display.wrapper, \"CodeMirror-focused\");\n      // This test prevents this from firing when a context\n      // menu is closed (since the input reset would kill the\n      // select-all detection hack)\n      if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) {\n        cm.display.input.reset();\n        if (webkit) { setTimeout(function () { return cm.display.input.reset(true); }, 20); } // Issue #1730\n      }\n      cm.display.input.receivedFocus();\n    }\n    restartBlink(cm);\n  }\n  function onBlur(cm, e) {\n    if (cm.state.delayingBlurEvent) { return }\n\n    if (cm.state.focused) {\n      signal(cm, \"blur\", cm, e);\n      cm.state.focused = false;\n      rmClass(cm.display.wrapper, \"CodeMirror-focused\");\n    }\n    clearInterval(cm.display.blinker);\n    setTimeout(function () { if (!cm.state.focused) { cm.display.shift = false; } }, 150);\n  }\n\n  // Read the actual heights of the rendered lines, and update their\n  // stored heights to match.\n  function updateHeightsInViewport(cm) {\n    var display = cm.display;\n    var prevBottom = display.lineDiv.offsetTop;\n    for (var i = 0; i < display.view.length; i++) {\n      var cur = display.view[i], wrapping = cm.options.lineWrapping;\n      var height = (void 0), width = 0;\n      if (cur.hidden) { continue }\n      if (ie && ie_version < 8) {\n        var bot = cur.node.offsetTop + cur.node.offsetHeight;\n        height = bot - prevBottom;\n        prevBottom = bot;\n      } else {\n        var box = cur.node.getBoundingClientRect();\n        height = box.bottom - box.top;\n        // Check that lines don't extend past the right of the current\n        // editor width\n        if (!wrapping && cur.text.firstChild)\n          { width = cur.text.firstChild.getBoundingClientRect().right - box.left - 1; }\n      }\n      var diff = cur.line.height - height;\n      if (diff > .005 || diff < -.005) {\n        updateLineHeight(cur.line, height);\n        updateWidgetHeight(cur.line);\n        if (cur.rest) { for (var j = 0; j < cur.rest.length; j++)\n          { updateWidgetHeight(cur.rest[j]); } }\n      }\n      if (width > cm.display.sizerWidth) {\n        var chWidth = Math.ceil(width / charWidth(cm.display));\n        if (chWidth > cm.display.maxLineLength) {\n          cm.display.maxLineLength = chWidth;\n          cm.display.maxLine = cur.line;\n          cm.display.maxLineChanged = true;\n        }\n      }\n    }\n  }\n\n  // Read and store the height of line widgets associated with the\n  // given line.\n  function updateWidgetHeight(line) {\n    if (line.widgets) { for (var i = 0; i < line.widgets.length; ++i) {\n      var w = line.widgets[i], parent = w.node.parentNode;\n      if (parent) { w.height = parent.offsetHeight; }\n    } }\n  }\n\n  // Compute the lines that are visible in a given viewport (defaults\n  // the the current scroll position). viewport may contain top,\n  // height, and ensure (see op.scrollToPos) properties.\n  function visibleLines(display, doc, viewport) {\n    var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop;\n    top = Math.floor(top - paddingTop(display));\n    var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight;\n\n    var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom);\n    // Ensure is a {from: {line, ch}, to: {line, ch}} object, and\n    // forces those lines into the viewport (if possible).\n    if (viewport && viewport.ensure) {\n      var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line;\n      if (ensureFrom < from) {\n        from = ensureFrom;\n        to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight);\n      } else if (Math.min(ensureTo, doc.lastLine()) >= to) {\n        from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight);\n        to = ensureTo;\n      }\n    }\n    return {from: from, to: Math.max(to, from + 1)}\n  }\n\n  // SCROLLING THINGS INTO VIEW\n\n  // If an editor sits on the top or bottom of the window, partially\n  // scrolled out of view, this ensures that the cursor is visible.\n  function maybeScrollWindow(cm, rect) {\n    if (signalDOMEvent(cm, \"scrollCursorIntoView\")) { return }\n\n    var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null;\n    if (rect.top + box.top < 0) { doScroll = true; }\n    else if (rect.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) { doScroll = false; }\n    if (doScroll != null && !phantom) {\n      var scrollNode = elt(\"div\", \"\\u200b\", null, (\"position: absolute;\\n                         top: \" + (rect.top - display.viewOffset - paddingTop(cm.display)) + \"px;\\n                         height: \" + (rect.bottom - rect.top + scrollGap(cm) + display.barHeight) + \"px;\\n                         left: \" + (rect.left) + \"px; width: \" + (Math.max(2, rect.right - rect.left)) + \"px;\"));\n      cm.display.lineSpace.appendChild(scrollNode);\n      scrollNode.scrollIntoView(doScroll);\n      cm.display.lineSpace.removeChild(scrollNode);\n    }\n  }\n\n  // Scroll a given position into view (immediately), verifying that\n  // it actually became visible (as line heights are accurately\n  // measured, the position of something may 'drift' during drawing).\n  function scrollPosIntoView(cm, pos, end, margin) {\n    if (margin == null) { margin = 0; }\n    var rect;\n    if (!cm.options.lineWrapping && pos == end) {\n      // Set pos and end to the cursor positions around the character pos sticks to\n      // If pos.sticky == \"before\", that is around pos.ch - 1, otherwise around pos.ch\n      // If pos == Pos(_, 0, \"before\"), pos and end are unchanged\n      pos = pos.ch ? Pos(pos.line, pos.sticky == \"before\" ? pos.ch - 1 : pos.ch, \"after\") : pos;\n      end = pos.sticky == \"before\" ? Pos(pos.line, pos.ch + 1, \"before\") : pos;\n    }\n    for (var limit = 0; limit < 5; limit++) {\n      var changed = false;\n      var coords = cursorCoords(cm, pos);\n      var endCoords = !end || end == pos ? coords : cursorCoords(cm, end);\n      rect = {left: Math.min(coords.left, endCoords.left),\n              top: Math.min(coords.top, endCoords.top) - margin,\n              right: Math.max(coords.left, endCoords.left),\n              bottom: Math.max(coords.bottom, endCoords.bottom) + margin};\n      var scrollPos = calculateScrollPos(cm, rect);\n      var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft;\n      if (scrollPos.scrollTop != null) {\n        updateScrollTop(cm, scrollPos.scrollTop);\n        if (Math.abs(cm.doc.scrollTop - startTop) > 1) { changed = true; }\n      }\n      if (scrollPos.scrollLeft != null) {\n        setScrollLeft(cm, scrollPos.scrollLeft);\n        if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) { changed = true; }\n      }\n      if (!changed) { break }\n    }\n    return rect\n  }\n\n  // Scroll a given set of coordinates into view (immediately).\n  function scrollIntoView(cm, rect) {\n    var scrollPos = calculateScrollPos(cm, rect);\n    if (scrollPos.scrollTop != null) { updateScrollTop(cm, scrollPos.scrollTop); }\n    if (scrollPos.scrollLeft != null) { setScrollLeft(cm, scrollPos.scrollLeft); }\n  }\n\n  // Calculate a new scroll position needed to scroll the given\n  // rectangle into view. Returns an object with scrollTop and\n  // scrollLeft properties. When these are undefined, the\n  // vertical/horizontal position does not need to be adjusted.\n  function calculateScrollPos(cm, rect) {\n    var display = cm.display, snapMargin = textHeight(cm.display);\n    if (rect.top < 0) { rect.top = 0; }\n    var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop;\n    var screen = displayHeight(cm), result = {};\n    if (rect.bottom - rect.top > screen) { rect.bottom = rect.top + screen; }\n    var docBottom = cm.doc.height + paddingVert(display);\n    var atTop = rect.top < snapMargin, atBottom = rect.bottom > docBottom - snapMargin;\n    if (rect.top < screentop) {\n      result.scrollTop = atTop ? 0 : rect.top;\n    } else if (rect.bottom > screentop + screen) {\n      var newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - screen);\n      if (newTop != screentop) { result.scrollTop = newTop; }\n    }\n\n    var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft;\n    var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0);\n    var tooWide = rect.right - rect.left > screenw;\n    if (tooWide) { rect.right = rect.left + screenw; }\n    if (rect.left < 10)\n      { result.scrollLeft = 0; }\n    else if (rect.left < screenleft)\n      { result.scrollLeft = Math.max(0, rect.left - (tooWide ? 0 : 10)); }\n    else if (rect.right > screenw + screenleft - 3)\n      { result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw; }\n    return result\n  }\n\n  // Store a relative adjustment to the scroll position in the current\n  // operation (to be applied when the operation finishes).\n  function addToScrollTop(cm, top) {\n    if (top == null) { return }\n    resolveScrollToPos(cm);\n    cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top;\n  }\n\n  // Make sure that at the end of the operation the current cursor is\n  // shown.\n  function ensureCursorVisible(cm) {\n    resolveScrollToPos(cm);\n    var cur = cm.getCursor();\n    cm.curOp.scrollToPos = {from: cur, to: cur, margin: cm.options.cursorScrollMargin};\n  }\n\n  function scrollToCoords(cm, x, y) {\n    if (x != null || y != null) { resolveScrollToPos(cm); }\n    if (x != null) { cm.curOp.scrollLeft = x; }\n    if (y != null) { cm.curOp.scrollTop = y; }\n  }\n\n  function scrollToRange(cm, range$$1) {\n    resolveScrollToPos(cm);\n    cm.curOp.scrollToPos = range$$1;\n  }\n\n  // When an operation has its scrollToPos property set, and another\n  // scroll action is applied before the end of the operation, this\n  // 'simulates' scrolling that position into view in a cheap way, so\n  // that the effect of intermediate scroll commands is not ignored.\n  function resolveScrollToPos(cm) {\n    var range$$1 = cm.curOp.scrollToPos;\n    if (range$$1) {\n      cm.curOp.scrollToPos = null;\n      var from = estimateCoords(cm, range$$1.from), to = estimateCoords(cm, range$$1.to);\n      scrollToCoordsRange(cm, from, to, range$$1.margin);\n    }\n  }\n\n  function scrollToCoordsRange(cm, from, to, margin) {\n    var sPos = calculateScrollPos(cm, {\n      left: Math.min(from.left, to.left),\n      top: Math.min(from.top, to.top) - margin,\n      right: Math.max(from.right, to.right),\n      bottom: Math.max(from.bottom, to.bottom) + margin\n    });\n    scrollToCoords(cm, sPos.scrollLeft, sPos.scrollTop);\n  }\n\n  // Sync the scrollable area and scrollbars, ensure the viewport\n  // covers the visible area.\n  function updateScrollTop(cm, val) {\n    if (Math.abs(cm.doc.scrollTop - val) < 2) { return }\n    if (!gecko) { updateDisplaySimple(cm, {top: val}); }\n    setScrollTop(cm, val, true);\n    if (gecko) { updateDisplaySimple(cm); }\n    startWorker(cm, 100);\n  }\n\n  function setScrollTop(cm, val, forceScroll) {\n    val = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, val);\n    if (cm.display.scroller.scrollTop == val && !forceScroll) { return }\n    cm.doc.scrollTop = val;\n    cm.display.scrollbars.setScrollTop(val);\n    if (cm.display.scroller.scrollTop != val) { cm.display.scroller.scrollTop = val; }\n  }\n\n  // Sync scroller and scrollbar, ensure the gutter elements are\n  // aligned.\n  function setScrollLeft(cm, val, isScroller, forceScroll) {\n    val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth);\n    if ((isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) && !forceScroll) { return }\n    cm.doc.scrollLeft = val;\n    alignHorizontally(cm);\n    if (cm.display.scroller.scrollLeft != val) { cm.display.scroller.scrollLeft = val; }\n    cm.display.scrollbars.setScrollLeft(val);\n  }\n\n  // SCROLLBARS\n\n  // Prepare DOM reads needed to update the scrollbars. Done in one\n  // shot to minimize update/measure roundtrips.\n  function measureForScrollbars(cm) {\n    var d = cm.display, gutterW = d.gutters.offsetWidth;\n    var docH = Math.round(cm.doc.height + paddingVert(cm.display));\n    return {\n      clientHeight: d.scroller.clientHeight,\n      viewHeight: d.wrapper.clientHeight,\n      scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth,\n      viewWidth: d.wrapper.clientWidth,\n      barLeft: cm.options.fixedGutter ? gutterW : 0,\n      docHeight: docH,\n      scrollHeight: docH + scrollGap(cm) + d.barHeight,\n      nativeBarWidth: d.nativeBarWidth,\n      gutterWidth: gutterW\n    }\n  }\n\n  var NativeScrollbars = function(place, scroll, cm) {\n    this.cm = cm;\n    var vert = this.vert = elt(\"div\", [elt(\"div\", null, null, \"min-width: 1px\")], \"CodeMirror-vscrollbar\");\n    var horiz = this.horiz = elt(\"div\", [elt(\"div\", null, null, \"height: 100%; min-height: 1px\")], \"CodeMirror-hscrollbar\");\n    vert.tabIndex = horiz.tabIndex = -1;\n    place(vert); place(horiz);\n\n    on(vert, \"scroll\", function () {\n      if (vert.clientHeight) { scroll(vert.scrollTop, \"vertical\"); }\n    });\n    on(horiz, \"scroll\", function () {\n      if (horiz.clientWidth) { scroll(horiz.scrollLeft, \"horizontal\"); }\n    });\n\n    this.checkedZeroWidth = false;\n    // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).\n    if (ie && ie_version < 8) { this.horiz.style.minHeight = this.vert.style.minWidth = \"18px\"; }\n  };\n\n  NativeScrollbars.prototype.update = function (measure) {\n    var needsH = measure.scrollWidth > measure.clientWidth + 1;\n    var needsV = measure.scrollHeight > measure.clientHeight + 1;\n    var sWidth = measure.nativeBarWidth;\n\n    if (needsV) {\n      this.vert.style.display = \"block\";\n      this.vert.style.bottom = needsH ? sWidth + \"px\" : \"0\";\n      var totalHeight = measure.viewHeight - (needsH ? sWidth : 0);\n      // A bug in IE8 can cause this value to be negative, so guard it.\n      this.vert.firstChild.style.height =\n        Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + \"px\";\n    } else {\n      this.vert.style.display = \"\";\n      this.vert.firstChild.style.height = \"0\";\n    }\n\n    if (needsH) {\n      this.horiz.style.display = \"block\";\n      this.horiz.style.right = needsV ? sWidth + \"px\" : \"0\";\n      this.horiz.style.left = measure.barLeft + \"px\";\n      var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0);\n      this.horiz.firstChild.style.width =\n        Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + \"px\";\n    } else {\n      this.horiz.style.display = \"\";\n      this.horiz.firstChild.style.width = \"0\";\n    }\n\n    if (!this.checkedZeroWidth && measure.clientHeight > 0) {\n      if (sWidth == 0) { this.zeroWidthHack(); }\n      this.checkedZeroWidth = true;\n    }\n\n    return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}\n  };\n\n  NativeScrollbars.prototype.setScrollLeft = function (pos) {\n    if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos; }\n    if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz, \"horiz\"); }\n  };\n\n  NativeScrollbars.prototype.setScrollTop = function (pos) {\n    if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos; }\n    if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert, \"vert\"); }\n  };\n\n  NativeScrollbars.prototype.zeroWidthHack = function () {\n    var w = mac && !mac_geMountainLion ? \"12px\" : \"18px\";\n    this.horiz.style.height = this.vert.style.width = w;\n    this.horiz.style.pointerEvents = this.vert.style.pointerEvents = \"none\";\n    this.disableHoriz = new Delayed;\n    this.disableVert = new Delayed;\n  };\n\n  NativeScrollbars.prototype.enableZeroWidthBar = function (bar, delay, type) {\n    bar.style.pointerEvents = \"auto\";\n    function maybeDisable() {\n      // To find out whether the scrollbar is still visible, we\n      // check whether the element under the pixel in the bottom\n      // right corner of the scrollbar box is the scrollbar box\n      // itself (when the bar is still visible) or its filler child\n      // (when the bar is hidden). If it is still visible, we keep\n      // it enabled, if it's hidden, we disable pointer events.\n      var box = bar.getBoundingClientRect();\n      var elt$$1 = type == \"vert\" ? document.elementFromPoint(box.right - 1, (box.top + box.bottom) / 2)\n          : document.elementFromPoint((box.right + box.left) / 2, box.bottom - 1);\n      if (elt$$1 != bar) { bar.style.pointerEvents = \"none\"; }\n      else { delay.set(1000, maybeDisable); }\n    }\n    delay.set(1000, maybeDisable);\n  };\n\n  NativeScrollbars.prototype.clear = function () {\n    var parent = this.horiz.parentNode;\n    parent.removeChild(this.horiz);\n    parent.removeChild(this.vert);\n  };\n\n  var NullScrollbars = function () {};\n\n  NullScrollbars.prototype.update = function () { return {bottom: 0, right: 0} };\n  NullScrollbars.prototype.setScrollLeft = function () {};\n  NullScrollbars.prototype.setScrollTop = function () {};\n  NullScrollbars.prototype.clear = function () {};\n\n  function updateScrollbars(cm, measure) {\n    if (!measure) { measure = measureForScrollbars(cm); }\n    var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight;\n    updateScrollbarsInner(cm, measure);\n    for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) {\n      if (startWidth != cm.display.barWidth && cm.options.lineWrapping)\n        { updateHeightsInViewport(cm); }\n      updateScrollbarsInner(cm, measureForScrollbars(cm));\n      startWidth = cm.display.barWidth; startHeight = cm.display.barHeight;\n    }\n  }\n\n  // Re-synchronize the fake scrollbars with the actual size of the\n  // content.\n  function updateScrollbarsInner(cm, measure) {\n    var d = cm.display;\n    var sizes = d.scrollbars.update(measure);\n\n    d.sizer.style.paddingRight = (d.barWidth = sizes.right) + \"px\";\n    d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + \"px\";\n    d.heightForcer.style.borderBottom = sizes.bottom + \"px solid transparent\";\n\n    if (sizes.right && sizes.bottom) {\n      d.scrollbarFiller.style.display = \"block\";\n      d.scrollbarFiller.style.height = sizes.bottom + \"px\";\n      d.scrollbarFiller.style.width = sizes.right + \"px\";\n    } else { d.scrollbarFiller.style.display = \"\"; }\n    if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {\n      d.gutterFiller.style.display = \"block\";\n      d.gutterFiller.style.height = sizes.bottom + \"px\";\n      d.gutterFiller.style.width = measure.gutterWidth + \"px\";\n    } else { d.gutterFiller.style.display = \"\"; }\n  }\n\n  var scrollbarModel = {\"native\": NativeScrollbars, \"null\": NullScrollbars};\n\n  function initScrollbars(cm) {\n    if (cm.display.scrollbars) {\n      cm.display.scrollbars.clear();\n      if (cm.display.scrollbars.addClass)\n        { rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); }\n    }\n\n    cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](function (node) {\n      cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller);\n      // Prevent clicks in the scrollbars from killing focus\n      on(node, \"mousedown\", function () {\n        if (cm.state.focused) { setTimeout(function () { return cm.display.input.focus(); }, 0); }\n      });\n      node.setAttribute(\"cm-not-content\", \"true\");\n    }, function (pos, axis) {\n      if (axis == \"horizontal\") { setScrollLeft(cm, pos); }\n      else { updateScrollTop(cm, pos); }\n    }, cm);\n    if (cm.display.scrollbars.addClass)\n      { addClass(cm.display.wrapper, cm.display.scrollbars.addClass); }\n  }\n\n  // Operations are used to wrap a series of changes to the editor\n  // state in such a way that each change won't have to update the\n  // cursor and display (which would be awkward, slow, and\n  // error-prone). Instead, display updates are batched and then all\n  // combined and executed at once.\n\n  var nextOpId = 0;\n  // Start a new operation.\n  function startOperation(cm) {\n    cm.curOp = {\n      cm: cm,\n      viewChanged: false,      // Flag that indicates that lines might need to be redrawn\n      startHeight: cm.doc.height, // Used to detect need to update scrollbar\n      forceUpdate: false,      // Used to force a redraw\n      updateInput: 0,       // Whether to reset the input textarea\n      typing: false,           // Whether this reset should be careful to leave existing text (for compositing)\n      changeObjs: null,        // Accumulated changes, for firing change events\n      cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on\n      cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already\n      selectionChanged: false, // Whether the selection needs to be redrawn\n      updateMaxLine: false,    // Set when the widest line needs to be determined anew\n      scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet\n      scrollToPos: null,       // Used to scroll to a specific position\n      focus: false,\n      id: ++nextOpId           // Unique ID\n    };\n    pushOperation(cm.curOp);\n  }\n\n  // Finish an operation, updating the display and signalling delayed events\n  function endOperation(cm) {\n    var op = cm.curOp;\n    if (op) { finishOperation(op, function (group) {\n      for (var i = 0; i < group.ops.length; i++)\n        { group.ops[i].cm.curOp = null; }\n      endOperations(group);\n    }); }\n  }\n\n  // The DOM updates done when an operation finishes are batched so\n  // that the minimum number of relayouts are required.\n  function endOperations(group) {\n    var ops = group.ops;\n    for (var i = 0; i < ops.length; i++) // Read DOM\n      { endOperation_R1(ops[i]); }\n    for (var i$1 = 0; i$1 < ops.length; i$1++) // Write DOM (maybe)\n      { endOperation_W1(ops[i$1]); }\n    for (var i$2 = 0; i$2 < ops.length; i$2++) // Read DOM\n      { endOperation_R2(ops[i$2]); }\n    for (var i$3 = 0; i$3 < ops.length; i$3++) // Write DOM (maybe)\n      { endOperation_W2(ops[i$3]); }\n    for (var i$4 = 0; i$4 < ops.length; i$4++) // Read DOM\n      { endOperation_finish(ops[i$4]); }\n  }\n\n  function endOperation_R1(op) {\n    var cm = op.cm, display = cm.display;\n    maybeClipScrollbars(cm);\n    if (op.updateMaxLine) { findMaxLine(cm); }\n\n    op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null ||\n      op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom ||\n                         op.scrollToPos.to.line >= display.viewTo) ||\n      display.maxLineChanged && cm.options.lineWrapping;\n    op.update = op.mustUpdate &&\n      new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate);\n  }\n\n  function endOperation_W1(op) {\n    op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update);\n  }\n\n  function endOperation_R2(op) {\n    var cm = op.cm, display = cm.display;\n    if (op.updatedDisplay) { updateHeightsInViewport(cm); }\n\n    op.barMeasure = measureForScrollbars(cm);\n\n    // If the max line changed since it was last measured, measure it,\n    // and ensure the document's width matches it.\n    // updateDisplay_W2 will use these properties to do the actual resizing\n    if (display.maxLineChanged && !cm.options.lineWrapping) {\n      op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3;\n      cm.display.sizerWidth = op.adjustWidthTo;\n      op.barMeasure.scrollWidth =\n        Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth);\n      op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm));\n    }\n\n    if (op.updatedDisplay || op.selectionChanged)\n      { op.preparedSelection = display.input.prepareSelection(); }\n  }\n\n  function endOperation_W2(op) {\n    var cm = op.cm;\n\n    if (op.adjustWidthTo != null) {\n      cm.display.sizer.style.minWidth = op.adjustWidthTo + \"px\";\n      if (op.maxScrollLeft < cm.doc.scrollLeft)\n        { setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true); }\n      cm.display.maxLineChanged = false;\n    }\n\n    var takeFocus = op.focus && op.focus == activeElt();\n    if (op.preparedSelection)\n      { cm.display.input.showSelection(op.preparedSelection, takeFocus); }\n    if (op.updatedDisplay || op.startHeight != cm.doc.height)\n      { updateScrollbars(cm, op.barMeasure); }\n    if (op.updatedDisplay)\n      { setDocumentHeight(cm, op.barMeasure); }\n\n    if (op.selectionChanged) { restartBlink(cm); }\n\n    if (cm.state.focused && op.updateInput)\n      { cm.display.input.reset(op.typing); }\n    if (takeFocus) { ensureFocus(op.cm); }\n  }\n\n  function endOperation_finish(op) {\n    var cm = op.cm, display = cm.display, doc = cm.doc;\n\n    if (op.updatedDisplay) { postUpdateDisplay(cm, op.update); }\n\n    // Abort mouse wheel delta measurement, when scrolling explicitly\n    if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos))\n      { display.wheelStartX = display.wheelStartY = null; }\n\n    // Propagate the scroll position to the actual DOM scroller\n    if (op.scrollTop != null) { setScrollTop(cm, op.scrollTop, op.forceScroll); }\n\n    if (op.scrollLeft != null) { setScrollLeft(cm, op.scrollLeft, true, true); }\n    // If we need to scroll a specific position into view, do so.\n    if (op.scrollToPos) {\n      var rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from),\n                                   clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin);\n      maybeScrollWindow(cm, rect);\n    }\n\n    // Fire events for markers that are hidden/unidden by editing or\n    // undoing\n    var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers;\n    if (hidden) { for (var i = 0; i < hidden.length; ++i)\n      { if (!hidden[i].lines.length) { signal(hidden[i], \"hide\"); } } }\n    if (unhidden) { for (var i$1 = 0; i$1 < unhidden.length; ++i$1)\n      { if (unhidden[i$1].lines.length) { signal(unhidden[i$1], \"unhide\"); } } }\n\n    if (display.wrapper.offsetHeight)\n      { doc.scrollTop = cm.display.scroller.scrollTop; }\n\n    // Fire change events, and delayed event handlers\n    if (op.changeObjs)\n      { signal(cm, \"changes\", cm, op.changeObjs); }\n    if (op.update)\n      { op.update.finish(); }\n  }\n\n  // Run the given function in an operation\n  function runInOp(cm, f) {\n    if (cm.curOp) { return f() }\n    startOperation(cm);\n    try { return f() }\n    finally { endOperation(cm); }\n  }\n  // Wraps a function in an operation. Returns the wrapped function.\n  function operation(cm, f) {\n    return function() {\n      if (cm.curOp) { return f.apply(cm, arguments) }\n      startOperation(cm);\n      try { return f.apply(cm, arguments) }\n      finally { endOperation(cm); }\n    }\n  }\n  // Used to add methods to editor and doc instances, wrapping them in\n  // operations.\n  function methodOp(f) {\n    return function() {\n      if (this.curOp) { return f.apply(this, arguments) }\n      startOperation(this);\n      try { return f.apply(this, arguments) }\n      finally { endOperation(this); }\n    }\n  }\n  function docMethodOp(f) {\n    return function() {\n      var cm = this.cm;\n      if (!cm || cm.curOp) { return f.apply(this, arguments) }\n      startOperation(cm);\n      try { return f.apply(this, arguments) }\n      finally { endOperation(cm); }\n    }\n  }\n\n  // HIGHLIGHT WORKER\n\n  function startWorker(cm, time) {\n    if (cm.doc.highlightFrontier < cm.display.viewTo)\n      { cm.state.highlight.set(time, bind(highlightWorker, cm)); }\n  }\n\n  function highlightWorker(cm) {\n    var doc = cm.doc;\n    if (doc.highlightFrontier >= cm.display.viewTo) { return }\n    var end = +new Date + cm.options.workTime;\n    var context = getContextBefore(cm, doc.highlightFrontier);\n    var changedLines = [];\n\n    doc.iter(context.line, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function (line) {\n      if (context.line >= cm.display.viewFrom) { // Visible\n        var oldStyles = line.styles;\n        var resetState = line.text.length > cm.options.maxHighlightLength ? copyState(doc.mode, context.state) : null;\n        var highlighted = highlightLine(cm, line, context, true);\n        if (resetState) { context.state = resetState; }\n        line.styles = highlighted.styles;\n        var oldCls = line.styleClasses, newCls = highlighted.classes;\n        if (newCls) { line.styleClasses = newCls; }\n        else if (oldCls) { line.styleClasses = null; }\n        var ischange = !oldStyles || oldStyles.length != line.styles.length ||\n          oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass);\n        for (var i = 0; !ischange && i < oldStyles.length; ++i) { ischange = oldStyles[i] != line.styles[i]; }\n        if (ischange) { changedLines.push(context.line); }\n        line.stateAfter = context.save();\n        context.nextLine();\n      } else {\n        if (line.text.length <= cm.options.maxHighlightLength)\n          { processLine(cm, line.text, context); }\n        line.stateAfter = context.line % 5 == 0 ? context.save() : null;\n        context.nextLine();\n      }\n      if (+new Date > end) {\n        startWorker(cm, cm.options.workDelay);\n        return true\n      }\n    });\n    doc.highlightFrontier = context.line;\n    doc.modeFrontier = Math.max(doc.modeFrontier, context.line);\n    if (changedLines.length) { runInOp(cm, function () {\n      for (var i = 0; i < changedLines.length; i++)\n        { regLineChange(cm, changedLines[i], \"text\"); }\n    }); }\n  }\n\n  // DISPLAY DRAWING\n\n  var DisplayUpdate = function(cm, viewport, force) {\n    var display = cm.display;\n\n    this.viewport = viewport;\n    // Store some values that we'll need later (but don't want to force a relayout for)\n    this.visible = visibleLines(display, cm.doc, viewport);\n    this.editorIsHidden = !display.wrapper.offsetWidth;\n    this.wrapperHeight = display.wrapper.clientHeight;\n    this.wrapperWidth = display.wrapper.clientWidth;\n    this.oldDisplayWidth = displayWidth(cm);\n    this.force = force;\n    this.dims = getDimensions(cm);\n    this.events = [];\n  };\n\n  DisplayUpdate.prototype.signal = function (emitter, type) {\n    if (hasHandler(emitter, type))\n      { this.events.push(arguments); }\n  };\n  DisplayUpdate.prototype.finish = function () {\n      var this$1 = this;\n\n    for (var i = 0; i < this.events.length; i++)\n      { signal.apply(null, this$1.events[i]); }\n  };\n\n  function maybeClipScrollbars(cm) {\n    var display = cm.display;\n    if (!display.scrollbarsClipped && display.scroller.offsetWidth) {\n      display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth;\n      display.heightForcer.style.height = scrollGap(cm) + \"px\";\n      display.sizer.style.marginBottom = -display.nativeBarWidth + \"px\";\n      display.sizer.style.borderRightWidth = scrollGap(cm) + \"px\";\n      display.scrollbarsClipped = true;\n    }\n  }\n\n  function selectionSnapshot(cm) {\n    if (cm.hasFocus()) { return null }\n    var active = activeElt();\n    if (!active || !contains(cm.display.lineDiv, active)) { return null }\n    var result = {activeElt: active};\n    if (window.getSelection) {\n      var sel = window.getSelection();\n      if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) {\n        result.anchorNode = sel.anchorNode;\n        result.anchorOffset = sel.anchorOffset;\n        result.focusNode = sel.focusNode;\n        result.focusOffset = sel.focusOffset;\n      }\n    }\n    return result\n  }\n\n  function restoreSelection(snapshot) {\n    if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt()) { return }\n    snapshot.activeElt.focus();\n    if (snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) {\n      var sel = window.getSelection(), range$$1 = document.createRange();\n      range$$1.setEnd(snapshot.anchorNode, snapshot.anchorOffset);\n      range$$1.collapse(false);\n      sel.removeAllRanges();\n      sel.addRange(range$$1);\n      sel.extend(snapshot.focusNode, snapshot.focusOffset);\n    }\n  }\n\n  // Does the actual updating of the line display. Bails out\n  // (returning false) when there is nothing to be done and forced is\n  // false.\n  function updateDisplayIfNeeded(cm, update) {\n    var display = cm.display, doc = cm.doc;\n\n    if (update.editorIsHidden) {\n      resetView(cm);\n      return false\n    }\n\n    // Bail out if the visible area is already rendered and nothing changed.\n    if (!update.force &&\n        update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo &&\n        (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) &&\n        display.renderedView == display.view && countDirtyView(cm) == 0)\n      { return false }\n\n    if (maybeUpdateLineNumberWidth(cm)) {\n      resetView(cm);\n      update.dims = getDimensions(cm);\n    }\n\n    // Compute a suitable new viewport (from & to)\n    var end = doc.first + doc.size;\n    var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first);\n    var to = Math.min(end, update.visible.to + cm.options.viewportMargin);\n    if (display.viewFrom < from && from - display.viewFrom < 20) { from = Math.max(doc.first, display.viewFrom); }\n    if (display.viewTo > to && display.viewTo - to < 20) { to = Math.min(end, display.viewTo); }\n    if (sawCollapsedSpans) {\n      from = visualLineNo(cm.doc, from);\n      to = visualLineEndNo(cm.doc, to);\n    }\n\n    var different = from != display.viewFrom || to != display.viewTo ||\n      display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth;\n    adjustView(cm, from, to);\n\n    display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom));\n    // Position the mover div to align with the current scroll position\n    cm.display.mover.style.top = display.viewOffset + \"px\";\n\n    var toUpdate = countDirtyView(cm);\n    if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view &&\n        (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo))\n      { return false }\n\n    // For big changes, we hide the enclosing element during the\n    // update, since that speeds up the operations on most browsers.\n    var selSnapshot = selectionSnapshot(cm);\n    if (toUpdate > 4) { display.lineDiv.style.display = \"none\"; }\n    patchDisplay(cm, display.updateLineNumbers, update.dims);\n    if (toUpdate > 4) { display.lineDiv.style.display = \"\"; }\n    display.renderedView = display.view;\n    // There might have been a widget with a focused element that got\n    // hidden or updated, if so re-focus it.\n    restoreSelection(selSnapshot);\n\n    // Prevent selection and cursors from interfering with the scroll\n    // width and height.\n    removeChildren(display.cursorDiv);\n    removeChildren(display.selectionDiv);\n    display.gutters.style.height = display.sizer.style.minHeight = 0;\n\n    if (different) {\n      display.lastWrapHeight = update.wrapperHeight;\n      display.lastWrapWidth = update.wrapperWidth;\n      startWorker(cm, 400);\n    }\n\n    display.updateLineNumbers = null;\n\n    return true\n  }\n\n  function postUpdateDisplay(cm, update) {\n    var viewport = update.viewport;\n\n    for (var first = true;; first = false) {\n      if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) {\n        // Clip forced viewport to actual scrollable area.\n        if (viewport && viewport.top != null)\n          { viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)}; }\n        // Updated line heights might result in the drawn area not\n        // actually covering the viewport. Keep looping until it does.\n        update.visible = visibleLines(cm.display, cm.doc, viewport);\n        if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo)\n          { break }\n      }\n      if (!updateDisplayIfNeeded(cm, update)) { break }\n      updateHeightsInViewport(cm);\n      var barMeasure = measureForScrollbars(cm);\n      updateSelection(cm);\n      updateScrollbars(cm, barMeasure);\n      setDocumentHeight(cm, barMeasure);\n      update.force = false;\n    }\n\n    update.signal(cm, \"update\", cm);\n    if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) {\n      update.signal(cm, \"viewportChange\", cm, cm.display.viewFrom, cm.display.viewTo);\n      cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo;\n    }\n  }\n\n  function updateDisplaySimple(cm, viewport) {\n    var update = new DisplayUpdate(cm, viewport);\n    if (updateDisplayIfNeeded(cm, update)) {\n      updateHeightsInViewport(cm);\n      postUpdateDisplay(cm, update);\n      var barMeasure = measureForScrollbars(cm);\n      updateSelection(cm);\n      updateScrollbars(cm, barMeasure);\n      setDocumentHeight(cm, barMeasure);\n      update.finish();\n    }\n  }\n\n  // Sync the actual display DOM structure with display.view, removing\n  // nodes for lines that are no longer in view, and creating the ones\n  // that are not there yet, and updating the ones that are out of\n  // date.\n  function patchDisplay(cm, updateNumbersFrom, dims) {\n    var display = cm.display, lineNumbers = cm.options.lineNumbers;\n    var container = display.lineDiv, cur = container.firstChild;\n\n    function rm(node) {\n      var next = node.nextSibling;\n      // Works around a throw-scroll bug in OS X Webkit\n      if (webkit && mac && cm.display.currentWheelTarget == node)\n        { node.style.display = \"none\"; }\n      else\n        { node.parentNode.removeChild(node); }\n      return next\n    }\n\n    var view = display.view, lineN = display.viewFrom;\n    // Loop over the elements in the view, syncing cur (the DOM nodes\n    // in display.lineDiv) with the view as we go.\n    for (var i = 0; i < view.length; i++) {\n      var lineView = view[i];\n      if (lineView.hidden) ; else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet\n        var node = buildLineElement(cm, lineView, lineN, dims);\n        container.insertBefore(node, cur);\n      } else { // Already drawn\n        while (cur != lineView.node) { cur = rm(cur); }\n        var updateNumber = lineNumbers && updateNumbersFrom != null &&\n          updateNumbersFrom <= lineN && lineView.lineNumber;\n        if (lineView.changes) {\n          if (indexOf(lineView.changes, \"gutter\") > -1) { updateNumber = false; }\n          updateLineForChanges(cm, lineView, lineN, dims);\n        }\n        if (updateNumber) {\n          removeChildren(lineView.lineNumber);\n          lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN)));\n        }\n        cur = lineView.node.nextSibling;\n      }\n      lineN += lineView.size;\n    }\n    while (cur) { cur = rm(cur); }\n  }\n\n  function updateGutterSpace(display) {\n    var width = display.gutters.offsetWidth;\n    display.sizer.style.marginLeft = width + \"px\";\n  }\n\n  function setDocumentHeight(cm, measure) {\n    cm.display.sizer.style.minHeight = measure.docHeight + \"px\";\n    cm.display.heightForcer.style.top = measure.docHeight + \"px\";\n    cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + \"px\";\n  }\n\n  // Re-align line numbers and gutter marks to compensate for\n  // horizontal scrolling.\n  function alignHorizontally(cm) {\n    var display = cm.display, view = display.view;\n    if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) { return }\n    var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft;\n    var gutterW = display.gutters.offsetWidth, left = comp + \"px\";\n    for (var i = 0; i < view.length; i++) { if (!view[i].hidden) {\n      if (cm.options.fixedGutter) {\n        if (view[i].gutter)\n          { view[i].gutter.style.left = left; }\n        if (view[i].gutterBackground)\n          { view[i].gutterBackground.style.left = left; }\n      }\n      var align = view[i].alignable;\n      if (align) { for (var j = 0; j < align.length; j++)\n        { align[j].style.left = left; } }\n    } }\n    if (cm.options.fixedGutter)\n      { display.gutters.style.left = (comp + gutterW) + \"px\"; }\n  }\n\n  // Used to ensure that the line number gutter is still the right\n  // size for the current document size. Returns true when an update\n  // is needed.\n  function maybeUpdateLineNumberWidth(cm) {\n    if (!cm.options.lineNumbers) { return false }\n    var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display;\n    if (last.length != display.lineNumChars) {\n      var test = display.measure.appendChild(elt(\"div\", [elt(\"div\", last)],\n                                                 \"CodeMirror-linenumber CodeMirror-gutter-elt\"));\n      var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW;\n      display.lineGutter.style.width = \"\";\n      display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1;\n      display.lineNumWidth = display.lineNumInnerWidth + padding;\n      display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;\n      display.lineGutter.style.width = display.lineNumWidth + \"px\";\n      updateGutterSpace(cm.display);\n      return true\n    }\n    return false\n  }\n\n  function getGutters(gutters, lineNumbers) {\n    var result = [], sawLineNumbers = false;\n    for (var i = 0; i < gutters.length; i++) {\n      var name = gutters[i], style = null;\n      if (typeof name != \"string\") { style = name.style; name = name.className; }\n      if (name == \"CodeMirror-linenumbers\") {\n        if (!lineNumbers) { continue }\n        else { sawLineNumbers = true; }\n      }\n      result.push({className: name, style: style});\n    }\n    if (lineNumbers && !sawLineNumbers) { result.push({className: \"CodeMirror-linenumbers\", style: null}); }\n    return result\n  }\n\n  // Rebuild the gutter elements, ensure the margin to the left of the\n  // code matches their width.\n  function renderGutters(display) {\n    var gutters = display.gutters, specs = display.gutterSpecs;\n    removeChildren(gutters);\n    display.lineGutter = null;\n    for (var i = 0; i < specs.length; ++i) {\n      var ref = specs[i];\n      var className = ref.className;\n      var style = ref.style;\n      var gElt = gutters.appendChild(elt(\"div\", null, \"CodeMirror-gutter \" + className));\n      if (style) { gElt.style.cssText = style; }\n      if (className == \"CodeMirror-linenumbers\") {\n        display.lineGutter = gElt;\n        gElt.style.width = (display.lineNumWidth || 1) + \"px\";\n      }\n    }\n    gutters.style.display = specs.length ? \"\" : \"none\";\n    updateGutterSpace(display);\n  }\n\n  function updateGutters(cm) {\n    renderGutters(cm.display);\n    regChange(cm);\n    alignHorizontally(cm);\n  }\n\n  // The display handles the DOM integration, both for input reading\n  // and content drawing. It holds references to DOM nodes and\n  // display-related state.\n\n  function Display(place, doc, input, options) {\n    var d = this;\n    this.input = input;\n\n    // Covers bottom-right square when both scrollbars are present.\n    d.scrollbarFiller = elt(\"div\", null, \"CodeMirror-scrollbar-filler\");\n    d.scrollbarFiller.setAttribute(\"cm-not-content\", \"true\");\n    // Covers bottom of gutter when coverGutterNextToScrollbar is on\n    // and h scrollbar is present.\n    d.gutterFiller = elt(\"div\", null, \"CodeMirror-gutter-filler\");\n    d.gutterFiller.setAttribute(\"cm-not-content\", \"true\");\n    // Will contain the actual code, positioned to cover the viewport.\n    d.lineDiv = eltP(\"div\", null, \"CodeMirror-code\");\n    // Elements are added to these to represent selection and cursors.\n    d.selectionDiv = elt(\"div\", null, null, \"position: relative; z-index: 1\");\n    d.cursorDiv = elt(\"div\", null, \"CodeMirror-cursors\");\n    // A visibility: hidden element used to find the size of things.\n    d.measure = elt(\"div\", null, \"CodeMirror-measure\");\n    // When lines outside of the viewport are measured, they are drawn in this.\n    d.lineMeasure = elt(\"div\", null, \"CodeMirror-measure\");\n    // Wraps everything that needs to exist inside the vertically-padded coordinate system\n    d.lineSpace = eltP(\"div\", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv],\n                      null, \"position: relative; outline: none\");\n    var lines = eltP(\"div\", [d.lineSpace], \"CodeMirror-lines\");\n    // Moved around its parent to cover visible view.\n    d.mover = elt(\"div\", [lines], null, \"position: relative\");\n    // Set to the height of the document, allowing scrolling.\n    d.sizer = elt(\"div\", [d.mover], \"CodeMirror-sizer\");\n    d.sizerWidth = null;\n    // Behavior of elts with overflow: auto and padding is\n    // inconsistent across browsers. This is used to ensure the\n    // scrollable area is big enough.\n    d.heightForcer = elt(\"div\", null, null, \"position: absolute; height: \" + scrollerGap + \"px; width: 1px;\");\n    // Will contain the gutters, if any.\n    d.gutters = elt(\"div\", null, \"CodeMirror-gutters\");\n    d.lineGutter = null;\n    // Actual scrollable element.\n    d.scroller = elt(\"div\", [d.sizer, d.heightForcer, d.gutters], \"CodeMirror-scroll\");\n    d.scroller.setAttribute(\"tabIndex\", \"-1\");\n    // The element in which the editor lives.\n    d.wrapper = elt(\"div\", [d.scrollbarFiller, d.gutterFiller, d.scroller], \"CodeMirror\");\n\n    // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported)\n    if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }\n    if (!webkit && !(gecko && mobile)) { d.scroller.draggable = true; }\n\n    if (place) {\n      if (place.appendChild) { place.appendChild(d.wrapper); }\n      else { place(d.wrapper); }\n    }\n\n    // Current rendered range (may be bigger than the view window).\n    d.viewFrom = d.viewTo = doc.first;\n    d.reportedViewFrom = d.reportedViewTo = doc.first;\n    // Information about the rendered lines.\n    d.view = [];\n    d.renderedView = null;\n    // Holds info about a single rendered line when it was rendered\n    // for measurement, while not in view.\n    d.externalMeasured = null;\n    // Empty space (in pixels) above the view\n    d.viewOffset = 0;\n    d.lastWrapHeight = d.lastWrapWidth = 0;\n    d.updateLineNumbers = null;\n\n    d.nativeBarWidth = d.barHeight = d.barWidth = 0;\n    d.scrollbarsClipped = false;\n\n    // Used to only resize the line number gutter when necessary (when\n    // the amount of lines crosses a boundary that makes its width change)\n    d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;\n    // Set to true when a non-horizontal-scrolling line widget is\n    // added. As an optimization, line widget aligning is skipped when\n    // this is false.\n    d.alignWidgets = false;\n\n    d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;\n\n    // Tracks the maximum line length so that the horizontal scrollbar\n    // can be kept static when scrolling.\n    d.maxLine = null;\n    d.maxLineLength = 0;\n    d.maxLineChanged = false;\n\n    // Used for measuring wheel scrolling granularity\n    d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;\n\n    // True when shift is held down.\n    d.shift = false;\n\n    // Used to track whether anything happened since the context menu\n    // was opened.\n    d.selForContextMenu = null;\n\n    d.activeTouch = null;\n\n    d.gutterSpecs = getGutters(options.gutters, options.lineNumbers);\n    renderGutters(d);\n\n    input.init(d);\n  }\n\n  // Since the delta values reported on mouse wheel events are\n  // unstandardized between browsers and even browser versions, and\n  // generally horribly unpredictable, this code starts by measuring\n  // the scroll effect that the first few mouse wheel events have,\n  // and, from that, detects the way it can convert deltas to pixel\n  // offsets afterwards.\n  //\n  // The reason we want to know the amount a wheel event will scroll\n  // is that it gives us a chance to update the display before the\n  // actual scrolling happens, reducing flickering.\n\n  var wheelSamples = 0, wheelPixelsPerUnit = null;\n  // Fill in a browser-detected starting value on browsers where we\n  // know one. These don't have to be accurate -- the result of them\n  // being wrong would just be a slight flicker on the first wheel\n  // scroll (if it is large enough).\n  if (ie) { wheelPixelsPerUnit = -.53; }\n  else if (gecko) { wheelPixelsPerUnit = 15; }\n  else if (chrome) { wheelPixelsPerUnit = -.7; }\n  else if (safari) { wheelPixelsPerUnit = -1/3; }\n\n  function wheelEventDelta(e) {\n    var dx = e.wheelDeltaX, dy = e.wheelDeltaY;\n    if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) { dx = e.detail; }\n    if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) { dy = e.detail; }\n    else if (dy == null) { dy = e.wheelDelta; }\n    return {x: dx, y: dy}\n  }\n  function wheelEventPixels(e) {\n    var delta = wheelEventDelta(e);\n    delta.x *= wheelPixelsPerUnit;\n    delta.y *= wheelPixelsPerUnit;\n    return delta\n  }\n\n  function onScrollWheel(cm, e) {\n    var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y;\n\n    var display = cm.display, scroll = display.scroller;\n    // Quit if there's nothing to scroll here\n    var canScrollX = scroll.scrollWidth > scroll.clientWidth;\n    var canScrollY = scroll.scrollHeight > scroll.clientHeight;\n    if (!(dx && canScrollX || dy && canScrollY)) { return }\n\n    // Webkit browsers on OS X abort momentum scrolls when the target\n    // of the scroll event is removed from the scrollable element.\n    // This hack (see related code in patchDisplay) makes sure the\n    // element is kept around.\n    if (dy && mac && webkit) {\n      outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) {\n        for (var i = 0; i < view.length; i++) {\n          if (view[i].node == cur) {\n            cm.display.currentWheelTarget = cur;\n            break outer\n          }\n        }\n      }\n    }\n\n    // On some browsers, horizontal scrolling will cause redraws to\n    // happen before the gutter has been realigned, causing it to\n    // wriggle around in a most unseemly way. When we have an\n    // estimated pixels/delta value, we just handle horizontal\n    // scrolling entirely here. It'll be slightly off from native, but\n    // better than glitching out.\n    if (dx && !gecko && !presto && wheelPixelsPerUnit != null) {\n      if (dy && canScrollY)\n        { updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * wheelPixelsPerUnit)); }\n      setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * wheelPixelsPerUnit));\n      // Only prevent default scrolling if vertical scrolling is\n      // actually possible. Otherwise, it causes vertical scroll\n      // jitter on OSX trackpads when deltaX is small and deltaY\n      // is large (issue #3579)\n      if (!dy || (dy && canScrollY))\n        { e_preventDefault(e); }\n      display.wheelStartX = null; // Abort measurement, if in progress\n      return\n    }\n\n    // 'Project' the visible viewport to cover the area that is being\n    // scrolled into view (if we know enough to estimate it).\n    if (dy && wheelPixelsPerUnit != null) {\n      var pixels = dy * wheelPixelsPerUnit;\n      var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight;\n      if (pixels < 0) { top = Math.max(0, top + pixels - 50); }\n      else { bot = Math.min(cm.doc.height, bot + pixels + 50); }\n      updateDisplaySimple(cm, {top: top, bottom: bot});\n    }\n\n    if (wheelSamples < 20) {\n      if (display.wheelStartX == null) {\n        display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop;\n        display.wheelDX = dx; display.wheelDY = dy;\n        setTimeout(function () {\n          if (display.wheelStartX == null) { return }\n          var movedX = scroll.scrollLeft - display.wheelStartX;\n          var movedY = scroll.scrollTop - display.wheelStartY;\n          var sample = (movedY && display.wheelDY && movedY / display.wheelDY) ||\n            (movedX && display.wheelDX && movedX / display.wheelDX);\n          display.wheelStartX = display.wheelStartY = null;\n          if (!sample) { return }\n          wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1);\n          ++wheelSamples;\n        }, 200);\n      } else {\n        display.wheelDX += dx; display.wheelDY += dy;\n      }\n    }\n  }\n\n  // Selection objects are immutable. A new one is created every time\n  // the selection changes. A selection is one or more non-overlapping\n  // (and non-touching) ranges, sorted, and an integer that indicates\n  // which one is the primary selection (the one that's scrolled into\n  // view, that getCursor returns, etc).\n  var Selection = function(ranges, primIndex) {\n    this.ranges = ranges;\n    this.primIndex = primIndex;\n  };\n\n  Selection.prototype.primary = function () { return this.ranges[this.primIndex] };\n\n  Selection.prototype.equals = function (other) {\n      var this$1 = this;\n\n    if (other == this) { return true }\n    if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) { return false }\n    for (var i = 0; i < this.ranges.length; i++) {\n      var here = this$1.ranges[i], there = other.ranges[i];\n      if (!equalCursorPos(here.anchor, there.anchor) || !equalCursorPos(here.head, there.head)) { return false }\n    }\n    return true\n  };\n\n  Selection.prototype.deepCopy = function () {\n      var this$1 = this;\n\n    var out = [];\n    for (var i = 0; i < this.ranges.length; i++)\n      { out[i] = new Range(copyPos(this$1.ranges[i].anchor), copyPos(this$1.ranges[i].head)); }\n    return new Selection(out, this.primIndex)\n  };\n\n  Selection.prototype.somethingSelected = function () {\n      var this$1 = this;\n\n    for (var i = 0; i < this.ranges.length; i++)\n      { if (!this$1.ranges[i].empty()) { return true } }\n    return false\n  };\n\n  Selection.prototype.contains = function (pos, end) {\n      var this$1 = this;\n\n    if (!end) { end = pos; }\n    for (var i = 0; i < this.ranges.length; i++) {\n      var range = this$1.ranges[i];\n      if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0)\n        { return i }\n    }\n    return -1\n  };\n\n  var Range = function(anchor, head) {\n    this.anchor = anchor; this.head = head;\n  };\n\n  Range.prototype.from = function () { return minPos(this.anchor, this.head) };\n  Range.prototype.to = function () { return maxPos(this.anchor, this.head) };\n  Range.prototype.empty = function () { return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch };\n\n  // Take an unsorted, potentially overlapping set of ranges, and\n  // build a selection out of it. 'Consumes' ranges array (modifying\n  // it).\n  function normalizeSelection(cm, ranges, primIndex) {\n    var mayTouch = cm && cm.options.selectionsMayTouch;\n    var prim = ranges[primIndex];\n    ranges.sort(function (a, b) { return cmp(a.from(), b.from()); });\n    primIndex = indexOf(ranges, prim);\n    for (var i = 1; i < ranges.length; i++) {\n      var cur = ranges[i], prev = ranges[i - 1];\n      var diff = cmp(prev.to(), cur.from());\n      if (mayTouch && !cur.empty() ? diff > 0 : diff >= 0) {\n        var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to());\n        var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head;\n        if (i <= primIndex) { --primIndex; }\n        ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to));\n      }\n    }\n    return new Selection(ranges, primIndex)\n  }\n\n  function simpleSelection(anchor, head) {\n    return new Selection([new Range(anchor, head || anchor)], 0)\n  }\n\n  // Compute the position of the end of a change (its 'to' property\n  // refers to the pre-change end).\n  function changeEnd(change) {\n    if (!change.text) { return change.to }\n    return Pos(change.from.line + change.text.length - 1,\n               lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0))\n  }\n\n  // Adjust a position to refer to the post-change position of the\n  // same text, or the end of the change if the change covers it.\n  function adjustForChange(pos, change) {\n    if (cmp(pos, change.from) < 0) { return pos }\n    if (cmp(pos, change.to) <= 0) { return changeEnd(change) }\n\n    var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch;\n    if (pos.line == change.to.line) { ch += changeEnd(change).ch - change.to.ch; }\n    return Pos(line, ch)\n  }\n\n  function computeSelAfterChange(doc, change) {\n    var out = [];\n    for (var i = 0; i < doc.sel.ranges.length; i++) {\n      var range = doc.sel.ranges[i];\n      out.push(new Range(adjustForChange(range.anchor, change),\n                         adjustForChange(range.head, change)));\n    }\n    return normalizeSelection(doc.cm, out, doc.sel.primIndex)\n  }\n\n  function offsetPos(pos, old, nw) {\n    if (pos.line == old.line)\n      { return Pos(nw.line, pos.ch - old.ch + nw.ch) }\n    else\n      { return Pos(nw.line + (pos.line - old.line), pos.ch) }\n  }\n\n  // Used by replaceSelections to allow moving the selection to the\n  // start or around the replaced test. Hint may be \"start\" or \"around\".\n  function computeReplacedSel(doc, changes, hint) {\n    var out = [];\n    var oldPrev = Pos(doc.first, 0), newPrev = oldPrev;\n    for (var i = 0; i < changes.length; i++) {\n      var change = changes[i];\n      var from = offsetPos(change.from, oldPrev, newPrev);\n      var to = offsetPos(changeEnd(change), oldPrev, newPrev);\n      oldPrev = change.to;\n      newPrev = to;\n      if (hint == \"around\") {\n        var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0;\n        out[i] = new Range(inv ? to : from, inv ? from : to);\n      } else {\n        out[i] = new Range(from, from);\n      }\n    }\n    return new Selection(out, doc.sel.primIndex)\n  }\n\n  // Used to get the editor into a consistent state again when options change.\n\n  function loadMode(cm) {\n    cm.doc.mode = getMode(cm.options, cm.doc.modeOption);\n    resetModeState(cm);\n  }\n\n  function resetModeState(cm) {\n    cm.doc.iter(function (line) {\n      if (line.stateAfter) { line.stateAfter = null; }\n      if (line.styles) { line.styles = null; }\n    });\n    cm.doc.modeFrontier = cm.doc.highlightFrontier = cm.doc.first;\n    startWorker(cm, 100);\n    cm.state.modeGen++;\n    if (cm.curOp) { regChange(cm); }\n  }\n\n  // DOCUMENT DATA STRUCTURE\n\n  // By default, updates that start and end at the beginning of a line\n  // are treated specially, in order to make the association of line\n  // widgets and marker elements with the text behave more intuitive.\n  function isWholeLineUpdate(doc, change) {\n    return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == \"\" &&\n      (!doc.cm || doc.cm.options.wholeLineUpdateBefore)\n  }\n\n  // Perform a change on the document data structure.\n  function updateDoc(doc, change, markedSpans, estimateHeight$$1) {\n    function spansFor(n) {return markedSpans ? markedSpans[n] : null}\n    function update(line, text, spans) {\n      updateLine(line, text, spans, estimateHeight$$1);\n      signalLater(line, \"change\", line, change);\n    }\n    function linesFor(start, end) {\n      var result = [];\n      for (var i = start; i < end; ++i)\n        { result.push(new Line(text[i], spansFor(i), estimateHeight$$1)); }\n      return result\n    }\n\n    var from = change.from, to = change.to, text = change.text;\n    var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);\n    var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line;\n\n    // Adjust the line structure\n    if (change.full) {\n      doc.insert(0, linesFor(0, text.length));\n      doc.remove(text.length, doc.size - text.length);\n    } else if (isWholeLineUpdate(doc, change)) {\n      // This is a whole-line replace. Treated specially to make\n      // sure line objects move the way they are supposed to.\n      var added = linesFor(0, text.length - 1);\n      update(lastLine, lastLine.text, lastSpans);\n      if (nlines) { doc.remove(from.line, nlines); }\n      if (added.length) { doc.insert(from.line, added); }\n    } else if (firstLine == lastLine) {\n      if (text.length == 1) {\n        update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans);\n      } else {\n        var added$1 = linesFor(1, text.length - 1);\n        added$1.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight$$1));\n        update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));\n        doc.insert(from.line + 1, added$1);\n      }\n    } else if (text.length == 1) {\n      update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0));\n      doc.remove(from.line + 1, nlines);\n    } else {\n      update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));\n      update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans);\n      var added$2 = linesFor(1, text.length - 1);\n      if (nlines > 1) { doc.remove(from.line + 1, nlines - 1); }\n      doc.insert(from.line + 1, added$2);\n    }\n\n    signalLater(doc, \"change\", doc, change);\n  }\n\n  // Call f for all linked documents.\n  function linkedDocs(doc, f, sharedHistOnly) {\n    function propagate(doc, skip, sharedHist) {\n      if (doc.linked) { for (var i = 0; i < doc.linked.length; ++i) {\n        var rel = doc.linked[i];\n        if (rel.doc == skip) { continue }\n        var shared = sharedHist && rel.sharedHist;\n        if (sharedHistOnly && !shared) { continue }\n        f(rel.doc, shared);\n        propagate(rel.doc, doc, shared);\n      } }\n    }\n    propagate(doc, null, true);\n  }\n\n  // Attach a document to an editor.\n  function attachDoc(cm, doc) {\n    if (doc.cm) { throw new Error(\"This document is already in use.\") }\n    cm.doc = doc;\n    doc.cm = cm;\n    estimateLineHeights(cm);\n    loadMode(cm);\n    setDirectionClass(cm);\n    if (!cm.options.lineWrapping) { findMaxLine(cm); }\n    cm.options.mode = doc.modeOption;\n    regChange(cm);\n  }\n\n  function setDirectionClass(cm) {\n  (cm.doc.direction == \"rtl\" ? addClass : rmClass)(cm.display.lineDiv, \"CodeMirror-rtl\");\n  }\n\n  function directionChanged(cm) {\n    runInOp(cm, function () {\n      setDirectionClass(cm);\n      regChange(cm);\n    });\n  }\n\n  function History(startGen) {\n    // Arrays of change events and selections. Doing something adds an\n    // event to done and clears undo. Undoing moves events from done\n    // to undone, redoing moves them in the other direction.\n    this.done = []; this.undone = [];\n    this.undoDepth = Infinity;\n    // Used to track when changes can be merged into a single undo\n    // event\n    this.lastModTime = this.lastSelTime = 0;\n    this.lastOp = this.lastSelOp = null;\n    this.lastOrigin = this.lastSelOrigin = null;\n    // Used by the isClean() method\n    this.generation = this.maxGeneration = startGen || 1;\n  }\n\n  // Create a history change event from an updateDoc-style change\n  // object.\n  function historyChangeFromChange(doc, change) {\n    var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)};\n    attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);\n    linkedDocs(doc, function (doc) { return attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); }, true);\n    return histChange\n  }\n\n  // Pop all selection events off the end of a history array. Stop at\n  // a change event.\n  function clearSelectionEvents(array) {\n    while (array.length) {\n      var last = lst(array);\n      if (last.ranges) { array.pop(); }\n      else { break }\n    }\n  }\n\n  // Find the top change event in the history. Pop off selection\n  // events that are in the way.\n  function lastChangeEvent(hist, force) {\n    if (force) {\n      clearSelectionEvents(hist.done);\n      return lst(hist.done)\n    } else if (hist.done.length && !lst(hist.done).ranges) {\n      return lst(hist.done)\n    } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) {\n      hist.done.pop();\n      return lst(hist.done)\n    }\n  }\n\n  // Register a change in the history. Merges changes that are within\n  // a single operation, or are close together with an origin that\n  // allows merging (starting with \"+\") into a single event.\n  function addChangeToHistory(doc, change, selAfter, opId) {\n    var hist = doc.history;\n    hist.undone.length = 0;\n    var time = +new Date, cur;\n    var last;\n\n    if ((hist.lastOp == opId ||\n         hist.lastOrigin == change.origin && change.origin &&\n         ((change.origin.charAt(0) == \"+\" && hist.lastModTime > time - (doc.cm ? doc.cm.options.historyEventDelay : 500)) ||\n          change.origin.charAt(0) == \"*\")) &&\n        (cur = lastChangeEvent(hist, hist.lastOp == opId))) {\n      // Merge this change into the last event\n      last = lst(cur.changes);\n      if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) {\n        // Optimized case for simple insertion -- don't want to add\n        // new changesets for every character typed\n        last.to = changeEnd(change);\n      } else {\n        // Add new sub-event\n        cur.changes.push(historyChangeFromChange(doc, change));\n      }\n    } else {\n      // Can not be merged, start a new event.\n      var before = lst(hist.done);\n      if (!before || !before.ranges)\n        { pushSelectionToHistory(doc.sel, hist.done); }\n      cur = {changes: [historyChangeFromChange(doc, change)],\n             generation: hist.generation};\n      hist.done.push(cur);\n      while (hist.done.length > hist.undoDepth) {\n        hist.done.shift();\n        if (!hist.done[0].ranges) { hist.done.shift(); }\n      }\n    }\n    hist.done.push(selAfter);\n    hist.generation = ++hist.maxGeneration;\n    hist.lastModTime = hist.lastSelTime = time;\n    hist.lastOp = hist.lastSelOp = opId;\n    hist.lastOrigin = hist.lastSelOrigin = change.origin;\n\n    if (!last) { signal(doc, \"historyAdded\"); }\n  }\n\n  function selectionEventCanBeMerged(doc, origin, prev, sel) {\n    var ch = origin.charAt(0);\n    return ch == \"*\" ||\n      ch == \"+\" &&\n      prev.ranges.length == sel.ranges.length &&\n      prev.somethingSelected() == sel.somethingSelected() &&\n      new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500)\n  }\n\n  // Called whenever the selection changes, sets the new selection as\n  // the pending selection in the history, and pushes the old pending\n  // selection into the 'done' array when it was significantly\n  // different (in number of selected ranges, emptiness, or time).\n  function addSelectionToHistory(doc, sel, opId, options) {\n    var hist = doc.history, origin = options && options.origin;\n\n    // A new event is started when the previous origin does not match\n    // the current, or the origins don't allow matching. Origins\n    // starting with * are always merged, those starting with + are\n    // merged when similar and close together in time.\n    if (opId == hist.lastSelOp ||\n        (origin && hist.lastSelOrigin == origin &&\n         (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin ||\n          selectionEventCanBeMerged(doc, origin, lst(hist.done), sel))))\n      { hist.done[hist.done.length - 1] = sel; }\n    else\n      { pushSelectionToHistory(sel, hist.done); }\n\n    hist.lastSelTime = +new Date;\n    hist.lastSelOrigin = origin;\n    hist.lastSelOp = opId;\n    if (options && options.clearRedo !== false)\n      { clearSelectionEvents(hist.undone); }\n  }\n\n  function pushSelectionToHistory(sel, dest) {\n    var top = lst(dest);\n    if (!(top && top.ranges && top.equals(sel)))\n      { dest.push(sel); }\n  }\n\n  // Used to store marked span information in the history.\n  function attachLocalSpans(doc, change, from, to) {\n    var existing = change[\"spans_\" + doc.id], n = 0;\n    doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function (line) {\n      if (line.markedSpans)\n        { (existing || (existing = change[\"spans_\" + doc.id] = {}))[n] = line.markedSpans; }\n      ++n;\n    });\n  }\n\n  // When un/re-doing restores text containing marked spans, those\n  // that have been explicitly cleared should not be restored.\n  function removeClearedSpans(spans) {\n    if (!spans) { return null }\n    var out;\n    for (var i = 0; i < spans.length; ++i) {\n      if (spans[i].marker.explicitlyCleared) { if (!out) { out = spans.slice(0, i); } }\n      else if (out) { out.push(spans[i]); }\n    }\n    return !out ? spans : out.length ? out : null\n  }\n\n  // Retrieve and filter the old marked spans stored in a change event.\n  function getOldSpans(doc, change) {\n    var found = change[\"spans_\" + doc.id];\n    if (!found) { return null }\n    var nw = [];\n    for (var i = 0; i < change.text.length; ++i)\n      { nw.push(removeClearedSpans(found[i])); }\n    return nw\n  }\n\n  // Used for un/re-doing changes from the history. Combines the\n  // result of computing the existing spans with the set of spans that\n  // existed in the history (so that deleting around a span and then\n  // undoing brings back the span).\n  function mergeOldSpans(doc, change) {\n    var old = getOldSpans(doc, change);\n    var stretched = stretchSpansOverChange(doc, change);\n    if (!old) { return stretched }\n    if (!stretched) { return old }\n\n    for (var i = 0; i < old.length; ++i) {\n      var oldCur = old[i], stretchCur = stretched[i];\n      if (oldCur && stretchCur) {\n        spans: for (var j = 0; j < stretchCur.length; ++j) {\n          var span = stretchCur[j];\n          for (var k = 0; k < oldCur.length; ++k)\n            { if (oldCur[k].marker == span.marker) { continue spans } }\n          oldCur.push(span);\n        }\n      } else if (stretchCur) {\n        old[i] = stretchCur;\n      }\n    }\n    return old\n  }\n\n  // Used both to provide a JSON-safe object in .getHistory, and, when\n  // detaching a document, to split the history in two\n  function copyHistoryArray(events, newGroup, instantiateSel) {\n    var copy = [];\n    for (var i = 0; i < events.length; ++i) {\n      var event = events[i];\n      if (event.ranges) {\n        copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event);\n        continue\n      }\n      var changes = event.changes, newChanges = [];\n      copy.push({changes: newChanges});\n      for (var j = 0; j < changes.length; ++j) {\n        var change = changes[j], m = (void 0);\n        newChanges.push({from: change.from, to: change.to, text: change.text});\n        if (newGroup) { for (var prop in change) { if (m = prop.match(/^spans_(\\d+)$/)) {\n          if (indexOf(newGroup, Number(m[1])) > -1) {\n            lst(newChanges)[prop] = change[prop];\n            delete change[prop];\n          }\n        } } }\n      }\n    }\n    return copy\n  }\n\n  // The 'scroll' parameter given to many of these indicated whether\n  // the new cursor position should be scrolled into view after\n  // modifying the selection.\n\n  // If shift is held or the extend flag is set, extends a range to\n  // include a given position (and optionally a second position).\n  // Otherwise, simply returns the range between the given positions.\n  // Used for cursor motion and such.\n  function extendRange(range, head, other, extend) {\n    if (extend) {\n      var anchor = range.anchor;\n      if (other) {\n        var posBefore = cmp(head, anchor) < 0;\n        if (posBefore != (cmp(other, anchor) < 0)) {\n          anchor = head;\n          head = other;\n        } else if (posBefore != (cmp(head, other) < 0)) {\n          head = other;\n        }\n      }\n      return new Range(anchor, head)\n    } else {\n      return new Range(other || head, head)\n    }\n  }\n\n  // Extend the primary selection range, discard the rest.\n  function extendSelection(doc, head, other, options, extend) {\n    if (extend == null) { extend = doc.cm && (doc.cm.display.shift || doc.extend); }\n    setSelection(doc, new Selection([extendRange(doc.sel.primary(), head, other, extend)], 0), options);\n  }\n\n  // Extend all selections (pos is an array of selections with length\n  // equal the number of selections)\n  function extendSelections(doc, heads, options) {\n    var out = [];\n    var extend = doc.cm && (doc.cm.display.shift || doc.extend);\n    for (var i = 0; i < doc.sel.ranges.length; i++)\n      { out[i] = extendRange(doc.sel.ranges[i], heads[i], null, extend); }\n    var newSel = normalizeSelection(doc.cm, out, doc.sel.primIndex);\n    setSelection(doc, newSel, options);\n  }\n\n  // Updates a single range in the selection.\n  function replaceOneSelection(doc, i, range, options) {\n    var ranges = doc.sel.ranges.slice(0);\n    ranges[i] = range;\n    setSelection(doc, normalizeSelection(doc.cm, ranges, doc.sel.primIndex), options);\n  }\n\n  // Reset the selection to a single range.\n  function setSimpleSelection(doc, anchor, head, options) {\n    setSelection(doc, simpleSelection(anchor, head), options);\n  }\n\n  // Give beforeSelectionChange handlers a change to influence a\n  // selection update.\n  function filterSelectionChange(doc, sel, options) {\n    var obj = {\n      ranges: sel.ranges,\n      update: function(ranges) {\n        var this$1 = this;\n\n        this.ranges = [];\n        for (var i = 0; i < ranges.length; i++)\n          { this$1.ranges[i] = new Range(clipPos(doc, ranges[i].anchor),\n                                     clipPos(doc, ranges[i].head)); }\n      },\n      origin: options && options.origin\n    };\n    signal(doc, \"beforeSelectionChange\", doc, obj);\n    if (doc.cm) { signal(doc.cm, \"beforeSelectionChange\", doc.cm, obj); }\n    if (obj.ranges != sel.ranges) { return normalizeSelection(doc.cm, obj.ranges, obj.ranges.length - 1) }\n    else { return sel }\n  }\n\n  function setSelectionReplaceHistory(doc, sel, options) {\n    var done = doc.history.done, last = lst(done);\n    if (last && last.ranges) {\n      done[done.length - 1] = sel;\n      setSelectionNoUndo(doc, sel, options);\n    } else {\n      setSelection(doc, sel, options);\n    }\n  }\n\n  // Set a new selection.\n  function setSelection(doc, sel, options) {\n    setSelectionNoUndo(doc, sel, options);\n    addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options);\n  }\n\n  function setSelectionNoUndo(doc, sel, options) {\n    if (hasHandler(doc, \"beforeSelectionChange\") || doc.cm && hasHandler(doc.cm, \"beforeSelectionChange\"))\n      { sel = filterSelectionChange(doc, sel, options); }\n\n    var bias = options && options.bias ||\n      (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1);\n    setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true));\n\n    if (!(options && options.scroll === false) && doc.cm)\n      { ensureCursorVisible(doc.cm); }\n  }\n\n  function setSelectionInner(doc, sel) {\n    if (sel.equals(doc.sel)) { return }\n\n    doc.sel = sel;\n\n    if (doc.cm) {\n      doc.cm.curOp.updateInput = 1;\n      doc.cm.curOp.selectionChanged = true;\n      signalCursorActivity(doc.cm);\n    }\n    signalLater(doc, \"cursorActivity\", doc);\n  }\n\n  // Verify that the selection does not partially select any atomic\n  // marked ranges.\n  function reCheckSelection(doc) {\n    setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false));\n  }\n\n  // Return a selection that does not partially select any atomic\n  // ranges.\n  function skipAtomicInSelection(doc, sel, bias, mayClear) {\n    var out;\n    for (var i = 0; i < sel.ranges.length; i++) {\n      var range = sel.ranges[i];\n      var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i];\n      var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear);\n      var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear);\n      if (out || newAnchor != range.anchor || newHead != range.head) {\n        if (!out) { out = sel.ranges.slice(0, i); }\n        out[i] = new Range(newAnchor, newHead);\n      }\n    }\n    return out ? normalizeSelection(doc.cm, out, sel.primIndex) : sel\n  }\n\n  function skipAtomicInner(doc, pos, oldPos, dir, mayClear) {\n    var line = getLine(doc, pos.line);\n    if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) {\n      var sp = line.markedSpans[i], m = sp.marker;\n\n      // Determine if we should prevent the cursor being placed to the left/right of an atomic marker\n      // Historically this was determined using the inclusiveLeft/Right option, but the new way to control it\n      // is with selectLeft/Right\n      var preventCursorLeft = (\"selectLeft\" in m) ? !m.selectLeft : m.inclusiveLeft;\n      var preventCursorRight = (\"selectRight\" in m) ? !m.selectRight : m.inclusiveRight;\n\n      if ((sp.from == null || (preventCursorLeft ? sp.from <= pos.ch : sp.from < pos.ch)) &&\n          (sp.to == null || (preventCursorRight ? sp.to >= pos.ch : sp.to > pos.ch))) {\n        if (mayClear) {\n          signal(m, \"beforeCursorEnter\");\n          if (m.explicitlyCleared) {\n            if (!line.markedSpans) { break }\n            else {--i; continue}\n          }\n        }\n        if (!m.atomic) { continue }\n\n        if (oldPos) {\n          var near = m.find(dir < 0 ? 1 : -1), diff = (void 0);\n          if (dir < 0 ? preventCursorRight : preventCursorLeft)\n            { near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null); }\n          if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0))\n            { return skipAtomicInner(doc, near, pos, dir, mayClear) }\n        }\n\n        var far = m.find(dir < 0 ? -1 : 1);\n        if (dir < 0 ? preventCursorLeft : preventCursorRight)\n          { far = movePos(doc, far, dir, far.line == pos.line ? line : null); }\n        return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null\n      }\n    } }\n    return pos\n  }\n\n  // Ensure a given position is not inside an atomic range.\n  function skipAtomic(doc, pos, oldPos, bias, mayClear) {\n    var dir = bias || 1;\n    var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) ||\n        (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) ||\n        skipAtomicInner(doc, pos, oldPos, -dir, mayClear) ||\n        (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true));\n    if (!found) {\n      doc.cantEdit = true;\n      return Pos(doc.first, 0)\n    }\n    return found\n  }\n\n  function movePos(doc, pos, dir, line) {\n    if (dir < 0 && pos.ch == 0) {\n      if (pos.line > doc.first) { return clipPos(doc, Pos(pos.line - 1)) }\n      else { return null }\n    } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) {\n      if (pos.line < doc.first + doc.size - 1) { return Pos(pos.line + 1, 0) }\n      else { return null }\n    } else {\n      return new Pos(pos.line, pos.ch + dir)\n    }\n  }\n\n  function selectAll(cm) {\n    cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll);\n  }\n\n  // UPDATING\n\n  // Allow \"beforeChange\" event handlers to influence a change\n  function filterChange(doc, change, update) {\n    var obj = {\n      canceled: false,\n      from: change.from,\n      to: change.to,\n      text: change.text,\n      origin: change.origin,\n      cancel: function () { return obj.canceled = true; }\n    };\n    if (update) { obj.update = function (from, to, text, origin) {\n      if (from) { obj.from = clipPos(doc, from); }\n      if (to) { obj.to = clipPos(doc, to); }\n      if (text) { obj.text = text; }\n      if (origin !== undefined) { obj.origin = origin; }\n    }; }\n    signal(doc, \"beforeChange\", doc, obj);\n    if (doc.cm) { signal(doc.cm, \"beforeChange\", doc.cm, obj); }\n\n    if (obj.canceled) {\n      if (doc.cm) { doc.cm.curOp.updateInput = 2; }\n      return null\n    }\n    return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}\n  }\n\n  // Apply a change to a document, and add it to the document's\n  // history, and propagating it to all linked documents.\n  function makeChange(doc, change, ignoreReadOnly) {\n    if (doc.cm) {\n      if (!doc.cm.curOp) { return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly) }\n      if (doc.cm.state.suppressEdits) { return }\n    }\n\n    if (hasHandler(doc, \"beforeChange\") || doc.cm && hasHandler(doc.cm, \"beforeChange\")) {\n      change = filterChange(doc, change, true);\n      if (!change) { return }\n    }\n\n    // Possibly split or suppress the update based on the presence\n    // of read-only spans in its range.\n    var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to);\n    if (split) {\n      for (var i = split.length - 1; i >= 0; --i)\n        { makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [\"\"] : change.text, origin: change.origin}); }\n    } else {\n      makeChangeInner(doc, change);\n    }\n  }\n\n  function makeChangeInner(doc, change) {\n    if (change.text.length == 1 && change.text[0] == \"\" && cmp(change.from, change.to) == 0) { return }\n    var selAfter = computeSelAfterChange(doc, change);\n    addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN);\n\n    makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change));\n    var rebased = [];\n\n    linkedDocs(doc, function (doc, sharedHist) {\n      if (!sharedHist && indexOf(rebased, doc.history) == -1) {\n        rebaseHist(doc.history, change);\n        rebased.push(doc.history);\n      }\n      makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change));\n    });\n  }\n\n  // Revert a change stored in a document's history.\n  function makeChangeFromHistory(doc, type, allowSelectionOnly) {\n    var suppress = doc.cm && doc.cm.state.suppressEdits;\n    if (suppress && !allowSelectionOnly) { return }\n\n    var hist = doc.history, event, selAfter = doc.sel;\n    var source = type == \"undo\" ? hist.done : hist.undone, dest = type == \"undo\" ? hist.undone : hist.done;\n\n    // Verify that there is a useable event (so that ctrl-z won't\n    // needlessly clear selection events)\n    var i = 0;\n    for (; i < source.length; i++) {\n      event = source[i];\n      if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges)\n        { break }\n    }\n    if (i == source.length) { return }\n    hist.lastOrigin = hist.lastSelOrigin = null;\n\n    for (;;) {\n      event = source.pop();\n      if (event.ranges) {\n        pushSelectionToHistory(event, dest);\n        if (allowSelectionOnly && !event.equals(doc.sel)) {\n          setSelection(doc, event, {clearRedo: false});\n          return\n        }\n        selAfter = event;\n      } else if (suppress) {\n        source.push(event);\n        return\n      } else { break }\n    }\n\n    // Build up a reverse change object to add to the opposite history\n    // stack (redo when undoing, and vice versa).\n    var antiChanges = [];\n    pushSelectionToHistory(selAfter, dest);\n    dest.push({changes: antiChanges, generation: hist.generation});\n    hist.generation = event.generation || ++hist.maxGeneration;\n\n    var filter = hasHandler(doc, \"beforeChange\") || doc.cm && hasHandler(doc.cm, \"beforeChange\");\n\n    var loop = function ( i ) {\n      var change = event.changes[i];\n      change.origin = type;\n      if (filter && !filterChange(doc, change, false)) {\n        source.length = 0;\n        return {}\n      }\n\n      antiChanges.push(historyChangeFromChange(doc, change));\n\n      var after = i ? computeSelAfterChange(doc, change) : lst(source);\n      makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change));\n      if (!i && doc.cm) { doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}); }\n      var rebased = [];\n\n      // Propagate to the linked documents\n      linkedDocs(doc, function (doc, sharedHist) {\n        if (!sharedHist && indexOf(rebased, doc.history) == -1) {\n          rebaseHist(doc.history, change);\n          rebased.push(doc.history);\n        }\n        makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change));\n      });\n    };\n\n    for (var i$1 = event.changes.length - 1; i$1 >= 0; --i$1) {\n      var returned = loop( i$1 );\n\n      if ( returned ) return returned.v;\n    }\n  }\n\n  // Sub-views need their line numbers shifted when text is added\n  // above or below them in the parent document.\n  function shiftDoc(doc, distance) {\n    if (distance == 0) { return }\n    doc.first += distance;\n    doc.sel = new Selection(map(doc.sel.ranges, function (range) { return new Range(\n      Pos(range.anchor.line + distance, range.anchor.ch),\n      Pos(range.head.line + distance, range.head.ch)\n    ); }), doc.sel.primIndex);\n    if (doc.cm) {\n      regChange(doc.cm, doc.first, doc.first - distance, distance);\n      for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++)\n        { regLineChange(doc.cm, l, \"gutter\"); }\n    }\n  }\n\n  // More lower-level change function, handling only a single document\n  // (not linked ones).\n  function makeChangeSingleDoc(doc, change, selAfter, spans) {\n    if (doc.cm && !doc.cm.curOp)\n      { return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans) }\n\n    if (change.to.line < doc.first) {\n      shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line));\n      return\n    }\n    if (change.from.line > doc.lastLine()) { return }\n\n    // Clip the change to the size of this doc\n    if (change.from.line < doc.first) {\n      var shift = change.text.length - 1 - (doc.first - change.from.line);\n      shiftDoc(doc, shift);\n      change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch),\n                text: [lst(change.text)], origin: change.origin};\n    }\n    var last = doc.lastLine();\n    if (change.to.line > last) {\n      change = {from: change.from, to: Pos(last, getLine(doc, last).text.length),\n                text: [change.text[0]], origin: change.origin};\n    }\n\n    change.removed = getBetween(doc, change.from, change.to);\n\n    if (!selAfter) { selAfter = computeSelAfterChange(doc, change); }\n    if (doc.cm) { makeChangeSingleDocInEditor(doc.cm, change, spans); }\n    else { updateDoc(doc, change, spans); }\n    setSelectionNoUndo(doc, selAfter, sel_dontScroll);\n\n    if (doc.cantEdit && skipAtomic(doc, Pos(doc.firstLine(), 0)))\n      { doc.cantEdit = false; }\n  }\n\n  // Handle the interaction of a change to a document with the editor\n  // that this document is part of.\n  function makeChangeSingleDocInEditor(cm, change, spans) {\n    var doc = cm.doc, display = cm.display, from = change.from, to = change.to;\n\n    var recomputeMaxLength = false, checkWidthStart = from.line;\n    if (!cm.options.lineWrapping) {\n      checkWidthStart = lineNo(visualLine(getLine(doc, from.line)));\n      doc.iter(checkWidthStart, to.line + 1, function (line) {\n        if (line == display.maxLine) {\n          recomputeMaxLength = true;\n          return true\n        }\n      });\n    }\n\n    if (doc.sel.contains(change.from, change.to) > -1)\n      { signalCursorActivity(cm); }\n\n    updateDoc(doc, change, spans, estimateHeight(cm));\n\n    if (!cm.options.lineWrapping) {\n      doc.iter(checkWidthStart, from.line + change.text.length, function (line) {\n        var len = lineLength(line);\n        if (len > display.maxLineLength) {\n          display.maxLine = line;\n          display.maxLineLength = len;\n          display.maxLineChanged = true;\n          recomputeMaxLength = false;\n        }\n      });\n      if (recomputeMaxLength) { cm.curOp.updateMaxLine = true; }\n    }\n\n    retreatFrontier(doc, from.line);\n    startWorker(cm, 400);\n\n    var lendiff = change.text.length - (to.line - from.line) - 1;\n    // Remember that these lines changed, for updating the display\n    if (change.full)\n      { regChange(cm); }\n    else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change))\n      { regLineChange(cm, from.line, \"text\"); }\n    else\n      { regChange(cm, from.line, to.line + 1, lendiff); }\n\n    var changesHandler = hasHandler(cm, \"changes\"), changeHandler = hasHandler(cm, \"change\");\n    if (changeHandler || changesHandler) {\n      var obj = {\n        from: from, to: to,\n        text: change.text,\n        removed: change.removed,\n        origin: change.origin\n      };\n      if (changeHandler) { signalLater(cm, \"change\", cm, obj); }\n      if (changesHandler) { (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj); }\n    }\n    cm.display.selForContextMenu = null;\n  }\n\n  function replaceRange(doc, code, from, to, origin) {\n    var assign;\n\n    if (!to) { to = from; }\n    if (cmp(to, from) < 0) { (assign = [to, from], from = assign[0], to = assign[1]); }\n    if (typeof code == \"string\") { code = doc.splitLines(code); }\n    makeChange(doc, {from: from, to: to, text: code, origin: origin});\n  }\n\n  // Rebasing/resetting history to deal with externally-sourced changes\n\n  function rebaseHistSelSingle(pos, from, to, diff) {\n    if (to < pos.line) {\n      pos.line += diff;\n    } else if (from < pos.line) {\n      pos.line = from;\n      pos.ch = 0;\n    }\n  }\n\n  // Tries to rebase an array of history events given a change in the\n  // document. If the change touches the same lines as the event, the\n  // event, and everything 'behind' it, is discarded. If the change is\n  // before the event, the event's positions are updated. Uses a\n  // copy-on-write scheme for the positions, to avoid having to\n  // reallocate them all on every rebase, but also avoid problems with\n  // shared position objects being unsafely updated.\n  function rebaseHistArray(array, from, to, diff) {\n    for (var i = 0; i < array.length; ++i) {\n      var sub = array[i], ok = true;\n      if (sub.ranges) {\n        if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; }\n        for (var j = 0; j < sub.ranges.length; j++) {\n          rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff);\n          rebaseHistSelSingle(sub.ranges[j].head, from, to, diff);\n        }\n        continue\n      }\n      for (var j$1 = 0; j$1 < sub.changes.length; ++j$1) {\n        var cur = sub.changes[j$1];\n        if (to < cur.from.line) {\n          cur.from = Pos(cur.from.line + diff, cur.from.ch);\n          cur.to = Pos(cur.to.line + diff, cur.to.ch);\n        } else if (from <= cur.to.line) {\n          ok = false;\n          break\n        }\n      }\n      if (!ok) {\n        array.splice(0, i + 1);\n        i = 0;\n      }\n    }\n  }\n\n  function rebaseHist(hist, change) {\n    var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1;\n    rebaseHistArray(hist.done, from, to, diff);\n    rebaseHistArray(hist.undone, from, to, diff);\n  }\n\n  // Utility for applying a change to a line by handle or number,\n  // returning the number and optionally registering the line as\n  // changed.\n  function changeLine(doc, handle, changeType, op) {\n    var no = handle, line = handle;\n    if (typeof handle == \"number\") { line = getLine(doc, clipLine(doc, handle)); }\n    else { no = lineNo(handle); }\n    if (no == null) { return null }\n    if (op(line, no) && doc.cm) { regLineChange(doc.cm, no, changeType); }\n    return line\n  }\n\n  // The document is represented as a BTree consisting of leaves, with\n  // chunk of lines in them, and branches, with up to ten leaves or\n  // other branch nodes below them. The top node is always a branch\n  // node, and is the document object itself (meaning it has\n  // additional methods and properties).\n  //\n  // All nodes have parent links. The tree is used both to go from\n  // line numbers to line objects, and to go from objects to numbers.\n  // It also indexes by height, and is used to convert between height\n  // and line object, and to find the total height of the document.\n  //\n  // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html\n\n  function LeafChunk(lines) {\n    var this$1 = this;\n\n    this.lines = lines;\n    this.parent = null;\n    var height = 0;\n    for (var i = 0; i < lines.length; ++i) {\n      lines[i].parent = this$1;\n      height += lines[i].height;\n    }\n    this.height = height;\n  }\n\n  LeafChunk.prototype = {\n    chunkSize: function() { return this.lines.length },\n\n    // Remove the n lines at offset 'at'.\n    removeInner: function(at, n) {\n      var this$1 = this;\n\n      for (var i = at, e = at + n; i < e; ++i) {\n        var line = this$1.lines[i];\n        this$1.height -= line.height;\n        cleanUpLine(line);\n        signalLater(line, \"delete\");\n      }\n      this.lines.splice(at, n);\n    },\n\n    // Helper used to collapse a small branch into a single leaf.\n    collapse: function(lines) {\n      lines.push.apply(lines, this.lines);\n    },\n\n    // Insert the given array of lines at offset 'at', count them as\n    // having the given height.\n    insertInner: function(at, lines, height) {\n      var this$1 = this;\n\n      this.height += height;\n      this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));\n      for (var i = 0; i < lines.length; ++i) { lines[i].parent = this$1; }\n    },\n\n    // Used to iterate over a part of the tree.\n    iterN: function(at, n, op) {\n      var this$1 = this;\n\n      for (var e = at + n; at < e; ++at)\n        { if (op(this$1.lines[at])) { return true } }\n    }\n  };\n\n  function BranchChunk(children) {\n    var this$1 = this;\n\n    this.children = children;\n    var size = 0, height = 0;\n    for (var i = 0; i < children.length; ++i) {\n      var ch = children[i];\n      size += ch.chunkSize(); height += ch.height;\n      ch.parent = this$1;\n    }\n    this.size = size;\n    this.height = height;\n    this.parent = null;\n  }\n\n  BranchChunk.prototype = {\n    chunkSize: function() { return this.size },\n\n    removeInner: function(at, n) {\n      var this$1 = this;\n\n      this.size -= n;\n      for (var i = 0; i < this.children.length; ++i) {\n        var child = this$1.children[i], sz = child.chunkSize();\n        if (at < sz) {\n          var rm = Math.min(n, sz - at), oldHeight = child.height;\n          child.removeInner(at, rm);\n          this$1.height -= oldHeight - child.height;\n          if (sz == rm) { this$1.children.splice(i--, 1); child.parent = null; }\n          if ((n -= rm) == 0) { break }\n          at = 0;\n        } else { at -= sz; }\n      }\n      // If the result is smaller than 25 lines, ensure that it is a\n      // single leaf node.\n      if (this.size - n < 25 &&\n          (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) {\n        var lines = [];\n        this.collapse(lines);\n        this.children = [new LeafChunk(lines)];\n        this.children[0].parent = this;\n      }\n    },\n\n    collapse: function(lines) {\n      var this$1 = this;\n\n      for (var i = 0; i < this.children.length; ++i) { this$1.children[i].collapse(lines); }\n    },\n\n    insertInner: function(at, lines, height) {\n      var this$1 = this;\n\n      this.size += lines.length;\n      this.height += height;\n      for (var i = 0; i < this.children.length; ++i) {\n        var child = this$1.children[i], sz = child.chunkSize();\n        if (at <= sz) {\n          child.insertInner(at, lines, height);\n          if (child.lines && child.lines.length > 50) {\n            // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced.\n            // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest.\n            var remaining = child.lines.length % 25 + 25;\n            for (var pos = remaining; pos < child.lines.length;) {\n              var leaf = new LeafChunk(child.lines.slice(pos, pos += 25));\n              child.height -= leaf.height;\n              this$1.children.splice(++i, 0, leaf);\n              leaf.parent = this$1;\n            }\n            child.lines = child.lines.slice(0, remaining);\n            this$1.maybeSpill();\n          }\n          break\n        }\n        at -= sz;\n      }\n    },\n\n    // When a node has grown, check whether it should be split.\n    maybeSpill: function() {\n      if (this.children.length <= 10) { return }\n      var me = this;\n      do {\n        var spilled = me.children.splice(me.children.length - 5, 5);\n        var sibling = new BranchChunk(spilled);\n        if (!me.parent) { // Become the parent node\n          var copy = new BranchChunk(me.children);\n          copy.parent = me;\n          me.children = [copy, sibling];\n          me = copy;\n       } else {\n          me.size -= sibling.size;\n          me.height -= sibling.height;\n          var myIndex = indexOf(me.parent.children, me);\n          me.parent.children.splice(myIndex + 1, 0, sibling);\n        }\n        sibling.parent = me.parent;\n      } while (me.children.length > 10)\n      me.parent.maybeSpill();\n    },\n\n    iterN: function(at, n, op) {\n      var this$1 = this;\n\n      for (var i = 0; i < this.children.length; ++i) {\n        var child = this$1.children[i], sz = child.chunkSize();\n        if (at < sz) {\n          var used = Math.min(n, sz - at);\n          if (child.iterN(at, used, op)) { return true }\n          if ((n -= used) == 0) { break }\n          at = 0;\n        } else { at -= sz; }\n      }\n    }\n  };\n\n  // Line widgets are block elements displayed above or below a line.\n\n  var LineWidget = function(doc, node, options) {\n    var this$1 = this;\n\n    if (options) { for (var opt in options) { if (options.hasOwnProperty(opt))\n      { this$1[opt] = options[opt]; } } }\n    this.doc = doc;\n    this.node = node;\n  };\n\n  LineWidget.prototype.clear = function () {\n      var this$1 = this;\n\n    var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line);\n    if (no == null || !ws) { return }\n    for (var i = 0; i < ws.length; ++i) { if (ws[i] == this$1) { ws.splice(i--, 1); } }\n    if (!ws.length) { line.widgets = null; }\n    var height = widgetHeight(this);\n    updateLineHeight(line, Math.max(0, line.height - height));\n    if (cm) {\n      runInOp(cm, function () {\n        adjustScrollWhenAboveVisible(cm, line, -height);\n        regLineChange(cm, no, \"widget\");\n      });\n      signalLater(cm, \"lineWidgetCleared\", cm, this, no);\n    }\n  };\n\n  LineWidget.prototype.changed = function () {\n      var this$1 = this;\n\n    var oldH = this.height, cm = this.doc.cm, line = this.line;\n    this.height = null;\n    var diff = widgetHeight(this) - oldH;\n    if (!diff) { return }\n    if (!lineIsHidden(this.doc, line)) { updateLineHeight(line, line.height + diff); }\n    if (cm) {\n      runInOp(cm, function () {\n        cm.curOp.forceUpdate = true;\n        adjustScrollWhenAboveVisible(cm, line, diff);\n        signalLater(cm, \"lineWidgetChanged\", cm, this$1, lineNo(line));\n      });\n    }\n  };\n  eventMixin(LineWidget);\n\n  function adjustScrollWhenAboveVisible(cm, line, diff) {\n    if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop))\n      { addToScrollTop(cm, diff); }\n  }\n\n  function addLineWidget(doc, handle, node, options) {\n    var widget = new LineWidget(doc, node, options);\n    var cm = doc.cm;\n    if (cm && widget.noHScroll) { cm.display.alignWidgets = true; }\n    changeLine(doc, handle, \"widget\", function (line) {\n      var widgets = line.widgets || (line.widgets = []);\n      if (widget.insertAt == null) { widgets.push(widget); }\n      else { widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); }\n      widget.line = line;\n      if (cm && !lineIsHidden(doc, line)) {\n        var aboveVisible = heightAtLine(line) < doc.scrollTop;\n        updateLineHeight(line, line.height + widgetHeight(widget));\n        if (aboveVisible) { addToScrollTop(cm, widget.height); }\n        cm.curOp.forceUpdate = true;\n      }\n      return true\n    });\n    if (cm) { signalLater(cm, \"lineWidgetAdded\", cm, widget, typeof handle == \"number\" ? handle : lineNo(handle)); }\n    return widget\n  }\n\n  // TEXTMARKERS\n\n  // Created with markText and setBookmark methods. A TextMarker is a\n  // handle that can be used to clear or find a marked position in the\n  // document. Line objects hold arrays (markedSpans) containing\n  // {from, to, marker} object pointing to such marker objects, and\n  // indicating that such a marker is present on that line. Multiple\n  // lines may point to the same marker when it spans across lines.\n  // The spans will have null for their from/to properties when the\n  // marker continues beyond the start/end of the line. Markers have\n  // links back to the lines they currently touch.\n\n  // Collapsed markers have unique ids, in order to be able to order\n  // them, which is needed for uniquely determining an outer marker\n  // when they overlap (they may nest, but not partially overlap).\n  var nextMarkerId = 0;\n\n  var TextMarker = function(doc, type) {\n    this.lines = [];\n    this.type = type;\n    this.doc = doc;\n    this.id = ++nextMarkerId;\n  };\n\n  // Clear the marker.\n  TextMarker.prototype.clear = function () {\n      var this$1 = this;\n\n    if (this.explicitlyCleared) { return }\n    var cm = this.doc.cm, withOp = cm && !cm.curOp;\n    if (withOp) { startOperation(cm); }\n    if (hasHandler(this, \"clear\")) {\n      var found = this.find();\n      if (found) { signalLater(this, \"clear\", found.from, found.to); }\n    }\n    var min = null, max = null;\n    for (var i = 0; i < this.lines.length; ++i) {\n      var line = this$1.lines[i];\n      var span = getMarkedSpanFor(line.markedSpans, this$1);\n      if (cm && !this$1.collapsed) { regLineChange(cm, lineNo(line), \"text\"); }\n      else if (cm) {\n        if (span.to != null) { max = lineNo(line); }\n        if (span.from != null) { min = lineNo(line); }\n      }\n      line.markedSpans = removeMarkedSpan(line.markedSpans, span);\n      if (span.from == null && this$1.collapsed && !lineIsHidden(this$1.doc, line) && cm)\n        { updateLineHeight(line, textHeight(cm.display)); }\n    }\n    if (cm && this.collapsed && !cm.options.lineWrapping) { for (var i$1 = 0; i$1 < this.lines.length; ++i$1) {\n      var visual = visualLine(this$1.lines[i$1]), len = lineLength(visual);\n      if (len > cm.display.maxLineLength) {\n        cm.display.maxLine = visual;\n        cm.display.maxLineLength = len;\n        cm.display.maxLineChanged = true;\n      }\n    } }\n\n    if (min != null && cm && this.collapsed) { regChange(cm, min, max + 1); }\n    this.lines.length = 0;\n    this.explicitlyCleared = true;\n    if (this.atomic && this.doc.cantEdit) {\n      this.doc.cantEdit = false;\n      if (cm) { reCheckSelection(cm.doc); }\n    }\n    if (cm) { signalLater(cm, \"markerCleared\", cm, this, min, max); }\n    if (withOp) { endOperation(cm); }\n    if (this.parent) { this.parent.clear(); }\n  };\n\n  // Find the position of the marker in the document. Returns a {from,\n  // to} object by default. Side can be passed to get a specific side\n  // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the\n  // Pos objects returned contain a line object, rather than a line\n  // number (used to prevent looking up the same line twice).\n  TextMarker.prototype.find = function (side, lineObj) {\n      var this$1 = this;\n\n    if (side == null && this.type == \"bookmark\") { side = 1; }\n    var from, to;\n    for (var i = 0; i < this.lines.length; ++i) {\n      var line = this$1.lines[i];\n      var span = getMarkedSpanFor(line.markedSpans, this$1);\n      if (span.from != null) {\n        from = Pos(lineObj ? line : lineNo(line), span.from);\n        if (side == -1) { return from }\n      }\n      if (span.to != null) {\n        to = Pos(lineObj ? line : lineNo(line), span.to);\n        if (side == 1) { return to }\n      }\n    }\n    return from && {from: from, to: to}\n  };\n\n  // Signals that the marker's widget changed, and surrounding layout\n  // should be recomputed.\n  TextMarker.prototype.changed = function () {\n      var this$1 = this;\n\n    var pos = this.find(-1, true), widget = this, cm = this.doc.cm;\n    if (!pos || !cm) { return }\n    runInOp(cm, function () {\n      var line = pos.line, lineN = lineNo(pos.line);\n      var view = findViewForLine(cm, lineN);\n      if (view) {\n        clearLineMeasurementCacheFor(view);\n        cm.curOp.selectionChanged = cm.curOp.forceUpdate = true;\n      }\n      cm.curOp.updateMaxLine = true;\n      if (!lineIsHidden(widget.doc, line) && widget.height != null) {\n        var oldHeight = widget.height;\n        widget.height = null;\n        var dHeight = widgetHeight(widget) - oldHeight;\n        if (dHeight)\n          { updateLineHeight(line, line.height + dHeight); }\n      }\n      signalLater(cm, \"markerChanged\", cm, this$1);\n    });\n  };\n\n  TextMarker.prototype.attachLine = function (line) {\n    if (!this.lines.length && this.doc.cm) {\n      var op = this.doc.cm.curOp;\n      if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1)\n        { (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); }\n    }\n    this.lines.push(line);\n  };\n\n  TextMarker.prototype.detachLine = function (line) {\n    this.lines.splice(indexOf(this.lines, line), 1);\n    if (!this.lines.length && this.doc.cm) {\n      var op = this.doc.cm.curOp\n      ;(op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this);\n    }\n  };\n  eventMixin(TextMarker);\n\n  // Create a marker, wire it up to the right lines, and\n  function markText(doc, from, to, options, type) {\n    // Shared markers (across linked documents) are handled separately\n    // (markTextShared will call out to this again, once per\n    // document).\n    if (options && options.shared) { return markTextShared(doc, from, to, options, type) }\n    // Ensure we are in an operation.\n    if (doc.cm && !doc.cm.curOp) { return operation(doc.cm, markText)(doc, from, to, options, type) }\n\n    var marker = new TextMarker(doc, type), diff = cmp(from, to);\n    if (options) { copyObj(options, marker, false); }\n    // Don't connect empty markers unless clearWhenEmpty is false\n    if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false)\n      { return marker }\n    if (marker.replacedWith) {\n      // Showing up as a widget implies collapsed (widget replaces text)\n      marker.collapsed = true;\n      marker.widgetNode = eltP(\"span\", [marker.replacedWith], \"CodeMirror-widget\");\n      if (!options.handleMouseEvents) { marker.widgetNode.setAttribute(\"cm-ignore-events\", \"true\"); }\n      if (options.insertLeft) { marker.widgetNode.insertLeft = true; }\n    }\n    if (marker.collapsed) {\n      if (conflictingCollapsedRange(doc, from.line, from, to, marker) ||\n          from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker))\n        { throw new Error(\"Inserting collapsed marker partially overlapping an existing one\") }\n      seeCollapsedSpans();\n    }\n\n    if (marker.addToHistory)\n      { addChangeToHistory(doc, {from: from, to: to, origin: \"markText\"}, doc.sel, NaN); }\n\n    var curLine = from.line, cm = doc.cm, updateMaxLine;\n    doc.iter(curLine, to.line + 1, function (line) {\n      if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine)\n        { updateMaxLine = true; }\n      if (marker.collapsed && curLine != from.line) { updateLineHeight(line, 0); }\n      addMarkedSpan(line, new MarkedSpan(marker,\n                                         curLine == from.line ? from.ch : null,\n                                         curLine == to.line ? to.ch : null));\n      ++curLine;\n    });\n    // lineIsHidden depends on the presence of the spans, so needs a second pass\n    if (marker.collapsed) { doc.iter(from.line, to.line + 1, function (line) {\n      if (lineIsHidden(doc, line)) { updateLineHeight(line, 0); }\n    }); }\n\n    if (marker.clearOnEnter) { on(marker, \"beforeCursorEnter\", function () { return marker.clear(); }); }\n\n    if (marker.readOnly) {\n      seeReadOnlySpans();\n      if (doc.history.done.length || doc.history.undone.length)\n        { doc.clearHistory(); }\n    }\n    if (marker.collapsed) {\n      marker.id = ++nextMarkerId;\n      marker.atomic = true;\n    }\n    if (cm) {\n      // Sync editor state\n      if (updateMaxLine) { cm.curOp.updateMaxLine = true; }\n      if (marker.collapsed)\n        { regChange(cm, from.line, to.line + 1); }\n      else if (marker.className || marker.startStyle || marker.endStyle || marker.css ||\n               marker.attributes || marker.title)\n        { for (var i = from.line; i <= to.line; i++) { regLineChange(cm, i, \"text\"); } }\n      if (marker.atomic) { reCheckSelection(cm.doc); }\n      signalLater(cm, \"markerAdded\", cm, marker);\n    }\n    return marker\n  }\n\n  // SHARED TEXTMARKERS\n\n  // A shared marker spans multiple linked documents. It is\n  // implemented as a meta-marker-object controlling multiple normal\n  // markers.\n  var SharedTextMarker = function(markers, primary) {\n    var this$1 = this;\n\n    this.markers = markers;\n    this.primary = primary;\n    for (var i = 0; i < markers.length; ++i)\n      { markers[i].parent = this$1; }\n  };\n\n  SharedTextMarker.prototype.clear = function () {\n      var this$1 = this;\n\n    if (this.explicitlyCleared) { return }\n    this.explicitlyCleared = true;\n    for (var i = 0; i < this.markers.length; ++i)\n      { this$1.markers[i].clear(); }\n    signalLater(this, \"clear\");\n  };\n\n  SharedTextMarker.prototype.find = function (side, lineObj) {\n    return this.primary.find(side, lineObj)\n  };\n  eventMixin(SharedTextMarker);\n\n  function markTextShared(doc, from, to, options, type) {\n    options = copyObj(options);\n    options.shared = false;\n    var markers = [markText(doc, from, to, options, type)], primary = markers[0];\n    var widget = options.widgetNode;\n    linkedDocs(doc, function (doc) {\n      if (widget) { options.widgetNode = widget.cloneNode(true); }\n      markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type));\n      for (var i = 0; i < doc.linked.length; ++i)\n        { if (doc.linked[i].isParent) { return } }\n      primary = lst(markers);\n    });\n    return new SharedTextMarker(markers, primary)\n  }\n\n  function findSharedMarkers(doc) {\n    return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), function (m) { return m.parent; })\n  }\n\n  function copySharedMarkers(doc, markers) {\n    for (var i = 0; i < markers.length; i++) {\n      var marker = markers[i], pos = marker.find();\n      var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to);\n      if (cmp(mFrom, mTo)) {\n        var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type);\n        marker.markers.push(subMark);\n        subMark.parent = marker;\n      }\n    }\n  }\n\n  function detachSharedMarkers(markers) {\n    var loop = function ( i ) {\n      var marker = markers[i], linked = [marker.primary.doc];\n      linkedDocs(marker.primary.doc, function (d) { return linked.push(d); });\n      for (var j = 0; j < marker.markers.length; j++) {\n        var subMarker = marker.markers[j];\n        if (indexOf(linked, subMarker.doc) == -1) {\n          subMarker.parent = null;\n          marker.markers.splice(j--, 1);\n        }\n      }\n    };\n\n    for (var i = 0; i < markers.length; i++) loop( i );\n  }\n\n  var nextDocId = 0;\n  var Doc = function(text, mode, firstLine, lineSep, direction) {\n    if (!(this instanceof Doc)) { return new Doc(text, mode, firstLine, lineSep, direction) }\n    if (firstLine == null) { firstLine = 0; }\n\n    BranchChunk.call(this, [new LeafChunk([new Line(\"\", null)])]);\n    this.first = firstLine;\n    this.scrollTop = this.scrollLeft = 0;\n    this.cantEdit = false;\n    this.cleanGeneration = 1;\n    this.modeFrontier = this.highlightFrontier = firstLine;\n    var start = Pos(firstLine, 0);\n    this.sel = simpleSelection(start);\n    this.history = new History(null);\n    this.id = ++nextDocId;\n    this.modeOption = mode;\n    this.lineSep = lineSep;\n    this.direction = (direction == \"rtl\") ? \"rtl\" : \"ltr\";\n    this.extend = false;\n\n    if (typeof text == \"string\") { text = this.splitLines(text); }\n    updateDoc(this, {from: start, to: start, text: text});\n    setSelection(this, simpleSelection(start), sel_dontScroll);\n  };\n\n  Doc.prototype = createObj(BranchChunk.prototype, {\n    constructor: Doc,\n    // Iterate over the document. Supports two forms -- with only one\n    // argument, it calls that for each line in the document. With\n    // three, it iterates over the range given by the first two (with\n    // the second being non-inclusive).\n    iter: function(from, to, op) {\n      if (op) { this.iterN(from - this.first, to - from, op); }\n      else { this.iterN(this.first, this.first + this.size, from); }\n    },\n\n    // Non-public interface for adding and removing lines.\n    insert: function(at, lines) {\n      var height = 0;\n      for (var i = 0; i < lines.length; ++i) { height += lines[i].height; }\n      this.insertInner(at - this.first, lines, height);\n    },\n    remove: function(at, n) { this.removeInner(at - this.first, n); },\n\n    // From here, the methods are part of the public interface. Most\n    // are also available from CodeMirror (editor) instances.\n\n    getValue: function(lineSep) {\n      var lines = getLines(this, this.first, this.first + this.size);\n      if (lineSep === false) { return lines }\n      return lines.join(lineSep || this.lineSeparator())\n    },\n    setValue: docMethodOp(function(code) {\n      var top = Pos(this.first, 0), last = this.first + this.size - 1;\n      makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length),\n                        text: this.splitLines(code), origin: \"setValue\", full: true}, true);\n      if (this.cm) { scrollToCoords(this.cm, 0, 0); }\n      setSelection(this, simpleSelection(top), sel_dontScroll);\n    }),\n    replaceRange: function(code, from, to, origin) {\n      from = clipPos(this, from);\n      to = to ? clipPos(this, to) : from;\n      replaceRange(this, code, from, to, origin);\n    },\n    getRange: function(from, to, lineSep) {\n      var lines = getBetween(this, clipPos(this, from), clipPos(this, to));\n      if (lineSep === false) { return lines }\n      return lines.join(lineSep || this.lineSeparator())\n    },\n\n    getLine: function(line) {var l = this.getLineHandle(line); return l && l.text},\n\n    getLineHandle: function(line) {if (isLine(this, line)) { return getLine(this, line) }},\n    getLineNumber: function(line) {return lineNo(line)},\n\n    getLineHandleVisualStart: function(line) {\n      if (typeof line == \"number\") { line = getLine(this, line); }\n      return visualLine(line)\n    },\n\n    lineCount: function() {return this.size},\n    firstLine: function() {return this.first},\n    lastLine: function() {return this.first + this.size - 1},\n\n    clipPos: function(pos) {return clipPos(this, pos)},\n\n    getCursor: function(start) {\n      var range$$1 = this.sel.primary(), pos;\n      if (start == null || start == \"head\") { pos = range$$1.head; }\n      else if (start == \"anchor\") { pos = range$$1.anchor; }\n      else if (start == \"end\" || start == \"to\" || start === false) { pos = range$$1.to(); }\n      else { pos = range$$1.from(); }\n      return pos\n    },\n    listSelections: function() { return this.sel.ranges },\n    somethingSelected: function() {return this.sel.somethingSelected()},\n\n    setCursor: docMethodOp(function(line, ch, options) {\n      setSimpleSelection(this, clipPos(this, typeof line == \"number\" ? Pos(line, ch || 0) : line), null, options);\n    }),\n    setSelection: docMethodOp(function(anchor, head, options) {\n      setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options);\n    }),\n    extendSelection: docMethodOp(function(head, other, options) {\n      extendSelection(this, clipPos(this, head), other && clipPos(this, other), options);\n    }),\n    extendSelections: docMethodOp(function(heads, options) {\n      extendSelections(this, clipPosArray(this, heads), options);\n    }),\n    extendSelectionsBy: docMethodOp(function(f, options) {\n      var heads = map(this.sel.ranges, f);\n      extendSelections(this, clipPosArray(this, heads), options);\n    }),\n    setSelections: docMethodOp(function(ranges, primary, options) {\n      var this$1 = this;\n\n      if (!ranges.length) { return }\n      var out = [];\n      for (var i = 0; i < ranges.length; i++)\n        { out[i] = new Range(clipPos(this$1, ranges[i].anchor),\n                           clipPos(this$1, ranges[i].head)); }\n      if (primary == null) { primary = Math.min(ranges.length - 1, this.sel.primIndex); }\n      setSelection(this, normalizeSelection(this.cm, out, primary), options);\n    }),\n    addSelection: docMethodOp(function(anchor, head, options) {\n      var ranges = this.sel.ranges.slice(0);\n      ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor)));\n      setSelection(this, normalizeSelection(this.cm, ranges, ranges.length - 1), options);\n    }),\n\n    getSelection: function(lineSep) {\n      var this$1 = this;\n\n      var ranges = this.sel.ranges, lines;\n      for (var i = 0; i < ranges.length; i++) {\n        var sel = getBetween(this$1, ranges[i].from(), ranges[i].to());\n        lines = lines ? lines.concat(sel) : sel;\n      }\n      if (lineSep === false) { return lines }\n      else { return lines.join(lineSep || this.lineSeparator()) }\n    },\n    getSelections: function(lineSep) {\n      var this$1 = this;\n\n      var parts = [], ranges = this.sel.ranges;\n      for (var i = 0; i < ranges.length; i++) {\n        var sel = getBetween(this$1, ranges[i].from(), ranges[i].to());\n        if (lineSep !== false) { sel = sel.join(lineSep || this$1.lineSeparator()); }\n        parts[i] = sel;\n      }\n      return parts\n    },\n    replaceSelection: function(code, collapse, origin) {\n      var dup = [];\n      for (var i = 0; i < this.sel.ranges.length; i++)\n        { dup[i] = code; }\n      this.replaceSelections(dup, collapse, origin || \"+input\");\n    },\n    replaceSelections: docMethodOp(function(code, collapse, origin) {\n      var this$1 = this;\n\n      var changes = [], sel = this.sel;\n      for (var i = 0; i < sel.ranges.length; i++) {\n        var range$$1 = sel.ranges[i];\n        changes[i] = {from: range$$1.from(), to: range$$1.to(), text: this$1.splitLines(code[i]), origin: origin};\n      }\n      var newSel = collapse && collapse != \"end\" && computeReplacedSel(this, changes, collapse);\n      for (var i$1 = changes.length - 1; i$1 >= 0; i$1--)\n        { makeChange(this$1, changes[i$1]); }\n      if (newSel) { setSelectionReplaceHistory(this, newSel); }\n      else if (this.cm) { ensureCursorVisible(this.cm); }\n    }),\n    undo: docMethodOp(function() {makeChangeFromHistory(this, \"undo\");}),\n    redo: docMethodOp(function() {makeChangeFromHistory(this, \"redo\");}),\n    undoSelection: docMethodOp(function() {makeChangeFromHistory(this, \"undo\", true);}),\n    redoSelection: docMethodOp(function() {makeChangeFromHistory(this, \"redo\", true);}),\n\n    setExtending: function(val) {this.extend = val;},\n    getExtending: function() {return this.extend},\n\n    historySize: function() {\n      var hist = this.history, done = 0, undone = 0;\n      for (var i = 0; i < hist.done.length; i++) { if (!hist.done[i].ranges) { ++done; } }\n      for (var i$1 = 0; i$1 < hist.undone.length; i$1++) { if (!hist.undone[i$1].ranges) { ++undone; } }\n      return {undo: done, redo: undone}\n    },\n    clearHistory: function() {this.history = new History(this.history.maxGeneration);},\n\n    markClean: function() {\n      this.cleanGeneration = this.changeGeneration(true);\n    },\n    changeGeneration: function(forceSplit) {\n      if (forceSplit)\n        { this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null; }\n      return this.history.generation\n    },\n    isClean: function (gen) {\n      return this.history.generation == (gen || this.cleanGeneration)\n    },\n\n    getHistory: function() {\n      return {done: copyHistoryArray(this.history.done),\n              undone: copyHistoryArray(this.history.undone)}\n    },\n    setHistory: function(histData) {\n      var hist = this.history = new History(this.history.maxGeneration);\n      hist.done = copyHistoryArray(histData.done.slice(0), null, true);\n      hist.undone = copyHistoryArray(histData.undone.slice(0), null, true);\n    },\n\n    setGutterMarker: docMethodOp(function(line, gutterID, value) {\n      return changeLine(this, line, \"gutter\", function (line) {\n        var markers = line.gutterMarkers || (line.gutterMarkers = {});\n        markers[gutterID] = value;\n        if (!value && isEmpty(markers)) { line.gutterMarkers = null; }\n        return true\n      })\n    }),\n\n    clearGutter: docMethodOp(function(gutterID) {\n      var this$1 = this;\n\n      this.iter(function (line) {\n        if (line.gutterMarkers && line.gutterMarkers[gutterID]) {\n          changeLine(this$1, line, \"gutter\", function () {\n            line.gutterMarkers[gutterID] = null;\n            if (isEmpty(line.gutterMarkers)) { line.gutterMarkers = null; }\n            return true\n          });\n        }\n      });\n    }),\n\n    lineInfo: function(line) {\n      var n;\n      if (typeof line == \"number\") {\n        if (!isLine(this, line)) { return null }\n        n = line;\n        line = getLine(this, line);\n        if (!line) { return null }\n      } else {\n        n = lineNo(line);\n        if (n == null) { return null }\n      }\n      return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers,\n              textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass,\n              widgets: line.widgets}\n    },\n\n    addLineClass: docMethodOp(function(handle, where, cls) {\n      return changeLine(this, handle, where == \"gutter\" ? \"gutter\" : \"class\", function (line) {\n        var prop = where == \"text\" ? \"textClass\"\n                 : where == \"background\" ? \"bgClass\"\n                 : where == \"gutter\" ? \"gutterClass\" : \"wrapClass\";\n        if (!line[prop]) { line[prop] = cls; }\n        else if (classTest(cls).test(line[prop])) { return false }\n        else { line[prop] += \" \" + cls; }\n        return true\n      })\n    }),\n    removeLineClass: docMethodOp(function(handle, where, cls) {\n      return changeLine(this, handle, where == \"gutter\" ? \"gutter\" : \"class\", function (line) {\n        var prop = where == \"text\" ? \"textClass\"\n                 : where == \"background\" ? \"bgClass\"\n                 : where == \"gutter\" ? \"gutterClass\" : \"wrapClass\";\n        var cur = line[prop];\n        if (!cur) { return false }\n        else if (cls == null) { line[prop] = null; }\n        else {\n          var found = cur.match(classTest(cls));\n          if (!found) { return false }\n          var end = found.index + found[0].length;\n          line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? \"\" : \" \") + cur.slice(end) || null;\n        }\n        return true\n      })\n    }),\n\n    addLineWidget: docMethodOp(function(handle, node, options) {\n      return addLineWidget(this, handle, node, options)\n    }),\n    removeLineWidget: function(widget) { widget.clear(); },\n\n    markText: function(from, to, options) {\n      return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || \"range\")\n    },\n    setBookmark: function(pos, options) {\n      var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),\n                      insertLeft: options && options.insertLeft,\n                      clearWhenEmpty: false, shared: options && options.shared,\n                      handleMouseEvents: options && options.handleMouseEvents};\n      pos = clipPos(this, pos);\n      return markText(this, pos, pos, realOpts, \"bookmark\")\n    },\n    findMarksAt: function(pos) {\n      pos = clipPos(this, pos);\n      var markers = [], spans = getLine(this, pos.line).markedSpans;\n      if (spans) { for (var i = 0; i < spans.length; ++i) {\n        var span = spans[i];\n        if ((span.from == null || span.from <= pos.ch) &&\n            (span.to == null || span.to >= pos.ch))\n          { markers.push(span.marker.parent || span.marker); }\n      } }\n      return markers\n    },\n    findMarks: function(from, to, filter) {\n      from = clipPos(this, from); to = clipPos(this, to);\n      var found = [], lineNo$$1 = from.line;\n      this.iter(from.line, to.line + 1, function (line) {\n        var spans = line.markedSpans;\n        if (spans) { for (var i = 0; i < spans.length; i++) {\n          var span = spans[i];\n          if (!(span.to != null && lineNo$$1 == from.line && from.ch >= span.to ||\n                span.from == null && lineNo$$1 != from.line ||\n                span.from != null && lineNo$$1 == to.line && span.from >= to.ch) &&\n              (!filter || filter(span.marker)))\n            { found.push(span.marker.parent || span.marker); }\n        } }\n        ++lineNo$$1;\n      });\n      return found\n    },\n    getAllMarks: function() {\n      var markers = [];\n      this.iter(function (line) {\n        var sps = line.markedSpans;\n        if (sps) { for (var i = 0; i < sps.length; ++i)\n          { if (sps[i].from != null) { markers.push(sps[i].marker); } } }\n      });\n      return markers\n    },\n\n    posFromIndex: function(off) {\n      var ch, lineNo$$1 = this.first, sepSize = this.lineSeparator().length;\n      this.iter(function (line) {\n        var sz = line.text.length + sepSize;\n        if (sz > off) { ch = off; return true }\n        off -= sz;\n        ++lineNo$$1;\n      });\n      return clipPos(this, Pos(lineNo$$1, ch))\n    },\n    indexFromPos: function (coords) {\n      coords = clipPos(this, coords);\n      var index = coords.ch;\n      if (coords.line < this.first || coords.ch < 0) { return 0 }\n      var sepSize = this.lineSeparator().length;\n      this.iter(this.first, coords.line, function (line) { // iter aborts when callback returns a truthy value\n        index += line.text.length + sepSize;\n      });\n      return index\n    },\n\n    copy: function(copyHistory) {\n      var doc = new Doc(getLines(this, this.first, this.first + this.size),\n                        this.modeOption, this.first, this.lineSep, this.direction);\n      doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft;\n      doc.sel = this.sel;\n      doc.extend = false;\n      if (copyHistory) {\n        doc.history.undoDepth = this.history.undoDepth;\n        doc.setHistory(this.getHistory());\n      }\n      return doc\n    },\n\n    linkedDoc: function(options) {\n      if (!options) { options = {}; }\n      var from = this.first, to = this.first + this.size;\n      if (options.from != null && options.from > from) { from = options.from; }\n      if (options.to != null && options.to < to) { to = options.to; }\n      var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep, this.direction);\n      if (options.sharedHist) { copy.history = this.history\n      ; }(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist});\n      copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}];\n      copySharedMarkers(copy, findSharedMarkers(this));\n      return copy\n    },\n    unlinkDoc: function(other) {\n      var this$1 = this;\n\n      if (other instanceof CodeMirror) { other = other.doc; }\n      if (this.linked) { for (var i = 0; i < this.linked.length; ++i) {\n        var link = this$1.linked[i];\n        if (link.doc != other) { continue }\n        this$1.linked.splice(i, 1);\n        other.unlinkDoc(this$1);\n        detachSharedMarkers(findSharedMarkers(this$1));\n        break\n      } }\n      // If the histories were shared, split them again\n      if (other.history == this.history) {\n        var splitIds = [other.id];\n        linkedDocs(other, function (doc) { return splitIds.push(doc.id); }, true);\n        other.history = new History(null);\n        other.history.done = copyHistoryArray(this.history.done, splitIds);\n        other.history.undone = copyHistoryArray(this.history.undone, splitIds);\n      }\n    },\n    iterLinkedDocs: function(f) {linkedDocs(this, f);},\n\n    getMode: function() {return this.mode},\n    getEditor: function() {return this.cm},\n\n    splitLines: function(str) {\n      if (this.lineSep) { return str.split(this.lineSep) }\n      return splitLinesAuto(str)\n    },\n    lineSeparator: function() { return this.lineSep || \"\\n\" },\n\n    setDirection: docMethodOp(function (dir) {\n      if (dir != \"rtl\") { dir = \"ltr\"; }\n      if (dir == this.direction) { return }\n      this.direction = dir;\n      this.iter(function (line) { return line.order = null; });\n      if (this.cm) { directionChanged(this.cm); }\n    })\n  });\n\n  // Public alias.\n  Doc.prototype.eachLine = Doc.prototype.iter;\n\n  // Kludge to work around strange IE behavior where it'll sometimes\n  // re-fire a series of drag-related events right after the drop (#1551)\n  var lastDrop = 0;\n\n  function onDrop(e) {\n    var cm = this;\n    clearDragCursor(cm);\n    if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e))\n      { return }\n    e_preventDefault(e);\n    if (ie) { lastDrop = +new Date; }\n    var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;\n    if (!pos || cm.isReadOnly()) { return }\n    // Might be a file drop, in which case we simply extract the text\n    // and insert it.\n    if (files && files.length && window.FileReader && window.File) {\n      var n = files.length, text = Array(n), read = 0;\n      var loadFile = function (file, i) {\n        if (cm.options.allowDropFileTypes &&\n            indexOf(cm.options.allowDropFileTypes, file.type) == -1)\n          { return }\n\n        var reader = new FileReader;\n        reader.onload = operation(cm, function () {\n          var content = reader.result;\n          if (/[\\x00-\\x08\\x0e-\\x1f]{2}/.test(content)) { content = \"\"; }\n          text[i] = content;\n          if (++read == n) {\n            pos = clipPos(cm.doc, pos);\n            var change = {from: pos, to: pos,\n                          text: cm.doc.splitLines(text.join(cm.doc.lineSeparator())),\n                          origin: \"paste\"};\n            makeChange(cm.doc, change);\n            setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change)));\n          }\n        });\n        reader.readAsText(file);\n      };\n      for (var i = 0; i < n; ++i) { loadFile(files[i], i); }\n    } else { // Normal drop\n      // Don't do a replace if the drop happened inside of the selected text.\n      if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) {\n        cm.state.draggingText(e);\n        // Ensure the editor is re-focused\n        setTimeout(function () { return cm.display.input.focus(); }, 20);\n        return\n      }\n      try {\n        var text$1 = e.dataTransfer.getData(\"Text\");\n        if (text$1) {\n          var selected;\n          if (cm.state.draggingText && !cm.state.draggingText.copy)\n            { selected = cm.listSelections(); }\n          setSelectionNoUndo(cm.doc, simpleSelection(pos, pos));\n          if (selected) { for (var i$1 = 0; i$1 < selected.length; ++i$1)\n            { replaceRange(cm.doc, \"\", selected[i$1].anchor, selected[i$1].head, \"drag\"); } }\n          cm.replaceSelection(text$1, \"around\", \"paste\");\n          cm.display.input.focus();\n        }\n      }\n      catch(e){}\n    }\n  }\n\n  function onDragStart(cm, e) {\n    if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return }\n    if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) { return }\n\n    e.dataTransfer.setData(\"Text\", cm.getSelection());\n    e.dataTransfer.effectAllowed = \"copyMove\";\n\n    // Use dummy image instead of default browsers image.\n    // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.\n    if (e.dataTransfer.setDragImage && !safari) {\n      var img = elt(\"img\", null, null, \"position: fixed; left: 0; top: 0;\");\n      img.src = \"data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\";\n      if (presto) {\n        img.width = img.height = 1;\n        cm.display.wrapper.appendChild(img);\n        // Force a relayout, or Opera won't use our image for some obscure reason\n        img._top = img.offsetTop;\n      }\n      e.dataTransfer.setDragImage(img, 0, 0);\n      if (presto) { img.parentNode.removeChild(img); }\n    }\n  }\n\n  function onDragOver(cm, e) {\n    var pos = posFromMouse(cm, e);\n    if (!pos) { return }\n    var frag = document.createDocumentFragment();\n    drawSelectionCursor(cm, pos, frag);\n    if (!cm.display.dragCursor) {\n      cm.display.dragCursor = elt(\"div\", null, \"CodeMirror-cursors CodeMirror-dragcursors\");\n      cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv);\n    }\n    removeChildrenAndAdd(cm.display.dragCursor, frag);\n  }\n\n  function clearDragCursor(cm) {\n    if (cm.display.dragCursor) {\n      cm.display.lineSpace.removeChild(cm.display.dragCursor);\n      cm.display.dragCursor = null;\n    }\n  }\n\n  // These must be handled carefully, because naively registering a\n  // handler for each editor will cause the editors to never be\n  // garbage collected.\n\n  function forEachCodeMirror(f) {\n    if (!document.getElementsByClassName) { return }\n    var byClass = document.getElementsByClassName(\"CodeMirror\"), editors = [];\n    for (var i = 0; i < byClass.length; i++) {\n      var cm = byClass[i].CodeMirror;\n      if (cm) { editors.push(cm); }\n    }\n    if (editors.length) { editors[0].operation(function () {\n      for (var i = 0; i < editors.length; i++) { f(editors[i]); }\n    }); }\n  }\n\n  var globalsRegistered = false;\n  function ensureGlobalHandlers() {\n    if (globalsRegistered) { return }\n    registerGlobalHandlers();\n    globalsRegistered = true;\n  }\n  function registerGlobalHandlers() {\n    // When the window resizes, we need to refresh active editors.\n    var resizeTimer;\n    on(window, \"resize\", function () {\n      if (resizeTimer == null) { resizeTimer = setTimeout(function () {\n        resizeTimer = null;\n        forEachCodeMirror(onResize);\n      }, 100); }\n    });\n    // When the window loses focus, we want to show the editor as blurred\n    on(window, \"blur\", function () { return forEachCodeMirror(onBlur); });\n  }\n  // Called when the window resizes\n  function onResize(cm) {\n    var d = cm.display;\n    // Might be a text scaling operation, clear size caches.\n    d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;\n    d.scrollbarsClipped = false;\n    cm.setSize();\n  }\n\n  var keyNames = {\n    3: \"Pause\", 8: \"Backspace\", 9: \"Tab\", 13: \"Enter\", 16: \"Shift\", 17: \"Ctrl\", 18: \"Alt\",\n    19: \"Pause\", 20: \"CapsLock\", 27: \"Esc\", 32: \"Space\", 33: \"PageUp\", 34: \"PageDown\", 35: \"End\",\n    36: \"Home\", 37: \"Left\", 38: \"Up\", 39: \"Right\", 40: \"Down\", 44: \"PrintScrn\", 45: \"Insert\",\n    46: \"Delete\", 59: \";\", 61: \"=\", 91: \"Mod\", 92: \"Mod\", 93: \"Mod\",\n    106: \"*\", 107: \"=\", 109: \"-\", 110: \".\", 111: \"/\", 145: \"ScrollLock\",\n    173: \"-\", 186: \";\", 187: \"=\", 188: \",\", 189: \"-\", 190: \".\", 191: \"/\", 192: \"`\", 219: \"[\", 220: \"\\\\\",\n    221: \"]\", 222: \"'\", 63232: \"Up\", 63233: \"Down\", 63234: \"Left\", 63235: \"Right\", 63272: \"Delete\",\n    63273: \"Home\", 63275: \"End\", 63276: \"PageUp\", 63277: \"PageDown\", 63302: \"Insert\"\n  };\n\n  // Number keys\n  for (var i = 0; i < 10; i++) { keyNames[i + 48] = keyNames[i + 96] = String(i); }\n  // Alphabetic keys\n  for (var i$1 = 65; i$1 <= 90; i$1++) { keyNames[i$1] = String.fromCharCode(i$1); }\n  // Function keys\n  for (var i$2 = 1; i$2 <= 12; i$2++) { keyNames[i$2 + 111] = keyNames[i$2 + 63235] = \"F\" + i$2; }\n\n  var keyMap = {};\n\n  keyMap.basic = {\n    \"Left\": \"goCharLeft\", \"Right\": \"goCharRight\", \"Up\": \"goLineUp\", \"Down\": \"goLineDown\",\n    \"End\": \"goLineEnd\", \"Home\": \"goLineStartSmart\", \"PageUp\": \"goPageUp\", \"PageDown\": \"goPageDown\",\n    \"Delete\": \"delCharAfter\", \"Backspace\": \"delCharBefore\", \"Shift-Backspace\": \"delCharBefore\",\n    \"Tab\": \"defaultTab\", \"Shift-Tab\": \"indentAuto\",\n    \"Enter\": \"newlineAndIndent\", \"Insert\": \"toggleOverwrite\",\n    \"Esc\": \"singleSelection\"\n  };\n  // Note that the save and find-related commands aren't defined by\n  // default. User code or addons can define them. Unknown commands\n  // are simply ignored.\n  keyMap.pcDefault = {\n    \"Ctrl-A\": \"selectAll\", \"Ctrl-D\": \"deleteLine\", \"Ctrl-Z\": \"undo\", \"Shift-Ctrl-Z\": \"redo\", \"Ctrl-Y\": \"redo\",\n    \"Ctrl-Home\": \"goDocStart\", \"Ctrl-End\": \"goDocEnd\", \"Ctrl-Up\": \"goLineUp\", \"Ctrl-Down\": \"goLineDown\",\n    \"Ctrl-Left\": \"goGroupLeft\", \"Ctrl-Right\": \"goGroupRight\", \"Alt-Left\": \"goLineStart\", \"Alt-Right\": \"goLineEnd\",\n    \"Ctrl-Backspace\": \"delGroupBefore\", \"Ctrl-Delete\": \"delGroupAfter\", \"Ctrl-S\": \"save\", \"Ctrl-F\": \"find\",\n    \"Ctrl-G\": \"findNext\", \"Shift-Ctrl-G\": \"findPrev\", \"Shift-Ctrl-F\": \"replace\", \"Shift-Ctrl-R\": \"replaceAll\",\n    \"Ctrl-[\": \"indentLess\", \"Ctrl-]\": \"indentMore\",\n    \"Ctrl-U\": \"undoSelection\", \"Shift-Ctrl-U\": \"redoSelection\", \"Alt-U\": \"redoSelection\",\n    \"fallthrough\": \"basic\"\n  };\n  // Very basic readline/emacs-style bindings, which are standard on Mac.\n  keyMap.emacsy = {\n    \"Ctrl-F\": \"goCharRight\", \"Ctrl-B\": \"goCharLeft\", \"Ctrl-P\": \"goLineUp\", \"Ctrl-N\": \"goLineDown\",\n    \"Alt-F\": \"goWordRight\", \"Alt-B\": \"goWordLeft\", \"Ctrl-A\": \"goLineStart\", \"Ctrl-E\": \"goLineEnd\",\n    \"Ctrl-V\": \"goPageDown\", \"Shift-Ctrl-V\": \"goPageUp\", \"Ctrl-D\": \"delCharAfter\", \"Ctrl-H\": \"delCharBefore\",\n    \"Alt-D\": \"delWordAfter\", \"Alt-Backspace\": \"delWordBefore\", \"Ctrl-K\": \"killLine\", \"Ctrl-T\": \"transposeChars\",\n    \"Ctrl-O\": \"openLine\"\n  };\n  keyMap.macDefault = {\n    \"Cmd-A\": \"selectAll\", \"Cmd-D\": \"deleteLine\", \"Cmd-Z\": \"undo\", \"Shift-Cmd-Z\": \"redo\", \"Cmd-Y\": \"redo\",\n    \"Cmd-Home\": \"goDocStart\", \"Cmd-Up\": \"goDocStart\", \"Cmd-End\": \"goDocEnd\", \"Cmd-Down\": \"goDocEnd\", \"Alt-Left\": \"goGroupLeft\",\n    \"Alt-Right\": \"goGroupRight\", \"Cmd-Left\": \"goLineLeft\", \"Cmd-Right\": \"goLineRight\", \"Alt-Backspace\": \"delGroupBefore\",\n    \"Ctrl-Alt-Backspace\": \"delGroupAfter\", \"Alt-Delete\": \"delGroupAfter\", \"Cmd-S\": \"save\", \"Cmd-F\": \"find\",\n    \"Cmd-G\": \"findNext\", \"Shift-Cmd-G\": \"findPrev\", \"Cmd-Alt-F\": \"replace\", \"Shift-Cmd-Alt-F\": \"replaceAll\",\n    \"Cmd-[\": \"indentLess\", \"Cmd-]\": \"indentMore\", \"Cmd-Backspace\": \"delWrappedLineLeft\", \"Cmd-Delete\": \"delWrappedLineRight\",\n    \"Cmd-U\": \"undoSelection\", \"Shift-Cmd-U\": \"redoSelection\", \"Ctrl-Up\": \"goDocStart\", \"Ctrl-Down\": \"goDocEnd\",\n    \"fallthrough\": [\"basic\", \"emacsy\"]\n  };\n  keyMap[\"default\"] = mac ? keyMap.macDefault : keyMap.pcDefault;\n\n  // KEYMAP DISPATCH\n\n  function normalizeKeyName(name) {\n    var parts = name.split(/-(?!$)/);\n    name = parts[parts.length - 1];\n    var alt, ctrl, shift, cmd;\n    for (var i = 0; i < parts.length - 1; i++) {\n      var mod = parts[i];\n      if (/^(cmd|meta|m)$/i.test(mod)) { cmd = true; }\n      else if (/^a(lt)?$/i.test(mod)) { alt = true; }\n      else if (/^(c|ctrl|control)$/i.test(mod)) { ctrl = true; }\n      else if (/^s(hift)?$/i.test(mod)) { shift = true; }\n      else { throw new Error(\"Unrecognized modifier name: \" + mod) }\n    }\n    if (alt) { name = \"Alt-\" + name; }\n    if (ctrl) { name = \"Ctrl-\" + name; }\n    if (cmd) { name = \"Cmd-\" + name; }\n    if (shift) { name = \"Shift-\" + name; }\n    return name\n  }\n\n  // This is a kludge to keep keymaps mostly working as raw objects\n  // (backwards compatibility) while at the same time support features\n  // like normalization and multi-stroke key bindings. It compiles a\n  // new normalized keymap, and then updates the old object to reflect\n  // this.\n  function normalizeKeyMap(keymap) {\n    var copy = {};\n    for (var keyname in keymap) { if (keymap.hasOwnProperty(keyname)) {\n      var value = keymap[keyname];\n      if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) { continue }\n      if (value == \"...\") { delete keymap[keyname]; continue }\n\n      var keys = map(keyname.split(\" \"), normalizeKeyName);\n      for (var i = 0; i < keys.length; i++) {\n        var val = (void 0), name = (void 0);\n        if (i == keys.length - 1) {\n          name = keys.join(\" \");\n          val = value;\n        } else {\n          name = keys.slice(0, i + 1).join(\" \");\n          val = \"...\";\n        }\n        var prev = copy[name];\n        if (!prev) { copy[name] = val; }\n        else if (prev != val) { throw new Error(\"Inconsistent bindings for \" + name) }\n      }\n      delete keymap[keyname];\n    } }\n    for (var prop in copy) { keymap[prop] = copy[prop]; }\n    return keymap\n  }\n\n  function lookupKey(key, map$$1, handle, context) {\n    map$$1 = getKeyMap(map$$1);\n    var found = map$$1.call ? map$$1.call(key, context) : map$$1[key];\n    if (found === false) { return \"nothing\" }\n    if (found === \"...\") { return \"multi\" }\n    if (found != null && handle(found)) { return \"handled\" }\n\n    if (map$$1.fallthrough) {\n      if (Object.prototype.toString.call(map$$1.fallthrough) != \"[object Array]\")\n        { return lookupKey(key, map$$1.fallthrough, handle, context) }\n      for (var i = 0; i < map$$1.fallthrough.length; i++) {\n        var result = lookupKey(key, map$$1.fallthrough[i], handle, context);\n        if (result) { return result }\n      }\n    }\n  }\n\n  // Modifier key presses don't count as 'real' key presses for the\n  // purpose of keymap fallthrough.\n  function isModifierKey(value) {\n    var name = typeof value == \"string\" ? value : keyNames[value.keyCode];\n    return name == \"Ctrl\" || name == \"Alt\" || name == \"Shift\" || name == \"Mod\"\n  }\n\n  function addModifierNames(name, event, noShift) {\n    var base = name;\n    if (event.altKey && base != \"Alt\") { name = \"Alt-\" + name; }\n    if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != \"Ctrl\") { name = \"Ctrl-\" + name; }\n    if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != \"Cmd\") { name = \"Cmd-\" + name; }\n    if (!noShift && event.shiftKey && base != \"Shift\") { name = \"Shift-\" + name; }\n    return name\n  }\n\n  // Look up the name of a key as indicated by an event object.\n  function keyName(event, noShift) {\n    if (presto && event.keyCode == 34 && event[\"char\"]) { return false }\n    var name = keyNames[event.keyCode];\n    if (name == null || event.altGraphKey) { return false }\n    // Ctrl-ScrollLock has keyCode 3, same as Ctrl-Pause,\n    // so we'll use event.code when available (Chrome 48+, FF 38+, Safari 10.1+)\n    if (event.keyCode == 3 && event.code) { name = event.code; }\n    return addModifierNames(name, event, noShift)\n  }\n\n  function getKeyMap(val) {\n    return typeof val == \"string\" ? keyMap[val] : val\n  }\n\n  // Helper for deleting text near the selection(s), used to implement\n  // backspace, delete, and similar functionality.\n  function deleteNearSelection(cm, compute) {\n    var ranges = cm.doc.sel.ranges, kill = [];\n    // Build up a set of ranges to kill first, merging overlapping\n    // ranges.\n    for (var i = 0; i < ranges.length; i++) {\n      var toKill = compute(ranges[i]);\n      while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) {\n        var replaced = kill.pop();\n        if (cmp(replaced.from, toKill.from) < 0) {\n          toKill.from = replaced.from;\n          break\n        }\n      }\n      kill.push(toKill);\n    }\n    // Next, remove those actual ranges.\n    runInOp(cm, function () {\n      for (var i = kill.length - 1; i >= 0; i--)\n        { replaceRange(cm.doc, \"\", kill[i].from, kill[i].to, \"+delete\"); }\n      ensureCursorVisible(cm);\n    });\n  }\n\n  function moveCharLogically(line, ch, dir) {\n    var target = skipExtendingChars(line.text, ch + dir, dir);\n    return target < 0 || target > line.text.length ? null : target\n  }\n\n  function moveLogically(line, start, dir) {\n    var ch = moveCharLogically(line, start.ch, dir);\n    return ch == null ? null : new Pos(start.line, ch, dir < 0 ? \"after\" : \"before\")\n  }\n\n  function endOfLine(visually, cm, lineObj, lineNo, dir) {\n    if (visually) {\n      var order = getOrder(lineObj, cm.doc.direction);\n      if (order) {\n        var part = dir < 0 ? lst(order) : order[0];\n        var moveInStorageOrder = (dir < 0) == (part.level == 1);\n        var sticky = moveInStorageOrder ? \"after\" : \"before\";\n        var ch;\n        // With a wrapped rtl chunk (possibly spanning multiple bidi parts),\n        // it could be that the last bidi part is not on the last visual line,\n        // since visual lines contain content order-consecutive chunks.\n        // Thus, in rtl, we are looking for the first (content-order) character\n        // in the rtl chunk that is on the last line (that is, the same line\n        // as the last (content-order) character).\n        if (part.level > 0 || cm.doc.direction == \"rtl\") {\n          var prep = prepareMeasureForLine(cm, lineObj);\n          ch = dir < 0 ? lineObj.text.length - 1 : 0;\n          var targetTop = measureCharPrepared(cm, prep, ch).top;\n          ch = findFirst(function (ch) { return measureCharPrepared(cm, prep, ch).top == targetTop; }, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, ch);\n          if (sticky == \"before\") { ch = moveCharLogically(lineObj, ch, 1); }\n        } else { ch = dir < 0 ? part.to : part.from; }\n        return new Pos(lineNo, ch, sticky)\n      }\n    }\n    return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? \"before\" : \"after\")\n  }\n\n  function moveVisually(cm, line, start, dir) {\n    var bidi = getOrder(line, cm.doc.direction);\n    if (!bidi) { return moveLogically(line, start, dir) }\n    if (start.ch >= line.text.length) {\n      start.ch = line.text.length;\n      start.sticky = \"before\";\n    } else if (start.ch <= 0) {\n      start.ch = 0;\n      start.sticky = \"after\";\n    }\n    var partPos = getBidiPartAt(bidi, start.ch, start.sticky), part = bidi[partPos];\n    if (cm.doc.direction == \"ltr\" && part.level % 2 == 0 && (dir > 0 ? part.to > start.ch : part.from < start.ch)) {\n      // Case 1: We move within an ltr part in an ltr editor. Even with wrapped lines,\n      // nothing interesting happens.\n      return moveLogically(line, start, dir)\n    }\n\n    var mv = function (pos, dir) { return moveCharLogically(line, pos instanceof Pos ? pos.ch : pos, dir); };\n    var prep;\n    var getWrappedLineExtent = function (ch) {\n      if (!cm.options.lineWrapping) { return {begin: 0, end: line.text.length} }\n      prep = prep || prepareMeasureForLine(cm, line);\n      return wrappedLineExtentChar(cm, line, prep, ch)\n    };\n    var wrappedLineExtent = getWrappedLineExtent(start.sticky == \"before\" ? mv(start, -1) : start.ch);\n\n    if (cm.doc.direction == \"rtl\" || part.level == 1) {\n      var moveInStorageOrder = (part.level == 1) == (dir < 0);\n      var ch = mv(start, moveInStorageOrder ? 1 : -1);\n      if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLineExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) {\n        // Case 2: We move within an rtl part or in an rtl editor on the same visual line\n        var sticky = moveInStorageOrder ? \"before\" : \"after\";\n        return new Pos(start.line, ch, sticky)\n      }\n    }\n\n    // Case 3: Could not move within this bidi part in this visual line, so leave\n    // the current bidi part\n\n    var searchInVisualLine = function (partPos, dir, wrappedLineExtent) {\n      var getRes = function (ch, moveInStorageOrder) { return moveInStorageOrder\n        ? new Pos(start.line, mv(ch, 1), \"before\")\n        : new Pos(start.line, ch, \"after\"); };\n\n      for (; partPos >= 0 && partPos < bidi.length; partPos += dir) {\n        var part = bidi[partPos];\n        var moveInStorageOrder = (dir > 0) == (part.level != 1);\n        var ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExtent.end, -1);\n        if (part.from <= ch && ch < part.to) { return getRes(ch, moveInStorageOrder) }\n        ch = moveInStorageOrder ? part.from : mv(part.to, -1);\n        if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) { return getRes(ch, moveInStorageOrder) }\n      }\n    };\n\n    // Case 3a: Look for other bidi parts on the same visual line\n    var res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent);\n    if (res) { return res }\n\n    // Case 3b: Look for other bidi parts on the next visual line\n    var nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1);\n    if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) {\n      res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineExtent(nextCh));\n      if (res) { return res }\n    }\n\n    // Case 4: Nowhere to move\n    return null\n  }\n\n  // Commands are parameter-less actions that can be performed on an\n  // editor, mostly used for keybindings.\n  var commands = {\n    selectAll: selectAll,\n    singleSelection: function (cm) { return cm.setSelection(cm.getCursor(\"anchor\"), cm.getCursor(\"head\"), sel_dontScroll); },\n    killLine: function (cm) { return deleteNearSelection(cm, function (range) {\n      if (range.empty()) {\n        var len = getLine(cm.doc, range.head.line).text.length;\n        if (range.head.ch == len && range.head.line < cm.lastLine())\n          { return {from: range.head, to: Pos(range.head.line + 1, 0)} }\n        else\n          { return {from: range.head, to: Pos(range.head.line, len)} }\n      } else {\n        return {from: range.from(), to: range.to()}\n      }\n    }); },\n    deleteLine: function (cm) { return deleteNearSelection(cm, function (range) { return ({\n      from: Pos(range.from().line, 0),\n      to: clipPos(cm.doc, Pos(range.to().line + 1, 0))\n    }); }); },\n    delLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { return ({\n      from: Pos(range.from().line, 0), to: range.from()\n    }); }); },\n    delWrappedLineLeft: function (cm) { return deleteNearSelection(cm, function (range) {\n      var top = cm.charCoords(range.head, \"div\").top + 5;\n      var leftPos = cm.coordsChar({left: 0, top: top}, \"div\");\n      return {from: leftPos, to: range.from()}\n    }); },\n    delWrappedLineRight: function (cm) { return deleteNearSelection(cm, function (range) {\n      var top = cm.charCoords(range.head, \"div\").top + 5;\n      var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, \"div\");\n      return {from: range.from(), to: rightPos }\n    }); },\n    undo: function (cm) { return cm.undo(); },\n    redo: function (cm) { return cm.redo(); },\n    undoSelection: function (cm) { return cm.undoSelection(); },\n    redoSelection: function (cm) { return cm.redoSelection(); },\n    goDocStart: function (cm) { return cm.extendSelection(Pos(cm.firstLine(), 0)); },\n    goDocEnd: function (cm) { return cm.extendSelection(Pos(cm.lastLine())); },\n    goLineStart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStart(cm, range.head.line); },\n      {origin: \"+move\", bias: 1}\n    ); },\n    goLineStartSmart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStartSmart(cm, range.head); },\n      {origin: \"+move\", bias: 1}\n    ); },\n    goLineEnd: function (cm) { return cm.extendSelectionsBy(function (range) { return lineEnd(cm, range.head.line); },\n      {origin: \"+move\", bias: -1}\n    ); },\n    goLineRight: function (cm) { return cm.extendSelectionsBy(function (range) {\n      var top = cm.cursorCoords(range.head, \"div\").top + 5;\n      return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, \"div\")\n    }, sel_move); },\n    goLineLeft: function (cm) { return cm.extendSelectionsBy(function (range) {\n      var top = cm.cursorCoords(range.head, \"div\").top + 5;\n      return cm.coordsChar({left: 0, top: top}, \"div\")\n    }, sel_move); },\n    goLineLeftSmart: function (cm) { return cm.extendSelectionsBy(function (range) {\n      var top = cm.cursorCoords(range.head, \"div\").top + 5;\n      var pos = cm.coordsChar({left: 0, top: top}, \"div\");\n      if (pos.ch < cm.getLine(pos.line).search(/\\S/)) { return lineStartSmart(cm, range.head) }\n      return pos\n    }, sel_move); },\n    goLineUp: function (cm) { return cm.moveV(-1, \"line\"); },\n    goLineDown: function (cm) { return cm.moveV(1, \"line\"); },\n    goPageUp: function (cm) { return cm.moveV(-1, \"page\"); },\n    goPageDown: function (cm) { return cm.moveV(1, \"page\"); },\n    goCharLeft: function (cm) { return cm.moveH(-1, \"char\"); },\n    goCharRight: function (cm) { return cm.moveH(1, \"char\"); },\n    goColumnLeft: function (cm) { return cm.moveH(-1, \"column\"); },\n    goColumnRight: function (cm) { return cm.moveH(1, \"column\"); },\n    goWordLeft: function (cm) { return cm.moveH(-1, \"word\"); },\n    goGroupRight: function (cm) { return cm.moveH(1, \"group\"); },\n    goGroupLeft: function (cm) { return cm.moveH(-1, \"group\"); },\n    goWordRight: function (cm) { return cm.moveH(1, \"word\"); },\n    delCharBefore: function (cm) { return cm.deleteH(-1, \"char\"); },\n    delCharAfter: function (cm) { return cm.deleteH(1, \"char\"); },\n    delWordBefore: function (cm) { return cm.deleteH(-1, \"word\"); },\n    delWordAfter: function (cm) { return cm.deleteH(1, \"word\"); },\n    delGroupBefore: function (cm) { return cm.deleteH(-1, \"group\"); },\n    delGroupAfter: function (cm) { return cm.deleteH(1, \"group\"); },\n    indentAuto: function (cm) { return cm.indentSelection(\"smart\"); },\n    indentMore: function (cm) { return cm.indentSelection(\"add\"); },\n    indentLess: function (cm) { return cm.indentSelection(\"subtract\"); },\n    insertTab: function (cm) { return cm.replaceSelection(\"\\t\"); },\n    insertSoftTab: function (cm) {\n      var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize;\n      for (var i = 0; i < ranges.length; i++) {\n        var pos = ranges[i].from();\n        var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize);\n        spaces.push(spaceStr(tabSize - col % tabSize));\n      }\n      cm.replaceSelections(spaces);\n    },\n    defaultTab: function (cm) {\n      if (cm.somethingSelected()) { cm.indentSelection(\"add\"); }\n      else { cm.execCommand(\"insertTab\"); }\n    },\n    // Swap the two chars left and right of each selection's head.\n    // Move cursor behind the two swapped characters afterwards.\n    //\n    // Doesn't consider line feeds a character.\n    // Doesn't scan more than one line above to find a character.\n    // Doesn't do anything on an empty line.\n    // Doesn't do anything with non-empty selections.\n    transposeChars: function (cm) { return runInOp(cm, function () {\n      var ranges = cm.listSelections(), newSel = [];\n      for (var i = 0; i < ranges.length; i++) {\n        if (!ranges[i].empty()) { continue }\n        var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text;\n        if (line) {\n          if (cur.ch == line.length) { cur = new Pos(cur.line, cur.ch - 1); }\n          if (cur.ch > 0) {\n            cur = new Pos(cur.line, cur.ch + 1);\n            cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2),\n                            Pos(cur.line, cur.ch - 2), cur, \"+transpose\");\n          } else if (cur.line > cm.doc.first) {\n            var prev = getLine(cm.doc, cur.line - 1).text;\n            if (prev) {\n              cur = new Pos(cur.line, 1);\n              cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() +\n                              prev.charAt(prev.length - 1),\n                              Pos(cur.line - 1, prev.length - 1), cur, \"+transpose\");\n            }\n          }\n        }\n        newSel.push(new Range(cur, cur));\n      }\n      cm.setSelections(newSel);\n    }); },\n    newlineAndIndent: function (cm) { return runInOp(cm, function () {\n      var sels = cm.listSelections();\n      for (var i = sels.length - 1; i >= 0; i--)\n        { cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, \"+input\"); }\n      sels = cm.listSelections();\n      for (var i$1 = 0; i$1 < sels.length; i$1++)\n        { cm.indentLine(sels[i$1].from().line, null, true); }\n      ensureCursorVisible(cm);\n    }); },\n    openLine: function (cm) { return cm.replaceSelection(\"\\n\", \"start\"); },\n    toggleOverwrite: function (cm) { return cm.toggleOverwrite(); }\n  };\n\n\n  function lineStart(cm, lineN) {\n    var line = getLine(cm.doc, lineN);\n    var visual = visualLine(line);\n    if (visual != line) { lineN = lineNo(visual); }\n    return endOfLine(true, cm, visual, lineN, 1)\n  }\n  function lineEnd(cm, lineN) {\n    var line = getLine(cm.doc, lineN);\n    var visual = visualLineEnd(line);\n    if (visual != line) { lineN = lineNo(visual); }\n    return endOfLine(true, cm, line, lineN, -1)\n  }\n  function lineStartSmart(cm, pos) {\n    var start = lineStart(cm, pos.line);\n    var line = getLine(cm.doc, start.line);\n    var order = getOrder(line, cm.doc.direction);\n    if (!order || order[0].level == 0) {\n      var firstNonWS = Math.max(0, line.text.search(/\\S/));\n      var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch;\n      return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky)\n    }\n    return start\n  }\n\n  // Run a handler that was bound to a key.\n  function doHandleBinding(cm, bound, dropShift) {\n    if (typeof bound == \"string\") {\n      bound = commands[bound];\n      if (!bound) { return false }\n    }\n    // Ensure previous input has been read, so that the handler sees a\n    // consistent view of the document\n    cm.display.input.ensurePolled();\n    var prevShift = cm.display.shift, done = false;\n    try {\n      if (cm.isReadOnly()) { cm.state.suppressEdits = true; }\n      if (dropShift) { cm.display.shift = false; }\n      done = bound(cm) != Pass;\n    } finally {\n      cm.display.shift = prevShift;\n      cm.state.suppressEdits = false;\n    }\n    return done\n  }\n\n  function lookupKeyForEditor(cm, name, handle) {\n    for (var i = 0; i < cm.state.keyMaps.length; i++) {\n      var result = lookupKey(name, cm.state.keyMaps[i], handle, cm);\n      if (result) { return result }\n    }\n    return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm))\n      || lookupKey(name, cm.options.keyMap, handle, cm)\n  }\n\n  // Note that, despite the name, this function is also used to check\n  // for bound mouse clicks.\n\n  var stopSeq = new Delayed;\n\n  function dispatchKey(cm, name, e, handle) {\n    var seq = cm.state.keySeq;\n    if (seq) {\n      if (isModifierKey(name)) { return \"handled\" }\n      if (/\\'$/.test(name))\n        { cm.state.keySeq = null; }\n      else\n        { stopSeq.set(50, function () {\n          if (cm.state.keySeq == seq) {\n            cm.state.keySeq = null;\n            cm.display.input.reset();\n          }\n        }); }\n      if (dispatchKeyInner(cm, seq + \" \" + name, e, handle)) { return true }\n    }\n    return dispatchKeyInner(cm, name, e, handle)\n  }\n\n  function dispatchKeyInner(cm, name, e, handle) {\n    var result = lookupKeyForEditor(cm, name, handle);\n\n    if (result == \"multi\")\n      { cm.state.keySeq = name; }\n    if (result == \"handled\")\n      { signalLater(cm, \"keyHandled\", cm, name, e); }\n\n    if (result == \"handled\" || result == \"multi\") {\n      e_preventDefault(e);\n      restartBlink(cm);\n    }\n\n    return !!result\n  }\n\n  // Handle a key from the keydown event.\n  function handleKeyBinding(cm, e) {\n    var name = keyName(e, true);\n    if (!name) { return false }\n\n    if (e.shiftKey && !cm.state.keySeq) {\n      // First try to resolve full name (including 'Shift-'). Failing\n      // that, see if there is a cursor-motion command (starting with\n      // 'go') bound to the keyname without 'Shift-'.\n      return dispatchKey(cm, \"Shift-\" + name, e, function (b) { return doHandleBinding(cm, b, true); })\n          || dispatchKey(cm, name, e, function (b) {\n               if (typeof b == \"string\" ? /^go[A-Z]/.test(b) : b.motion)\n                 { return doHandleBinding(cm, b) }\n             })\n    } else {\n      return dispatchKey(cm, name, e, function (b) { return doHandleBinding(cm, b); })\n    }\n  }\n\n  // Handle a key from the keypress event\n  function handleCharBinding(cm, e, ch) {\n    return dispatchKey(cm, \"'\" + ch + \"'\", e, function (b) { return doHandleBinding(cm, b, true); })\n  }\n\n  var lastStoppedKey = null;\n  function onKeyDown(e) {\n    var cm = this;\n    cm.curOp.focus = activeElt();\n    if (signalDOMEvent(cm, e)) { return }\n    // IE does strange things with escape.\n    if (ie && ie_version < 11 && e.keyCode == 27) { e.returnValue = false; }\n    var code = e.keyCode;\n    cm.display.shift = code == 16 || e.shiftKey;\n    var handled = handleKeyBinding(cm, e);\n    if (presto) {\n      lastStoppedKey = handled ? code : null;\n      // Opera has no cut event... we try to at least catch the key combo\n      if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey))\n        { cm.replaceSelection(\"\", null, \"cut\"); }\n    }\n\n    // Turn mouse into crosshair when Alt is held on Mac.\n    if (code == 18 && !/\\bCodeMirror-crosshair\\b/.test(cm.display.lineDiv.className))\n      { showCrossHair(cm); }\n  }\n\n  function showCrossHair(cm) {\n    var lineDiv = cm.display.lineDiv;\n    addClass(lineDiv, \"CodeMirror-crosshair\");\n\n    function up(e) {\n      if (e.keyCode == 18 || !e.altKey) {\n        rmClass(lineDiv, \"CodeMirror-crosshair\");\n        off(document, \"keyup\", up);\n        off(document, \"mouseover\", up);\n      }\n    }\n    on(document, \"keyup\", up);\n    on(document, \"mouseover\", up);\n  }\n\n  function onKeyUp(e) {\n    if (e.keyCode == 16) { this.doc.sel.shift = false; }\n    signalDOMEvent(this, e);\n  }\n\n  function onKeyPress(e) {\n    var cm = this;\n    if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) { return }\n    var keyCode = e.keyCode, charCode = e.charCode;\n    if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return}\n    if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) { return }\n    var ch = String.fromCharCode(charCode == null ? keyCode : charCode);\n    // Some browsers fire keypress events for backspace\n    if (ch == \"\\x08\") { return }\n    if (handleCharBinding(cm, e, ch)) { return }\n    cm.display.input.onKeyPress(e);\n  }\n\n  var DOUBLECLICK_DELAY = 400;\n\n  var PastClick = function(time, pos, button) {\n    this.time = time;\n    this.pos = pos;\n    this.button = button;\n  };\n\n  PastClick.prototype.compare = function (time, pos, button) {\n    return this.time + DOUBLECLICK_DELAY > time &&\n      cmp(pos, this.pos) == 0 && button == this.button\n  };\n\n  var lastClick, lastDoubleClick;\n  function clickRepeat(pos, button) {\n    var now = +new Date;\n    if (lastDoubleClick && lastDoubleClick.compare(now, pos, button)) {\n      lastClick = lastDoubleClick = null;\n      return \"triple\"\n    } else if (lastClick && lastClick.compare(now, pos, button)) {\n      lastDoubleClick = new PastClick(now, pos, button);\n      lastClick = null;\n      return \"double\"\n    } else {\n      lastClick = new PastClick(now, pos, button);\n      lastDoubleClick = null;\n      return \"single\"\n    }\n  }\n\n  // A mouse down can be a single click, double click, triple click,\n  // start of selection drag, start of text drag, new cursor\n  // (ctrl-click), rectangle drag (alt-drag), or xwin\n  // middle-click-paste. Or it might be a click on something we should\n  // not interfere with, such as a scrollbar or widget.\n  function onMouseDown(e) {\n    var cm = this, display = cm.display;\n    if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) { return }\n    display.input.ensurePolled();\n    display.shift = e.shiftKey;\n\n    if (eventInWidget(display, e)) {\n      if (!webkit) {\n        // Briefly turn off draggability, to allow widgets to do\n        // normal dragging things.\n        display.scroller.draggable = false;\n        setTimeout(function () { return display.scroller.draggable = true; }, 100);\n      }\n      return\n    }\n    if (clickInGutter(cm, e)) { return }\n    var pos = posFromMouse(cm, e), button = e_button(e), repeat = pos ? clickRepeat(pos, button) : \"single\";\n    window.focus();\n\n    // #3261: make sure, that we're not starting a second selection\n    if (button == 1 && cm.state.selectingText)\n      { cm.state.selectingText(e); }\n\n    if (pos && handleMappedButton(cm, button, pos, repeat, e)) { return }\n\n    if (button == 1) {\n      if (pos) { leftButtonDown(cm, pos, repeat, e); }\n      else if (e_target(e) == display.scroller) { e_preventDefault(e); }\n    } else if (button == 2) {\n      if (pos) { extendSelection(cm.doc, pos); }\n      setTimeout(function () { return display.input.focus(); }, 20);\n    } else if (button == 3) {\n      if (captureRightClick) { cm.display.input.onContextMenu(e); }\n      else { delayBlurEvent(cm); }\n    }\n  }\n\n  function handleMappedButton(cm, button, pos, repeat, event) {\n    var name = \"Click\";\n    if (repeat == \"double\") { name = \"Double\" + name; }\n    else if (repeat == \"triple\") { name = \"Triple\" + name; }\n    name = (button == 1 ? \"Left\" : button == 2 ? \"Middle\" : \"Right\") + name;\n\n    return dispatchKey(cm,  addModifierNames(name, event), event, function (bound) {\n      if (typeof bound == \"string\") { bound = commands[bound]; }\n      if (!bound) { return false }\n      var done = false;\n      try {\n        if (cm.isReadOnly()) { cm.state.suppressEdits = true; }\n        done = bound(cm, pos) != Pass;\n      } finally {\n        cm.state.suppressEdits = false;\n      }\n      return done\n    })\n  }\n\n  function configureMouse(cm, repeat, event) {\n    var option = cm.getOption(\"configureMouse\");\n    var value = option ? option(cm, repeat, event) : {};\n    if (value.unit == null) {\n      var rect = chromeOS ? event.shiftKey && event.metaKey : event.altKey;\n      value.unit = rect ? \"rectangle\" : repeat == \"single\" ? \"char\" : repeat == \"double\" ? \"word\" : \"line\";\n    }\n    if (value.extend == null || cm.doc.extend) { value.extend = cm.doc.extend || event.shiftKey; }\n    if (value.addNew == null) { value.addNew = mac ? event.metaKey : event.ctrlKey; }\n    if (value.moveOnDrag == null) { value.moveOnDrag = !(mac ? event.altKey : event.ctrlKey); }\n    return value\n  }\n\n  function leftButtonDown(cm, pos, repeat, event) {\n    if (ie) { setTimeout(bind(ensureFocus, cm), 0); }\n    else { cm.curOp.focus = activeElt(); }\n\n    var behavior = configureMouse(cm, repeat, event);\n\n    var sel = cm.doc.sel, contained;\n    if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() &&\n        repeat == \"single\" && (contained = sel.contains(pos)) > -1 &&\n        (cmp((contained = sel.ranges[contained]).from(), pos) < 0 || pos.xRel > 0) &&\n        (cmp(contained.to(), pos) > 0 || pos.xRel < 0))\n      { leftButtonStartDrag(cm, event, pos, behavior); }\n    else\n      { leftButtonSelect(cm, event, pos, behavior); }\n  }\n\n  // Start a text drag. When it ends, see if any dragging actually\n  // happen, and treat as a click if it didn't.\n  function leftButtonStartDrag(cm, event, pos, behavior) {\n    var display = cm.display, moved = false;\n    var dragEnd = operation(cm, function (e) {\n      if (webkit) { display.scroller.draggable = false; }\n      cm.state.draggingText = false;\n      off(display.wrapper.ownerDocument, \"mouseup\", dragEnd);\n      off(display.wrapper.ownerDocument, \"mousemove\", mouseMove);\n      off(display.scroller, \"dragstart\", dragStart);\n      off(display.scroller, \"drop\", dragEnd);\n      if (!moved) {\n        e_preventDefault(e);\n        if (!behavior.addNew)\n          { extendSelection(cm.doc, pos, null, null, behavior.extend); }\n        // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081)\n        if (webkit || ie && ie_version == 9)\n          { setTimeout(function () {display.wrapper.ownerDocument.body.focus(); display.input.focus();}, 20); }\n        else\n          { display.input.focus(); }\n      }\n    });\n    var mouseMove = function(e2) {\n      moved = moved || Math.abs(event.clientX - e2.clientX) + Math.abs(event.clientY - e2.clientY) >= 10;\n    };\n    var dragStart = function () { return moved = true; };\n    // Let the drag handler handle this.\n    if (webkit) { display.scroller.draggable = true; }\n    cm.state.draggingText = dragEnd;\n    dragEnd.copy = !behavior.moveOnDrag;\n    // IE's approach to draggable\n    if (display.scroller.dragDrop) { display.scroller.dragDrop(); }\n    on(display.wrapper.ownerDocument, \"mouseup\", dragEnd);\n    on(display.wrapper.ownerDocument, \"mousemove\", mouseMove);\n    on(display.scroller, \"dragstart\", dragStart);\n    on(display.scroller, \"drop\", dragEnd);\n\n    delayBlurEvent(cm);\n    setTimeout(function () { return display.input.focus(); }, 20);\n  }\n\n  function rangeForUnit(cm, pos, unit) {\n    if (unit == \"char\") { return new Range(pos, pos) }\n    if (unit == \"word\") { return cm.findWordAt(pos) }\n    if (unit == \"line\") { return new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) }\n    var result = unit(cm, pos);\n    return new Range(result.from, result.to)\n  }\n\n  // Normal selection, as opposed to text dragging.\n  function leftButtonSelect(cm, event, start, behavior) {\n    var display = cm.display, doc = cm.doc;\n    e_preventDefault(event);\n\n    var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges;\n    if (behavior.addNew && !behavior.extend) {\n      ourIndex = doc.sel.contains(start);\n      if (ourIndex > -1)\n        { ourRange = ranges[ourIndex]; }\n      else\n        { ourRange = new Range(start, start); }\n    } else {\n      ourRange = doc.sel.primary();\n      ourIndex = doc.sel.primIndex;\n    }\n\n    if (behavior.unit == \"rectangle\") {\n      if (!behavior.addNew) { ourRange = new Range(start, start); }\n      start = posFromMouse(cm, event, true, true);\n      ourIndex = -1;\n    } else {\n      var range$$1 = rangeForUnit(cm, start, behavior.unit);\n      if (behavior.extend)\n        { ourRange = extendRange(ourRange, range$$1.anchor, range$$1.head, behavior.extend); }\n      else\n        { ourRange = range$$1; }\n    }\n\n    if (!behavior.addNew) {\n      ourIndex = 0;\n      setSelection(doc, new Selection([ourRange], 0), sel_mouse);\n      startSel = doc.sel;\n    } else if (ourIndex == -1) {\n      ourIndex = ranges.length;\n      setSelection(doc, normalizeSelection(cm, ranges.concat([ourRange]), ourIndex),\n                   {scroll: false, origin: \"*mouse\"});\n    } else if (ranges.length > 1 && ranges[ourIndex].empty() && behavior.unit == \"char\" && !behavior.extend) {\n      setSelection(doc, normalizeSelection(cm, ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0),\n                   {scroll: false, origin: \"*mouse\"});\n      startSel = doc.sel;\n    } else {\n      replaceOneSelection(doc, ourIndex, ourRange, sel_mouse);\n    }\n\n    var lastPos = start;\n    function extendTo(pos) {\n      if (cmp(lastPos, pos) == 0) { return }\n      lastPos = pos;\n\n      if (behavior.unit == \"rectangle\") {\n        var ranges = [], tabSize = cm.options.tabSize;\n        var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize);\n        var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize);\n        var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol);\n        for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line));\n             line <= end; line++) {\n          var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize);\n          if (left == right)\n            { ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))); }\n          else if (text.length > leftPos)\n            { ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))); }\n        }\n        if (!ranges.length) { ranges.push(new Range(start, start)); }\n        setSelection(doc, normalizeSelection(cm, startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex),\n                     {origin: \"*mouse\", scroll: false});\n        cm.scrollIntoView(pos);\n      } else {\n        var oldRange = ourRange;\n        var range$$1 = rangeForUnit(cm, pos, behavior.unit);\n        var anchor = oldRange.anchor, head;\n        if (cmp(range$$1.anchor, anchor) > 0) {\n          head = range$$1.head;\n          anchor = minPos(oldRange.from(), range$$1.anchor);\n        } else {\n          head = range$$1.anchor;\n          anchor = maxPos(oldRange.to(), range$$1.head);\n        }\n        var ranges$1 = startSel.ranges.slice(0);\n        ranges$1[ourIndex] = bidiSimplify(cm, new Range(clipPos(doc, anchor), head));\n        setSelection(doc, normalizeSelection(cm, ranges$1, ourIndex), sel_mouse);\n      }\n    }\n\n    var editorSize = display.wrapper.getBoundingClientRect();\n    // Used to ensure timeout re-tries don't fire when another extend\n    // happened in the meantime (clearTimeout isn't reliable -- at\n    // least on Chrome, the timeouts still happen even when cleared,\n    // if the clear happens after their scheduled firing time).\n    var counter = 0;\n\n    function extend(e) {\n      var curCount = ++counter;\n      var cur = posFromMouse(cm, e, true, behavior.unit == \"rectangle\");\n      if (!cur) { return }\n      if (cmp(cur, lastPos) != 0) {\n        cm.curOp.focus = activeElt();\n        extendTo(cur);\n        var visible = visibleLines(display, doc);\n        if (cur.line >= visible.to || cur.line < visible.from)\n          { setTimeout(operation(cm, function () {if (counter == curCount) { extend(e); }}), 150); }\n      } else {\n        var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0;\n        if (outside) { setTimeout(operation(cm, function () {\n          if (counter != curCount) { return }\n          display.scroller.scrollTop += outside;\n          extend(e);\n        }), 50); }\n      }\n    }\n\n    function done(e) {\n      cm.state.selectingText = false;\n      counter = Infinity;\n      // If e is null or undefined we interpret this as someone trying\n      // to explicitly cancel the selection rather than the user\n      // letting go of the mouse button.\n      if (e) {\n        e_preventDefault(e);\n        display.input.focus();\n      }\n      off(display.wrapper.ownerDocument, \"mousemove\", move);\n      off(display.wrapper.ownerDocument, \"mouseup\", up);\n      doc.history.lastSelOrigin = null;\n    }\n\n    var move = operation(cm, function (e) {\n      if (e.buttons === 0 || !e_button(e)) { done(e); }\n      else { extend(e); }\n    });\n    var up = operation(cm, done);\n    cm.state.selectingText = up;\n    on(display.wrapper.ownerDocument, \"mousemove\", move);\n    on(display.wrapper.ownerDocument, \"mouseup\", up);\n  }\n\n  // Used when mouse-selecting to adjust the anchor to the proper side\n  // of a bidi jump depending on the visual position of the head.\n  function bidiSimplify(cm, range$$1) {\n    var anchor = range$$1.anchor;\n    var head = range$$1.head;\n    var anchorLine = getLine(cm.doc, anchor.line);\n    if (cmp(anchor, head) == 0 && anchor.sticky == head.sticky) { return range$$1 }\n    var order = getOrder(anchorLine);\n    if (!order) { return range$$1 }\n    var index = getBidiPartAt(order, anchor.ch, anchor.sticky), part = order[index];\n    if (part.from != anchor.ch && part.to != anchor.ch) { return range$$1 }\n    var boundary = index + ((part.from == anchor.ch) == (part.level != 1) ? 0 : 1);\n    if (boundary == 0 || boundary == order.length) { return range$$1 }\n\n    // Compute the relative visual position of the head compared to the\n    // anchor (<0 is to the left, >0 to the right)\n    var leftSide;\n    if (head.line != anchor.line) {\n      leftSide = (head.line - anchor.line) * (cm.doc.direction == \"ltr\" ? 1 : -1) > 0;\n    } else {\n      var headIndex = getBidiPartAt(order, head.ch, head.sticky);\n      var dir = headIndex - index || (head.ch - anchor.ch) * (part.level == 1 ? -1 : 1);\n      if (headIndex == boundary - 1 || headIndex == boundary)\n        { leftSide = dir < 0; }\n      else\n        { leftSide = dir > 0; }\n    }\n\n    var usePart = order[boundary + (leftSide ? -1 : 0)];\n    var from = leftSide == (usePart.level == 1);\n    var ch = from ? usePart.from : usePart.to, sticky = from ? \"after\" : \"before\";\n    return anchor.ch == ch && anchor.sticky == sticky ? range$$1 : new Range(new Pos(anchor.line, ch, sticky), head)\n  }\n\n\n  // Determines whether an event happened in the gutter, and fires the\n  // handlers for the corresponding event.\n  function gutterEvent(cm, e, type, prevent) {\n    var mX, mY;\n    if (e.touches) {\n      mX = e.touches[0].clientX;\n      mY = e.touches[0].clientY;\n    } else {\n      try { mX = e.clientX; mY = e.clientY; }\n      catch(e) { return false }\n    }\n    if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) { return false }\n    if (prevent) { e_preventDefault(e); }\n\n    var display = cm.display;\n    var lineBox = display.lineDiv.getBoundingClientRect();\n\n    if (mY > lineBox.bottom || !hasHandler(cm, type)) { return e_defaultPrevented(e) }\n    mY -= lineBox.top - display.viewOffset;\n\n    for (var i = 0; i < cm.display.gutterSpecs.length; ++i) {\n      var g = display.gutters.childNodes[i];\n      if (g && g.getBoundingClientRect().right >= mX) {\n        var line = lineAtHeight(cm.doc, mY);\n        var gutter = cm.display.gutterSpecs[i];\n        signal(cm, type, cm, line, gutter.className, e);\n        return e_defaultPrevented(e)\n      }\n    }\n  }\n\n  function clickInGutter(cm, e) {\n    return gutterEvent(cm, e, \"gutterClick\", true)\n  }\n\n  // CONTEXT MENU HANDLING\n\n  // To make the context menu work, we need to briefly unhide the\n  // textarea (making it as unobtrusive as possible) to let the\n  // right-click take effect on it.\n  function onContextMenu(cm, e) {\n    if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) { return }\n    if (signalDOMEvent(cm, e, \"contextmenu\")) { return }\n    if (!captureRightClick) { cm.display.input.onContextMenu(e); }\n  }\n\n  function contextMenuInGutter(cm, e) {\n    if (!hasHandler(cm, \"gutterContextMenu\")) { return false }\n    return gutterEvent(cm, e, \"gutterContextMenu\", false)\n  }\n\n  function themeChanged(cm) {\n    cm.display.wrapper.className = cm.display.wrapper.className.replace(/\\s*cm-s-\\S+/g, \"\") +\n      cm.options.theme.replace(/(^|\\s)\\s*/g, \" cm-s-\");\n    clearCaches(cm);\n  }\n\n  var Init = {toString: function(){return \"CodeMirror.Init\"}};\n\n  var defaults = {};\n  var optionHandlers = {};\n\n  function defineOptions(CodeMirror) {\n    var optionHandlers = CodeMirror.optionHandlers;\n\n    function option(name, deflt, handle, notOnInit) {\n      CodeMirror.defaults[name] = deflt;\n      if (handle) { optionHandlers[name] =\n        notOnInit ? function (cm, val, old) {if (old != Init) { handle(cm, val, old); }} : handle; }\n    }\n\n    CodeMirror.defineOption = option;\n\n    // Passed to option handlers when there is no old value.\n    CodeMirror.Init = Init;\n\n    // These two are, on init, called from the constructor because they\n    // have to be initialized before the editor can start at all.\n    option(\"value\", \"\", function (cm, val) { return cm.setValue(val); }, true);\n    option(\"mode\", null, function (cm, val) {\n      cm.doc.modeOption = val;\n      loadMode(cm);\n    }, true);\n\n    option(\"indentUnit\", 2, loadMode, true);\n    option(\"indentWithTabs\", false);\n    option(\"smartIndent\", true);\n    option(\"tabSize\", 4, function (cm) {\n      resetModeState(cm);\n      clearCaches(cm);\n      regChange(cm);\n    }, true);\n\n    option(\"lineSeparator\", null, function (cm, val) {\n      cm.doc.lineSep = val;\n      if (!val) { return }\n      var newBreaks = [], lineNo = cm.doc.first;\n      cm.doc.iter(function (line) {\n        for (var pos = 0;;) {\n          var found = line.text.indexOf(val, pos);\n          if (found == -1) { break }\n          pos = found + val.length;\n          newBreaks.push(Pos(lineNo, found));\n        }\n        lineNo++;\n      });\n      for (var i = newBreaks.length - 1; i >= 0; i--)\n        { replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)); }\n    });\n    option(\"specialChars\", /[\\u0000-\\u001f\\u007f-\\u009f\\u00ad\\u061c\\u200b-\\u200f\\u2028\\u2029\\ufeff\\ufff9-\\ufffc]/g, function (cm, val, old) {\n      cm.state.specialChars = new RegExp(val.source + (val.test(\"\\t\") ? \"\" : \"|\\t\"), \"g\");\n      if (old != Init) { cm.refresh(); }\n    });\n    option(\"specialCharPlaceholder\", defaultSpecialCharPlaceholder, function (cm) { return cm.refresh(); }, true);\n    option(\"electricChars\", true);\n    option(\"inputStyle\", mobile ? \"contenteditable\" : \"textarea\", function () {\n      throw new Error(\"inputStyle can not (yet) be changed in a running editor\") // FIXME\n    }, true);\n    option(\"spellcheck\", false, function (cm, val) { return cm.getInputField().spellcheck = val; }, true);\n    option(\"autocorrect\", false, function (cm, val) { return cm.getInputField().autocorrect = val; }, true);\n    option(\"autocapitalize\", false, function (cm, val) { return cm.getInputField().autocapitalize = val; }, true);\n    option(\"rtlMoveVisually\", !windows);\n    option(\"wholeLineUpdateBefore\", true);\n\n    option(\"theme\", \"default\", function (cm) {\n      themeChanged(cm);\n      updateGutters(cm);\n    }, true);\n    option(\"keyMap\", \"default\", function (cm, val, old) {\n      var next = getKeyMap(val);\n      var prev = old != Init && getKeyMap(old);\n      if (prev && prev.detach) { prev.detach(cm, next); }\n      if (next.attach) { next.attach(cm, prev || null); }\n    });\n    option(\"extraKeys\", null);\n    option(\"configureMouse\", null);\n\n    option(\"lineWrapping\", false, wrappingChanged, true);\n    option(\"gutters\", [], function (cm, val) {\n      cm.display.gutterSpecs = getGutters(val, cm.options.lineNumbers);\n      updateGutters(cm);\n    }, true);\n    option(\"fixedGutter\", true, function (cm, val) {\n      cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + \"px\" : \"0\";\n      cm.refresh();\n    }, true);\n    option(\"coverGutterNextToScrollbar\", false, function (cm) { return updateScrollbars(cm); }, true);\n    option(\"scrollbarStyle\", \"native\", function (cm) {\n      initScrollbars(cm);\n      updateScrollbars(cm);\n      cm.display.scrollbars.setScrollTop(cm.doc.scrollTop);\n      cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft);\n    }, true);\n    option(\"lineNumbers\", false, function (cm, val) {\n      cm.display.gutterSpecs = getGutters(cm.options.gutters, val);\n      updateGutters(cm);\n    }, true);\n    option(\"firstLineNumber\", 1, updateGutters, true);\n    option(\"lineNumberFormatter\", function (integer) { return integer; }, updateGutters, true);\n    option(\"showCursorWhenSelecting\", false, updateSelection, true);\n\n    option(\"resetSelectionOnContextMenu\", true);\n    option(\"lineWiseCopyCut\", true);\n    option(\"pasteLinesPerSelection\", true);\n    option(\"selectionsMayTouch\", false);\n\n    option(\"readOnly\", false, function (cm, val) {\n      if (val == \"nocursor\") {\n        onBlur(cm);\n        cm.display.input.blur();\n      }\n      cm.display.input.readOnlyChanged(val);\n    });\n    option(\"disableInput\", false, function (cm, val) {if (!val) { cm.display.input.reset(); }}, true);\n    option(\"dragDrop\", true, dragDropChanged);\n    option(\"allowDropFileTypes\", null);\n\n    option(\"cursorBlinkRate\", 530);\n    option(\"cursorScrollMargin\", 0);\n    option(\"cursorHeight\", 1, updateSelection, true);\n    option(\"singleCursorHeightPerLine\", true, updateSelection, true);\n    option(\"workTime\", 100);\n    option(\"workDelay\", 100);\n    option(\"flattenSpans\", true, resetModeState, true);\n    option(\"addModeClass\", false, resetModeState, true);\n    option(\"pollInterval\", 100);\n    option(\"undoDepth\", 200, function (cm, val) { return cm.doc.history.undoDepth = val; });\n    option(\"historyEventDelay\", 1250);\n    option(\"viewportMargin\", 10, function (cm) { return cm.refresh(); }, true);\n    option(\"maxHighlightLength\", 10000, resetModeState, true);\n    option(\"moveInputWithCursor\", true, function (cm, val) {\n      if (!val) { cm.display.input.resetPosition(); }\n    });\n\n    option(\"tabindex\", null, function (cm, val) { return cm.display.input.getField().tabIndex = val || \"\"; });\n    option(\"autofocus\", null);\n    option(\"direction\", \"ltr\", function (cm, val) { return cm.doc.setDirection(val); }, true);\n    option(\"phrases\", null);\n  }\n\n  function dragDropChanged(cm, value, old) {\n    var wasOn = old && old != Init;\n    if (!value != !wasOn) {\n      var funcs = cm.display.dragFunctions;\n      var toggle = value ? on : off;\n      toggle(cm.display.scroller, \"dragstart\", funcs.start);\n      toggle(cm.display.scroller, \"dragenter\", funcs.enter);\n      toggle(cm.display.scroller, \"dragover\", funcs.over);\n      toggle(cm.display.scroller, \"dragleave\", funcs.leave);\n      toggle(cm.display.scroller, \"drop\", funcs.drop);\n    }\n  }\n\n  function wrappingChanged(cm) {\n    if (cm.options.lineWrapping) {\n      addClass(cm.display.wrapper, \"CodeMirror-wrap\");\n      cm.display.sizer.style.minWidth = \"\";\n      cm.display.sizerWidth = null;\n    } else {\n      rmClass(cm.display.wrapper, \"CodeMirror-wrap\");\n      findMaxLine(cm);\n    }\n    estimateLineHeights(cm);\n    regChange(cm);\n    clearCaches(cm);\n    setTimeout(function () { return updateScrollbars(cm); }, 100);\n  }\n\n  // A CodeMirror instance represents an editor. This is the object\n  // that user code is usually dealing with.\n\n  function CodeMirror(place, options) {\n    var this$1 = this;\n\n    if (!(this instanceof CodeMirror)) { return new CodeMirror(place, options) }\n\n    this.options = options = options ? copyObj(options) : {};\n    // Determine effective options based on given values and defaults.\n    copyObj(defaults, options, false);\n\n    var doc = options.value;\n    if (typeof doc == \"string\") { doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction); }\n    else if (options.mode) { doc.modeOption = options.mode; }\n    this.doc = doc;\n\n    var input = new CodeMirror.inputStyles[options.inputStyle](this);\n    var display = this.display = new Display(place, doc, input, options);\n    display.wrapper.CodeMirror = this;\n    themeChanged(this);\n    if (options.lineWrapping)\n      { this.display.wrapper.className += \" CodeMirror-wrap\"; }\n    initScrollbars(this);\n\n    this.state = {\n      keyMaps: [],  // stores maps added by addKeyMap\n      overlays: [], // highlighting overlays, as added by addOverlay\n      modeGen: 0,   // bumped when mode/overlay changes, used to invalidate highlighting info\n      overwrite: false,\n      delayingBlurEvent: false,\n      focused: false,\n      suppressEdits: false, // used to disable editing during key handlers when in readOnly mode\n      pasteIncoming: -1, cutIncoming: -1, // help recognize paste/cut edits in input.poll\n      selectingText: false,\n      draggingText: false,\n      highlight: new Delayed(), // stores highlight worker timeout\n      keySeq: null,  // Unfinished key sequence\n      specialChars: null\n    };\n\n    if (options.autofocus && !mobile) { display.input.focus(); }\n\n    // Override magic textarea content restore that IE sometimes does\n    // on our hidden textarea on reload\n    if (ie && ie_version < 11) { setTimeout(function () { return this$1.display.input.reset(true); }, 20); }\n\n    registerEventHandlers(this);\n    ensureGlobalHandlers();\n\n    startOperation(this);\n    this.curOp.forceUpdate = true;\n    attachDoc(this, doc);\n\n    if ((options.autofocus && !mobile) || this.hasFocus())\n      { setTimeout(bind(onFocus, this), 20); }\n    else\n      { onBlur(this); }\n\n    for (var opt in optionHandlers) { if (optionHandlers.hasOwnProperty(opt))\n      { optionHandlers[opt](this$1, options[opt], Init); } }\n    maybeUpdateLineNumberWidth(this);\n    if (options.finishInit) { options.finishInit(this); }\n    for (var i = 0; i < initHooks.length; ++i) { initHooks[i](this$1); }\n    endOperation(this);\n    // Suppress optimizelegibility in Webkit, since it breaks text\n    // measuring on line wrapping boundaries.\n    if (webkit && options.lineWrapping &&\n        getComputedStyle(display.lineDiv).textRendering == \"optimizelegibility\")\n      { display.lineDiv.style.textRendering = \"auto\"; }\n  }\n\n  // The default configuration options.\n  CodeMirror.defaults = defaults;\n  // Functions to run when options are changed.\n  CodeMirror.optionHandlers = optionHandlers;\n\n  // Attach the necessary event handlers when initializing the editor\n  function registerEventHandlers(cm) {\n    var d = cm.display;\n    on(d.scroller, \"mousedown\", operation(cm, onMouseDown));\n    // Older IE's will not fire a second mousedown for a double click\n    if (ie && ie_version < 11)\n      { on(d.scroller, \"dblclick\", operation(cm, function (e) {\n        if (signalDOMEvent(cm, e)) { return }\n        var pos = posFromMouse(cm, e);\n        if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) { return }\n        e_preventDefault(e);\n        var word = cm.findWordAt(pos);\n        extendSelection(cm.doc, word.anchor, word.head);\n      })); }\n    else\n      { on(d.scroller, \"dblclick\", function (e) { return signalDOMEvent(cm, e) || e_preventDefault(e); }); }\n    // Some browsers fire contextmenu *after* opening the menu, at\n    // which point we can't mess with it anymore. Context menu is\n    // handled in onMouseDown for these browsers.\n    on(d.scroller, \"contextmenu\", function (e) { return onContextMenu(cm, e); });\n\n    // Used to suppress mouse event handling when a touch happens\n    var touchFinished, prevTouch = {end: 0};\n    function finishTouch() {\n      if (d.activeTouch) {\n        touchFinished = setTimeout(function () { return d.activeTouch = null; }, 1000);\n        prevTouch = d.activeTouch;\n        prevTouch.end = +new Date;\n      }\n    }\n    function isMouseLikeTouchEvent(e) {\n      if (e.touches.length != 1) { return false }\n      var touch = e.touches[0];\n      return touch.radiusX <= 1 && touch.radiusY <= 1\n    }\n    function farAway(touch, other) {\n      if (other.left == null) { return true }\n      var dx = other.left - touch.left, dy = other.top - touch.top;\n      return dx * dx + dy * dy > 20 * 20\n    }\n    on(d.scroller, \"touchstart\", function (e) {\n      if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e) && !clickInGutter(cm, e)) {\n        d.input.ensurePolled();\n        clearTimeout(touchFinished);\n        var now = +new Date;\n        d.activeTouch = {start: now, moved: false,\n                         prev: now - prevTouch.end <= 300 ? prevTouch : null};\n        if (e.touches.length == 1) {\n          d.activeTouch.left = e.touches[0].pageX;\n          d.activeTouch.top = e.touches[0].pageY;\n        }\n      }\n    });\n    on(d.scroller, \"touchmove\", function () {\n      if (d.activeTouch) { d.activeTouch.moved = true; }\n    });\n    on(d.scroller, \"touchend\", function (e) {\n      var touch = d.activeTouch;\n      if (touch && !eventInWidget(d, e) && touch.left != null &&\n          !touch.moved && new Date - touch.start < 300) {\n        var pos = cm.coordsChar(d.activeTouch, \"page\"), range;\n        if (!touch.prev || farAway(touch, touch.prev)) // Single tap\n          { range = new Range(pos, pos); }\n        else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap\n          { range = cm.findWordAt(pos); }\n        else // Triple tap\n          { range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))); }\n        cm.setSelection(range.anchor, range.head);\n        cm.focus();\n        e_preventDefault(e);\n      }\n      finishTouch();\n    });\n    on(d.scroller, \"touchcancel\", finishTouch);\n\n    // Sync scrolling between fake scrollbars and real scrollable\n    // area, ensure viewport is updated when scrolling.\n    on(d.scroller, \"scroll\", function () {\n      if (d.scroller.clientHeight) {\n        updateScrollTop(cm, d.scroller.scrollTop);\n        setScrollLeft(cm, d.scroller.scrollLeft, true);\n        signal(cm, \"scroll\", cm);\n      }\n    });\n\n    // Listen to wheel events in order to try and update the viewport on time.\n    on(d.scroller, \"mousewheel\", function (e) { return onScrollWheel(cm, e); });\n    on(d.scroller, \"DOMMouseScroll\", function (e) { return onScrollWheel(cm, e); });\n\n    // Prevent wrapper from ever scrolling\n    on(d.wrapper, \"scroll\", function () { return d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });\n\n    d.dragFunctions = {\n      enter: function (e) {if (!signalDOMEvent(cm, e)) { e_stop(e); }},\n      over: function (e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e); }},\n      start: function (e) { return onDragStart(cm, e); },\n      drop: operation(cm, onDrop),\n      leave: function (e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm); }}\n    };\n\n    var inp = d.input.getField();\n    on(inp, \"keyup\", function (e) { return onKeyUp.call(cm, e); });\n    on(inp, \"keydown\", operation(cm, onKeyDown));\n    on(inp, \"keypress\", operation(cm, onKeyPress));\n    on(inp, \"focus\", function (e) { return onFocus(cm, e); });\n    on(inp, \"blur\", function (e) { return onBlur(cm, e); });\n  }\n\n  var initHooks = [];\n  CodeMirror.defineInitHook = function (f) { return initHooks.push(f); };\n\n  // Indent the given line. The how parameter can be \"smart\",\n  // \"add\"/null, \"subtract\", or \"prev\". When aggressive is false\n  // (typically set to true for forced single-line indents), empty\n  // lines are not indented, and places where the mode returns Pass\n  // are left alone.\n  function indentLine(cm, n, how, aggressive) {\n    var doc = cm.doc, state;\n    if (how == null) { how = \"add\"; }\n    if (how == \"smart\") {\n      // Fall back to \"prev\" when the mode doesn't have an indentation\n      // method.\n      if (!doc.mode.indent) { how = \"prev\"; }\n      else { state = getContextBefore(cm, n).state; }\n    }\n\n    var tabSize = cm.options.tabSize;\n    var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);\n    if (line.stateAfter) { line.stateAfter = null; }\n    var curSpaceString = line.text.match(/^\\s*/)[0], indentation;\n    if (!aggressive && !/\\S/.test(line.text)) {\n      indentation = 0;\n      how = \"not\";\n    } else if (how == \"smart\") {\n      indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text);\n      if (indentation == Pass || indentation > 150) {\n        if (!aggressive) { return }\n        how = \"prev\";\n      }\n    }\n    if (how == \"prev\") {\n      if (n > doc.first) { indentation = countColumn(getLine(doc, n-1).text, null, tabSize); }\n      else { indentation = 0; }\n    } else if (how == \"add\") {\n      indentation = curSpace + cm.options.indentUnit;\n    } else if (how == \"subtract\") {\n      indentation = curSpace - cm.options.indentUnit;\n    } else if (typeof how == \"number\") {\n      indentation = curSpace + how;\n    }\n    indentation = Math.max(0, indentation);\n\n    var indentString = \"\", pos = 0;\n    if (cm.options.indentWithTabs)\n      { for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += \"\\t\";} }\n    if (pos < indentation) { indentString += spaceStr(indentation - pos); }\n\n    if (indentString != curSpaceString) {\n      replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), \"+input\");\n      line.stateAfter = null;\n      return true\n    } else {\n      // Ensure that, if the cursor was in the whitespace at the start\n      // of the line, it is moved to the end of that space.\n      for (var i$1 = 0; i$1 < doc.sel.ranges.length; i$1++) {\n        var range = doc.sel.ranges[i$1];\n        if (range.head.line == n && range.head.ch < curSpaceString.length) {\n          var pos$1 = Pos(n, curSpaceString.length);\n          replaceOneSelection(doc, i$1, new Range(pos$1, pos$1));\n          break\n        }\n      }\n    }\n  }\n\n  // This will be set to a {lineWise: bool, text: [string]} object, so\n  // that, when pasting, we know what kind of selections the copied\n  // text was made out of.\n  var lastCopied = null;\n\n  function setLastCopied(newLastCopied) {\n    lastCopied = newLastCopied;\n  }\n\n  function applyTextInput(cm, inserted, deleted, sel, origin) {\n    var doc = cm.doc;\n    cm.display.shift = false;\n    if (!sel) { sel = doc.sel; }\n\n    var recent = +new Date - 200;\n    var paste = origin == \"paste\" || cm.state.pasteIncoming > recent;\n    var textLines = splitLinesAuto(inserted), multiPaste = null;\n    // When pasting N lines into N selections, insert one line per selection\n    if (paste && sel.ranges.length > 1) {\n      if (lastCopied && lastCopied.text.join(\"\\n\") == inserted) {\n        if (sel.ranges.length % lastCopied.text.length == 0) {\n          multiPaste = [];\n          for (var i = 0; i < lastCopied.text.length; i++)\n            { multiPaste.push(doc.splitLines(lastCopied.text[i])); }\n        }\n      } else if (textLines.length == sel.ranges.length && cm.options.pasteLinesPerSelection) {\n        multiPaste = map(textLines, function (l) { return [l]; });\n      }\n    }\n\n    var updateInput = cm.curOp.updateInput;\n    // Normal behavior is to insert the new text into every selection\n    for (var i$1 = sel.ranges.length - 1; i$1 >= 0; i$1--) {\n      var range$$1 = sel.ranges[i$1];\n      var from = range$$1.from(), to = range$$1.to();\n      if (range$$1.empty()) {\n        if (deleted && deleted > 0) // Handle deletion\n          { from = Pos(from.line, from.ch - deleted); }\n        else if (cm.state.overwrite && !paste) // Handle overwrite\n          { to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); }\n        else if (paste && lastCopied && lastCopied.lineWise && lastCopied.text.join(\"\\n\") == inserted)\n          { from = to = Pos(from.line, 0); }\n      }\n      var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i$1 % multiPaste.length] : textLines,\n                         origin: origin || (paste ? \"paste\" : cm.state.cutIncoming > recent ? \"cut\" : \"+input\")};\n      makeChange(cm.doc, changeEvent);\n      signalLater(cm, \"inputRead\", cm, changeEvent);\n    }\n    if (inserted && !paste)\n      { triggerElectric(cm, inserted); }\n\n    ensureCursorVisible(cm);\n    if (cm.curOp.updateInput < 2) { cm.curOp.updateInput = updateInput; }\n    cm.curOp.typing = true;\n    cm.state.pasteIncoming = cm.state.cutIncoming = -1;\n  }\n\n  function handlePaste(e, cm) {\n    var pasted = e.clipboardData && e.clipboardData.getData(\"Text\");\n    if (pasted) {\n      e.preventDefault();\n      if (!cm.isReadOnly() && !cm.options.disableInput)\n        { runInOp(cm, function () { return applyTextInput(cm, pasted, 0, null, \"paste\"); }); }\n      return true\n    }\n  }\n\n  function triggerElectric(cm, inserted) {\n    // When an 'electric' character is inserted, immediately trigger a reindent\n    if (!cm.options.electricChars || !cm.options.smartIndent) { return }\n    var sel = cm.doc.sel;\n\n    for (var i = sel.ranges.length - 1; i >= 0; i--) {\n      var range$$1 = sel.ranges[i];\n      if (range$$1.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range$$1.head.line)) { continue }\n      var mode = cm.getModeAt(range$$1.head);\n      var indented = false;\n      if (mode.electricChars) {\n        for (var j = 0; j < mode.electricChars.length; j++)\n          { if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) {\n            indented = indentLine(cm, range$$1.head.line, \"smart\");\n            break\n          } }\n      } else if (mode.electricInput) {\n        if (mode.electricInput.test(getLine(cm.doc, range$$1.head.line).text.slice(0, range$$1.head.ch)))\n          { indented = indentLine(cm, range$$1.head.line, \"smart\"); }\n      }\n      if (indented) { signalLater(cm, \"electricInput\", cm, range$$1.head.line); }\n    }\n  }\n\n  function copyableRanges(cm) {\n    var text = [], ranges = [];\n    for (var i = 0; i < cm.doc.sel.ranges.length; i++) {\n      var line = cm.doc.sel.ranges[i].head.line;\n      var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)};\n      ranges.push(lineRange);\n      text.push(cm.getRange(lineRange.anchor, lineRange.head));\n    }\n    return {text: text, ranges: ranges}\n  }\n\n  function disableBrowserMagic(field, spellcheck, autocorrect, autocapitalize) {\n    field.setAttribute(\"autocorrect\", autocorrect ? \"\" : \"off\");\n    field.setAttribute(\"autocapitalize\", autocapitalize ? \"\" : \"off\");\n    field.setAttribute(\"spellcheck\", !!spellcheck);\n  }\n\n  function hiddenTextarea() {\n    var te = elt(\"textarea\", null, null, \"position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; outline: none\");\n    var div = elt(\"div\", [te], null, \"overflow: hidden; position: relative; width: 3px; height: 0px;\");\n    // The textarea is kept positioned near the cursor to prevent the\n    // fact that it'll be scrolled into view on input from scrolling\n    // our fake cursor out of view. On webkit, when wrap=off, paste is\n    // very slow. So make the area wide instead.\n    if (webkit) { te.style.width = \"1000px\"; }\n    else { te.setAttribute(\"wrap\", \"off\"); }\n    // If border: 0; -- iOS fails to open keyboard (issue #1287)\n    if (ios) { te.style.border = \"1px solid black\"; }\n    disableBrowserMagic(te);\n    return div\n  }\n\n  // The publicly visible API. Note that methodOp(f) means\n  // 'wrap f in an operation, performed on its `this` parameter'.\n\n  // This is not the complete set of editor methods. Most of the\n  // methods defined on the Doc type are also injected into\n  // CodeMirror.prototype, for backwards compatibility and\n  // convenience.\n\n  function addEditorMethods(CodeMirror) {\n    var optionHandlers = CodeMirror.optionHandlers;\n\n    var helpers = CodeMirror.helpers = {};\n\n    CodeMirror.prototype = {\n      constructor: CodeMirror,\n      focus: function(){window.focus(); this.display.input.focus();},\n\n      setOption: function(option, value) {\n        var options = this.options, old = options[option];\n        if (options[option] == value && option != \"mode\") { return }\n        options[option] = value;\n        if (optionHandlers.hasOwnProperty(option))\n          { operation(this, optionHandlers[option])(this, value, old); }\n        signal(this, \"optionChange\", this, option);\n      },\n\n      getOption: function(option) {return this.options[option]},\n      getDoc: function() {return this.doc},\n\n      addKeyMap: function(map$$1, bottom) {\n        this.state.keyMaps[bottom ? \"push\" : \"unshift\"](getKeyMap(map$$1));\n      },\n      removeKeyMap: function(map$$1) {\n        var maps = this.state.keyMaps;\n        for (var i = 0; i < maps.length; ++i)\n          { if (maps[i] == map$$1 || maps[i].name == map$$1) {\n            maps.splice(i, 1);\n            return true\n          } }\n      },\n\n      addOverlay: methodOp(function(spec, options) {\n        var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec);\n        if (mode.startState) { throw new Error(\"Overlays may not be stateful.\") }\n        insertSorted(this.state.overlays,\n                     {mode: mode, modeSpec: spec, opaque: options && options.opaque,\n                      priority: (options && options.priority) || 0},\n                     function (overlay) { return overlay.priority; });\n        this.state.modeGen++;\n        regChange(this);\n      }),\n      removeOverlay: methodOp(function(spec) {\n        var this$1 = this;\n\n        var overlays = this.state.overlays;\n        for (var i = 0; i < overlays.length; ++i) {\n          var cur = overlays[i].modeSpec;\n          if (cur == spec || typeof spec == \"string\" && cur.name == spec) {\n            overlays.splice(i, 1);\n            this$1.state.modeGen++;\n            regChange(this$1);\n            return\n          }\n        }\n      }),\n\n      indentLine: methodOp(function(n, dir, aggressive) {\n        if (typeof dir != \"string\" && typeof dir != \"number\") {\n          if (dir == null) { dir = this.options.smartIndent ? \"smart\" : \"prev\"; }\n          else { dir = dir ? \"add\" : \"subtract\"; }\n        }\n        if (isLine(this.doc, n)) { indentLine(this, n, dir, aggressive); }\n      }),\n      indentSelection: methodOp(function(how) {\n        var this$1 = this;\n\n        var ranges = this.doc.sel.ranges, end = -1;\n        for (var i = 0; i < ranges.length; i++) {\n          var range$$1 = ranges[i];\n          if (!range$$1.empty()) {\n            var from = range$$1.from(), to = range$$1.to();\n            var start = Math.max(end, from.line);\n            end = Math.min(this$1.lastLine(), to.line - (to.ch ? 0 : 1)) + 1;\n            for (var j = start; j < end; ++j)\n              { indentLine(this$1, j, how); }\n            var newRanges = this$1.doc.sel.ranges;\n            if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0)\n              { replaceOneSelection(this$1.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll); }\n          } else if (range$$1.head.line > end) {\n            indentLine(this$1, range$$1.head.line, how, true);\n            end = range$$1.head.line;\n            if (i == this$1.doc.sel.primIndex) { ensureCursorVisible(this$1); }\n          }\n        }\n      }),\n\n      // Fetch the parser token for a given character. Useful for hacks\n      // that want to inspect the mode state (say, for completion).\n      getTokenAt: function(pos, precise) {\n        return takeToken(this, pos, precise)\n      },\n\n      getLineTokens: function(line, precise) {\n        return takeToken(this, Pos(line), precise, true)\n      },\n\n      getTokenTypeAt: function(pos) {\n        pos = clipPos(this.doc, pos);\n        var styles = getLineStyles(this, getLine(this.doc, pos.line));\n        var before = 0, after = (styles.length - 1) / 2, ch = pos.ch;\n        var type;\n        if (ch == 0) { type = styles[2]; }\n        else { for (;;) {\n          var mid = (before + after) >> 1;\n          if ((mid ? styles[mid * 2 - 1] : 0) >= ch) { after = mid; }\n          else if (styles[mid * 2 + 1] < ch) { before = mid + 1; }\n          else { type = styles[mid * 2 + 2]; break }\n        } }\n        var cut = type ? type.indexOf(\"overlay \") : -1;\n        return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1)\n      },\n\n      getModeAt: function(pos) {\n        var mode = this.doc.mode;\n        if (!mode.innerMode) { return mode }\n        return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode\n      },\n\n      getHelper: function(pos, type) {\n        return this.getHelpers(pos, type)[0]\n      },\n\n      getHelpers: function(pos, type) {\n        var this$1 = this;\n\n        var found = [];\n        if (!helpers.hasOwnProperty(type)) { return found }\n        var help = helpers[type], mode = this.getModeAt(pos);\n        if (typeof mode[type] == \"string\") {\n          if (help[mode[type]]) { found.push(help[mode[type]]); }\n        } else if (mode[type]) {\n          for (var i = 0; i < mode[type].length; i++) {\n            var val = help[mode[type][i]];\n            if (val) { found.push(val); }\n          }\n        } else if (mode.helperType && help[mode.helperType]) {\n          found.push(help[mode.helperType]);\n        } else if (help[mode.name]) {\n          found.push(help[mode.name]);\n        }\n        for (var i$1 = 0; i$1 < help._global.length; i$1++) {\n          var cur = help._global[i$1];\n          if (cur.pred(mode, this$1) && indexOf(found, cur.val) == -1)\n            { found.push(cur.val); }\n        }\n        return found\n      },\n\n      getStateAfter: function(line, precise) {\n        var doc = this.doc;\n        line = clipLine(doc, line == null ? doc.first + doc.size - 1: line);\n        return getContextBefore(this, line + 1, precise).state\n      },\n\n      cursorCoords: function(start, mode) {\n        var pos, range$$1 = this.doc.sel.primary();\n        if (start == null) { pos = range$$1.head; }\n        else if (typeof start == \"object\") { pos = clipPos(this.doc, start); }\n        else { pos = start ? range$$1.from() : range$$1.to(); }\n        return cursorCoords(this, pos, mode || \"page\")\n      },\n\n      charCoords: function(pos, mode) {\n        return charCoords(this, clipPos(this.doc, pos), mode || \"page\")\n      },\n\n      coordsChar: function(coords, mode) {\n        coords = fromCoordSystem(this, coords, mode || \"page\");\n        return coordsChar(this, coords.left, coords.top)\n      },\n\n      lineAtHeight: function(height, mode) {\n        height = fromCoordSystem(this, {top: height, left: 0}, mode || \"page\").top;\n        return lineAtHeight(this.doc, height + this.display.viewOffset)\n      },\n      heightAtLine: function(line, mode, includeWidgets) {\n        var end = false, lineObj;\n        if (typeof line == \"number\") {\n          var last = this.doc.first + this.doc.size - 1;\n          if (line < this.doc.first) { line = this.doc.first; }\n          else if (line > last) { line = last; end = true; }\n          lineObj = getLine(this.doc, line);\n        } else {\n          lineObj = line;\n        }\n        return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || \"page\", includeWidgets || end).top +\n          (end ? this.doc.height - heightAtLine(lineObj) : 0)\n      },\n\n      defaultTextHeight: function() { return textHeight(this.display) },\n      defaultCharWidth: function() { return charWidth(this.display) },\n\n      getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo}},\n\n      addWidget: function(pos, node, scroll, vert, horiz) {\n        var display = this.display;\n        pos = cursorCoords(this, clipPos(this.doc, pos));\n        var top = pos.bottom, left = pos.left;\n        node.style.position = \"absolute\";\n        node.setAttribute(\"cm-ignore-events\", \"true\");\n        this.display.input.setUneditable(node);\n        display.sizer.appendChild(node);\n        if (vert == \"over\") {\n          top = pos.top;\n        } else if (vert == \"above\" || vert == \"near\") {\n          var vspace = Math.max(display.wrapper.clientHeight, this.doc.height),\n          hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth);\n          // Default to positioning above (if specified and possible); otherwise default to positioning below\n          if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight)\n            { top = pos.top - node.offsetHeight; }\n          else if (pos.bottom + node.offsetHeight <= vspace)\n            { top = pos.bottom; }\n          if (left + node.offsetWidth > hspace)\n            { left = hspace - node.offsetWidth; }\n        }\n        node.style.top = top + \"px\";\n        node.style.left = node.style.right = \"\";\n        if (horiz == \"right\") {\n          left = display.sizer.clientWidth - node.offsetWidth;\n          node.style.right = \"0px\";\n        } else {\n          if (horiz == \"left\") { left = 0; }\n          else if (horiz == \"middle\") { left = (display.sizer.clientWidth - node.offsetWidth) / 2; }\n          node.style.left = left + \"px\";\n        }\n        if (scroll)\n          { scrollIntoView(this, {left: left, top: top, right: left + node.offsetWidth, bottom: top + node.offsetHeight}); }\n      },\n\n      triggerOnKeyDown: methodOp(onKeyDown),\n      triggerOnKeyPress: methodOp(onKeyPress),\n      triggerOnKeyUp: onKeyUp,\n      triggerOnMouseDown: methodOp(onMouseDown),\n\n      execCommand: function(cmd) {\n        if (commands.hasOwnProperty(cmd))\n          { return commands[cmd].call(null, this) }\n      },\n\n      triggerElectric: methodOp(function(text) { triggerElectric(this, text); }),\n\n      findPosH: function(from, amount, unit, visually) {\n        var this$1 = this;\n\n        var dir = 1;\n        if (amount < 0) { dir = -1; amount = -amount; }\n        var cur = clipPos(this.doc, from);\n        for (var i = 0; i < amount; ++i) {\n          cur = findPosH(this$1.doc, cur, dir, unit, visually);\n          if (cur.hitSide) { break }\n        }\n        return cur\n      },\n\n      moveH: methodOp(function(dir, unit) {\n        var this$1 = this;\n\n        this.extendSelectionsBy(function (range$$1) {\n          if (this$1.display.shift || this$1.doc.extend || range$$1.empty())\n            { return findPosH(this$1.doc, range$$1.head, dir, unit, this$1.options.rtlMoveVisually) }\n          else\n            { return dir < 0 ? range$$1.from() : range$$1.to() }\n        }, sel_move);\n      }),\n\n      deleteH: methodOp(function(dir, unit) {\n        var sel = this.doc.sel, doc = this.doc;\n        if (sel.somethingSelected())\n          { doc.replaceSelection(\"\", null, \"+delete\"); }\n        else\n          { deleteNearSelection(this, function (range$$1) {\n            var other = findPosH(doc, range$$1.head, dir, unit, false);\n            return dir < 0 ? {from: other, to: range$$1.head} : {from: range$$1.head, to: other}\n          }); }\n      }),\n\n      findPosV: function(from, amount, unit, goalColumn) {\n        var this$1 = this;\n\n        var dir = 1, x = goalColumn;\n        if (amount < 0) { dir = -1; amount = -amount; }\n        var cur = clipPos(this.doc, from);\n        for (var i = 0; i < amount; ++i) {\n          var coords = cursorCoords(this$1, cur, \"div\");\n          if (x == null) { x = coords.left; }\n          else { coords.left = x; }\n          cur = findPosV(this$1, coords, dir, unit);\n          if (cur.hitSide) { break }\n        }\n        return cur\n      },\n\n      moveV: methodOp(function(dir, unit) {\n        var this$1 = this;\n\n        var doc = this.doc, goals = [];\n        var collapse = !this.display.shift && !doc.extend && doc.sel.somethingSelected();\n        doc.extendSelectionsBy(function (range$$1) {\n          if (collapse)\n            { return dir < 0 ? range$$1.from() : range$$1.to() }\n          var headPos = cursorCoords(this$1, range$$1.head, \"div\");\n          if (range$$1.goalColumn != null) { headPos.left = range$$1.goalColumn; }\n          goals.push(headPos.left);\n          var pos = findPosV(this$1, headPos, dir, unit);\n          if (unit == \"page\" && range$$1 == doc.sel.primary())\n            { addToScrollTop(this$1, charCoords(this$1, pos, \"div\").top - headPos.top); }\n          return pos\n        }, sel_move);\n        if (goals.length) { for (var i = 0; i < doc.sel.ranges.length; i++)\n          { doc.sel.ranges[i].goalColumn = goals[i]; } }\n      }),\n\n      // Find the word at the given position (as returned by coordsChar).\n      findWordAt: function(pos) {\n        var doc = this.doc, line = getLine(doc, pos.line).text;\n        var start = pos.ch, end = pos.ch;\n        if (line) {\n          var helper = this.getHelper(pos, \"wordChars\");\n          if ((pos.sticky == \"before\" || end == line.length) && start) { --start; } else { ++end; }\n          var startChar = line.charAt(start);\n          var check = isWordChar(startChar, helper)\n            ? function (ch) { return isWordChar(ch, helper); }\n            : /\\s/.test(startChar) ? function (ch) { return /\\s/.test(ch); }\n            : function (ch) { return (!/\\s/.test(ch) && !isWordChar(ch)); };\n          while (start > 0 && check(line.charAt(start - 1))) { --start; }\n          while (end < line.length && check(line.charAt(end))) { ++end; }\n        }\n        return new Range(Pos(pos.line, start), Pos(pos.line, end))\n      },\n\n      toggleOverwrite: function(value) {\n        if (value != null && value == this.state.overwrite) { return }\n        if (this.state.overwrite = !this.state.overwrite)\n          { addClass(this.display.cursorDiv, \"CodeMirror-overwrite\"); }\n        else\n          { rmClass(this.display.cursorDiv, \"CodeMirror-overwrite\"); }\n\n        signal(this, \"overwriteToggle\", this, this.state.overwrite);\n      },\n      hasFocus: function() { return this.display.input.getField() == activeElt() },\n      isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) },\n\n      scrollTo: methodOp(function (x, y) { scrollToCoords(this, x, y); }),\n      getScrollInfo: function() {\n        var scroller = this.display.scroller;\n        return {left: scroller.scrollLeft, top: scroller.scrollTop,\n                height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight,\n                width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth,\n                clientHeight: displayHeight(this), clientWidth: displayWidth(this)}\n      },\n\n      scrollIntoView: methodOp(function(range$$1, margin) {\n        if (range$$1 == null) {\n          range$$1 = {from: this.doc.sel.primary().head, to: null};\n          if (margin == null) { margin = this.options.cursorScrollMargin; }\n        } else if (typeof range$$1 == \"number\") {\n          range$$1 = {from: Pos(range$$1, 0), to: null};\n        } else if (range$$1.from == null) {\n          range$$1 = {from: range$$1, to: null};\n        }\n        if (!range$$1.to) { range$$1.to = range$$1.from; }\n        range$$1.margin = margin || 0;\n\n        if (range$$1.from.line != null) {\n          scrollToRange(this, range$$1);\n        } else {\n          scrollToCoordsRange(this, range$$1.from, range$$1.to, range$$1.margin);\n        }\n      }),\n\n      setSize: methodOp(function(width, height) {\n        var this$1 = this;\n\n        var interpret = function (val) { return typeof val == \"number\" || /^\\d+$/.test(String(val)) ? val + \"px\" : val; };\n        if (width != null) { this.display.wrapper.style.width = interpret(width); }\n        if (height != null) { this.display.wrapper.style.height = interpret(height); }\n        if (this.options.lineWrapping) { clearLineMeasurementCache(this); }\n        var lineNo$$1 = this.display.viewFrom;\n        this.doc.iter(lineNo$$1, this.display.viewTo, function (line) {\n          if (line.widgets) { for (var i = 0; i < line.widgets.length; i++)\n            { if (line.widgets[i].noHScroll) { regLineChange(this$1, lineNo$$1, \"widget\"); break } } }\n          ++lineNo$$1;\n        });\n        this.curOp.forceUpdate = true;\n        signal(this, \"refresh\", this);\n      }),\n\n      operation: function(f){return runInOp(this, f)},\n      startOperation: function(){return startOperation(this)},\n      endOperation: function(){return endOperation(this)},\n\n      refresh: methodOp(function() {\n        var oldHeight = this.display.cachedTextHeight;\n        regChange(this);\n        this.curOp.forceUpdate = true;\n        clearCaches(this);\n        scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop);\n        updateGutterSpace(this.display);\n        if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5)\n          { estimateLineHeights(this); }\n        signal(this, \"refresh\", this);\n      }),\n\n      swapDoc: methodOp(function(doc) {\n        var old = this.doc;\n        old.cm = null;\n        // Cancel the current text selection if any (#5821)\n        if (this.state.selectingText) { this.state.selectingText(); }\n        attachDoc(this, doc);\n        clearCaches(this);\n        this.display.input.reset();\n        scrollToCoords(this, doc.scrollLeft, doc.scrollTop);\n        this.curOp.forceScroll = true;\n        signalLater(this, \"swapDoc\", this, old);\n        return old\n      }),\n\n      phrase: function(phraseText) {\n        var phrases = this.options.phrases;\n        return phrases && Object.prototype.hasOwnProperty.call(phrases, phraseText) ? phrases[phraseText] : phraseText\n      },\n\n      getInputField: function(){return this.display.input.getField()},\n      getWrapperElement: function(){return this.display.wrapper},\n      getScrollerElement: function(){return this.display.scroller},\n      getGutterElement: function(){return this.display.gutters}\n    };\n    eventMixin(CodeMirror);\n\n    CodeMirror.registerHelper = function(type, name, value) {\n      if (!helpers.hasOwnProperty(type)) { helpers[type] = CodeMirror[type] = {_global: []}; }\n      helpers[type][name] = value;\n    };\n    CodeMirror.registerGlobalHelper = function(type, name, predicate, value) {\n      CodeMirror.registerHelper(type, name, value);\n      helpers[type]._global.push({pred: predicate, val: value});\n    };\n  }\n\n  // Used for horizontal relative motion. Dir is -1 or 1 (left or\n  // right), unit can be \"char\", \"column\" (like char, but doesn't\n  // cross line boundaries), \"word\" (across next word), or \"group\" (to\n  // the start of next group of word or non-word-non-whitespace\n  // chars). The visually param controls whether, in right-to-left\n  // text, direction 1 means to move towards the next index in the\n  // string, or towards the character to the right of the current\n  // position. The resulting position will have a hitSide=true\n  // property if it reached the end of the document.\n  function findPosH(doc, pos, dir, unit, visually) {\n    var oldPos = pos;\n    var origDir = dir;\n    var lineObj = getLine(doc, pos.line);\n    function findNextLine() {\n      var l = pos.line + dir;\n      if (l < doc.first || l >= doc.first + doc.size) { return false }\n      pos = new Pos(l, pos.ch, pos.sticky);\n      return lineObj = getLine(doc, l)\n    }\n    function moveOnce(boundToLine) {\n      var next;\n      if (visually) {\n        next = moveVisually(doc.cm, lineObj, pos, dir);\n      } else {\n        next = moveLogically(lineObj, pos, dir);\n      }\n      if (next == null) {\n        if (!boundToLine && findNextLine())\n          { pos = endOfLine(visually, doc.cm, lineObj, pos.line, dir); }\n        else\n          { return false }\n      } else {\n        pos = next;\n      }\n      return true\n    }\n\n    if (unit == \"char\") {\n      moveOnce();\n    } else if (unit == \"column\") {\n      moveOnce(true);\n    } else if (unit == \"word\" || unit == \"group\") {\n      var sawType = null, group = unit == \"group\";\n      var helper = doc.cm && doc.cm.getHelper(pos, \"wordChars\");\n      for (var first = true;; first = false) {\n        if (dir < 0 && !moveOnce(!first)) { break }\n        var cur = lineObj.text.charAt(pos.ch) || \"\\n\";\n        var type = isWordChar(cur, helper) ? \"w\"\n          : group && cur == \"\\n\" ? \"n\"\n          : !group || /\\s/.test(cur) ? null\n          : \"p\";\n        if (group && !first && !type) { type = \"s\"; }\n        if (sawType && sawType != type) {\n          if (dir < 0) {dir = 1; moveOnce(); pos.sticky = \"after\";}\n          break\n        }\n\n        if (type) { sawType = type; }\n        if (dir > 0 && !moveOnce(!first)) { break }\n      }\n    }\n    var result = skipAtomic(doc, pos, oldPos, origDir, true);\n    if (equalCursorPos(oldPos, result)) { result.hitSide = true; }\n    return result\n  }\n\n  // For relative vertical movement. Dir may be -1 or 1. Unit can be\n  // \"page\" or \"line\". The resulting position will have a hitSide=true\n  // property if it reached the end of the document.\n  function findPosV(cm, pos, dir, unit) {\n    var doc = cm.doc, x = pos.left, y;\n    if (unit == \"page\") {\n      var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);\n      var moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3);\n      y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount;\n\n    } else if (unit == \"line\") {\n      y = dir > 0 ? pos.bottom + 3 : pos.top - 3;\n    }\n    var target;\n    for (;;) {\n      target = coordsChar(cm, x, y);\n      if (!target.outside) { break }\n      if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break }\n      y += dir * 5;\n    }\n    return target\n  }\n\n  // CONTENTEDITABLE INPUT STYLE\n\n  var ContentEditableInput = function(cm) {\n    this.cm = cm;\n    this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null;\n    this.polling = new Delayed();\n    this.composing = null;\n    this.gracePeriod = false;\n    this.readDOMTimeout = null;\n  };\n\n  ContentEditableInput.prototype.init = function (display) {\n      var this$1 = this;\n\n    var input = this, cm = input.cm;\n    var div = input.div = display.lineDiv;\n    disableBrowserMagic(div, cm.options.spellcheck, cm.options.autocorrect, cm.options.autocapitalize);\n\n    on(div, \"paste\", function (e) {\n      if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return }\n      // IE doesn't fire input events, so we schedule a read for the pasted content in this way\n      if (ie_version <= 11) { setTimeout(operation(cm, function () { return this$1.updateFromDOM(); }), 20); }\n    });\n\n    on(div, \"compositionstart\", function (e) {\n      this$1.composing = {data: e.data, done: false};\n    });\n    on(div, \"compositionupdate\", function (e) {\n      if (!this$1.composing) { this$1.composing = {data: e.data, done: false}; }\n    });\n    on(div, \"compositionend\", function (e) {\n      if (this$1.composing) {\n        if (e.data != this$1.composing.data) { this$1.readFromDOMSoon(); }\n        this$1.composing.done = true;\n      }\n    });\n\n    on(div, \"touchstart\", function () { return input.forceCompositionEnd(); });\n\n    on(div, \"input\", function () {\n      if (!this$1.composing) { this$1.readFromDOMSoon(); }\n    });\n\n    function onCopyCut(e) {\n      if (signalDOMEvent(cm, e)) { return }\n      if (cm.somethingSelected()) {\n        setLastCopied({lineWise: false, text: cm.getSelections()});\n        if (e.type == \"cut\") { cm.replaceSelection(\"\", null, \"cut\"); }\n      } else if (!cm.options.lineWiseCopyCut) {\n        return\n      } else {\n        var ranges = copyableRanges(cm);\n        setLastCopied({lineWise: true, text: ranges.text});\n        if (e.type == \"cut\") {\n          cm.operation(function () {\n            cm.setSelections(ranges.ranges, 0, sel_dontScroll);\n            cm.replaceSelection(\"\", null, \"cut\");\n          });\n        }\n      }\n      if (e.clipboardData) {\n        e.clipboardData.clearData();\n        var content = lastCopied.text.join(\"\\n\");\n        // iOS exposes the clipboard API, but seems to discard content inserted into it\n        e.clipboardData.setData(\"Text\", content);\n        if (e.clipboardData.getData(\"Text\") == content) {\n          e.preventDefault();\n          return\n        }\n      }\n      // Old-fashioned briefly-focus-a-textarea hack\n      var kludge = hiddenTextarea(), te = kludge.firstChild;\n      cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild);\n      te.value = lastCopied.text.join(\"\\n\");\n      var hadFocus = document.activeElement;\n      selectInput(te);\n      setTimeout(function () {\n        cm.display.lineSpace.removeChild(kludge);\n        hadFocus.focus();\n        if (hadFocus == div) { input.showPrimarySelection(); }\n      }, 50);\n    }\n    on(div, \"copy\", onCopyCut);\n    on(div, \"cut\", onCopyCut);\n  };\n\n  ContentEditableInput.prototype.prepareSelection = function () {\n    var result = prepareSelection(this.cm, false);\n    result.focus = this.cm.state.focused;\n    return result\n  };\n\n  ContentEditableInput.prototype.showSelection = function (info, takeFocus) {\n    if (!info || !this.cm.display.view.length) { return }\n    if (info.focus || takeFocus) { this.showPrimarySelection(); }\n    this.showMultipleSelections(info);\n  };\n\n  ContentEditableInput.prototype.getSelection = function () {\n    return this.cm.display.wrapper.ownerDocument.getSelection()\n  };\n\n  ContentEditableInput.prototype.showPrimarySelection = function () {\n    var sel = this.getSelection(), cm = this.cm, prim = cm.doc.sel.primary();\n    var from = prim.from(), to = prim.to();\n\n    if (cm.display.viewTo == cm.display.viewFrom || from.line >= cm.display.viewTo || to.line < cm.display.viewFrom) {\n      sel.removeAllRanges();\n      return\n    }\n\n    var curAnchor = domToPos(cm, sel.anchorNode, sel.anchorOffset);\n    var curFocus = domToPos(cm, sel.focusNode, sel.focusOffset);\n    if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad &&\n        cmp(minPos(curAnchor, curFocus), from) == 0 &&\n        cmp(maxPos(curAnchor, curFocus), to) == 0)\n      { return }\n\n    var view = cm.display.view;\n    var start = (from.line >= cm.display.viewFrom && posToDOM(cm, from)) ||\n        {node: view[0].measure.map[2], offset: 0};\n    var end = to.line < cm.display.viewTo && posToDOM(cm, to);\n    if (!end) {\n      var measure = view[view.length - 1].measure;\n      var map$$1 = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map;\n      end = {node: map$$1[map$$1.length - 1], offset: map$$1[map$$1.length - 2] - map$$1[map$$1.length - 3]};\n    }\n\n    if (!start || !end) {\n      sel.removeAllRanges();\n      return\n    }\n\n    var old = sel.rangeCount && sel.getRangeAt(0), rng;\n    try { rng = range(start.node, start.offset, end.offset, end.node); }\n    catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible\n    if (rng) {\n      if (!gecko && cm.state.focused) {\n        sel.collapse(start.node, start.offset);\n        if (!rng.collapsed) {\n          sel.removeAllRanges();\n          sel.addRange(rng);\n        }\n      } else {\n        sel.removeAllRanges();\n        sel.addRange(rng);\n      }\n      if (old && sel.anchorNode == null) { sel.addRange(old); }\n      else if (gecko) { this.startGracePeriod(); }\n    }\n    this.rememberSelection();\n  };\n\n  ContentEditableInput.prototype.startGracePeriod = function () {\n      var this$1 = this;\n\n    clearTimeout(this.gracePeriod);\n    this.gracePeriod = setTimeout(function () {\n      this$1.gracePeriod = false;\n      if (this$1.selectionChanged())\n        { this$1.cm.operation(function () { return this$1.cm.curOp.selectionChanged = true; }); }\n    }, 20);\n  };\n\n  ContentEditableInput.prototype.showMultipleSelections = function (info) {\n    removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors);\n    removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection);\n  };\n\n  ContentEditableInput.prototype.rememberSelection = function () {\n    var sel = this.getSelection();\n    this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset;\n    this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset;\n  };\n\n  ContentEditableInput.prototype.selectionInEditor = function () {\n    var sel = this.getSelection();\n    if (!sel.rangeCount) { return false }\n    var node = sel.getRangeAt(0).commonAncestorContainer;\n    return contains(this.div, node)\n  };\n\n  ContentEditableInput.prototype.focus = function () {\n    if (this.cm.options.readOnly != \"nocursor\") {\n      if (!this.selectionInEditor())\n        { this.showSelection(this.prepareSelection(), true); }\n      this.div.focus();\n    }\n  };\n  ContentEditableInput.prototype.blur = function () { this.div.blur(); };\n  ContentEditableInput.prototype.getField = function () { return this.div };\n\n  ContentEditableInput.prototype.supportsTouch = function () { return true };\n\n  ContentEditableInput.prototype.receivedFocus = function () {\n    var input = this;\n    if (this.selectionInEditor())\n      { this.pollSelection(); }\n    else\n      { runInOp(this.cm, function () { return input.cm.curOp.selectionChanged = true; }); }\n\n    function poll() {\n      if (input.cm.state.focused) {\n        input.pollSelection();\n        input.polling.set(input.cm.options.pollInterval, poll);\n      }\n    }\n    this.polling.set(this.cm.options.pollInterval, poll);\n  };\n\n  ContentEditableInput.prototype.selectionChanged = function () {\n    var sel = this.getSelection();\n    return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset ||\n      sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset\n  };\n\n  ContentEditableInput.prototype.pollSelection = function () {\n    if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged()) { return }\n    var sel = this.getSelection(), cm = this.cm;\n    // On Android Chrome (version 56, at least), backspacing into an\n    // uneditable block element will put the cursor in that element,\n    // and then, because it's not editable, hide the virtual keyboard.\n    // Because Android doesn't allow us to actually detect backspace\n    // presses in a sane way, this code checks for when that happens\n    // and simulates a backspace press in this case.\n    if (android && chrome && this.cm.display.gutterSpecs.length && isInGutter(sel.anchorNode)) {\n      this.cm.triggerOnKeyDown({type: \"keydown\", keyCode: 8, preventDefault: Math.abs});\n      this.blur();\n      this.focus();\n      return\n    }\n    if (this.composing) { return }\n    this.rememberSelection();\n    var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset);\n    var head = domToPos(cm, sel.focusNode, sel.focusOffset);\n    if (anchor && head) { runInOp(cm, function () {\n      setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll);\n      if (anchor.bad || head.bad) { cm.curOp.selectionChanged = true; }\n    }); }\n  };\n\n  ContentEditableInput.prototype.pollContent = function () {\n    if (this.readDOMTimeout != null) {\n      clearTimeout(this.readDOMTimeout);\n      this.readDOMTimeout = null;\n    }\n\n    var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary();\n    var from = sel.from(), to = sel.to();\n    if (from.ch == 0 && from.line > cm.firstLine())\n      { from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length); }\n    if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine())\n      { to = Pos(to.line + 1, 0); }\n    if (from.line < display.viewFrom || to.line > display.viewTo - 1) { return false }\n\n    var fromIndex, fromLine, fromNode;\n    if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) {\n      fromLine = lineNo(display.view[0].line);\n      fromNode = display.view[0].node;\n    } else {\n      fromLine = lineNo(display.view[fromIndex].line);\n      fromNode = display.view[fromIndex - 1].node.nextSibling;\n    }\n    var toIndex = findViewIndex(cm, to.line);\n    var toLine, toNode;\n    if (toIndex == display.view.length - 1) {\n      toLine = display.viewTo - 1;\n      toNode = display.lineDiv.lastChild;\n    } else {\n      toLine = lineNo(display.view[toIndex + 1].line) - 1;\n      toNode = display.view[toIndex + 1].node.previousSibling;\n    }\n\n    if (!fromNode) { return false }\n    var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine));\n    var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length));\n    while (newText.length > 1 && oldText.length > 1) {\n      if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine--; }\n      else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++; }\n      else { break }\n    }\n\n    var cutFront = 0, cutEnd = 0;\n    var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length);\n    while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront))\n      { ++cutFront; }\n    var newBot = lst(newText), oldBot = lst(oldText);\n    var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0),\n                             oldBot.length - (oldText.length == 1 ? cutFront : 0));\n    while (cutEnd < maxCutEnd &&\n           newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1))\n      { ++cutEnd; }\n    // Try to move start of change to start of selection if ambiguous\n    if (newText.length == 1 && oldText.length == 1 && fromLine == from.line) {\n      while (cutFront && cutFront > from.ch &&\n             newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) {\n        cutFront--;\n        cutEnd++;\n      }\n    }\n\n    newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\\u200b+/, \"\");\n    newText[0] = newText[0].slice(cutFront).replace(/\\u200b+$/, \"\");\n\n    var chFrom = Pos(fromLine, cutFront);\n    var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0);\n    if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) {\n      replaceRange(cm.doc, newText, chFrom, chTo, \"+input\");\n      return true\n    }\n  };\n\n  ContentEditableInput.prototype.ensurePolled = function () {\n    this.forceCompositionEnd();\n  };\n  ContentEditableInput.prototype.reset = function () {\n    this.forceCompositionEnd();\n  };\n  ContentEditableInput.prototype.forceCompositionEnd = function () {\n    if (!this.composing) { return }\n    clearTimeout(this.readDOMTimeout);\n    this.composing = null;\n    this.updateFromDOM();\n    this.div.blur();\n    this.div.focus();\n  };\n  ContentEditableInput.prototype.readFromDOMSoon = function () {\n      var this$1 = this;\n\n    if (this.readDOMTimeout != null) { return }\n    this.readDOMTimeout = setTimeout(function () {\n      this$1.readDOMTimeout = null;\n      if (this$1.composing) {\n        if (this$1.composing.done) { this$1.composing = null; }\n        else { return }\n      }\n      this$1.updateFromDOM();\n    }, 80);\n  };\n\n  ContentEditableInput.prototype.updateFromDOM = function () {\n      var this$1 = this;\n\n    if (this.cm.isReadOnly() || !this.pollContent())\n      { runInOp(this.cm, function () { return regChange(this$1.cm); }); }\n  };\n\n  ContentEditableInput.prototype.setUneditable = function (node) {\n    node.contentEditable = \"false\";\n  };\n\n  ContentEditableInput.prototype.onKeyPress = function (e) {\n    if (e.charCode == 0 || this.composing) { return }\n    e.preventDefault();\n    if (!this.cm.isReadOnly())\n      { operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0); }\n  };\n\n  ContentEditableInput.prototype.readOnlyChanged = function (val) {\n    this.div.contentEditable = String(val != \"nocursor\");\n  };\n\n  ContentEditableInput.prototype.onContextMenu = function () {};\n  ContentEditableInput.prototype.resetPosition = function () {};\n\n  ContentEditableInput.prototype.needsContentAttribute = true;\n\n  function posToDOM(cm, pos) {\n    var view = findViewForLine(cm, pos.line);\n    if (!view || view.hidden) { return null }\n    var line = getLine(cm.doc, pos.line);\n    var info = mapFromLineView(view, line, pos.line);\n\n    var order = getOrder(line, cm.doc.direction), side = \"left\";\n    if (order) {\n      var partPos = getBidiPartAt(order, pos.ch);\n      side = partPos % 2 ? \"right\" : \"left\";\n    }\n    var result = nodeAndOffsetInLineMap(info.map, pos.ch, side);\n    result.offset = result.collapse == \"right\" ? result.end : result.start;\n    return result\n  }\n\n  function isInGutter(node) {\n    for (var scan = node; scan; scan = scan.parentNode)\n      { if (/CodeMirror-gutter-wrapper/.test(scan.className)) { return true } }\n    return false\n  }\n\n  function badPos(pos, bad) { if (bad) { pos.bad = true; } return pos }\n\n  function domTextBetween(cm, from, to, fromLine, toLine) {\n    var text = \"\", closing = false, lineSep = cm.doc.lineSeparator(), extraLinebreak = false;\n    function recognizeMarker(id) { return function (marker) { return marker.id == id; } }\n    function close() {\n      if (closing) {\n        text += lineSep;\n        if (extraLinebreak) { text += lineSep; }\n        closing = extraLinebreak = false;\n      }\n    }\n    function addText(str) {\n      if (str) {\n        close();\n        text += str;\n      }\n    }\n    function walk(node) {\n      if (node.nodeType == 1) {\n        var cmText = node.getAttribute(\"cm-text\");\n        if (cmText) {\n          addText(cmText);\n          return\n        }\n        var markerID = node.getAttribute(\"cm-marker\"), range$$1;\n        if (markerID) {\n          var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID));\n          if (found.length && (range$$1 = found[0].find(0)))\n            { addText(getBetween(cm.doc, range$$1.from, range$$1.to).join(lineSep)); }\n          return\n        }\n        if (node.getAttribute(\"contenteditable\") == \"false\") { return }\n        var isBlock = /^(pre|div|p|li|table|br)$/i.test(node.nodeName);\n        if (!/^br$/i.test(node.nodeName) && node.textContent.length == 0) { return }\n\n        if (isBlock) { close(); }\n        for (var i = 0; i < node.childNodes.length; i++)\n          { walk(node.childNodes[i]); }\n\n        if (/^(pre|p)$/i.test(node.nodeName)) { extraLinebreak = true; }\n        if (isBlock) { closing = true; }\n      } else if (node.nodeType == 3) {\n        addText(node.nodeValue.replace(/\\u200b/g, \"\").replace(/\\u00a0/g, \" \"));\n      }\n    }\n    for (;;) {\n      walk(from);\n      if (from == to) { break }\n      from = from.nextSibling;\n      extraLinebreak = false;\n    }\n    return text\n  }\n\n  function domToPos(cm, node, offset) {\n    var lineNode;\n    if (node == cm.display.lineDiv) {\n      lineNode = cm.display.lineDiv.childNodes[offset];\n      if (!lineNode) { return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true) }\n      node = null; offset = 0;\n    } else {\n      for (lineNode = node;; lineNode = lineNode.parentNode) {\n        if (!lineNode || lineNode == cm.display.lineDiv) { return null }\n        if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) { break }\n      }\n    }\n    for (var i = 0; i < cm.display.view.length; i++) {\n      var lineView = cm.display.view[i];\n      if (lineView.node == lineNode)\n        { return locateNodeInLineView(lineView, node, offset) }\n    }\n  }\n\n  function locateNodeInLineView(lineView, node, offset) {\n    var wrapper = lineView.text.firstChild, bad = false;\n    if (!node || !contains(wrapper, node)) { return badPos(Pos(lineNo(lineView.line), 0), true) }\n    if (node == wrapper) {\n      bad = true;\n      node = wrapper.childNodes[offset];\n      offset = 0;\n      if (!node) {\n        var line = lineView.rest ? lst(lineView.rest) : lineView.line;\n        return badPos(Pos(lineNo(line), line.text.length), bad)\n      }\n    }\n\n    var textNode = node.nodeType == 3 ? node : null, topNode = node;\n    if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) {\n      textNode = node.firstChild;\n      if (offset) { offset = textNode.nodeValue.length; }\n    }\n    while (topNode.parentNode != wrapper) { topNode = topNode.parentNode; }\n    var measure = lineView.measure, maps = measure.maps;\n\n    function find(textNode, topNode, offset) {\n      for (var i = -1; i < (maps ? maps.length : 0); i++) {\n        var map$$1 = i < 0 ? measure.map : maps[i];\n        for (var j = 0; j < map$$1.length; j += 3) {\n          var curNode = map$$1[j + 2];\n          if (curNode == textNode || curNode == topNode) {\n            var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]);\n            var ch = map$$1[j] + offset;\n            if (offset < 0 || curNode != textNode) { ch = map$$1[j + (offset ? 1 : 0)]; }\n            return Pos(line, ch)\n          }\n        }\n      }\n    }\n    var found = find(textNode, topNode, offset);\n    if (found) { return badPos(found, bad) }\n\n    // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems\n    for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) {\n      found = find(after, after.firstChild, 0);\n      if (found)\n        { return badPos(Pos(found.line, found.ch - dist), bad) }\n      else\n        { dist += after.textContent.length; }\n    }\n    for (var before = topNode.previousSibling, dist$1 = offset; before; before = before.previousSibling) {\n      found = find(before, before.firstChild, -1);\n      if (found)\n        { return badPos(Pos(found.line, found.ch + dist$1), bad) }\n      else\n        { dist$1 += before.textContent.length; }\n    }\n  }\n\n  // TEXTAREA INPUT STYLE\n\n  var TextareaInput = function(cm) {\n    this.cm = cm;\n    // See input.poll and input.reset\n    this.prevInput = \"\";\n\n    // Flag that indicates whether we expect input to appear real soon\n    // now (after some event like 'keypress' or 'input') and are\n    // polling intensively.\n    this.pollingFast = false;\n    // Self-resetting timeout for the poller\n    this.polling = new Delayed();\n    // Used to work around IE issue with selection being forgotten when focus moves away from textarea\n    this.hasSelection = false;\n    this.composing = null;\n  };\n\n  TextareaInput.prototype.init = function (display) {\n      var this$1 = this;\n\n    var input = this, cm = this.cm;\n    this.createField(display);\n    var te = this.textarea;\n\n    display.wrapper.insertBefore(this.wrapper, display.wrapper.firstChild);\n\n    // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore)\n    if (ios) { te.style.width = \"0px\"; }\n\n    on(te, \"input\", function () {\n      if (ie && ie_version >= 9 && this$1.hasSelection) { this$1.hasSelection = null; }\n      input.poll();\n    });\n\n    on(te, \"paste\", function (e) {\n      if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return }\n\n      cm.state.pasteIncoming = +new Date;\n      input.fastPoll();\n    });\n\n    function prepareCopyCut(e) {\n      if (signalDOMEvent(cm, e)) { return }\n      if (cm.somethingSelected()) {\n        setLastCopied({lineWise: false, text: cm.getSelections()});\n      } else if (!cm.options.lineWiseCopyCut) {\n        return\n      } else {\n        var ranges = copyableRanges(cm);\n        setLastCopied({lineWise: true, text: ranges.text});\n        if (e.type == \"cut\") {\n          cm.setSelections(ranges.ranges, null, sel_dontScroll);\n        } else {\n          input.prevInput = \"\";\n          te.value = ranges.text.join(\"\\n\");\n          selectInput(te);\n        }\n      }\n      if (e.type == \"cut\") { cm.state.cutIncoming = +new Date; }\n    }\n    on(te, \"cut\", prepareCopyCut);\n    on(te, \"copy\", prepareCopyCut);\n\n    on(display.scroller, \"paste\", function (e) {\n      if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return }\n      if (!te.dispatchEvent) {\n        cm.state.pasteIncoming = +new Date;\n        input.focus();\n        return\n      }\n\n      // Pass the `paste` event to the textarea so it's handled by its event listener.\n      var event = new Event(\"paste\");\n      event.clipboardData = e.clipboardData;\n      te.dispatchEvent(event);\n    });\n\n    // Prevent normal selection in the editor (we handle our own)\n    on(display.lineSpace, \"selectstart\", function (e) {\n      if (!eventInWidget(display, e)) { e_preventDefault(e); }\n    });\n\n    on(te, \"compositionstart\", function () {\n      var start = cm.getCursor(\"from\");\n      if (input.composing) { input.composing.range.clear(); }\n      input.composing = {\n        start: start,\n        range: cm.markText(start, cm.getCursor(\"to\"), {className: \"CodeMirror-composing\"})\n      };\n    });\n    on(te, \"compositionend\", function () {\n      if (input.composing) {\n        input.poll();\n        input.composing.range.clear();\n        input.composing = null;\n      }\n    });\n  };\n\n  TextareaInput.prototype.createField = function (_display) {\n    // Wraps and hides input textarea\n    this.wrapper = hiddenTextarea();\n    // The semihidden textarea that is focused when the editor is\n    // focused, and receives input.\n    this.textarea = this.wrapper.firstChild;\n  };\n\n  TextareaInput.prototype.prepareSelection = function () {\n    // Redraw the selection and/or cursor\n    var cm = this.cm, display = cm.display, doc = cm.doc;\n    var result = prepareSelection(cm);\n\n    // Move the hidden textarea near the cursor to prevent scrolling artifacts\n    if (cm.options.moveInputWithCursor) {\n      var headPos = cursorCoords(cm, doc.sel.primary().head, \"div\");\n      var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect();\n      result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10,\n                                          headPos.top + lineOff.top - wrapOff.top));\n      result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10,\n                                           headPos.left + lineOff.left - wrapOff.left));\n    }\n\n    return result\n  };\n\n  TextareaInput.prototype.showSelection = function (drawn) {\n    var cm = this.cm, display = cm.display;\n    removeChildrenAndAdd(display.cursorDiv, drawn.cursors);\n    removeChildrenAndAdd(display.selectionDiv, drawn.selection);\n    if (drawn.teTop != null) {\n      this.wrapper.style.top = drawn.teTop + \"px\";\n      this.wrapper.style.left = drawn.teLeft + \"px\";\n    }\n  };\n\n  // Reset the input to correspond to the selection (or to be empty,\n  // when not typing and nothing is selected)\n  TextareaInput.prototype.reset = function (typing) {\n    if (this.contextMenuPending || this.composing) { return }\n    var cm = this.cm;\n    if (cm.somethingSelected()) {\n      this.prevInput = \"\";\n      var content = cm.getSelection();\n      this.textarea.value = content;\n      if (cm.state.focused) { selectInput(this.textarea); }\n      if (ie && ie_version >= 9) { this.hasSelection = content; }\n    } else if (!typing) {\n      this.prevInput = this.textarea.value = \"\";\n      if (ie && ie_version >= 9) { this.hasSelection = null; }\n    }\n  };\n\n  TextareaInput.prototype.getField = function () { return this.textarea };\n\n  TextareaInput.prototype.supportsTouch = function () { return false };\n\n  TextareaInput.prototype.focus = function () {\n    if (this.cm.options.readOnly != \"nocursor\" && (!mobile || activeElt() != this.textarea)) {\n      try { this.textarea.focus(); }\n      catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM\n    }\n  };\n\n  TextareaInput.prototype.blur = function () { this.textarea.blur(); };\n\n  TextareaInput.prototype.resetPosition = function () {\n    this.wrapper.style.top = this.wrapper.style.left = 0;\n  };\n\n  TextareaInput.prototype.receivedFocus = function () { this.slowPoll(); };\n\n  // Poll for input changes, using the normal rate of polling. This\n  // runs as long as the editor is focused.\n  TextareaInput.prototype.slowPoll = function () {\n      var this$1 = this;\n\n    if (this.pollingFast) { return }\n    this.polling.set(this.cm.options.pollInterval, function () {\n      this$1.poll();\n      if (this$1.cm.state.focused) { this$1.slowPoll(); }\n    });\n  };\n\n  // When an event has just come in that is likely to add or change\n  // something in the input textarea, we poll faster, to ensure that\n  // the change appears on the screen quickly.\n  TextareaInput.prototype.fastPoll = function () {\n    var missed = false, input = this;\n    input.pollingFast = true;\n    function p() {\n      var changed = input.poll();\n      if (!changed && !missed) {missed = true; input.polling.set(60, p);}\n      else {input.pollingFast = false; input.slowPoll();}\n    }\n    input.polling.set(20, p);\n  };\n\n  // Read input from the textarea, and update the document to match.\n  // When something is selected, it is present in the textarea, and\n  // selected (unless it is huge, in which case a placeholder is\n  // used). When nothing is selected, the cursor sits after previously\n  // seen text (can be empty), which is stored in prevInput (we must\n  // not reset the textarea when typing, because that breaks IME).\n  TextareaInput.prototype.poll = function () {\n      var this$1 = this;\n\n    var cm = this.cm, input = this.textarea, prevInput = this.prevInput;\n    // Since this is called a *lot*, try to bail out as cheaply as\n    // possible when it is clear that nothing happened. hasSelection\n    // will be the case when there is a lot of text in the textarea,\n    // in which case reading its value would be expensive.\n    if (this.contextMenuPending || !cm.state.focused ||\n        (hasSelection(input) && !prevInput && !this.composing) ||\n        cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq)\n      { return false }\n\n    var text = input.value;\n    // If nothing changed, bail.\n    if (text == prevInput && !cm.somethingSelected()) { return false }\n    // Work around nonsensical selection resetting in IE9/10, and\n    // inexplicable appearance of private area unicode characters on\n    // some key combos in Mac (#2689).\n    if (ie && ie_version >= 9 && this.hasSelection === text ||\n        mac && /[\\uf700-\\uf7ff]/.test(text)) {\n      cm.display.input.reset();\n      return false\n    }\n\n    if (cm.doc.sel == cm.display.selForContextMenu) {\n      var first = text.charCodeAt(0);\n      if (first == 0x200b && !prevInput) { prevInput = \"\\u200b\"; }\n      if (first == 0x21da) { this.reset(); return this.cm.execCommand(\"undo\") }\n    }\n    // Find the part of the input that is actually new\n    var same = 0, l = Math.min(prevInput.length, text.length);\n    while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) { ++same; }\n\n    runInOp(cm, function () {\n      applyTextInput(cm, text.slice(same), prevInput.length - same,\n                     null, this$1.composing ? \"*compose\" : null);\n\n      // Don't leave long text in the textarea, since it makes further polling slow\n      if (text.length > 1000 || text.indexOf(\"\\n\") > -1) { input.value = this$1.prevInput = \"\"; }\n      else { this$1.prevInput = text; }\n\n      if (this$1.composing) {\n        this$1.composing.range.clear();\n        this$1.composing.range = cm.markText(this$1.composing.start, cm.getCursor(\"to\"),\n                                           {className: \"CodeMirror-composing\"});\n      }\n    });\n    return true\n  };\n\n  TextareaInput.prototype.ensurePolled = function () {\n    if (this.pollingFast && this.poll()) { this.pollingFast = false; }\n  };\n\n  TextareaInput.prototype.onKeyPress = function () {\n    if (ie && ie_version >= 9) { this.hasSelection = null; }\n    this.fastPoll();\n  };\n\n  TextareaInput.prototype.onContextMenu = function (e) {\n    var input = this, cm = input.cm, display = cm.display, te = input.textarea;\n    if (input.contextMenuPending) { input.contextMenuPending(); }\n    var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;\n    if (!pos || presto) { return } // Opera is difficult.\n\n    // Reset the current text selection only if the click is done outside of the selection\n    // and 'resetSelectionOnContextMenu' option is true.\n    var reset = cm.options.resetSelectionOnContextMenu;\n    if (reset && cm.doc.sel.contains(pos) == -1)\n      { operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll); }\n\n    var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText;\n    var wrapperBox = input.wrapper.offsetParent.getBoundingClientRect();\n    input.wrapper.style.cssText = \"position: static\";\n    te.style.cssText = \"position: absolute; width: 30px; height: 30px;\\n      top: \" + (e.clientY - wrapperBox.top - 5) + \"px; left: \" + (e.clientX - wrapperBox.left - 5) + \"px;\\n      z-index: 1000; background: \" + (ie ? \"rgba(255, 255, 255, .05)\" : \"transparent\") + \";\\n      outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);\";\n    var oldScrollY;\n    if (webkit) { oldScrollY = window.scrollY; } // Work around Chrome issue (#2712)\n    display.input.focus();\n    if (webkit) { window.scrollTo(null, oldScrollY); }\n    display.input.reset();\n    // Adds \"Select all\" to context menu in FF\n    if (!cm.somethingSelected()) { te.value = input.prevInput = \" \"; }\n    input.contextMenuPending = rehide;\n    display.selForContextMenu = cm.doc.sel;\n    clearTimeout(display.detectingSelectAll);\n\n    // Select-all will be greyed out if there's nothing to select, so\n    // this adds a zero-width space so that we can later check whether\n    // it got selected.\n    function prepareSelectAllHack() {\n      if (te.selectionStart != null) {\n        var selected = cm.somethingSelected();\n        var extval = \"\\u200b\" + (selected ? te.value : \"\");\n        te.value = \"\\u21da\"; // Used to catch context-menu undo\n        te.value = extval;\n        input.prevInput = selected ? \"\" : \"\\u200b\";\n        te.selectionStart = 1; te.selectionEnd = extval.length;\n        // Re-set this, in case some other handler touched the\n        // selection in the meantime.\n        display.selForContextMenu = cm.doc.sel;\n      }\n    }\n    function rehide() {\n      if (input.contextMenuPending != rehide) { return }\n      input.contextMenuPending = false;\n      input.wrapper.style.cssText = oldWrapperCSS;\n      te.style.cssText = oldCSS;\n      if (ie && ie_version < 9) { display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos); }\n\n      // Try to detect the user choosing select-all\n      if (te.selectionStart != null) {\n        if (!ie || (ie && ie_version < 9)) { prepareSelectAllHack(); }\n        var i = 0, poll = function () {\n          if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 &&\n              te.selectionEnd > 0 && input.prevInput == \"\\u200b\") {\n            operation(cm, selectAll)(cm);\n          } else if (i++ < 10) {\n            display.detectingSelectAll = setTimeout(poll, 500);\n          } else {\n            display.selForContextMenu = null;\n            display.input.reset();\n          }\n        };\n        display.detectingSelectAll = setTimeout(poll, 200);\n      }\n    }\n\n    if (ie && ie_version >= 9) { prepareSelectAllHack(); }\n    if (captureRightClick) {\n      e_stop(e);\n      var mouseup = function () {\n        off(window, \"mouseup\", mouseup);\n        setTimeout(rehide, 20);\n      };\n      on(window, \"mouseup\", mouseup);\n    } else {\n      setTimeout(rehide, 50);\n    }\n  };\n\n  TextareaInput.prototype.readOnlyChanged = function (val) {\n    if (!val) { this.reset(); }\n    this.textarea.disabled = val == \"nocursor\";\n  };\n\n  TextareaInput.prototype.setUneditable = function () {};\n\n  TextareaInput.prototype.needsContentAttribute = false;\n\n  function fromTextArea(textarea, options) {\n    options = options ? copyObj(options) : {};\n    options.value = textarea.value;\n    if (!options.tabindex && textarea.tabIndex)\n      { options.tabindex = textarea.tabIndex; }\n    if (!options.placeholder && textarea.placeholder)\n      { options.placeholder = textarea.placeholder; }\n    // Set autofocus to true if this textarea is focused, or if it has\n    // autofocus and no other element is focused.\n    if (options.autofocus == null) {\n      var hasFocus = activeElt();\n      options.autofocus = hasFocus == textarea ||\n        textarea.getAttribute(\"autofocus\") != null && hasFocus == document.body;\n    }\n\n    function save() {textarea.value = cm.getValue();}\n\n    var realSubmit;\n    if (textarea.form) {\n      on(textarea.form, \"submit\", save);\n      // Deplorable hack to make the submit method do the right thing.\n      if (!options.leaveSubmitMethodAlone) {\n        var form = textarea.form;\n        realSubmit = form.submit;\n        try {\n          var wrappedSubmit = form.submit = function () {\n            save();\n            form.submit = realSubmit;\n            form.submit();\n            form.submit = wrappedSubmit;\n          };\n        } catch(e) {}\n      }\n    }\n\n    options.finishInit = function (cm) {\n      cm.save = save;\n      cm.getTextArea = function () { return textarea; };\n      cm.toTextArea = function () {\n        cm.toTextArea = isNaN; // Prevent this from being ran twice\n        save();\n        textarea.parentNode.removeChild(cm.getWrapperElement());\n        textarea.style.display = \"\";\n        if (textarea.form) {\n          off(textarea.form, \"submit\", save);\n          if (typeof textarea.form.submit == \"function\")\n            { textarea.form.submit = realSubmit; }\n        }\n      };\n    };\n\n    textarea.style.display = \"none\";\n    var cm = CodeMirror(function (node) { return textarea.parentNode.insertBefore(node, textarea.nextSibling); },\n      options);\n    return cm\n  }\n\n  function addLegacyProps(CodeMirror) {\n    CodeMirror.off = off;\n    CodeMirror.on = on;\n    CodeMirror.wheelEventPixels = wheelEventPixels;\n    CodeMirror.Doc = Doc;\n    CodeMirror.splitLines = splitLinesAuto;\n    CodeMirror.countColumn = countColumn;\n    CodeMirror.findColumn = findColumn;\n    CodeMirror.isWordChar = isWordCharBasic;\n    CodeMirror.Pass = Pass;\n    CodeMirror.signal = signal;\n    CodeMirror.Line = Line;\n    CodeMirror.changeEnd = changeEnd;\n    CodeMirror.scrollbarModel = scrollbarModel;\n    CodeMirror.Pos = Pos;\n    CodeMirror.cmpPos = cmp;\n    CodeMirror.modes = modes;\n    CodeMirror.mimeModes = mimeModes;\n    CodeMirror.resolveMode = resolveMode;\n    CodeMirror.getMode = getMode;\n    CodeMirror.modeExtensions = modeExtensions;\n    CodeMirror.extendMode = extendMode;\n    CodeMirror.copyState = copyState;\n    CodeMirror.startState = startState;\n    CodeMirror.innerMode = innerMode;\n    CodeMirror.commands = commands;\n    CodeMirror.keyMap = keyMap;\n    CodeMirror.keyName = keyName;\n    CodeMirror.isModifierKey = isModifierKey;\n    CodeMirror.lookupKey = lookupKey;\n    CodeMirror.normalizeKeyMap = normalizeKeyMap;\n    CodeMirror.StringStream = StringStream;\n    CodeMirror.SharedTextMarker = SharedTextMarker;\n    CodeMirror.TextMarker = TextMarker;\n    CodeMirror.LineWidget = LineWidget;\n    CodeMirror.e_preventDefault = e_preventDefault;\n    CodeMirror.e_stopPropagation = e_stopPropagation;\n    CodeMirror.e_stop = e_stop;\n    CodeMirror.addClass = addClass;\n    CodeMirror.contains = contains;\n    CodeMirror.rmClass = rmClass;\n    CodeMirror.keyNames = keyNames;\n  }\n\n  // EDITOR CONSTRUCTOR\n\n  defineOptions(CodeMirror);\n\n  addEditorMethods(CodeMirror);\n\n  // Set up methods on CodeMirror's prototype to redirect to the editor's document.\n  var dontDelegate = \"iter insert remove copy getEditor constructor\".split(\" \");\n  for (var prop in Doc.prototype) { if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0)\n    { CodeMirror.prototype[prop] = (function(method) {\n      return function() {return method.apply(this.doc, arguments)}\n    })(Doc.prototype[prop]); } }\n\n  eventMixin(Doc);\n  CodeMirror.inputStyles = {\"textarea\": TextareaInput, \"contenteditable\": ContentEditableInput};\n\n  // Extra arguments are stored as the mode's dependencies, which is\n  // used by (legacy) mechanisms like loadmode.js to automatically\n  // load a mode. (Preferred mechanism is the require/define calls.)\n  CodeMirror.defineMode = function(name/*, mode, …*/) {\n    if (!CodeMirror.defaults.mode && name != \"null\") { CodeMirror.defaults.mode = name; }\n    defineMode.apply(this, arguments);\n  };\n\n  CodeMirror.defineMIME = defineMIME;\n\n  // Minimal default mode.\n  CodeMirror.defineMode(\"null\", function () { return ({token: function (stream) { return stream.skipToEnd(); }}); });\n  CodeMirror.defineMIME(\"text/plain\", \"null\");\n\n  // EXTENSIONS\n\n  CodeMirror.defineExtension = function (name, func) {\n    CodeMirror.prototype[name] = func;\n  };\n  CodeMirror.defineDocExtension = function (name, func) {\n    Doc.prototype[name] = func;\n  };\n\n  CodeMirror.fromTextArea = fromTextArea;\n\n  addLegacyProps(CodeMirror);\n\n  CodeMirror.version = \"5.48.4\";\n\n  return CodeMirror;\n\n})));\n"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/codemirror/dracula.css",
    "content": "/*\n\n    Name:       dracula\n    Author:     Michael Kaminsky (http://github.com/mkaminsky11)\n\n    Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)\n\n*/\n\n\n.cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters {\n  background-color: #282a36 !important;\n  color: #f8f8f2 !important;\n  border: none;\n}\n.cm-s-dracula .CodeMirror-gutters { color: #282a36; }\n.cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; }\n.cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; }\n.cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula span.cm-comment { color: #6272a4; }\n.cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; }\n.cm-s-dracula span.cm-number { color: #bd93f9; }\n.cm-s-dracula span.cm-variable { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-2 { color: white; }\n.cm-s-dracula span.cm-def { color: #50fa7b; }\n.cm-s-dracula span.cm-operator { color: #ff79c6; }\n.cm-s-dracula span.cm-keyword { color: #ff79c6; }\n.cm-s-dracula span.cm-atom { color: #bd93f9; }\n.cm-s-dracula span.cm-meta { color: #f8f8f2; }\n.cm-s-dracula span.cm-tag { color: #ff79c6; }\n.cm-s-dracula span.cm-attribute { color: #50fa7b; }\n.cm-s-dracula span.cm-qualifier { color: #50fa7b; }\n.cm-s-dracula span.cm-property { color: #66d9ef; }\n.cm-s-dracula span.cm-builtin { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-3, .cm-s-dracula span.cm-type { color: #ffb86c; }\n\n.cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); }\n.cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }\n"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/codemirror/idea.css",
    "content": "/**\n    Name:       IDEA default theme\n    From IntelliJ IDEA by JetBrains\n */\n\n.cm-s-idea span.cm-meta { color: #808000; }\n.cm-s-idea span.cm-number { color: #0000FF; }\n.cm-s-idea span.cm-keyword { line-height: 1em; font-weight: bold; color: #000080; }\n.cm-s-idea span.cm-atom { font-weight: bold; color: #000080; }\n.cm-s-idea span.cm-def { color: #000000; }\n.cm-s-idea span.cm-variable { color: black; }\n.cm-s-idea span.cm-variable-2 { color: black; }\n.cm-s-idea span.cm-variable-3, .cm-s-idea span.cm-type { color: black; }\n.cm-s-idea span.cm-property { color: black; }\n.cm-s-idea span.cm-operator { color: black; }\n.cm-s-idea span.cm-comment { color: #808080; }\n.cm-s-idea span.cm-string { color: #008000; }\n.cm-s-idea span.cm-string-2 { color: #008000; }\n.cm-s-idea span.cm-qualifier { color: #555; }\n.cm-s-idea span.cm-error { color: #FF0000; }\n.cm-s-idea span.cm-attribute { color: #0000FF; }\n.cm-s-idea span.cm-tag { color: #000080; }\n.cm-s-idea span.cm-link { color: #0000FF; }\n.cm-s-idea .CodeMirror-activeline-background { background: #FFFAE3; }\n\n.cm-s-idea span.cm-builtin { color: #30a; }\n.cm-s-idea span.cm-bracket { color: #cc7; }\n.cm-s-idea  { font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;}\n\n\n.cm-s-idea .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; }\n\n.CodeMirror-hints.idea {\n  font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;\n  color: #616569;\n  background-color: #ebf3fd !important;\n}\n\n.CodeMirror-hints.idea .CodeMirror-hint-active {\n  background-color: #a2b8c9 !important;\n  color: #5c6065 !important;\n}"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/codemirror/javascript.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nCodeMirror.defineMode(\"javascript\", function(config, parserConfig) {\n  var indentUnit = config.indentUnit;\n  var statementIndent = parserConfig.statementIndent;\n  var jsonldMode = parserConfig.jsonld;\n  var jsonMode = parserConfig.json || jsonldMode;\n  var isTS = parserConfig.typescript;\n  var wordRE = parserConfig.wordCharacters || /[\\w$\\xa1-\\uffff]/;\n\n  // Tokenizer\n\n  var keywords = function(){\n    function kw(type) {return {type: type, style: \"keyword\"};}\n    var A = kw(\"keyword a\"), B = kw(\"keyword b\"), C = kw(\"keyword c\"), D = kw(\"keyword d\");\n    var operator = kw(\"operator\"), atom = {type: \"atom\", style: \"atom\"};\n\n    return {\n      \"if\": kw(\"if\"), \"while\": A, \"with\": A, \"else\": B, \"do\": B, \"try\": B, \"finally\": B,\n      \"return\": D, \"break\": D, \"continue\": D, \"new\": kw(\"new\"), \"delete\": C, \"void\": C, \"throw\": C,\n      \"debugger\": kw(\"debugger\"), \"var\": kw(\"var\"), \"const\": kw(\"var\"), \"let\": kw(\"var\"),\n      \"function\": kw(\"function\"), \"catch\": kw(\"catch\"),\n      \"for\": kw(\"for\"), \"switch\": kw(\"switch\"), \"case\": kw(\"case\"), \"default\": kw(\"default\"),\n      \"in\": operator, \"typeof\": operator, \"instanceof\": operator,\n      \"true\": atom, \"false\": atom, \"null\": atom, \"undefined\": atom, \"NaN\": atom, \"Infinity\": atom,\n      \"this\": kw(\"this\"), \"class\": kw(\"class\"), \"super\": kw(\"atom\"),\n      \"yield\": C, \"export\": kw(\"export\"), \"import\": kw(\"import\"), \"extends\": C,\n      \"await\": C\n    };\n  }();\n\n  var isOperatorChar = /[+\\-*&%=<>!?|~^@]/;\n  var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)\"/;\n\n  function readRegexp(stream) {\n    var escaped = false, next, inSet = false;\n    while ((next = stream.next()) != null) {\n      if (!escaped) {\n        if (next == \"/\" && !inSet) return;\n        if (next == \"[\") inSet = true;\n        else if (inSet && next == \"]\") inSet = false;\n      }\n      escaped = !escaped && next == \"\\\\\";\n    }\n  }\n\n  // Used as scratch variables to communicate multiple values without\n  // consing up tons of objects.\n  var type, content;\n  function ret(tp, style, cont) {\n    type = tp; content = cont;\n    return style;\n  }\n  function tokenBase(stream, state) {\n    var ch = stream.next();\n    if (ch == '\"' || ch == \"'\") {\n      state.tokenize = tokenString(ch);\n      return state.tokenize(stream, state);\n    } else if (ch == \".\" && stream.match(/^\\d[\\d_]*(?:[eE][+\\-]?[\\d_]+)?/)) {\n      return ret(\"number\", \"number\");\n    } else if (ch == \".\" && stream.match(\"..\")) {\n      return ret(\"spread\", \"meta\");\n    } else if (/[\\[\\]{}\\(\\),;\\:\\.]/.test(ch)) {\n      return ret(ch);\n    } else if (ch == \"=\" && stream.eat(\">\")) {\n      return ret(\"=>\", \"operator\");\n    } else if (ch == \"0\" && stream.match(/^(?:x[\\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)) {\n      return ret(\"number\", \"number\");\n    } else if (/\\d/.test(ch)) {\n      stream.match(/^[\\d_]*(?:n|(?:\\.[\\d_]*)?(?:[eE][+\\-]?[\\d_]+)?)?/);\n      return ret(\"number\", \"number\");\n    } else if (ch == \"/\") {\n      if (stream.eat(\"*\")) {\n        state.tokenize = tokenComment;\n        return tokenComment(stream, state);\n      } else if (stream.eat(\"/\")) {\n        stream.skipToEnd();\n        return ret(\"comment\", \"comment\");\n      } else if (expressionAllowed(stream, state, 1)) {\n        readRegexp(stream);\n        stream.match(/^\\b(([gimyus])(?![gimyus]*\\2))+\\b/);\n        return ret(\"regexp\", \"string-2\");\n      } else {\n        stream.eat(\"=\");\n        return ret(\"operator\", \"operator\", stream.current());\n      }\n    } else if (ch == \"`\") {\n      state.tokenize = tokenQuasi;\n      return tokenQuasi(stream, state);\n    } else if (ch == \"#\") {\n      stream.skipToEnd();\n      return ret(\"error\", \"error\");\n    } else if (ch == \"<\" && stream.match(\"!--\") || ch == \"-\" && stream.match(\"->\")) {\n      stream.skipToEnd()\n      return ret(\"comment\", \"comment\")\n    } else if (isOperatorChar.test(ch)) {\n      if (ch != \">\" || !state.lexical || state.lexical.type != \">\") {\n        if (stream.eat(\"=\")) {\n          if (ch == \"!\" || ch == \"=\") stream.eat(\"=\")\n        } else if (/[<>*+\\-]/.test(ch)) {\n          stream.eat(ch)\n          if (ch == \">\") stream.eat(ch)\n        }\n      }\n      return ret(\"operator\", \"operator\", stream.current());\n    } else if (wordRE.test(ch)) {\n      stream.eatWhile(wordRE);\n      var word = stream.current()\n      if (state.lastType != \".\") {\n        if (keywords.propertyIsEnumerable(word)) {\n          var kw = keywords[word]\n          return ret(kw.type, kw.style, word)\n        }\n        if (word == \"async\" && stream.match(/^(\\s|\\/\\*.*?\\*\\/)*[\\[\\(\\w]/, false))\n          return ret(\"async\", \"keyword\", word)\n      }\n      return ret(\"variable\", \"variable\", word)\n    }\n  }\n\n  function tokenString(quote) {\n    return function(stream, state) {\n      var escaped = false, next;\n      if (jsonldMode && stream.peek() == \"@\" && stream.match(isJsonldKeyword)){\n        state.tokenize = tokenBase;\n        return ret(\"jsonld-keyword\", \"meta\");\n      }\n      while ((next = stream.next()) != null) {\n        if (next == quote && !escaped) break;\n        escaped = !escaped && next == \"\\\\\";\n      }\n      if (!escaped) state.tokenize = tokenBase;\n      return ret(\"string\", \"string\");\n    };\n  }\n\n  function tokenComment(stream, state) {\n    var maybeEnd = false, ch;\n    while (ch = stream.next()) {\n      if (ch == \"/\" && maybeEnd) {\n        state.tokenize = tokenBase;\n        break;\n      }\n      maybeEnd = (ch == \"*\");\n    }\n    return ret(\"comment\", \"comment\");\n  }\n\n  function tokenQuasi(stream, state) {\n    var escaped = false, next;\n    while ((next = stream.next()) != null) {\n      if (!escaped && (next == \"`\" || next == \"$\" && stream.eat(\"{\"))) {\n        state.tokenize = tokenBase;\n        break;\n      }\n      escaped = !escaped && next == \"\\\\\";\n    }\n    return ret(\"quasi\", \"string-2\", stream.current());\n  }\n\n  var brackets = \"([{}])\";\n  // This is a crude lookahead trick to try and notice that we're\n  // parsing the argument patterns for a fat-arrow function before we\n  // actually hit the arrow token. It only works if the arrow is on\n  // the same line as the arguments and there's no strange noise\n  // (comments) in between. Fallback is to only notice when we hit the\n  // arrow, and not declare the arguments as locals for the arrow\n  // body.\n  function findFatArrow(stream, state) {\n    if (state.fatArrowAt) state.fatArrowAt = null;\n    var arrow = stream.string.indexOf(\"=>\", stream.start);\n    if (arrow < 0) return;\n\n    if (isTS) { // Try to skip TypeScript return type declarations after the arguments\n      var m = /:\\s*(?:\\w+(?:<[^>]*>|\\[\\])?|\\{[^}]*\\})\\s*$/.exec(stream.string.slice(stream.start, arrow))\n      if (m) arrow = m.index\n    }\n\n    var depth = 0, sawSomething = false;\n    for (var pos = arrow - 1; pos >= 0; --pos) {\n      var ch = stream.string.charAt(pos);\n      var bracket = brackets.indexOf(ch);\n      if (bracket >= 0 && bracket < 3) {\n        if (!depth) { ++pos; break; }\n        if (--depth == 0) { if (ch == \"(\") sawSomething = true; break; }\n      } else if (bracket >= 3 && bracket < 6) {\n        ++depth;\n      } else if (wordRE.test(ch)) {\n        sawSomething = true;\n      } else if (/[\"'\\/`]/.test(ch)) {\n        for (;; --pos) {\n          if (pos == 0) return\n          var next = stream.string.charAt(pos - 1)\n          if (next == ch && stream.string.charAt(pos - 2) != \"\\\\\") { pos--; break }\n        }\n      } else if (sawSomething && !depth) {\n        ++pos;\n        break;\n      }\n    }\n    if (sawSomething && !depth) state.fatArrowAt = pos;\n  }\n\n  // Parser\n\n  var atomicTypes = {\"atom\": true, \"number\": true, \"variable\": true, \"string\": true, \"regexp\": true, \"this\": true, \"jsonld-keyword\": true};\n\n  function JSLexical(indented, column, type, align, prev, info) {\n    this.indented = indented;\n    this.column = column;\n    this.type = type;\n    this.prev = prev;\n    this.info = info;\n    if (align != null) this.align = align;\n  }\n\n  function inScope(state, varname) {\n    for (var v = state.localVars; v; v = v.next)\n      if (v.name == varname) return true;\n    for (var cx = state.context; cx; cx = cx.prev) {\n      for (var v = cx.vars; v; v = v.next)\n        if (v.name == varname) return true;\n    }\n  }\n\n  function parseJS(state, style, type, content, stream) {\n    var cc = state.cc;\n    // Communicate our context to the combinators.\n    // (Less wasteful than consing up a hundred closures on every call.)\n    cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;\n\n    if (!state.lexical.hasOwnProperty(\"align\"))\n      state.lexical.align = true;\n\n    while(true) {\n      var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;\n      if (combinator(type, content)) {\n        while(cc.length && cc[cc.length - 1].lex)\n          cc.pop()();\n        if (cx.marked) return cx.marked;\n        if (type == \"variable\" && inScope(state, content)) return \"variable-2\";\n        return style;\n      }\n    }\n  }\n\n  // Combinator utils\n\n  var cx = {state: null, column: null, marked: null, cc: null};\n  function pass() {\n    for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);\n  }\n  function cont() {\n    pass.apply(null, arguments);\n    return true;\n  }\n  function inList(name, list) {\n    for (var v = list; v; v = v.next) if (v.name == name) return true\n    return false;\n  }\n  function register(varname) {\n    var state = cx.state;\n    cx.marked = \"def\";\n    if (state.context) {\n      if (state.lexical.info == \"var\" && state.context && state.context.block) {\n        // FIXME function decls are also not block scoped\n        var newContext = registerVarScoped(varname, state.context)\n        if (newContext != null) {\n          state.context = newContext\n          return\n        }\n      } else if (!inList(varname, state.localVars)) {\n        state.localVars = new Var(varname, state.localVars)\n        return\n      }\n    }\n    // Fall through means this is global\n    if (parserConfig.globalVars && !inList(varname, state.globalVars))\n      state.globalVars = new Var(varname, state.globalVars)\n  }\n  function registerVarScoped(varname, context) {\n    if (!context) {\n      return null\n    } else if (context.block) {\n      var inner = registerVarScoped(varname, context.prev)\n      if (!inner) return null\n      if (inner == context.prev) return context\n      return new Context(inner, context.vars, true)\n    } else if (inList(varname, context.vars)) {\n      return context\n    } else {\n      return new Context(context.prev, new Var(varname, context.vars), false)\n    }\n  }\n\n  function isModifier(name) {\n    return name == \"public\" || name == \"private\" || name == \"protected\" || name == \"abstract\" || name == \"readonly\"\n  }\n\n  // Combinators\n\n  function Context(prev, vars, block) { this.prev = prev; this.vars = vars; this.block = block }\n  function Var(name, next) { this.name = name; this.next = next }\n\n  var defaultVars = new Var(\"this\", new Var(\"arguments\", null))\n  function pushcontext() {\n    cx.state.context = new Context(cx.state.context, cx.state.localVars, false)\n    cx.state.localVars = defaultVars\n  }\n  function pushblockcontext() {\n    cx.state.context = new Context(cx.state.context, cx.state.localVars, true)\n    cx.state.localVars = null\n  }\n  function popcontext() {\n    cx.state.localVars = cx.state.context.vars\n    cx.state.context = cx.state.context.prev\n  }\n  popcontext.lex = true\n  function pushlex(type, info) {\n    var result = function() {\n      var state = cx.state, indent = state.indented;\n      if (state.lexical.type == \"stat\") indent = state.lexical.indented;\n      else for (var outer = state.lexical; outer && outer.type == \")\" && outer.align; outer = outer.prev)\n        indent = outer.indented;\n      state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);\n    };\n    result.lex = true;\n    return result;\n  }\n  function poplex() {\n    var state = cx.state;\n    if (state.lexical.prev) {\n      if (state.lexical.type == \")\")\n        state.indented = state.lexical.indented;\n      state.lexical = state.lexical.prev;\n    }\n  }\n  poplex.lex = true;\n\n  function expect(wanted) {\n    function exp(type) {\n      if (type == wanted) return cont();\n      else if (wanted == \";\" || type == \"}\" || type == \")\" || type == \"]\") return pass();\n      else return cont(exp);\n    };\n    return exp;\n  }\n\n  function statement(type, value) {\n    if (type == \"var\") return cont(pushlex(\"vardef\", value), vardef, expect(\";\"), poplex);\n    if (type == \"keyword a\") return cont(pushlex(\"form\"), parenExpr, statement, poplex);\n    if (type == \"keyword b\") return cont(pushlex(\"form\"), statement, poplex);\n    if (type == \"keyword d\") return cx.stream.match(/^\\s*$/, false) ? cont() : cont(pushlex(\"stat\"), maybeexpression, expect(\";\"), poplex);\n    if (type == \"debugger\") return cont(expect(\";\"));\n    if (type == \"{\") return cont(pushlex(\"}\"), pushblockcontext, block, poplex, popcontext);\n    if (type == \";\") return cont();\n    if (type == \"if\") {\n      if (cx.state.lexical.info == \"else\" && cx.state.cc[cx.state.cc.length - 1] == poplex)\n        cx.state.cc.pop()();\n      return cont(pushlex(\"form\"), parenExpr, statement, poplex, maybeelse);\n    }\n    if (type == \"function\") return cont(functiondef);\n    if (type == \"for\") return cont(pushlex(\"form\"), forspec, statement, poplex);\n    if (type == \"class\" || (isTS && value == \"interface\")) {\n      cx.marked = \"keyword\"\n      return cont(pushlex(\"form\", type == \"class\" ? type : value), className, poplex)\n    }\n    if (type == \"variable\") {\n      if (isTS && value == \"declare\") {\n        cx.marked = \"keyword\"\n        return cont(statement)\n      } else if (isTS && (value == \"module\" || value == \"enum\" || value == \"type\") && cx.stream.match(/^\\s*\\w/, false)) {\n        cx.marked = \"keyword\"\n        if (value == \"enum\") return cont(enumdef);\n        else if (value == \"type\") return cont(typename, expect(\"operator\"), typeexpr, expect(\";\"));\n        else return cont(pushlex(\"form\"), pattern, expect(\"{\"), pushlex(\"}\"), block, poplex, poplex)\n      } else if (isTS && value == \"namespace\") {\n        cx.marked = \"keyword\"\n        return cont(pushlex(\"form\"), expression, statement, poplex)\n      } else if (isTS && value == \"abstract\") {\n        cx.marked = \"keyword\"\n        return cont(statement)\n      } else {\n        return cont(pushlex(\"stat\"), maybelabel);\n      }\n    }\n    if (type == \"switch\") return cont(pushlex(\"form\"), parenExpr, expect(\"{\"), pushlex(\"}\", \"switch\"), pushblockcontext,\n                                      block, poplex, poplex, popcontext);\n    if (type == \"case\") return cont(expression, expect(\":\"));\n    if (type == \"default\") return cont(expect(\":\"));\n    if (type == \"catch\") return cont(pushlex(\"form\"), pushcontext, maybeCatchBinding, statement, poplex, popcontext);\n    if (type == \"export\") return cont(pushlex(\"stat\"), afterExport, poplex);\n    if (type == \"import\") return cont(pushlex(\"stat\"), afterImport, poplex);\n    if (type == \"async\") return cont(statement)\n    if (value == \"@\") return cont(expression, statement)\n    return pass(pushlex(\"stat\"), expression, expect(\";\"), poplex);\n  }\n  function maybeCatchBinding(type) {\n    if (type == \"(\") return cont(funarg, expect(\")\"))\n  }\n  function expression(type, value) {\n    return expressionInner(type, value, false);\n  }\n  function expressionNoComma(type, value) {\n    return expressionInner(type, value, true);\n  }\n  function parenExpr(type) {\n    if (type != \"(\") return pass()\n    return cont(pushlex(\")\"), expression, expect(\")\"), poplex)\n  }\n  function expressionInner(type, value, noComma) {\n    if (cx.state.fatArrowAt == cx.stream.start) {\n      var body = noComma ? arrowBodyNoComma : arrowBody;\n      if (type == \"(\") return cont(pushcontext, pushlex(\")\"), commasep(funarg, \")\"), poplex, expect(\"=>\"), body, popcontext);\n      else if (type == \"variable\") return pass(pushcontext, pattern, expect(\"=>\"), body, popcontext);\n    }\n\n    var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;\n    if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);\n    if (type == \"function\") return cont(functiondef, maybeop);\n    if (type == \"class\" || (isTS && value == \"interface\")) { cx.marked = \"keyword\"; return cont(pushlex(\"form\"), classExpression, poplex); }\n    if (type == \"keyword c\" || type == \"async\") return cont(noComma ? expressionNoComma : expression);\n    if (type == \"(\") return cont(pushlex(\")\"), maybeexpression, expect(\")\"), poplex, maybeop);\n    if (type == \"operator\" || type == \"spread\") return cont(noComma ? expressionNoComma : expression);\n    if (type == \"[\") return cont(pushlex(\"]\"), arrayLiteral, poplex, maybeop);\n    if (type == \"{\") return contCommasep(objprop, \"}\", null, maybeop);\n    if (type == \"quasi\") return pass(quasi, maybeop);\n    if (type == \"new\") return cont(maybeTarget(noComma));\n    if (type == \"import\") return cont(expression);\n    return cont();\n  }\n  function maybeexpression(type) {\n    if (type.match(/[;\\}\\)\\],]/)) return pass();\n    return pass(expression);\n  }\n\n  function maybeoperatorComma(type, value) {\n    if (type == \",\") return cont(maybeexpression);\n    return maybeoperatorNoComma(type, value, false);\n  }\n  function maybeoperatorNoComma(type, value, noComma) {\n    var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;\n    var expr = noComma == false ? expression : expressionNoComma;\n    if (type == \"=>\") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);\n    if (type == \"operator\") {\n      if (/\\+\\+|--/.test(value) || isTS && value == \"!\") return cont(me);\n      if (isTS && value == \"<\" && cx.stream.match(/^([^>]|<.*?>)*>\\s*\\(/, false))\n        return cont(pushlex(\">\"), commasep(typeexpr, \">\"), poplex, me);\n      if (value == \"?\") return cont(expression, expect(\":\"), expr);\n      return cont(expr);\n    }\n    if (type == \"quasi\") { return pass(quasi, me); }\n    if (type == \";\") return;\n    if (type == \"(\") return contCommasep(expressionNoComma, \")\", \"call\", me);\n    if (type == \".\") return cont(property, me);\n    if (type == \"[\") return cont(pushlex(\"]\"), maybeexpression, expect(\"]\"), poplex, me);\n    if (isTS && value == \"as\") { cx.marked = \"keyword\"; return cont(typeexpr, me) }\n    if (type == \"regexp\") {\n      cx.state.lastType = cx.marked = \"operator\"\n      cx.stream.backUp(cx.stream.pos - cx.stream.start - 1)\n      return cont(expr)\n    }\n  }\n  function quasi(type, value) {\n    if (type != \"quasi\") return pass();\n    if (value.slice(value.length - 2) != \"${\") return cont(quasi);\n    return cont(expression, continueQuasi);\n  }\n  function continueQuasi(type) {\n    if (type == \"}\") {\n      cx.marked = \"string-2\";\n      cx.state.tokenize = tokenQuasi;\n      return cont(quasi);\n    }\n  }\n  function arrowBody(type) {\n    findFatArrow(cx.stream, cx.state);\n    return pass(type == \"{\" ? statement : expression);\n  }\n  function arrowBodyNoComma(type) {\n    findFatArrow(cx.stream, cx.state);\n    return pass(type == \"{\" ? statement : expressionNoComma);\n  }\n  function maybeTarget(noComma) {\n    return function(type) {\n      if (type == \".\") return cont(noComma ? targetNoComma : target);\n      else if (type == \"variable\" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma)\n      else return pass(noComma ? expressionNoComma : expression);\n    };\n  }\n  function target(_, value) {\n    if (value == \"target\") { cx.marked = \"keyword\"; return cont(maybeoperatorComma); }\n  }\n  function targetNoComma(_, value) {\n    if (value == \"target\") { cx.marked = \"keyword\"; return cont(maybeoperatorNoComma); }\n  }\n  function maybelabel(type) {\n    if (type == \":\") return cont(poplex, statement);\n    return pass(maybeoperatorComma, expect(\";\"), poplex);\n  }\n  function property(type) {\n    if (type == \"variable\") {cx.marked = \"property\"; return cont();}\n  }\n  function objprop(type, value) {\n    if (type == \"async\") {\n      cx.marked = \"property\";\n      return cont(objprop);\n    } else if (type == \"variable\" || cx.style == \"keyword\") {\n      cx.marked = \"property\";\n      if (value == \"get\" || value == \"set\") return cont(getterSetter);\n      var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params\n      if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\\s*:\\s*/, false)))\n        cx.state.fatArrowAt = cx.stream.pos + m[0].length\n      return cont(afterprop);\n    } else if (type == \"number\" || type == \"string\") {\n      cx.marked = jsonldMode ? \"property\" : (cx.style + \" property\");\n      return cont(afterprop);\n    } else if (type == \"jsonld-keyword\") {\n      return cont(afterprop);\n    } else if (isTS && isModifier(value)) {\n      cx.marked = \"keyword\"\n      return cont(objprop)\n    } else if (type == \"[\") {\n      return cont(expression, maybetype, expect(\"]\"), afterprop);\n    } else if (type == \"spread\") {\n      return cont(expressionNoComma, afterprop);\n    } else if (value == \"*\") {\n      cx.marked = \"keyword\";\n      return cont(objprop);\n    } else if (type == \":\") {\n      return pass(afterprop)\n    }\n  }\n  function getterSetter(type) {\n    if (type != \"variable\") return pass(afterprop);\n    cx.marked = \"property\";\n    return cont(functiondef);\n  }\n  function afterprop(type) {\n    if (type == \":\") return cont(expressionNoComma);\n    if (type == \"(\") return pass(functiondef);\n  }\n  function commasep(what, end, sep) {\n    function proceed(type, value) {\n      if (sep ? sep.indexOf(type) > -1 : type == \",\") {\n        var lex = cx.state.lexical;\n        if (lex.info == \"call\") lex.pos = (lex.pos || 0) + 1;\n        return cont(function(type, value) {\n          if (type == end || value == end) return pass()\n          return pass(what)\n        }, proceed);\n      }\n      if (type == end || value == end) return cont();\n      if (sep && sep.indexOf(\";\") > -1) return pass(what)\n      return cont(expect(end));\n    }\n    return function(type, value) {\n      if (type == end || value == end) return cont();\n      return pass(what, proceed);\n    };\n  }\n  function contCommasep(what, end, info) {\n    for (var i = 3; i < arguments.length; i++)\n      cx.cc.push(arguments[i]);\n    return cont(pushlex(end, info), commasep(what, end), poplex);\n  }\n  function block(type) {\n    if (type == \"}\") return cont();\n    return pass(statement, block);\n  }\n  function maybetype(type, value) {\n    if (isTS) {\n      if (type == \":\") return cont(typeexpr);\n      if (value == \"?\") return cont(maybetype);\n    }\n  }\n  function maybetypeOrIn(type, value) {\n    if (isTS && (type == \":\" || value == \"in\")) return cont(typeexpr)\n  }\n  function mayberettype(type) {\n    if (isTS && type == \":\") {\n      if (cx.stream.match(/^\\s*\\w+\\s+is\\b/, false)) return cont(expression, isKW, typeexpr)\n      else return cont(typeexpr)\n    }\n  }\n  function isKW(_, value) {\n    if (value == \"is\") {\n      cx.marked = \"keyword\"\n      return cont()\n    }\n  }\n  function typeexpr(type, value) {\n    if (value == \"keyof\" || value == \"typeof\" || value == \"infer\") {\n      cx.marked = \"keyword\"\n      return cont(value == \"typeof\" ? expressionNoComma : typeexpr)\n    }\n    if (type == \"variable\" || value == \"void\") {\n      cx.marked = \"type\"\n      return cont(afterType)\n    }\n    if (value == \"|\" || value == \"&\") return cont(typeexpr)\n    if (type == \"string\" || type == \"number\" || type == \"atom\") return cont(afterType);\n    if (type == \"[\") return cont(pushlex(\"]\"), commasep(typeexpr, \"]\", \",\"), poplex, afterType)\n    if (type == \"{\") return cont(pushlex(\"}\"), commasep(typeprop, \"}\", \",;\"), poplex, afterType)\n    if (type == \"(\") return cont(commasep(typearg, \")\"), maybeReturnType, afterType)\n    if (type == \"<\") return cont(commasep(typeexpr, \">\"), typeexpr)\n  }\n  function maybeReturnType(type) {\n    if (type == \"=>\") return cont(typeexpr)\n  }\n  function typeprop(type, value) {\n    if (type == \"variable\" || cx.style == \"keyword\") {\n      cx.marked = \"property\"\n      return cont(typeprop)\n    } else if (value == \"?\" || type == \"number\" || type == \"string\") {\n      return cont(typeprop)\n    } else if (type == \":\") {\n      return cont(typeexpr)\n    } else if (type == \"[\") {\n      return cont(expect(\"variable\"), maybetypeOrIn, expect(\"]\"), typeprop)\n    } else if (type == \"(\") {\n      return pass(functiondecl, typeprop)\n    }\n  }\n  function typearg(type, value) {\n    if (type == \"variable\" && cx.stream.match(/^\\s*[?:]/, false) || value == \"?\") return cont(typearg)\n    if (type == \":\") return cont(typeexpr)\n    if (type == \"spread\") return cont(typearg)\n    return pass(typeexpr)\n  }\n  function afterType(type, value) {\n    if (value == \"<\") return cont(pushlex(\">\"), commasep(typeexpr, \">\"), poplex, afterType)\n    if (value == \"|\" || type == \".\" || value == \"&\") return cont(typeexpr)\n    if (type == \"[\") return cont(typeexpr, expect(\"]\"), afterType)\n    if (value == \"extends\" || value == \"implements\") { cx.marked = \"keyword\"; return cont(typeexpr) }\n    if (value == \"?\") return cont(typeexpr, expect(\":\"), typeexpr)\n  }\n  function maybeTypeArgs(_, value) {\n    if (value == \"<\") return cont(pushlex(\">\"), commasep(typeexpr, \">\"), poplex, afterType)\n  }\n  function typeparam() {\n    return pass(typeexpr, maybeTypeDefault)\n  }\n  function maybeTypeDefault(_, value) {\n    if (value == \"=\") return cont(typeexpr)\n  }\n  function vardef(_, value) {\n    if (value == \"enum\") {cx.marked = \"keyword\"; return cont(enumdef)}\n    return pass(pattern, maybetype, maybeAssign, vardefCont);\n  }\n  function pattern(type, value) {\n    if (isTS && isModifier(value)) { cx.marked = \"keyword\"; return cont(pattern) }\n    if (type == \"variable\") { register(value); return cont(); }\n    if (type == \"spread\") return cont(pattern);\n    if (type == \"[\") return contCommasep(eltpattern, \"]\");\n    if (type == \"{\") return contCommasep(proppattern, \"}\");\n  }\n  function proppattern(type, value) {\n    if (type == \"variable\" && !cx.stream.match(/^\\s*:/, false)) {\n      register(value);\n      return cont(maybeAssign);\n    }\n    if (type == \"variable\") cx.marked = \"property\";\n    if (type == \"spread\") return cont(pattern);\n    if (type == \"}\") return pass();\n    if (type == \"[\") return cont(expression, expect(']'), expect(':'), proppattern);\n    return cont(expect(\":\"), pattern, maybeAssign);\n  }\n  function eltpattern() {\n    return pass(pattern, maybeAssign)\n  }\n  function maybeAssign(_type, value) {\n    if (value == \"=\") return cont(expressionNoComma);\n  }\n  function vardefCont(type) {\n    if (type == \",\") return cont(vardef);\n  }\n  function maybeelse(type, value) {\n    if (type == \"keyword b\" && value == \"else\") return cont(pushlex(\"form\", \"else\"), statement, poplex);\n  }\n  function forspec(type, value) {\n    if (value == \"await\") return cont(forspec);\n    if (type == \"(\") return cont(pushlex(\")\"), forspec1, poplex);\n  }\n  function forspec1(type) {\n    if (type == \"var\") return cont(vardef, forspec2);\n    if (type == \"variable\") return cont(forspec2);\n    return pass(forspec2)\n  }\n  function forspec2(type, value) {\n    if (type == \")\") return cont()\n    if (type == \";\") return cont(forspec2)\n    if (value == \"in\" || value == \"of\") { cx.marked = \"keyword\"; return cont(expression, forspec2) }\n    return pass(expression, forspec2)\n  }\n  function functiondef(type, value) {\n    if (value == \"*\") {cx.marked = \"keyword\"; return cont(functiondef);}\n    if (type == \"variable\") {register(value); return cont(functiondef);}\n    if (type == \"(\") return cont(pushcontext, pushlex(\")\"), commasep(funarg, \")\"), poplex, mayberettype, statement, popcontext);\n    if (isTS && value == \"<\") return cont(pushlex(\">\"), commasep(typeparam, \">\"), poplex, functiondef)\n  }\n  function functiondecl(type, value) {\n    if (value == \"*\") {cx.marked = \"keyword\"; return cont(functiondecl);}\n    if (type == \"variable\") {register(value); return cont(functiondecl);}\n    if (type == \"(\") return cont(pushcontext, pushlex(\")\"), commasep(funarg, \")\"), poplex, mayberettype, popcontext);\n    if (isTS && value == \"<\") return cont(pushlex(\">\"), commasep(typeparam, \">\"), poplex, functiondecl)\n  }\n  function typename(type, value) {\n    if (type == \"keyword\" || type == \"variable\") {\n      cx.marked = \"type\"\n      return cont(typename)\n    } else if (value == \"<\") {\n      return cont(pushlex(\">\"), commasep(typeparam, \">\"), poplex)\n    }\n  }\n  function funarg(type, value) {\n    if (value == \"@\") cont(expression, funarg)\n    if (type == \"spread\") return cont(funarg);\n    if (isTS && isModifier(value)) { cx.marked = \"keyword\"; return cont(funarg); }\n    if (isTS && type == \"this\") return cont(maybetype, maybeAssign)\n    return pass(pattern, maybetype, maybeAssign);\n  }\n  function classExpression(type, value) {\n    // Class expressions may have an optional name.\n    if (type == \"variable\") return className(type, value);\n    return classNameAfter(type, value);\n  }\n  function className(type, value) {\n    if (type == \"variable\") {register(value); return cont(classNameAfter);}\n  }\n  function classNameAfter(type, value) {\n    if (value == \"<\") return cont(pushlex(\">\"), commasep(typeparam, \">\"), poplex, classNameAfter)\n    if (value == \"extends\" || value == \"implements\" || (isTS && type == \",\")) {\n      if (value == \"implements\") cx.marked = \"keyword\";\n      return cont(isTS ? typeexpr : expression, classNameAfter);\n    }\n    if (type == \"{\") return cont(pushlex(\"}\"), classBody, poplex);\n  }\n  function classBody(type, value) {\n    if (type == \"async\" ||\n        (type == \"variable\" &&\n         (value == \"static\" || value == \"get\" || value == \"set\" || (isTS && isModifier(value))) &&\n         cx.stream.match(/^\\s+[\\w$\\xa1-\\uffff]/, false))) {\n      cx.marked = \"keyword\";\n      return cont(classBody);\n    }\n    if (type == \"variable\" || cx.style == \"keyword\") {\n      cx.marked = \"property\";\n      return cont(isTS ? classfield : functiondef, classBody);\n    }\n    if (type == \"number\" || type == \"string\") return cont(isTS ? classfield : functiondef, classBody);\n    if (type == \"[\")\n      return cont(expression, maybetype, expect(\"]\"), isTS ? classfield : functiondef, classBody)\n    if (value == \"*\") {\n      cx.marked = \"keyword\";\n      return cont(classBody);\n    }\n    if (isTS && type == \"(\") return pass(functiondecl, classBody)\n    if (type == \";\" || type == \",\") return cont(classBody);\n    if (type == \"}\") return cont();\n    if (value == \"@\") return cont(expression, classBody)\n  }\n  function classfield(type, value) {\n    if (value == \"?\") return cont(classfield)\n    if (type == \":\") return cont(typeexpr, maybeAssign)\n    if (value == \"=\") return cont(expressionNoComma)\n    var context = cx.state.lexical.prev, isInterface = context && context.info == \"interface\"\n    return pass(isInterface ? functiondecl : functiondef)\n  }\n  function afterExport(type, value) {\n    if (value == \"*\") { cx.marked = \"keyword\"; return cont(maybeFrom, expect(\";\")); }\n    if (value == \"default\") { cx.marked = \"keyword\"; return cont(expression, expect(\";\")); }\n    if (type == \"{\") return cont(commasep(exportField, \"}\"), maybeFrom, expect(\";\"));\n    return pass(statement);\n  }\n  function exportField(type, value) {\n    if (value == \"as\") { cx.marked = \"keyword\"; return cont(expect(\"variable\")); }\n    if (type == \"variable\") return pass(expressionNoComma, exportField);\n  }\n  function afterImport(type) {\n    if (type == \"string\") return cont();\n    if (type == \"(\") return pass(expression);\n    return pass(importSpec, maybeMoreImports, maybeFrom);\n  }\n  function importSpec(type, value) {\n    if (type == \"{\") return contCommasep(importSpec, \"}\");\n    if (type == \"variable\") register(value);\n    if (value == \"*\") cx.marked = \"keyword\";\n    return cont(maybeAs);\n  }\n  function maybeMoreImports(type) {\n    if (type == \",\") return cont(importSpec, maybeMoreImports)\n  }\n  function maybeAs(_type, value) {\n    if (value == \"as\") { cx.marked = \"keyword\"; return cont(importSpec); }\n  }\n  function maybeFrom(_type, value) {\n    if (value == \"from\") { cx.marked = \"keyword\"; return cont(expression); }\n  }\n  function arrayLiteral(type) {\n    if (type == \"]\") return cont();\n    return pass(commasep(expressionNoComma, \"]\"));\n  }\n  function enumdef() {\n    return pass(pushlex(\"form\"), pattern, expect(\"{\"), pushlex(\"}\"), commasep(enummember, \"}\"), poplex, poplex)\n  }\n  function enummember() {\n    return pass(pattern, maybeAssign);\n  }\n\n  function isContinuedStatement(state, textAfter) {\n    return state.lastType == \"operator\" || state.lastType == \",\" ||\n      isOperatorChar.test(textAfter.charAt(0)) ||\n      /[,.]/.test(textAfter.charAt(0));\n  }\n\n  function expressionAllowed(stream, state, backUp) {\n    return state.tokenize == tokenBase &&\n      /^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\\[{}\\(,;:]|=>)$/.test(state.lastType) ||\n      (state.lastType == \"quasi\" && /\\{\\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))\n  }\n\n  // Interface\n\n  return {\n    startState: function(basecolumn) {\n      var state = {\n        tokenize: tokenBase,\n        lastType: \"sof\",\n        cc: [],\n        lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, \"block\", false),\n        localVars: parserConfig.localVars,\n        context: parserConfig.localVars && new Context(null, null, false),\n        indented: basecolumn || 0\n      };\n      if (parserConfig.globalVars && typeof parserConfig.globalVars == \"object\")\n        state.globalVars = parserConfig.globalVars;\n      return state;\n    },\n\n    token: function(stream, state) {\n      if (stream.sol()) {\n        if (!state.lexical.hasOwnProperty(\"align\"))\n          state.lexical.align = false;\n        state.indented = stream.indentation();\n        findFatArrow(stream, state);\n      }\n      if (state.tokenize != tokenComment && stream.eatSpace()) return null;\n      var style = state.tokenize(stream, state);\n      if (type == \"comment\") return style;\n      state.lastType = type == \"operator\" && (content == \"++\" || content == \"--\") ? \"incdec\" : type;\n      return parseJS(state, style, type, content, stream);\n    },\n\n    indent: function(state, textAfter) {\n      if (state.tokenize == tokenComment) return CodeMirror.Pass;\n      if (state.tokenize != tokenBase) return 0;\n      var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top\n      // Kludge to prevent 'maybelse' from blocking lexical scope pops\n      if (!/^\\s*else\\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {\n        var c = state.cc[i];\n        if (c == poplex) lexical = lexical.prev;\n        else if (c != maybeelse) break;\n      }\n      while ((lexical.type == \"stat\" || lexical.type == \"form\") &&\n             (firstChar == \"}\" || ((top = state.cc[state.cc.length - 1]) &&\n                                   (top == maybeoperatorComma || top == maybeoperatorNoComma) &&\n                                   !/^[,\\.=+\\-*:?[\\(]/.test(textAfter))))\n        lexical = lexical.prev;\n      if (statementIndent && lexical.type == \")\" && lexical.prev.type == \"stat\")\n        lexical = lexical.prev;\n      var type = lexical.type, closing = firstChar == type;\n\n      if (type == \"vardef\") return lexical.indented + (state.lastType == \"operator\" || state.lastType == \",\" ? lexical.info.length + 1 : 0);\n      else if (type == \"form\" && firstChar == \"{\") return lexical.indented;\n      else if (type == \"form\") return lexical.indented + indentUnit;\n      else if (type == \"stat\")\n        return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);\n      else if (lexical.info == \"switch\" && !closing && parserConfig.doubleIndentSwitch != false)\n        return lexical.indented + (/^(?:case|default)\\b/.test(textAfter) ? indentUnit : 2 * indentUnit);\n      else if (lexical.align) return lexical.column + (closing ? 0 : 1);\n      else return lexical.indented + (closing ? 0 : indentUnit);\n    },\n\n    electricInput: /^\\s*(?:case .*?:|default:|\\{|\\})$/,\n    blockCommentStart: jsonMode ? null : \"/*\",\n    blockCommentEnd: jsonMode ? null : \"*/\",\n    blockCommentContinue: jsonMode ? null : \" * \",\n    lineComment: jsonMode ? null : \"//\",\n    fold: \"brace\",\n    closeBrackets: \"()[]{}''\\\"\\\"``\",\n\n    helperType: jsonMode ? \"json\" : \"javascript\",\n    jsonldMode: jsonldMode,\n    jsonMode: jsonMode,\n\n    expressionAllowed: expressionAllowed,\n\n    skipExpression: function(state) {\n      var top = state.cc[state.cc.length - 1]\n      if (top == expression || top == expressionNoComma) state.cc.pop()\n    }\n  };\n});\n\nCodeMirror.registerHelper(\"wordChars\", \"javascript\", /[\\w$]/);\n\nCodeMirror.defineMIME(\"text/javascript\", \"javascript\");\nCodeMirror.defineMIME(\"text/ecmascript\", \"javascript\");\nCodeMirror.defineMIME(\"application/javascript\", \"javascript\");\nCodeMirror.defineMIME(\"application/x-javascript\", \"javascript\");\nCodeMirror.defineMIME(\"application/ecmascript\", \"javascript\");\nCodeMirror.defineMIME(\"application/json\", {name: \"javascript\", json: true});\nCodeMirror.defineMIME(\"application/x-json\", {name: \"javascript\", json: true});\nCodeMirror.defineMIME(\"application/ld+json\", {name: \"javascript\", jsonld: true});\nCodeMirror.defineMIME(\"text/typescript\", { name: \"javascript\", typescript: true });\nCodeMirror.defineMIME(\"application/typescript\", { name: \"javascript\", typescript: true });\n\n});\n"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/codemirror/placeholder.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\r\n// Distributed under an MIT license: https://codemirror.net/LICENSE\r\n\r\n(function(mod) {\r\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\r\n    mod(require(\"../../lib/codemirror\"));\r\n  else if (typeof define == \"function\" && define.amd) // AMD\r\n    define([\"../../lib/codemirror\"], mod);\r\n  else // Plain browser env\r\n    mod(CodeMirror);\r\n})(function(CodeMirror) {\r\n  CodeMirror.defineOption(\"placeholder\", \"\", function(cm, val, old) {\r\n    var prev = old && old != CodeMirror.Init;\r\n    if (val && !prev) {\r\n      cm.on(\"blur\", onBlur);\r\n      cm.on(\"change\", onChange);\r\n      cm.on(\"swapDoc\", onChange);\r\n      onChange(cm);\r\n    } else if (!val && prev) {\r\n      cm.off(\"blur\", onBlur);\r\n      cm.off(\"change\", onChange);\r\n      cm.off(\"swapDoc\", onChange);\r\n      clearPlaceholder(cm);\r\n      var wrapper = cm.getWrapperElement();\r\n      wrapper.className = wrapper.className.replace(\" CodeMirror-empty\", \"\");\r\n    }\r\n\r\n    if (val && !cm.hasFocus()) onBlur(cm);\r\n  });\r\n\r\n  function clearPlaceholder(cm) {\r\n    if (cm.state.placeholder) {\r\n      cm.state.placeholder.parentNode.removeChild(cm.state.placeholder);\r\n      cm.state.placeholder = null;\r\n    }\r\n  }\r\n  function setPlaceholder(cm) {\r\n    clearPlaceholder(cm);\r\n    var elt = cm.state.placeholder = document.createElement(\"pre\");\r\n    elt.style.cssText = \"height: 0; overflow: visible\";\r\n    elt.style.direction = cm.getOption(\"direction\");\r\n    elt.className = \"CodeMirror-placeholder CodeMirror-line-like\";\r\n    var placeHolder = cm.getOption(\"placeholder\")\r\n    if (typeof placeHolder == \"string\") placeHolder = document.createTextNode(placeHolder)\r\n    elt.appendChild(placeHolder)\r\n    cm.display.lineSpace.insertBefore(elt, cm.display.lineSpace.firstChild);\r\n  }\r\n\r\n  function onBlur(cm) {\r\n    if (isEmpty(cm)) setPlaceholder(cm);\r\n  }\r\n  function onChange(cm) {\r\n    var wrapper = cm.getWrapperElement(), empty = isEmpty(cm);\r\n    wrapper.className = wrapper.className.replace(\" CodeMirror-empty\", \"\") + (empty ? \" CodeMirror-empty\" : \"\");\r\n\r\n    if (empty) setPlaceholder(cm);\r\n    else clearPlaceholder(cm);\r\n  }\r\n\r\n  function isEmpty(cm) {\r\n    return (cm.lineCount() === 1) && (cm.getLine(0) === \"\");\r\n  }\r\n});"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/codemirror/show-hint.css",
    "content": ".CodeMirror-hints {\n  position: absolute;\n  z-index: 214483647;\n  /*overflow: hidden;*/\n  list-style: none;\n\n  margin: 0;\n  padding: 2px;\n\n  -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2);\n  -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2);\n  box-shadow: 2px 3px 5px rgba(0,0,0,.2);\n  border-radius: 3px;\n  border: 1px solid silver;\n\n  background: white;\n  font-size: 90%;\n  font-family: monospace;\n\n  max-height: 20em;\n  overflow-y: auto;\n}\n\n.CodeMirror-hint {\n  margin: 0;\n  padding: 0 4px;\n  border-radius: 2px;\n  /*white-space: pre;*/\n  color: black;\n  cursor: pointer;\n}\n\nli.CodeMirror-hint-active {\n  background: #08f;\n  color: white;\n}\n"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/codemirror/show-hint.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: http://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  var HINT_ELEMENT_CLASS        = \"CodeMirror-hint\";\n  var ACTIVE_HINT_ELEMENT_CLASS = \"CodeMirror-hint-active\";\n\n  // This is the old interface, kept around for now to stay\n  // backwards-compatible.\n  CodeMirror.showHint = function(cm, getHints, options) {\n    if (!getHints) return cm.showHint(options);\n    if (options && options.async) getHints.async = true;\n    var newOpts = {hint: getHints};\n    if (options) for (var prop in options) newOpts[prop] = options[prop];\n    return cm.showHint(newOpts);\n  };\n\n  CodeMirror.defineExtension(\"showHint\", function(options) {\n    options = parseOptions(this, this.getCursor(\"start\"), options);\n    var selections = this.listSelections()\n    if (selections.length > 1) return;\n    // By default, don't allow completion when something is selected.\n    // A hint function can have a `supportsSelection` property to\n    // indicate that it can handle selections.\n    if (this.somethingSelected()) {\n      if (!options.hint.supportsSelection) return;\n      // Don't try with cross-line selections\n      for (var i = 0; i < selections.length; i++)\n        if (selections[i].head.line != selections[i].anchor.line) return;\n    }\n\n    if (this.state.completionActive) this.state.completionActive.close();\n    var completion = this.state.completionActive = new Completion(this, options);\n    if (!completion.options.hint) return;\n\n    CodeMirror.signal(this, \"startCompletion\", this);\n    completion.update(true);\n  });\n  CodeMirror.defineExtension(\"showHint1\", function(options,datas) {\n    options = parseOptions(this, this.getCursor(\"start\"), options);\n    var selections = this.listSelections()\n    if (selections.length > 1) return;\n    // By default, don't allow completion when something is selected.\n    // A hint function can have a `supportsSelection` property to\n    // indicate that it can handle selections.\n    if (this.somethingSelected()) {\n      if (!options.hint.supportsSelection) return;\n      // Don't try with cross-line selections\n      for (var i = 0; i < selections.length; i++)\n        if (selections[i].head.line != selections[i].anchor.line) return;\n    }\n    if (this.state.completionActive) this.state.completionActive.close();\n    var completion = this.state.completionActive = new Completion(this, options);\n    if (!completion.options.hint) return;\n\n    CodeMirror.signal(this, \"startCompletion\", this);\n    completion.update1(true,datas);\n  });\n  function Completion(cm, options) {\n    this.cm = cm;\n    this.options = options;\n    this.widget = null;\n\tthis.comments = this.options.comments;\n    //this.widget1 = null;\n    this.debounce = 0;\n    this.tick = 0;\n    this.startPos = this.cm.getCursor(\"start\");\n    this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length;\n\n    var self = this;\n    cm.on(\"cursorActivity\", this.activityFunc = function() { self.cursorActivity(); });\n  }\n\n  var requestAnimationFrame = window.requestAnimationFrame || function(fn) {\n    return setTimeout(fn, 1000/60);\n  };\n  var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;\n\n  Completion.prototype = {\n    close: function() {\n      if (!this.active()) return;\n      this.cm.state.completionActive = null;\n      this.tick = null;\n      this.cm.off(\"cursorActivity\", this.activityFunc);\n\n      if (this.widget && this.data) CodeMirror.signal(this.data, \"close\");\n      if (this.widget) {\n        this.widget.close();\n       }\n      CodeMirror.signal(this.cm, \"endCompletion\", this.cm);\n    },\n\n    active: function() {\n      return this.cm.state.completionActive == this;\n    },\n\n    pick: function(data, i) {\n      var completion = data.list[i];\n      if (completion.hint) completion.hint(this.cm, data, completion);\n      else this.cm.replaceRange(getText(completion), completion.from || data.from,\n                                completion.to || data.to, \"complete\");\n      CodeMirror.signal(data, \"pick\", completion);\n      this.close();\n    },\n\n    cursorActivity: function() {\n      if (this.debounce) {\n        cancelAnimationFrame(this.debounce);\n        this.debounce = 0;\n      }\n\n      var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line);\n      if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch ||\n          pos.ch < this.startPos.ch || this.cm.somethingSelected() ||\n          (pos.ch && this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) {\n        this.close();\n      } else {\n        var self = this;\n        this.debounce = requestAnimationFrame(function() {self.update();});\n        if (this.widget)\n        {\n          this.widget.disable();\n         }\n       \n      }\n    },\n\n    update: function(first) {\n      if (this.tick == null) return;\n      if (!this.options.hint.async) {\n        this.finishUpdate(this.options.hint(this.cm, this.options), first);\n      } else {\n        var myTick = ++this.tick, self = this;\n        this.options.hint(this.cm, function(data) {\n          if (self.tick == myTick) self.finishUpdate(data, first);\n        }, this.options);\n      }\n    },\n    update1: function(first,datas) {\n      if (this.tick == null) return;\n      if (!this.options.hint.async) {\n        //\n        //datas = JSON.parse(datas);\n        this.finishUpdate1(datas, first);\n      } else {\n        var myTick = ++this.tick, self = this;\n        this.options.hint(this.cm, function(data) {\n          if (self.tick == myTick) self.finishUpdate(data, first);\n        }, this.options);\n      }\n    },\n    finishUpdate1: function(data, first) {\n      if (this.data)\n      {\n        CodeMirror.signal(this.data, \"update\");\n      }\n\n      var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle);\n      if (this.widget)\n      {\n        this.widget.close();\n      }\n      if (data && this.data && isNewCompletion(this.data, data))\n      {\n        return;\n      }\n      this.data = data;\n\n\n      if (data && data.list.length) {\n        if (picked && data.list.length == 1) {\n\t\t\t\n          this.pick(data, 0);\n        } else {\n          this.widget = new Widget1(this, data,this.comments);\n          CodeMirror.signal(data, \"shown\");\n        }\n      }\n    },\n    finishUpdate: function(data, first) {\n      if (this.data)\n      {\n        CodeMirror.signal(this.data, \"update\");\n      }\n\n      var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle);\n      if (this.widget)\n      {\n        this.widget.close();\n      }\n\n      if (data && this.data && isNewCompletion(this.data, data))\n      {\n        return;\n      }\n      this.data = data;\n\n\n      if (data && data.list.length) {\n        if (picked && data.list.length == 1) {\n          this.pick(data, 0);\n        } else {\n          this.widget = new Widget(this, data);\n          CodeMirror.signal(data, \"shown\");\n        }\n      }\n    }\n  };\n\n  function isNewCompletion(old, nw) {\n    var moved = CodeMirror.cmpPos(nw.from, old.from)\n    return moved > 0 && old.to.ch - old.from.ch != nw.to.ch - nw.from.ch\n  }\n\n  function parseOptions(cm, pos, options) {\n    var editor = cm.options.hintOptions;\n    var out = {};\n    for (var prop in defaultOptions) out[prop] = defaultOptions[prop];\n    if (editor) for (var prop in editor)\n      if (editor[prop] !== undefined) out[prop] = editor[prop];\n    if (options) for (var prop in options)\n      if (options[prop] !== undefined) out[prop] = options[prop];\n    if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos)\n    return out;\n  }\n\n  function getText(completion) {\n    if (typeof completion == \"string\") return completion;\n    else return completion.text;\n  }\n\n  function buildKeyMap(completion, handle) {\n    var baseMap = {\n      Up: function() {handle.moveFocus(-1);},\n      Down: function() {handle.moveFocus(1);},\n      PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);},\n      PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);},\n      Home: function() {handle.setFocus(0);},\n      End: function() {handle.setFocus(handle.length - 1);},\n      Enter: handle.pick,\n      Tab: handle.pick,\n      Esc: handle.close\n    };\n    var custom = completion.options.customKeys;\n    var ourMap = custom ? {} : baseMap;\n    function addBinding(key, val) {\n      var bound;\n      if (typeof val != \"string\")\n        bound = function(cm) { return val(cm, handle); };\n      // This mechanism is deprecated\n      else if (baseMap.hasOwnProperty(val))\n        bound = baseMap[val];\n      else\n        bound = val;\n      ourMap[key] = bound;\n    }\n    if (custom)\n      for (var key in custom) if (custom.hasOwnProperty(key))\n        addBinding(key, custom[key]);\n    var extra = completion.options.extraKeys;\n    if (extra)\n      for (var key in extra) if (extra.hasOwnProperty(key))\n        addBinding(key, extra[key]);\n    return ourMap;\n  }\n\n  function getHintElement(hintsElement, el) {\n    while (el && el != hintsElement) {\n      if (el.nodeName.toUpperCase() === \"LI\" && el.parentNode == hintsElement) return el;\n      el = el.parentNode;\n    }\n  }\n  function Widget1(completion, data,comments) {\n    this.completion = completion;\n    this.data = data;\n\tthis.comments = comments;\n    this.picked = false;\n    var widget = this, cm = completion.cm;\n    var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);\n    var hints = this.hints = document.createElement(\"ul\");\n      var hints2 = this.hints2 = document.createElement(\"ul\");\n    //文档说明提示框\n    var completions = data.list;\n    hints.className = \"CodeMirror-hints\";\n    this.selectedHint = data.selectedHint || 0;\n\n    var completions = data.list;\n    for (var i = 0; i < completions.length; ++i) {\n      var cur = completions[i];\n      if(!cur){\n    \t  continue;\n      }\n      var elt = hints.appendChild(document.createElement(\"li\"));\n      var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? \"\" : \" \" + ACTIVE_HINT_ELEMENT_CLASS);\n      if (cur.className != null) className = cur.className + \" \" + className;\n      elt.className = className;\n      if (cur.render) cur.render(elt, data, cur);\n      else elt.appendChild(document.createTextNode(cur.displayText || getText(cur)));\n      elt.hintId = i;\n    }\n\n    var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null);\n    var left = pos.left, top = pos.bottom, below = true;\n    hints.style.left = left + \"px\";\n    hints.style.top = top + \"px\";\n\n\n    // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.\n   // var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);\n    var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);\n    (completion.options.container || document.body).appendChild(hints);\n    var hintsWidth = 0;\n    var box = hints.getBoundingClientRect();\n    var overlapY = box.bottom - winH;\n    if (overlapY > 0) {\n      var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);\n      if (curTop - height > 0) { // Fits above cursor\n        hints.style.top = (top = pos.top - height) + \"px\";\n        hints2.style.top = (top = pos.top - height) + \"px\";\n          below = false;\n      } else if (height > winH) {\n        hints.style.height = (winH - 5) + \"px\";\n        hints.style.top = (top = pos.bottom - box.top) + \"px\";\n        hints2.style.top = (top = pos.bottom - box.top) + \"px\";\n        var cursor = cm.getCursor();\n        if (data.from.ch != cursor.ch) {\n          pos = cm.cursorCoords(cursor);\n          hints.style.left = (left = pos.left) + \"px\";\n          box = hints.getBoundingClientRect();\n        }\n      }\n    }\n    hintsWidth = box.width;\n    var overlapX = box.right - winW;\n    if (overlapX > 0) {\n      if (box.right - box.left > winW) {\n        hints.style.width = (winW - 5) + \"px\";\n        overlapX -= (box.right - box.left) - winW;\n        hintsWidth = minW - 5;\n      }\n      hints.style.left = (left = pos.left - overlapX) + \"px\";\n    }\n    \n    try{\n        hints2.className = \"CodeMirror-hints\";\n\n\t    var elt = hints2.appendChild(document.createElement(\"li\")), cur = completions[0];\n\t    var key = data.key;\n\t    var index = 0;\n\t    if(typeof(key) != \"undefined\"){\n\t    \tvar showList = data.showList;\n\t        index = showList[0];\n\t        if (cur.render) cur.render(elt, data, cur);\n\t\t    else elt.innerHTML = comments[key][index] || '';\n\t\t    elt.hintId = i;\n\t        var left = hints.style.left;\n\t        left = Number(left.substring(0,left.length-2));\n\t        var top = hints.style.top;\n\t        top = Number(top.substring(0,top.length-2));\n\t        if(document.documentElement.clientWidth - left < 210){\n\t        \tleft = left - 207;\n\t        }else{\n\t        \tleft = left + hintsWidth;\n\t        }\n\t        hints2.style.left =   left + \"px\";\n\t        hints2.style.height =  200 + \"px\";\n\t        hints2.style.width =  200 + \"px\";\n\t        hints2.style.top = top + \"px\";\n\t        (completion.options.container || document.body).appendChild(hints2);\n\t    }\n    }catch(e)\n    {\n    }\n\n\n    cm.addKeyMap(this.keyMap = buildKeyMap(completion, {\n      moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },\n      setFocus: function(n) { widget.changeActive(n); },\n      menuSize: function() { return widget.screenAmount(); },\n      length: completions.length,\n      close: function() { completion.close(); },\n      pick: function() { widget.pick(); },\n      data: data\n    }));\n\n    if (completion.options.closeOnUnfocus) {\n      var closingOnBlur;\n      cm.on(\"blur\", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); });\n      cm.on(\"focus\", this.onFocus = function() { clearTimeout(closingOnBlur); });\n    }\n\n    var startScroll = cm.getScrollInfo();\n    cm.on(\"scroll\", this.onScroll = function() {\n      var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();\n      var newTop = top + startScroll.top - curScroll.top;\n      var point = newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop);\n      if (!below) point += hints.offsetHeight;\n      if (point <= editor.top || point >= editor.bottom) return completion.close();\n      hints.style.top = newTop + \"px\";\n      hints.style.left = (left + startScroll.left - curScroll.left) + \"px\";\n    });\n    CodeMirror.on(hints, \"dblclick\", function(e) {\n      var t = getHintElement(hints, e.target || e.srcElement);\n      if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();}\n    });\n\n    CodeMirror.on(hints, \"click\", function(e) {\n      var t = getHintElement(hints, e.target || e.srcElement);\n      if (t && t.hintId != null) {\n        widget.changeActive(t.hintId);\n        if (completion.options.completeOnSingleClick) widget.pick();\n      }\n    });\n\n    CodeMirror.on(hints, \"mousedown\", function() {\n      setTimeout(function(){cm.focus();}, 20);\n    });\n\n    CodeMirror.signal(data, \"select\", completions[0], hints.firstChild);\n    return true;\n  }\n  function Widget(completion, data) {\n    this.completion = completion;\n    this.data = data;\n    this.picked = false;\n    var widget = this, cm = completion.cm;\n\n    var hints = this.hints = document.createElement(\"ul\");\n    hints.className = \"CodeMirror-hints\";\n    this.selectedHint = data.selectedHint || 0;\n\n    var completions = data.list;\n    for (var i = 0; i < completions.length; ++i) {\n      var elt = hints.appendChild(document.createElement(\"li\")), cur = completions[i];\n      var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? \"\" : \" \" + ACTIVE_HINT_ELEMENT_CLASS);\n      if (cur.className != null) className = cur.className + \" \" + className;\n      elt.className = className;\n      if (cur.render) cur.render(elt, data, cur);\n      else elt.appendChild(document.createTextNode(cur.displayText || getText(cur)));\n      elt.hintId = i;\n    }\n\n    var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null);\n    var left = pos.left, top = pos.bottom, below = true;\n    hints.style.left = left + \"px\";\n    hints.style.top = top + \"px\";\n\n    // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.\n    var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);\n    var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);\n    (completion.options.container || document.body).appendChild(hints);\n    var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;\n    if (overlapY > 0) {\n      var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);\n      if (curTop - height > 0) { // Fits above cursor\n        hints.style.top = (top = pos.top - height) + \"px\";\n        below = false;\n      } else if (height > winH) {\n        hints.style.height = (winH - 5) + \"px\";\n        hints.style.top = (top = pos.bottom - box.top) + \"px\";\n        var cursor = cm.getCursor();\n        if (data.from.ch != cursor.ch) {\n          pos = cm.cursorCoords(cursor);\n          hints.style.left = (left = pos.left) + \"px\";\n          box = hints.getBoundingClientRect();\n        }\n      }\n    }\n    var overlapX = box.right - winW;\n    if (overlapX > 0) {\n      if (box.right - box.left > winW) {\n        hints.style.width = (winW - 5) + \"px\";\n        overlapX -= (box.right - box.left) - winW;\n      }\n      hints.style.left = (left = pos.left - overlapX) + \"px\";\n    }\n\n    cm.addKeyMap(this.keyMap = buildKeyMap(completion, {\n      moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },\n      setFocus: function(n) { widget.changeActive(n); },\n      menuSize: function() { return widget.screenAmount(); },\n      length: completions.length,\n      close: function() { completion.close(); },\n      pick: function() { widget.pick(); },\n      data: data\n    }));\n\n    if (completion.options.closeOnUnfocus) {\n      var closingOnBlur;\n      cm.on(\"blur\", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); });\n      cm.on(\"focus\", this.onFocus = function() { clearTimeout(closingOnBlur); });\n    }\n\n    var startScroll = cm.getScrollInfo();\n    cm.on(\"scroll\", this.onScroll = function() {\n      var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();\n      var newTop = top + startScroll.top - curScroll.top;\n      var point = newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop);\n      if (!below) point += hints.offsetHeight;\n      if (point <= editor.top || point >= editor.bottom) return completion.close();\n      hints.style.top = newTop + \"px\";\n      hints.style.left = (left + startScroll.left - curScroll.left) + \"px\";\n    });\n\n    CodeMirror.on(hints, \"dblclick\", function(e) {\n      var t = getHintElement(hints, e.target || e.srcElement);\n      if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();}\n    });\n\n    CodeMirror.on(hints, \"click\", function(e) {\n      var t = getHintElement(hints, e.target || e.srcElement);\n      if (t && t.hintId != null) {\n        widget.changeActive(t.hintId);\n        if (completion.options.completeOnSingleClick) widget.pick();\n      }\n    });\n\n    CodeMirror.on(hints, \"mousedown\", function() {\n      setTimeout(function(){cm.focus();}, 20);\n    });\n\n    CodeMirror.signal(data, \"select\", completions[0], hints.firstChild);\n    return true;\n  }\n  Widget.prototype = {\n    close: function() {\n      if (this.completion.widget != this) return;\n      this.completion.widget = null;\n      this.hints.parentNode.removeChild(this.hints);\n      try{\n        this.hints2.parentNode.removeChild(this.hints2);//此处消失\n      }catch(e){\n\n      }\n      this.completion.cm.removeKeyMap(this.keyMap);\n\n      var cm = this.completion.cm;\n      if (this.completion.options.closeOnUnfocus) {\n        cm.off(\"blur\", this.onBlur);\n        cm.off(\"focus\", this.onFocus);\n      }\n      cm.off(\"scroll\", this.onScroll);\n    },\n\n    disable: function() {\n      this.completion.cm.removeKeyMap(this.keyMap);\n      var widget = this;\n      this.keyMap = {Enter: function() { widget.picked = true; }};\n      this.completion.cm.addKeyMap(this.keyMap);\n    },\n\n    pick: function() {\n      this.completion.pick(this.data, this.selectedHint);\n    },\n\n    changeActive: function(i, avoidWrap) {\n      if (i >= this.data.list.length)\n        i = avoidWrap ? this.data.list.length - 1 : 0;\n      else if (i < 0)\n        i = avoidWrap ? 0  : this.data.list.length - 1;\n      if (this.selectedHint == i) return;\n      var node = this.hints.childNodes[this.selectedHint];\n      node.className = node.className.replace(\" \" + ACTIVE_HINT_ELEMENT_CLASS, \"\");\n      node = this.hints.childNodes[this.selectedHint = i];\n      node.className += \" \" + ACTIVE_HINT_ELEMENT_CLASS;\n      if (node.offsetTop < this.hints.scrollTop)\n        this.hints.scrollTop = node.offsetTop - 3;\n      else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)\n        this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3;\n      CodeMirror.signal(this.data, \"select\", this.data.list[this.selectedHint], node);\n    },\n\n    screenAmount: function() {\n      return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;\n    }\n  };\n  Widget1.prototype = {\n    close: function() {\n      if (this.completion.widget != this) return;\n      this.completion.widget = null;\n      this.hints.parentNode.removeChild(this.hints);\n      try{\n        this.hints2.parentNode.removeChild(this.hints2);//此处消失\n      }catch(e){\n\n      }\n      this.completion.cm.removeKeyMap(this.keyMap);\n      var cm = this.completion.cm;\n      if (this.completion.options.closeOnUnfocus) {\n        cm.off(\"blur\", this.onBlur);\n        cm.off(\"focus\", this.onFocus);\n      }\n      cm.off(\"scroll\", this.onScroll);\n    },\n\n    disable: function() {\n      this.completion.cm.removeKeyMap(this.keyMap);\n      var widget = this;\n      this.keyMap = {Enter: function() { widget.picked = true; }};\n      this.completion.cm.addKeyMap(this.keyMap);\n    },\n\n    pick: function() {\n      this.completion.pick(this.data, this.selectedHint);\n    },\n\n    changeActive: function(i, avoidWrap) {\n\t\tif (i >= this.hints.childNodes.length)\n\t        i = avoidWrap ? this.hints.childNodes.length - 1 : 0;\n\t      else if (i < 0)\n\t        i = avoidWrap ? 0  : this.hints.childNodes.length - 1;\n      try{\n\n        this.hints2.innerHTML = \"\";\n        var key = this.data.key;\n        var index = i;\n        if(typeof(key) != \"undefined\"){\n          var showList = this.data.showList;\n          index = showList[i];\n          var elt = this.hints2.appendChild(document.createElement(\"li\"));\n          elt.innerHTML = this.comments[key][index] || '';\n        }\n      }catch(e){\n\n      }\n      if (this.selectedHint == i) return;\n      var node = this.hints.childNodes[this.selectedHint];\n      //if(!node) return;\n      node.className = node.className.replace(\" \" + ACTIVE_HINT_ELEMENT_CLASS, \"\");\n      node = this.hints.childNodes[this.selectedHint = i];\n      //if(!node) return;\n      node.className += \" \" + ACTIVE_HINT_ELEMENT_CLASS;\n      if (node.offsetTop < this.hints.scrollTop)\n        this.hints.scrollTop = node.offsetTop - 3;\n      else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)\n        this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3;\n      CodeMirror.signal(this.data, \"select\", this.data.list[this.selectedHint], node);\n    },\n\n    screenAmount: function() {\n      return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;\n    }\n  };\n\n  function applicableHelpers(cm, helpers) {\n    if (!cm.somethingSelected()) return helpers\n    var result = []\n    for (var i = 0; i < helpers.length; i++)\n      if (helpers[i].supportsSelection) result.push(helpers[i])\n    return result\n  }\n\n  function resolveAutoHints(cm, pos) {\n    var helpers = cm.getHelpers(pos, \"hint\"), words\n    if (helpers.length) {\n      var async = false, resolved\n      for (var i = 0; i < helpers.length; i++) if (helpers[i].async) async = true\n      if (async) {\n        resolved = function(cm, callback, options) {\n          var app = applicableHelpers(cm, helpers)\n          function run(i, result) {\n            if (i == app.length) return callback(null)\n            var helper = app[i]\n            if (helper.async) {\n              helper(cm, function(result) {\n                if (result) callback(result)\n                else run(i + 1)\n              }, options)\n            } else {\n              var result = helper(cm, options)\n              if (result) callback(result)\n              else run(i + 1)\n            }\n          }\n          run(0)\n        }\n        resolved.async = true\n      } else {\n        resolved = function(cm, options) {\n          var app = applicableHelpers(cm, helpers)\n          for (var i = 0; i < app.length; i++) {\n            var cur = app[i](cm, options)\n            if (cur && cur.list.length) return cur\n          }\n        }\n      }\n      resolved.supportsSelection = true\n      return resolved\n    } else if (words = cm.getHelper(cm.getCursor(), \"hintWords\")) {\n      return function(cm) { return CodeMirror.hint.fromList(cm, {words: words}) }\n    } else if (CodeMirror.hint.anyword) {\n      return function(cm, options) { return CodeMirror.hint.anyword(cm, options) }\n    } else {\n      return function() {}\n    }\n  }\n\n  CodeMirror.registerHelper(\"hint\", \"auto\", {\n    resolve: resolveAutoHints\n  });\n\n  CodeMirror.registerHelper(\"hint\", \"fromList\", function(cm, options) {\n    var cur = cm.getCursor(), token = cm.getTokenAt(cur);\n    var to = CodeMirror.Pos(cur.line, token.end);\n    if (token.string && /\\w/.test(token.string[token.string.length - 1])) {\n      var term = token.string, from = CodeMirror.Pos(cur.line, token.start);\n    } else {\n      var term = \"\", from = to;\n    }\n    var found = [];\n    for (var i = 0; i < options.words.length; i++) {\n      var word = options.words[i];\n      if (word.slice(0, term.length) == term)\n        found.push(word);\n    }\n\n    if (found.length) return {list: found, from: from, to: to};\n  });\n\n  CodeMirror.commands.autocomplete = CodeMirror.showHint;\n\n  var defaultOptions = {\n    hint: CodeMirror.hint.auto,\n    completeSingle: true,\n    alignWithWord: true,\n    closeCharacters: /[\\s()\\[\\]{};:>,]/,\n    closeOnUnfocus: true,\n    completeOnSingleClick: true,\n    container: null,\n    customKeys: null,\n    extraKeys: null\n  };\n\n  CodeMirror.defineOption(\"hintOptions\", null);\n});"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/codemirror/spiderflow-hint.js",
    "content": "var grammers = [];\r\n$.ajax({\r\n\turl : 'spider/grammers',\r\n\ttype : 'post',\r\n\tdataType : 'json',\r\n\tsuccess : function(json){\r\n\t\tif(json.code == 1){\r\n\t\t\tgrammers = json.data;\r\n\t\t\tfor(var i =0,len = grammers.length;i<len;i++){\r\n\t\t\t\tvar grammer = grammers[i];\r\n\t\t\t\tif(grammer&&grammer.method&&grammer.method.length > 3&&grammer.method.indexOf(\"get\") == 0){\r\n\t\t\t\t\tgrammer.method = grammer.method.substring(3,4).toLowerCase() + grammer.method.substring(4); \r\n\t\t\t\t}else if(grammer&&grammer.method){\r\n\t\t\t\t\tgrammer.method = grammer.method + '()';\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tgrammers = grammers || [];\r\n\t}\r\n})\r\nfunction searchGrammer(keyword,isClass){\r\n\tvar list1 = [];\r\n\tvar list2 = {};\r\n\tfor(var i =0,len = grammers.length;i<len;i++){\r\n\t\tvar grammer = grammers[i];\r\n\t\tif((!isClass) && (keyword==null||(grammer.method!=null&&grammer.method.indexOf(keyword) > -1))){\r\n\t\t\tlist1.push(grammer.method);\r\n\t\t\tlist2[grammer.method] = list2[grammer.method] || [];\r\n\t\t\tlist2[grammer.method].push(grammer);\r\n\t\t}else if(keyword == null || (grammer['function']&&grammer['function'].indexOf(keyword) > -1)){\r\n\t\t\tif(grammer['function']&&grammer.method == null){\r\n\t\t\t\tlist1.push(grammer['function']);\r\n\t\t\t\tlist2[grammer['function']] = list2[grammer['function']] || [];\r\n\t\t\t\tlist2[grammer['function']][0] = {owner:grammer.owner,comment : grammer.comment};\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tlist1 = list1.sort();\r\n\tvar set = [];\r\n\tif(list1.length > 0){\r\n\t\tset.push(list1[0]);\r\n\t}\r\n\tfor (var i=1, len=list1.length; i<len; i++) {\r\n\t\tlist1[i] !== list1[i-1] && set.push(list1[i]);\r\n    }\r\n\tfor (var key in list2){\r\n\t\tvar arr = list2[key];\r\n\t\tvar html = '';\r\n\t\tfor(var i =0,len = arr.length;i<len;i++){\r\n\t\t\tvar grammer = arr[i];\r\n\t\t\thtml+= '<div class=\"hint-grammer\">'\r\n\t\t\t\tif(grammer.owner){\r\n\t\t\t\t\thtml+= '<div class=\"hint-owner\">所属类：<span>'+grammer.owner.replace('<','&lt;')+'</span></div>';\t\t\t\t\t\r\n\t\t\t\t}\r\n\t\t\t\tif(grammer.comment){\r\n\t\t\t\t\thtml+= '<div class=\"hint-comment\">说明：<span>'+grammer.comment.replace('<','&lt;')+'</span></div>';\t\r\n\t\t\t\t}\r\n\t\t\t\tif(grammer.example){\r\n\t\t\t\t\thtml+= '<div class=\"hint-example\">'+grammer.example.replace('<','&lt;')+'</span></div>';\r\n\t\t\t\t}\r\n\t\t\t\tif(grammer.returns){\r\n\t\t\t\t\thtml+= '<div class=\"hint-return\">返回值：<span>'+grammer.returns.join(\"/\").replace('<','&lt;')+'</span></div>';\r\n\t\t\t\t}\r\n\t\t\thtml+= '</div>';\r\n\t\t}\r\n\t\tlist2[key] = html;\r\n\t}\r\n\treturn [set,list2];\r\n}\r\nfunction initHint(cm){\r\n\tcm.on('keyup',function(cm,e){\r\n\t\tif(e.keyCode ==38 || e.keyCode ==40 || e.keyCode == 13){\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tvar cur = cm.getCursor();\r\n\t\tvar ch = cur.ch;\r\n\t\tvar token = cm.getTokenAt(cur);\r\n\t\tvar curLine = cm.getLine(cur.line);\r\n\t\tvar str1 = curLine.charAt(cur.ch - 1);\r\n\t\tvar str2 = curLine.charAt(cur.ch - 2);\r\n\t\tif((str1=='{' &&str2=='$')){\r\n\t\t\tvar ret = searchGrammer(null,true);\r\n\t\t\tvar datas = {};\r\n\t\t\tdatas.list = ret[0];\r\n            datas.from = {};\r\n            datas.from.line = cur.line;\r\n            datas.from.ch = ch;\r\n            datas.to = {};\r\n            datas.to.line = cur.line;\r\n            datas.to.ch = ch;\r\n            datas.showList = ret[0];\r\n            datas.key = '.';\r\n            cm.showHint1({completeSingle: false,comments : {'.':ret[1]}},datas);\r\n\t\t}if(str1 == '.'){\r\n\t\t\tvar ret = searchGrammer(null);\r\n\t\t\tvar datas = {};\r\n\t\t\tdatas.list = ret[0];\r\n            datas.from = {};\r\n            datas.from.line = cur.line;\r\n            datas.from.ch = ch;\r\n            datas.to = {};\r\n            datas.to.line = cur.line;\r\n            datas.to.ch = ch;\r\n            datas.showList = ret[0];\r\n            datas.key = '.';\r\n            cm.showHint1({completeSingle: false,comments : {'.':ret[1]}},datas);\r\n\t\t}else{\r\n\t\t\tvar regx = /(\\w+)$/g;\r\n\t\t\tvar line = curLine.substring(0,cur.ch);\r\n\t\t\tvar keyword = regx.exec(line);\r\n\t\t\tif(keyword&&keyword[1]){\r\n\t\t\t\tkeyword = keyword[1];\r\n\t\t\t\tvar ret = searchGrammer(keyword);\r\n\t\t\t\tvar datas = {};\r\n\t\t\t\tdatas.list = ret[0];\r\n\t            datas.from = {};\r\n\t            datas.from.line = cur.line;\r\n\t            datas.from.ch = token.start;\r\n\t            datas.to = {};\r\n\t            datas.to.line = cur.line;\r\n\t            datas.to.ch = token.end;\r\n\t            datas.showList = ret[0];\r\n\t            datas.key = keyword;\r\n\t            var comments = {};\r\n\t            comments[keyword] = ret[1];\r\n\t            cm.showHint1({completeSingle: false,comments : comments},datas);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t}\r\n\t})\r\n}"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/codemirror/spiderflow.js",
    "content": "/**\r\n * freemarker\r\n */\r\n\r\n(function(mod) {\r\n\tif (typeof exports == \"object\" && typeof module == \"object\" ) // CommonJS\r\n\t\tmod(require(\"../../lib/codemirror\"));\r\n\telse if (typeof define == \"function\" && define.amd ) // AMD\r\n\t\tdefine([ \"../../lib/codemirror\" ], mod);\r\n\telse\r\n\t\t// Plain browser env\r\n\t\tmod(CodeMirror);\r\n})(function(CodeMirror) {\r\n\t\"use strict\";\r\n\r\n\tCodeMirror.defineMode(\"spiderflow\", function(config) {\r\n\t\t\t\t\"use strict\";\r\n\r\n\t\t\t\t// our default settings; check to see if they're overridden\r\n\t\t\t\tvar settings = {\r\n\t\t\t\t\tleftDelimiter : '<', rightDelimiter : '>', tagSyntax : 1\r\n\t\t\t\t// 1 angle_bracket,2 square_bracket\r\n\t\t\t\t};\r\n\t\t\t\tif (config.hasOwnProperty(\"tagSyntax\") ) {\r\n\t\t\t\t\tif (config.tagSyntax === 2 ) {\r\n\t\t\t\t\t\tsettings.tagSyntax = 2;\r\n\t\t\t\t\t\tsettings.leftDelimiter = '[';\r\n\t\t\t\t\t\tsettings.rightDelimiter = ']';\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tvar keyFunctions = [ \"assign\", \"attempt\", \"autoesc\", \"break\", \"case\", \"compress\", \"default\", \"else\",\r\n\t\t\t\t\t\t\"elseif\", \"escape\", \"fallback\", \"function\", \"flush\", \"ftl\", \"global\", \"if\", \"import\",\r\n\t\t\t\t\t\t\"include\", \"items\", \"list\", \"local\", \"lt\", \"macro\", \"nested\",\"noautoesc\", \"noescape\", \"noparse\", \"nt\",\"outputformat\",\r\n\t\t\t\t\t\t\"recover\", \"recurse\", \"return\", \"rt\", \"sep\", \"setting\", \"stop\", \"switch\", \"t\", \"visit\" ];\r\n\t\t\t\tvar specialVariables = [ \"auto_esc\" , \"current_template_name\", \"data_model\", \"error\", \"globals\", \"lang\", \"locale\",\r\n\t\t\t\t\t\t\"locale_object\", \"locals\", \"main\", \"main_template_name\", \"namespace\", \"node\", \"now\",\r\n\t\t\t\t\t\t\"output_encoding \" , \"output_format\" , \"template_name\", \"url_escaping_charset\", \"vars\", \"version\" ];\r\n\r\n\t\t\t\tvar freemarkerStartTagArray = [ \"#\", \"@\" ];\r\n\r\n\t\t\t\tvar freemarkerEndTagArray = [ \"/#\", \"/@\", \"/>\" ];\r\n\r\n\t\t\t\tvar last;\r\n\t\t\t\tvar freemarkerMode;\r\n\t\t\t\tvar regs = {\r\n\t\t\t\t\toperatorChars : /[+\\-*&%=<>!?:;,|&]/, validIdentifier : /[a-zA-Z0-9_]/, stringChar : /['\"]/\r\n\t\t\t\t};\r\n\t\t\t\t\r\n\t\t\t\tvar helpers = {\r\n\t\t\t\t\tcont : function(style, lastType, lastFreemarkerMode) {\r\n\t\t\t\t\t\tlast = lastType;\r\n\t\t\t\t\t\tfreemarkerMode = lastFreemarkerMode;\r\n\t\t\t\t\t\treturn style;\r\n\t\t\t\t\t}, chain : function(stream, state, parser) {\r\n\t\t\t\t\t\tstate.tokenize = parser;\r\n\t\t\t\t\t\treturn parser(stream, state);\r\n\t\t\t\t\t}\r\n\t\t\t\t};\r\n\r\n\t\t\t\t// our various parsers\r\n\t\t\t\tvar parsers = {\r\n\r\n\t\t\t\t\t// the main tokenizer\r\n\t\t\t\t\ttokenizer : function(stream, state) {\r\n\t\t\t\t\t\tif (stream.match(settings.leftDelimiter, true) ) {\r\n\t\t\t\t\t\t\tif (stream.match(\"#--\", true) ) {\r\n\t\t\t\t\t\t\t\treturn helpers.chain(stream, state, parsers.inBlock(\"comment\", \"--\"\r\n\t\t\t\t\t\t\t\t\t\t+ settings.rightDelimiter));\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\tfor (var i = 0; i < freemarkerStartTagArray.length; i++) {\r\n\t\t\t\t\t\t\t\t\tif (stream.match(freemarkerStartTagArray[i], false) ) {\r\n\t\t\t\t\t\t\t\t\t\tstate.tokenize = parsers.freemarkerTemplate;\r\n\t\t\t\t\t\t\t\t\t\tif (freemarkerStartTagArray[i] == \"@\" ) {\r\n\t\t\t\t\t\t\t\t\t\t\tfreemarkerMode = \"macro\";\r\n\t\t\t\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\t\t\t\tfreemarkerMode = \"tag\";\r\n\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t\tlast = \"startTag\";\r\n\t\t\t\t\t\t\t\t\t\treturn \"tag\";\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\tfor (var i = 0; i < freemarkerEndTagArray.length; i++) {\r\n\t\t\t\t\t\t\t\t\tif (stream.match(freemarkerEndTagArray[i], false) ) {\r\n\t\t\t\t\t\t\t\t\t\tstate.tokenize = parsers.freemarkerTemplate;\r\n\t\t\t\t\t\t\t\t\t\tif (freemarkerEndTagArray[i] == \"/@\" ) {\r\n\t\t\t\t\t\t\t\t\t\t\tfreemarkerMode = \"macro\";\r\n\t\t\t\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\t\t\t\tfreemarkerMode = \"tag\";\r\n\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t\tlast = \"endTag\";\r\n\t\t\t\t\t\t\t\t\t\treturn \"tag\";\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t} else if (stream.match(\"${\", false) ) {\r\n\t\t\t\t\t\t\tstate.tokenize = parsers.freemarkerTemplate;\r\n\t\t\t\t\t\t\tlast = \"startTag\";\r\n\t\t\t\t\t\t\tfreemarkerMode = \"echo\";\r\n\t\t\t\t\t\t\treturn \"keyword\";\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tstream.next();\r\n\t\t\t\t\t\treturn null;\r\n\t\t\t\t\t},\r\n\r\n\t\t\t\t\t// parsing freemarker content\r\n\t\t\t\t\tfreemarkerTemplate : function(stream, state) {\r\n\t\t\t\t\t\tif (stream.match(settings.rightDelimiter, true) ) {\r\n\t\t\t\t\t\t\tstate.depth--;\r\n\t\t\t\t\t\t\tif (state.depth <= 0 ) {\r\n\t\t\t\t\t\t\t\tstate.tokenize = parsers.tokenizer;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\treturn helpers.cont(\"tag\", null, null);\r\n\t\t\t\t\t\t} else if (\"echo\" == state.freemarkerMode && stream.match(\"}\", true) ) {\r\n\t\t\t\t\t\t\tstate.depth--;\r\n\t\t\t\t\t\t\tif (state.depth <= 0 ) {\r\n\t\t\t\t\t\t\t\tstate.tokenize = parsers.tokenizer;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\treturn helpers.cont(\"keyword\", null, null);\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tif (stream.match(settings.leftDelimiter, true) ) {\r\n\t\t\t\t\t\t\tfor (var i = 0; i < freemarkerStartTagArray.length; i++) {\r\n\t\t\t\t\t\t\t\tif (stream.match(freemarkerStartTagArray[i], false) ) {\r\n\t\t\t\t\t\t\t\t\tstate.depth++;\r\n\t\t\t\t\t\t\t\t\tif (freemarkerStartTagArray[i] == \"@\" ) {\r\n\t\t\t\t\t\t\t\t\t\treturn helpers.cont(\"tag\", \"startTag\", \"macro\");\r\n\t\t\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\t\t\treturn helpers.cont(\"tag\", \"startTag\", \"tag\");\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tfor (var i = 0; i < freemarkerEndTagArray.length; i++) {\r\n\t\t\t\t\t\t\t\tif (stream.match(freemarkerEndTagArray[i], false) ) {\r\n\t\t\t\t\t\t\t\t\tstate.depth++;\r\n\t\t\t\t\t\t\t\t\tif (freemarkerEndTagArray[i] == \"/@\" ) {\r\n\t\t\t\t\t\t\t\t\t\treturn helpers.cont(\"tag\", \"endTag\", \"macro\");\r\n\t\t\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\t\t\treturn helpers.cont(\"tag\", \"endTag\", \"tag\");\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t} else if (stream.match(\"${\", true) ) {\r\n\t\t\t\t\t\t\tstate.depth++;\r\n\t\t\t\t\t\t\treturn helpers.cont(\"keyword\", \"startTag\", \"echo\");\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tvar ch = stream.next();\r\n\t\t\t\t\t\tif (\".\" == ch ) {\r\n\t\t\t\t\t\t\tif(\"echo\" == state.freemarkerMode || \"whitespace\" == state.last ||\"operator\"== state.last){\r\n\t\t\t\t\t\t\t\tfor (var i = 0; i < specialVariables.length; i++) {\r\n\t\t\t\t\t\t\t\t\tif(stream.match(specialVariables[i],true)){\r\n\t\t\t\t\t\t\t\t\t\treturn helpers.cont(\"keyword\", \"variable\", state.freemarkerMode);\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tif(\"keyword\"==state.last && stream.eatWhile(regs.validIdentifier)){\r\n\t\t\t\t\t\t\t\treturn helpers.cont(\"keyword\", null, state.freemarkerMode);\r\n\t\t\t\t\t\t\t}else{\r\n\t\t\t\t\t\t\t\treturn helpers.cont(\"operator\", \"childVariable\", state.freemarkerMode);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t} else if (regs.stringChar.test(ch) ) {\r\n\t\t\t\t\t\t\tstate.tokenize = parsers.inAttribute(ch);\r\n\t\t\t\t\t\t\treturn helpers.cont(\"string\", \"string\", state.freemarkerMode);\r\n\t\t\t\t\t\t} else if (regs.operatorChars.test(ch) ) {\r\n\t\t\t\t\t\t\tif (\"?\" === ch ) {\r\n\t\t\t\t\t\t\t\treturn helpers.cont(\"operator\", \"builtin\", state.freemarkerMode);\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\treturn helpers.cont(\"operator\", \"operator\", state.freemarkerMode);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t} else if (\"[\" == ch || \"{\" == ch|| \"(\" == ch ) {\r\n\t\t\t\t\t\t\treturn helpers.cont(\"bracket\", \"bracket\", state.freemarkerMode);\r\n\t\t\t\t\t\t} else if (\"]\" == ch || \"}\" == ch || \")\" == ch  ) {\r\n\t\t\t\t\t\t\treturn helpers.cont(\"bracket\", \"variable\", state.freemarkerMode);\r\n\t\t\t\t\t\t} else if (\"/\" == ch ) {\r\n\t\t\t\t\t\t\treturn helpers.cont(\"tag\", \"endTag\", state.freemarkerMode);\r\n\t\t\t\t\t\t} else if (\"@\" == ch && \"macro\" == state.freemarkerMode ) {\r\n\t\t\t\t\t\t\tstream.eatWhile(regs.validIdentifier)\r\n\t\t\t\t\t\t\treturn helpers.cont(\"keyword\", \"keyword\", state.freemarkerMode);\r\n\t\t\t\t\t\t} else if (/\\d/.test(ch) ) {\r\n\t\t\t\t\t\t\tstream.eat(/x/i)\r\n\t\t\t\t\t\t\tstream.eatWhile(/\\d/);\r\n\t\t\t\t\t\t\treturn helpers.cont(\"number\", \"number\", state.freemarkerMode);\r\n\t\t\t\t\t\t} else if(\"tag\" == state.freemarkerMode && \"whitespace\" == state.last && (stream.match(\"as\",true) || stream.match(\"in\",true)|| stream.match(\"using\",true) )) {\r\n\t\t\t\t\t\t\treturn helpers.cont(\"keyword\", \"operator\", state.freemarkerMode);\r\n\t\t\t\t\t\t} else if(\"tag\" == state.freemarkerMode && \"whitespace\" == state.last && (stream.match(\"gte\",true) || stream.match(\"lte\",true) || stream.match(\"gt\",true) || stream.match(\"lt\",true) )) {\r\n\t\t\t\t\t\t\treturn helpers.cont(\"operator\", \"operator\", state.freemarkerMode);\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tif (\"builtin\" == state.last ) {\r\n\t\t\t\t\t\t\t\tstream.eat(\"?\");\r\n\t\t\t\t\t\t\t\tstream.eatWhile(regs.validIdentifier);\r\n\t\t\t\t\t\t\t\treturn helpers.cont(\"builtin\", \"variable\", state.freemarkerMode);\r\n\t\t\t\t\t\t\t} else if (\"whitespace\" == state.last||\"bracket\" == state.last) {\r\n\t\t\t\t\t\t\t\tif (\"macro\" == state.freemarkerMode ) {\r\n\t\t\t\t\t\t\t\t\tstream.eatWhile(regs.validIdentifier);\r\n\t\t\t\t\t\t\t        return helpers.cont(\"attribute\", \"attribute\", state.freemarkerMode);\r\n\t\t\t\t\t\t\t\t}  else {\r\n\t\t\t\t\t\t\t\t\tstream.eatWhile(regs.validIdentifier);\r\n\t\t\t\t\t\t\t        return helpers.cont(\"variable-2\", \"variable\", state.freemarkerMode);\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t    } else if (\"operator\" == state.last ) {\r\n\t\t\t\t\t\t\t\tstream.eatWhile(regs.validIdentifier);\r\n\t\t\t\t\t\t\t\treturn helpers.cont(\"variable-2\", \"variable\", state.freemarkerMode);\r\n\t\t\t\t\t\t\t} else if (\"childVariable\" == state.last ) {\r\n\t\t\t\t\t\t\t\tstream.eatWhile(regs.validIdentifier);\r\n\t\t\t\t\t\t\t\treturn helpers.cont(\"variable-3\", \"variable\", state.freemarkerMode);\r\n\t\t\t\t\t\t\t} else if (/\\s/.test(ch) ) {\r\n\t\t\t\t\t\t\t\tlast = \"whitespace\";\r\n\t\t\t\t\t\t\t\treturn null;\r\n\t\t\t\t\t\t\t} else if (\"string\" == state.last ) {\r\n\t\t\t\t\t\t\t\tstream.eatWhile(regs.validIdentifier);\r\n\t\t\t\t\t\t\t\treturn helpers.cont(\"attribute\", \"attribute\", state.freemarkerMode);\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\tif (\"startTag\" == state.last || \"endTag\" == state.last ) {\r\n\t\t\t\t\t\t\t\t\tif (\"echo\" == state.freemarkerMode ) {\r\n\t\t\t\t\t\t\t\t\t\tstream.eatWhile(regs.validIdentifier)\r\n\t\t\t\t\t\t\t\t\t\treturn helpers.cont(\"variable-2\", \"variable\", state.freemarkerMode);\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\tif (\"tag\" == state.freemarkerMode ) {\r\n\t\t\t\t\t\t\t\t\tvar str = \"\";\r\n\t\t\t\t\t\t\t\t\tif (ch != \"/\" ) {\r\n\t\t\t\t\t\t\t\t\t\tstr += ch;\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\tvar c = null;\r\n\t\t\t\t\t\t\t\t\twhile (c = stream.eat(regs.validIdentifier)) {\r\n\t\t\t\t\t\t\t\t\t\tstr += c;\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\tfor (var i = 0 ; i < keyFunctions.length; i++) {\r\n\t\t\t\t\t\t\t\t\t\tif (\"#\"+keyFunctions[i] == str ) {\r\n\t\t\t\t\t\t\t\t\t\t\treturn helpers.cont(\"keyword\", \"keyword\", state.freemarkerMode);\r\n\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\treturn helpers.cont(\"error\", \"tag\", state.freemarkerMode);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t},\r\n\r\n\t\t\t\t\tinAttribute : function(quote) {\r\n\t\t\t\t\t\treturn function(stream, state) {\r\n\t\t\t\t\t\t\tvar prevChar = null;\r\n\t\t\t\t\t\t\tvar currChar = null;\r\n\t\t\t\t\t\t\twhile (!stream.eol()) {\r\n\t\t\t\t\t\t\t\tcurrChar = stream.peek();\r\n\t\t\t\t\t\t\t\tif (stream.next() == quote && '\\\\' !== prevChar ) {\r\n\t\t\t\t\t\t\t\t\tstate.tokenize = parsers.freemarkerTemplate;\r\n\t\t\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\tprevChar = currChar;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\treturn \"string\";\r\n\t\t\t\t\t\t};\r\n\t\t\t\t\t},\r\n\r\n\t\t\t\t\tinBlock : function(style, terminator) {\r\n\t\t\t\t\t\treturn function(stream, state) {\r\n\t\t\t\t\t\t\twhile (!stream.eol()) {\r\n\t\t\t\t\t\t\t\tif (stream.match(terminator) ) {\r\n\t\t\t\t\t\t\t\t\tstate.tokenize = parsers.tokenizer;\r\n\t\t\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\tstream.next();\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\treturn style;\r\n\t\t\t\t\t\t};\r\n\t\t\t\t\t}\r\n\t\t\t\t};\r\n\r\n\t\t\t\t// the public API for CodeMirror\r\n\t\t\t\treturn {\r\n\t\t\t\t\tstartState : function() {\r\n\t\t\t\t\t\treturn {\r\n\t\t\t\t\t\t\ttokenize : parsers.tokenizer, mode : \"freemarker\", last : null, freemarkerMode : null,\r\n\t\t\t\t\t\t\tdepth : 0\r\n\t\t\t\t\t\t};\r\n\t\t\t\t\t}, token : function(stream, state) {\r\n\t\t\t\t\t\tstate.last = last;\r\n\t\t\t\t\t\tstate.freemarkerMode = freemarkerMode;\r\n\t\t\t\t\t\treturn state.tokenize(stream, state);\r\n\t\t\t\t\t}, electricChars : \"\"\r\n\t\t\t\t};\r\n\t\t\t});\r\n\r\n\tCodeMirror.defineMIME(\"text/spiderflow\", \"spiderflow\");\r\n\r\n});"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/codemirror/sql.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nCodeMirror.defineMode(\"sql\", function(config, parserConfig) {\n  var client         = parserConfig.client || {},\n      atoms          = parserConfig.atoms || {\"false\": true, \"true\": true, \"null\": true},\n      builtin        = parserConfig.builtin || set(defaultBuiltin),\n      keywords       = parserConfig.keywords || set(sqlKeywords),\n      operatorChars  = parserConfig.operatorChars || /^[*+\\-%<>!=&|~^\\/]/,\n      support        = parserConfig.support || {},\n      hooks          = parserConfig.hooks || {},\n      dateSQL        = parserConfig.dateSQL || {\"date\" : true, \"time\" : true, \"timestamp\" : true},\n      backslashStringEscapes = parserConfig.backslashStringEscapes !== false,\n      brackets       = parserConfig.brackets || /^[\\{}\\(\\)\\[\\]]/,\n      punctuation    = parserConfig.punctuation || /^[;.,:]/\n\n  function tokenBase(stream, state) {\n    var ch = stream.next();\n\n    // call hooks from the mime type\n    if (hooks[ch]) {\n      var result = hooks[ch](stream, state);\n      if (result !== false) return result;\n    }\n\n    if (support.hexNumber &&\n      ((ch == \"0\" && stream.match(/^[xX][0-9a-fA-F]+/))\n      || (ch == \"x\" || ch == \"X\") && stream.match(/^'[0-9a-fA-F]+'/))) {\n      // hex\n      // ref: http://dev.mysql.com/doc/refman/5.5/en/hexadecimal-literals.html\n      return \"number\";\n    } else if (support.binaryNumber &&\n      (((ch == \"b\" || ch == \"B\") && stream.match(/^'[01]+'/))\n      || (ch == \"0\" && stream.match(/^b[01]+/)))) {\n      // bitstring\n      // ref: http://dev.mysql.com/doc/refman/5.5/en/bit-field-literals.html\n      return \"number\";\n    } else if (ch.charCodeAt(0) > 47 && ch.charCodeAt(0) < 58) {\n      // numbers\n      // ref: http://dev.mysql.com/doc/refman/5.5/en/number-literals.html\n      stream.match(/^[0-9]*(\\.[0-9]+)?([eE][-+]?[0-9]+)?/);\n      support.decimallessFloat && stream.match(/^\\.(?!\\.)/);\n      return \"number\";\n    } else if (ch == \"?\" && (stream.eatSpace() || stream.eol() || stream.eat(\";\"))) {\n      // placeholders\n      return \"variable-3\";\n    } else if (ch == \"'\" || (ch == '\"' && support.doubleQuote)) {\n      // strings\n      // ref: http://dev.mysql.com/doc/refman/5.5/en/string-literals.html\n      state.tokenize = tokenLiteral(ch);\n      return state.tokenize(stream, state);\n    } else if ((((support.nCharCast && (ch == \"n\" || ch == \"N\"))\n        || (support.charsetCast && ch == \"_\" && stream.match(/[a-z][a-z0-9]*/i)))\n        && (stream.peek() == \"'\" || stream.peek() == '\"'))) {\n      // charset casting: _utf8'str', N'str', n'str'\n      // ref: http://dev.mysql.com/doc/refman/5.5/en/string-literals.html\n      return \"keyword\";\n    } else if (support.commentSlashSlash && ch == \"/\" && stream.eat(\"/\")) {\n      // 1-line comment\n      stream.skipToEnd();\n      return \"comment\";\n    } else if ((support.commentHash && ch == \"#\")\n        || (ch == \"-\" && stream.eat(\"-\") && (!support.commentSpaceRequired || stream.eat(\" \")))) {\n      // 1-line comments\n      // ref: https://kb.askmonty.org/en/comment-syntax/\n      stream.skipToEnd();\n      return \"comment\";\n    } else if (ch == \"/\" && stream.eat(\"*\")) {\n      // multi-line comments\n      // ref: https://kb.askmonty.org/en/comment-syntax/\n      state.tokenize = tokenComment(1);\n      return state.tokenize(stream, state);\n    } else if (ch == \".\") {\n      // .1 for 0.1\n      if (support.zerolessFloat && stream.match(/^(?:\\d+(?:e[+-]?\\d+)?)/i))\n        return \"number\";\n      if (stream.match(/^\\.+/))\n        return null\n      // .table_name (ODBC)\n      // // ref: http://dev.mysql.com/doc/refman/5.6/en/identifier-qualifiers.html\n      if (support.ODBCdotTable && stream.match(/^[\\w\\d_]+/))\n        return \"variable-2\";\n    } else if (operatorChars.test(ch)) {\n      // operators\n      stream.eatWhile(operatorChars);\n      return \"operator\";\n    } else if (brackets.test(ch)) {\n      // brackets\n      return \"bracket\";\n    } else if (punctuation.test(ch)) {\n      // punctuation\n      stream.eatWhile(punctuation);\n      return \"punctuation\";\n    } else if (ch == '{' &&\n        (stream.match(/^( )*(d|D|t|T|ts|TS)( )*'[^']*'( )*}/) || stream.match(/^( )*(d|D|t|T|ts|TS)( )*\"[^\"]*\"( )*}/))) {\n      // dates (weird ODBC syntax)\n      // ref: http://dev.mysql.com/doc/refman/5.5/en/date-and-time-literals.html\n      return \"number\";\n    } else {\n      stream.eatWhile(/^[_\\w\\d]/);\n      var word = stream.current().toLowerCase();\n      // dates (standard SQL syntax)\n      // ref: http://dev.mysql.com/doc/refman/5.5/en/date-and-time-literals.html\n      if (dateSQL.hasOwnProperty(word) && (stream.match(/^( )+'[^']*'/) || stream.match(/^( )+\"[^\"]*\"/)))\n        return \"number\";\n      if (atoms.hasOwnProperty(word)) return \"atom\";\n      if (builtin.hasOwnProperty(word)) return \"builtin\";\n      if (keywords.hasOwnProperty(word)) return \"keyword\";\n      if (client.hasOwnProperty(word)) return \"string-2\";\n      return null;\n    }\n  }\n\n  // 'string', with char specified in quote escaped by '\\'\n  function tokenLiteral(quote) {\n    return function(stream, state) {\n      var escaped = false, ch;\n      while ((ch = stream.next()) != null) {\n        if (ch == quote && !escaped) {\n          state.tokenize = tokenBase;\n          break;\n        }\n        escaped = backslashStringEscapes && !escaped && ch == \"\\\\\";\n      }\n      return \"string\";\n    };\n  }\n  function tokenComment(depth) {\n    return function(stream, state) {\n      var m = stream.match(/^.*?(\\/\\*|\\*\\/)/)\n      if (!m) stream.skipToEnd()\n      else if (m[1] == \"/*\") state.tokenize = tokenComment(depth + 1)\n      else if (depth > 1) state.tokenize = tokenComment(depth - 1)\n      else state.tokenize = tokenBase\n      return \"comment\"\n    }\n  }\n\n  function pushContext(stream, state, type) {\n    state.context = {\n      prev: state.context,\n      indent: stream.indentation(),\n      col: stream.column(),\n      type: type\n    };\n  }\n\n  function popContext(state) {\n    state.indent = state.context.indent;\n    state.context = state.context.prev;\n  }\n\n  return {\n    startState: function() {\n      return {tokenize: tokenBase, context: null};\n    },\n\n    token: function(stream, state) {\n      if (stream.sol()) {\n        if (state.context && state.context.align == null)\n          state.context.align = false;\n      }\n      if (state.tokenize == tokenBase && stream.eatSpace()) return null;\n\n      var style = state.tokenize(stream, state);\n      if (style == \"comment\") return style;\n\n      if (state.context && state.context.align == null)\n        state.context.align = true;\n\n      var tok = stream.current();\n      if (tok == \"(\")\n        pushContext(stream, state, \")\");\n      else if (tok == \"[\")\n        pushContext(stream, state, \"]\");\n      else if (state.context && state.context.type == tok)\n        popContext(state);\n      return style;\n    },\n\n    indent: function(state, textAfter) {\n      var cx = state.context;\n      if (!cx) return CodeMirror.Pass;\n      var closing = textAfter.charAt(0) == cx.type;\n      if (cx.align) return cx.col + (closing ? 0 : 1);\n      else return cx.indent + (closing ? 0 : config.indentUnit);\n    },\n\n    blockCommentStart: \"/*\",\n    blockCommentEnd: \"*/\",\n    lineComment: support.commentSlashSlash ? \"//\" : support.commentHash ? \"#\" : \"--\",\n    closeBrackets: \"()[]{}''\\\"\\\"``\"\n  };\n});\n\n  // `identifier`\n  function hookIdentifier(stream) {\n    // MySQL/MariaDB identifiers\n    // ref: http://dev.mysql.com/doc/refman/5.6/en/identifier-qualifiers.html\n    var ch;\n    while ((ch = stream.next()) != null) {\n      if (ch == \"`\" && !stream.eat(\"`\")) return \"variable-2\";\n    }\n    stream.backUp(stream.current().length - 1);\n    return stream.eatWhile(/\\w/) ? \"variable-2\" : null;\n  }\n\n  // \"identifier\"\n  function hookIdentifierDoublequote(stream) {\n    // Standard SQL /SQLite identifiers\n    // ref: http://web.archive.org/web/20160813185132/http://savage.net.au/SQL/sql-99.bnf.html#delimited%20identifier\n    // ref: http://sqlite.org/lang_keywords.html\n    var ch;\n    while ((ch = stream.next()) != null) {\n      if (ch == \"\\\"\" && !stream.eat(\"\\\"\")) return \"variable-2\";\n    }\n    stream.backUp(stream.current().length - 1);\n    return stream.eatWhile(/\\w/) ? \"variable-2\" : null;\n  }\n\n  // variable token\n  function hookVar(stream) {\n    // variables\n    // @@prefix.varName @varName\n    // varName can be quoted with ` or ' or \"\n    // ref: http://dev.mysql.com/doc/refman/5.5/en/user-variables.html\n    if (stream.eat(\"@\")) {\n      stream.match(/^session\\./);\n      stream.match(/^local\\./);\n      stream.match(/^global\\./);\n    }\n\n    if (stream.eat(\"'\")) {\n      stream.match(/^.*'/);\n      return \"variable-2\";\n    } else if (stream.eat('\"')) {\n      stream.match(/^.*\"/);\n      return \"variable-2\";\n    } else if (stream.eat(\"`\")) {\n      stream.match(/^.*`/);\n      return \"variable-2\";\n    } else if (stream.match(/^[0-9a-zA-Z$\\.\\_]+/)) {\n      return \"variable-2\";\n    }\n    return null;\n  };\n\n  // short client keyword token\n  function hookClient(stream) {\n    // \\N means NULL\n    // ref: http://dev.mysql.com/doc/refman/5.5/en/null-values.html\n    if (stream.eat(\"N\")) {\n        return \"atom\";\n    }\n    // \\g, etc\n    // ref: http://dev.mysql.com/doc/refman/5.5/en/mysql-commands.html\n    return stream.match(/^[a-zA-Z.#!?]/) ? \"variable-2\" : null;\n  }\n\n  // these keywords are used by all SQL dialects (however, a mode can still overwrite it)\n  var sqlKeywords = \"alter and as asc between by count create delete desc distinct drop from group having in insert into is join like not on or order select set table union update values where limit \";\n\n  // turn a space-separated list into an array\n  function set(str) {\n    var obj = {}, words = str.split(\" \");\n    for (var i = 0; i < words.length; ++i) obj[words[i]] = true;\n    return obj;\n  }\n\n  var defaultBuiltin = \"bool boolean bit blob enum long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision real date datetime year unsigned signed decimal numeric\"\n\n  // A generic SQL Mode. It's not a standard, it just try to support what is generally supported\n  CodeMirror.defineMIME(\"text/x-sql\", {\n    name: \"sql\",\n    keywords: set(sqlKeywords + \"begin\"),\n    builtin: set(defaultBuiltin),\n    atoms: set(\"false true null unknown\"),\n    dateSQL: set(\"date time timestamp\"),\n    support: set(\"ODBCdotTable doubleQuote binaryNumber hexNumber\")\n  });\n\n  CodeMirror.defineMIME(\"text/x-mssql\", {\n    name: \"sql\",\n    client: set(\"$partition binary_checksum checksum connectionproperty context_info current_request_id error_line error_message error_number error_procedure error_severity error_state formatmessage get_filestream_transaction_context getansinull host_id host_name isnull isnumeric min_active_rowversion newid newsequentialid rowcount_big xact_state object_id\"),\n    keywords: set(sqlKeywords + \"begin trigger proc view index for add constraint key primary foreign collate clustered nonclustered declare exec go if use index holdlock nolock nowait paglock readcommitted readcommittedlock readpast readuncommitted repeatableread rowlock serializable snapshot tablock tablockx updlock with\"),\n    builtin: set(\"bigint numeric bit smallint decimal smallmoney int tinyint money float real char varchar text nchar nvarchar ntext binary varbinary image cursor timestamp hierarchyid uniqueidentifier sql_variant xml table \"),\n    atoms: set(\"is not null like and or in left right between inner outer join all any some cross unpivot pivot exists\"),\n    operatorChars: /^[*+\\-%<>!=^\\&|\\/]/,\n    brackets: /^[\\{}\\(\\)]/,\n    punctuation: /^[;.,:/]/,\n    backslashStringEscapes: false,\n    dateSQL: set(\"date datetimeoffset datetime2 smalldatetime datetime time\"),\n    hooks: {\n      \"@\":   hookVar\n    }\n  });\n\n  CodeMirror.defineMIME(\"text/x-mysql\", {\n    name: \"sql\",\n    client: set(\"charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee\"),\n    keywords: set(sqlKeywords + \"accessible action add after algorithm all analyze asensitive at authors auto_increment autocommit avg avg_row_length before binary binlog both btree cache call cascade cascaded case catalog_name chain change changed character check checkpoint checksum class_origin client_statistics close coalesce code collate collation collations column columns comment commit committed completion concurrent condition connection consistent constraint contains continue contributors convert cross current current_date current_time current_timestamp current_user cursor data database databases day_hour day_microsecond day_minute day_second deallocate dec declare default delay_key_write delayed delimiter des_key_file describe deterministic dev_pop dev_samp deviance diagnostics directory disable discard distinctrow div dual dumpfile each elseif enable enclosed end ends engine engines enum errors escape escaped even event events every execute exists exit explain extended fast fetch field fields first flush for force foreign found_rows full fulltext function general get global grant grants group group_concat handler hash help high_priority hosts hour_microsecond hour_minute hour_second if ignore ignore_server_ids import index index_statistics infile inner innodb inout insensitive insert_method install interval invoker isolation iterate key keys kill language last leading leave left level limit linear lines list load local localtime localtimestamp lock logs low_priority master master_heartbeat_period master_ssl_verify_server_cert masters match max max_rows maxvalue message_text middleint migrate min min_rows minute_microsecond minute_second mod mode modifies modify mutex mysql_errno natural next no no_write_to_binlog offline offset one online open optimize option optionally out outer outfile pack_keys parser partition partitions password phase plugin plugins prepare preserve prev primary privileges procedure processlist profile profiles purge query quick range read read_write reads real rebuild recover references regexp relaylog release remove rename reorganize repair repeatable replace require resignal restrict resume return returns revoke right rlike rollback rollup row row_format rtree savepoint schedule schema schema_name schemas second_microsecond security sensitive separator serializable server session share show signal slave slow smallint snapshot soname spatial specific sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sqlexception sqlstate sqlwarning ssl start starting starts status std stddev stddev_pop stddev_samp storage straight_join subclass_origin sum suspend table_name table_statistics tables tablespace temporary terminated to trailing transaction trigger triggers truncate uncommitted undo uninstall unique unlock upgrade usage use use_frm user user_resources user_statistics using utc_date utc_time utc_timestamp value variables varying view views warnings when while with work write xa xor year_month zerofill begin do then else loop repeat\"),\n    builtin: set(\"bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision date datetime year unsigned signed numeric\"),\n    atoms: set(\"false true null unknown\"),\n    operatorChars: /^[*+\\-%<>!=&|^]/,\n    dateSQL: set(\"date time timestamp\"),\n    support: set(\"ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber doubleQuote nCharCast charsetCast commentHash commentSpaceRequired\"),\n    hooks: {\n      \"@\":   hookVar,\n      \"`\":   hookIdentifier,\n      \"\\\\\":  hookClient\n    }\n  });\n\n  CodeMirror.defineMIME(\"text/x-mariadb\", {\n    name: \"sql\",\n    client: set(\"charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee\"),\n    keywords: set(sqlKeywords + \"accessible action add after algorithm all always analyze asensitive at authors auto_increment autocommit avg avg_row_length before binary binlog both btree cache call cascade cascaded case catalog_name chain change changed character check checkpoint checksum class_origin client_statistics close coalesce code collate collation collations column columns comment commit committed completion concurrent condition connection consistent constraint contains continue contributors convert cross current current_date current_time current_timestamp current_user cursor data database databases day_hour day_microsecond day_minute day_second deallocate dec declare default delay_key_write delayed delimiter des_key_file describe deterministic dev_pop dev_samp deviance diagnostics directory disable discard distinctrow div dual dumpfile each elseif enable enclosed end ends engine engines enum errors escape escaped even event events every execute exists exit explain extended fast fetch field fields first flush for force foreign found_rows full fulltext function general generated get global grant grants group groupby_concat handler hard hash help high_priority hosts hour_microsecond hour_minute hour_second if ignore ignore_server_ids import index index_statistics infile inner innodb inout insensitive insert_method install interval invoker isolation iterate key keys kill language last leading leave left level limit linear lines list load local localtime localtimestamp lock logs low_priority master master_heartbeat_period master_ssl_verify_server_cert masters match max max_rows maxvalue message_text middleint migrate min min_rows minute_microsecond minute_second mod mode modifies modify mutex mysql_errno natural next no no_write_to_binlog offline offset one online open optimize option optionally out outer outfile pack_keys parser partition partitions password persistent phase plugin plugins prepare preserve prev primary privileges procedure processlist profile profiles purge query quick range read read_write reads real rebuild recover references regexp relaylog release remove rename reorganize repair repeatable replace require resignal restrict resume return returns revoke right rlike rollback rollup row row_format rtree savepoint schedule schema schema_name schemas second_microsecond security sensitive separator serializable server session share show shutdown signal slave slow smallint snapshot soft soname spatial specific sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sqlexception sqlstate sqlwarning ssl start starting starts status std stddev stddev_pop stddev_samp storage straight_join subclass_origin sum suspend table_name table_statistics tables tablespace temporary terminated to trailing transaction trigger triggers truncate uncommitted undo uninstall unique unlock upgrade usage use use_frm user user_resources user_statistics using utc_date utc_time utc_timestamp value variables varying view views virtual warnings when while with work write xa xor year_month zerofill begin do then else loop repeat\"),\n    builtin: set(\"bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision date datetime year unsigned signed numeric\"),\n    atoms: set(\"false true null unknown\"),\n    operatorChars: /^[*+\\-%<>!=&|^]/,\n    dateSQL: set(\"date time timestamp\"),\n    support: set(\"ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber doubleQuote nCharCast charsetCast commentHash commentSpaceRequired\"),\n    hooks: {\n      \"@\":   hookVar,\n      \"`\":   hookIdentifier,\n      \"\\\\\":  hookClient\n    }\n  });\n\n  // provided by the phpLiteAdmin project - phpliteadmin.org\n  CodeMirror.defineMIME(\"text/x-sqlite\", {\n    name: \"sql\",\n    // commands of the official SQLite client, ref: https://www.sqlite.org/cli.html#dotcmd\n    client: set(\"auth backup bail binary changes check clone databases dbinfo dump echo eqp exit explain fullschema headers help import imposter indexes iotrace limit lint load log mode nullvalue once open output print prompt quit read restore save scanstats schema separator session shell show stats system tables testcase timeout timer trace vfsinfo vfslist vfsname width\"),\n    // ref: http://sqlite.org/lang_keywords.html\n    keywords: set(sqlKeywords + \"abort action add after all analyze attach autoincrement before begin cascade case cast check collate column commit conflict constraint cross current_date current_time current_timestamp database default deferrable deferred detach each else end escape except exclusive exists explain fail for foreign full glob if ignore immediate index indexed initially inner instead intersect isnull key left limit match natural no notnull null of offset outer plan pragma primary query raise recursive references regexp reindex release rename replace restrict right rollback row savepoint temp temporary then to transaction trigger unique using vacuum view virtual when with without\"),\n    // SQLite is weakly typed, ref: http://sqlite.org/datatype3.html. This is just a list of some common types.\n    builtin: set(\"bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text clob bigint int int2 int8 integer float double char varchar date datetime year unsigned signed numeric real\"),\n    // ref: http://sqlite.org/syntax/literal-value.html\n    atoms: set(\"null current_date current_time current_timestamp\"),\n    // ref: http://sqlite.org/lang_expr.html#binaryops\n    operatorChars: /^[*+\\-%<>!=&|/~]/,\n    // SQLite is weakly typed, ref: http://sqlite.org/datatype3.html. This is just a list of some common types.\n    dateSQL: set(\"date time timestamp datetime\"),\n    support: set(\"decimallessFloat zerolessFloat\"),\n    identifierQuote: \"\\\"\",  //ref: http://sqlite.org/lang_keywords.html\n    hooks: {\n      // bind-parameters ref:http://sqlite.org/lang_expr.html#varparam\n      \"@\":   hookVar,\n      \":\":   hookVar,\n      \"?\":   hookVar,\n      \"$\":   hookVar,\n      // The preferred way to escape Identifiers is using double quotes, ref: http://sqlite.org/lang_keywords.html\n      \"\\\"\":   hookIdentifierDoublequote,\n      // there is also support for backtics, ref: http://sqlite.org/lang_keywords.html\n      \"`\":   hookIdentifier\n    }\n  });\n\n  // the query language used by Apache Cassandra is called CQL, but this mime type\n  // is called Cassandra to avoid confusion with Contextual Query Language\n  CodeMirror.defineMIME(\"text/x-cassandra\", {\n    name: \"sql\",\n    client: { },\n    keywords: set(\"add all allow alter and any apply as asc authorize batch begin by clustering columnfamily compact consistency count create custom delete desc distinct drop each_quorum exists filtering from grant if in index insert into key keyspace keyspaces level limit local_one local_quorum modify nan norecursive nosuperuser not of on one order password permission permissions primary quorum rename revoke schema select set storage superuser table three to token truncate ttl two type unlogged update use user users using values where with writetime\"),\n    builtin: set(\"ascii bigint blob boolean counter decimal double float frozen inet int list map static text timestamp timeuuid tuple uuid varchar varint\"),\n    atoms: set(\"false true infinity NaN\"),\n    operatorChars: /^[<>=]/,\n    dateSQL: { },\n    support: set(\"commentSlashSlash decimallessFloat\"),\n    hooks: { }\n  });\n\n  // this is based on Peter Raganitsch's 'plsql' mode\n  CodeMirror.defineMIME(\"text/x-plsql\", {\n    name:       \"sql\",\n    client:     set(\"appinfo arraysize autocommit autoprint autorecovery autotrace blockterminator break btitle cmdsep colsep compatibility compute concat copycommit copytypecheck define describe echo editfile embedded escape exec execute feedback flagger flush heading headsep instance linesize lno loboffset logsource long longchunksize markup native newpage numformat numwidth pagesize pause pno recsep recsepchar release repfooter repheader serveroutput shiftinout show showmode size spool sqlblanklines sqlcase sqlcode sqlcontinue sqlnumber sqlpluscompatibility sqlprefix sqlprompt sqlterminator suffix tab term termout time timing trimout trimspool ttitle underline verify version wrap\"),\n    keywords:   set(\"abort accept access add all alter and any array arraylen as asc assert assign at attributes audit authorization avg base_table begin between binary_integer body boolean by case cast char char_base check close cluster clusters colauth column comment commit compress connect connected constant constraint crash create current currval cursor data_base database date dba deallocate debugoff debugon decimal declare default definition delay delete desc digits dispose distinct do drop else elseif elsif enable end entry escape exception exception_init exchange exclusive exists exit external fast fetch file for force form from function generic goto grant group having identified if immediate in increment index indexes indicator initial initrans insert interface intersect into is key level library like limited local lock log logging long loop master maxextents maxtrans member minextents minus mislabel mode modify multiset new next no noaudit nocompress nologging noparallel not nowait number_base object of off offline on online only open option or order out package parallel partition pctfree pctincrease pctused pls_integer positive positiven pragma primary prior private privileges procedure public raise range raw read rebuild record ref references refresh release rename replace resource restrict return returning returns reverse revoke rollback row rowid rowlabel rownum rows run savepoint schema segment select separate session set share snapshot some space split sql start statement storage subtype successful synonym tabauth table tables tablespace task terminate then to trigger truncate type union unique unlimited unrecoverable unusable update use using validate value values variable view views when whenever where while with work\"),\n    builtin:    set(\"abs acos add_months ascii asin atan atan2 average bfile bfilename bigserial bit blob ceil character chartorowid chr clob concat convert cos cosh count dec decode deref dual dump dup_val_on_index empty error exp false float floor found glb greatest hextoraw initcap instr instrb int integer isopen last_day least length lengthb ln lower lpad ltrim lub make_ref max min mlslabel mod months_between natural naturaln nchar nclob new_time next_day nextval nls_charset_decl_len nls_charset_id nls_charset_name nls_initcap nls_lower nls_sort nls_upper nlssort no_data_found notfound null number numeric nvarchar2 nvl others power rawtohex real reftohex round rowcount rowidtochar rowtype rpad rtrim serial sign signtype sin sinh smallint soundex sqlcode sqlerrm sqrt stddev string substr substrb sum sysdate tan tanh to_char text to_date to_label to_multi_byte to_number to_single_byte translate true trunc uid unlogged upper user userenv varchar varchar2 variance varying vsize xml\"),\n    operatorChars: /^[*\\/+\\-%<>!=~]/,\n    dateSQL:    set(\"date time timestamp\"),\n    support:    set(\"doubleQuote nCharCast zerolessFloat binaryNumber hexNumber\")\n  });\n\n  // Created to support specific hive keywords\n  CodeMirror.defineMIME(\"text/x-hive\", {\n    name: \"sql\",\n    keywords: set(\"select alter $elem$ $key$ $value$ add after all analyze and archive as asc before between binary both bucket buckets by cascade case cast change cluster clustered clusterstatus collection column columns comment compute concatenate continue create cross cursor data database databases dbproperties deferred delete delimited desc describe directory disable distinct distribute drop else enable end escaped exclusive exists explain export extended external fetch fields fileformat first format formatted from full function functions grant group having hold_ddltime idxproperties if import in index indexes inpath inputdriver inputformat insert intersect into is items join keys lateral left like limit lines load local location lock locks mapjoin materialized minus msck no_drop nocompress not of offline on option or order out outer outputdriver outputformat overwrite partition partitioned partitions percent plus preserve procedure purge range rcfile read readonly reads rebuild recordreader recordwriter recover reduce regexp rename repair replace restrict revoke right rlike row schema schemas semi sequencefile serde serdeproperties set shared show show_database sort sorted ssl statistics stored streamtable table tables tablesample tblproperties temporary terminated textfile then tmp to touch transform trigger unarchive undo union uniquejoin unlock update use using utc utc_tmestamp view when where while with admin authorization char compact compactions conf cube current current_date current_timestamp day decimal defined dependency directories elem_type exchange file following for grouping hour ignore inner interval jar less logical macro minute month more none noscan over owner partialscan preceding pretty principals protection reload rewrite role roles rollup rows second server sets skewed transactions truncate unbounded unset uri user values window year\"),\n    builtin: set(\"bool boolean long timestamp tinyint smallint bigint int float double date datetime unsigned string array struct map uniontype key_type utctimestamp value_type varchar\"),\n    atoms: set(\"false true null unknown\"),\n    operatorChars: /^[*+\\-%<>!=]/,\n    dateSQL: set(\"date timestamp\"),\n    support: set(\"ODBCdotTable doubleQuote binaryNumber hexNumber\")\n  });\n\n  CodeMirror.defineMIME(\"text/x-pgsql\", {\n    name: \"sql\",\n    client: set(\"source\"),\n    // For PostgreSQL - https://www.postgresql.org/docs/11/sql-keywords-appendix.html\n    // For pl/pgsql lang - https://github.com/postgres/postgres/blob/REL_11_2/src/pl/plpgsql/src/pl_scanner.c\n    keywords: set(sqlKeywords + \"a abort abs absent absolute access according action ada add admin after aggregate alias all allocate also alter always analyse analyze and any are array array_agg array_max_cardinality as asc asensitive assert assertion assignment asymmetric at atomic attach attribute attributes authorization avg backward base64 before begin begin_frame begin_partition bernoulli between bigint binary bit bit_length blob blocked bom boolean both breadth by c cache call called cardinality cascade cascaded case cast catalog catalog_name ceil ceiling chain char char_length character character_length character_set_catalog character_set_name character_set_schema characteristics characters check checkpoint class class_origin clob close cluster coalesce cobol collate collation collation_catalog collation_name collation_schema collect column column_name columns command_function command_function_code comment comments commit committed concurrently condition condition_number configuration conflict connect connection connection_name constant constraint constraint_catalog constraint_name constraint_schema constraints constructor contains content continue control conversion convert copy corr corresponding cost count covar_pop covar_samp create cross csv cube cume_dist current current_catalog current_date current_default_transform_group current_path current_role current_row current_schema current_time current_timestamp current_transform_group_for_type current_user cursor cursor_name cycle data database datalink datatype date datetime_interval_code datetime_interval_precision day db deallocate debug dec decimal declare default defaults deferrable deferred defined definer degree delete delimiter delimiters dense_rank depends depth deref derived desc describe descriptor detach detail deterministic diagnostics dictionary disable discard disconnect dispatch distinct dlnewcopy dlpreviouscopy dlurlcomplete dlurlcompleteonly dlurlcompletewrite dlurlpath dlurlpathonly dlurlpathwrite dlurlscheme dlurlserver dlvalue do document domain double drop dump dynamic dynamic_function dynamic_function_code each element else elseif elsif empty enable encoding encrypted end end_frame end_partition endexec enforced enum equals errcode error escape event every except exception exclude excluding exclusive exec execute exists exit exp explain expression extension external extract false family fetch file filter final first first_value flag float floor following for force foreach foreign fortran forward found frame_row free freeze from fs full function functions fusion g general generated get global go goto grant granted greatest group grouping groups handler having header hex hierarchy hint hold hour id identity if ignore ilike immediate immediately immutable implementation implicit import in include including increment indent index indexes indicator info inherit inherits initially inline inner inout input insensitive insert instance instantiable instead int integer integrity intersect intersection interval into invoker is isnull isolation join k key key_member key_type label lag language large last last_value lateral lead leading leakproof least left length level library like like_regex limit link listen ln load local localtime localtimestamp location locator lock locked log logged loop lower m map mapping match matched materialized max max_cardinality maxvalue member merge message message_length message_octet_length message_text method min minute minvalue mod mode modifies module month more move multiset mumps name names namespace national natural nchar nclob nesting new next nfc nfd nfkc nfkd nil no none normalize normalized not nothing notice notify notnull nowait nth_value ntile null nullable nullif nulls number numeric object occurrences_regex octet_length octets of off offset oids old on only open operator option options or order ordering ordinality others out outer output over overlaps overlay overriding owned owner p pad parallel parameter parameter_mode parameter_name parameter_ordinal_position parameter_specific_catalog parameter_specific_name parameter_specific_schema parser partial partition pascal passing passthrough password path percent percent_rank percentile_cont percentile_disc perform period permission pg_context pg_datatype_name pg_exception_context pg_exception_detail pg_exception_hint placing plans pli policy portion position position_regex power precedes preceding precision prepare prepared preserve primary print_strict_params prior privileges procedural procedure procedures program public publication query quote raise range rank read reads real reassign recheck recovery recursive ref references referencing refresh regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy regr_syy reindex relative release rename repeatable replace replica requiring reset respect restart restore restrict result result_oid return returned_cardinality returned_length returned_octet_length returned_sqlstate returning returns reverse revoke right role rollback rollup routine routine_catalog routine_name routine_schema routines row row_count row_number rows rowtype rule savepoint scale schema schema_name schemas scope scope_catalog scope_name scope_schema scroll search second section security select selective self sensitive sequence sequences serializable server server_name session session_user set setof sets share show similar simple size skip slice smallint snapshot some source space specific specific_name specifictype sql sqlcode sqlerror sqlexception sqlstate sqlwarning sqrt stable stacked standalone start state statement static statistics stddev_pop stddev_samp stdin stdout storage strict strip structure style subclass_origin submultiset subscription substring substring_regex succeeds sum symmetric sysid system system_time system_user t table table_name tables tablesample tablespace temp template temporary text then ties time timestamp timezone_hour timezone_minute to token top_level_count trailing transaction transaction_active transactions_committed transactions_rolled_back transform transforms translate translate_regex translation treat trigger trigger_catalog trigger_name trigger_schema trim trim_array true truncate trusted type types uescape unbounded uncommitted under unencrypted union unique unknown unlink unlisten unlogged unnamed unnest until untyped update upper uri usage use_column use_variable user user_defined_type_catalog user_defined_type_code user_defined_type_name user_defined_type_schema using vacuum valid validate validator value value_of values var_pop var_samp varbinary varchar variable_conflict variadic varying verbose version versioning view views volatile warning when whenever where while whitespace width_bucket window with within without work wrapper write xml xmlagg xmlattributes xmlbinary xmlcast xmlcomment xmlconcat xmldeclaration xmldocument xmlelement xmlexists xmlforest xmliterate xmlnamespaces xmlparse xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltext xmlvalidate year yes zone\"),\n    // https://www.postgresql.org/docs/11/datatype.html\n    builtin: set(\"bigint int8 bigserial serial8 bit varying varbit boolean bool box bytea character char varchar cidr circle date double precision float8 inet integer int int4 interval json jsonb line lseg macaddr macaddr8 money numeric decimal path pg_lsn point polygon real float4 smallint int2 smallserial serial2 serial serial4 text time without zone with timetz timestamp timestamptz tsquery tsvector txid_snapshot uuid xml\"),\n    atoms: set(\"false true null unknown\"),\n    operatorChars: /^[*\\/+\\-%<>!=&|^\\/#@?~]/,\n    dateSQL: set(\"date time timestamp\"),\n    support: set(\"ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber nCharCast charsetCast\")\n  });\n\n  // Google's SQL-like query language, GQL\n  CodeMirror.defineMIME(\"text/x-gql\", {\n    name: \"sql\",\n    keywords: set(\"ancestor and asc by contains desc descendant distinct from group has in is limit offset on order select superset where\"),\n    atoms: set(\"false true\"),\n    builtin: set(\"blob datetime first key __key__ string integer double boolean null\"),\n    operatorChars: /^[*+\\-%<>!=]/\n  });\n\n  // Greenplum\n  CodeMirror.defineMIME(\"text/x-gpsql\", {\n    name: \"sql\",\n    client: set(\"source\"),\n    //https://github.com/greenplum-db/gpdb/blob/master/src/include/parser/kwlist.h\n    keywords: set(\"abort absolute access action active add admin after aggregate all also alter always analyse analyze and any array as asc assertion assignment asymmetric at authorization backward before begin between bigint binary bit boolean both by cache called cascade cascaded case cast chain char character characteristics check checkpoint class close cluster coalesce codegen collate column comment commit committed concurrency concurrently configuration connection constraint constraints contains content continue conversion copy cost cpu_rate_limit create createdb createexttable createrole createuser cross csv cube current current_catalog current_date current_role current_schema current_time current_timestamp current_user cursor cycle data database day deallocate dec decimal declare decode default defaults deferrable deferred definer delete delimiter delimiters deny desc dictionary disable discard distinct distributed do document domain double drop dxl each else enable encoding encrypted end enum errors escape every except exchange exclude excluding exclusive execute exists explain extension external extract false family fetch fields filespace fill filter first float following for force foreign format forward freeze from full function global grant granted greatest group group_id grouping handler hash having header hold host hour identity if ignore ilike immediate immutable implicit in including inclusive increment index indexes inherit inherits initially inline inner inout input insensitive insert instead int integer intersect interval into invoker is isnull isolation join key language large last leading least left level like limit list listen load local localtime localtimestamp location lock log login mapping master match maxvalue median merge minute minvalue missing mode modifies modify month move name names national natural nchar new newline next no nocreatedb nocreateexttable nocreaterole nocreateuser noinherit nologin none noovercommit nosuperuser not nothing notify notnull nowait null nullif nulls numeric object of off offset oids old on only operator option options or order ordered others out outer over overcommit overlaps overlay owned owner parser partial partition partitions passing password percent percentile_cont percentile_disc placing plans position preceding precision prepare prepared preserve primary prior privileges procedural procedure protocol queue quote randomly range read readable reads real reassign recheck recursive ref references reindex reject relative release rename repeatable replace replica reset resource restart restrict returning returns revoke right role rollback rollup rootpartition row rows rule savepoint scatter schema scroll search second security segment select sequence serializable session session_user set setof sets share show similar simple smallint some split sql stable standalone start statement statistics stdin stdout storage strict strip subpartition subpartitions substring superuser symmetric sysid system table tablespace temp template temporary text then threshold ties time timestamp to trailing transaction treat trigger trim true truncate trusted type unbounded uncommitted unencrypted union unique unknown unlisten until update user using vacuum valid validation validator value values varchar variadic varying verbose version view volatile web when where whitespace window with within without work writable write xml xmlattributes xmlconcat xmlelement xmlexists xmlforest xmlparse xmlpi xmlroot xmlserialize year yes zone\"),\n    builtin: set(\"bigint int8 bigserial serial8 bit varying varbit boolean bool box bytea character char varchar cidr circle date double precision float float8 inet integer int int4 interval json jsonb line lseg macaddr macaddr8 money numeric decimal path pg_lsn point polygon real float4 smallint int2 smallserial serial2 serial serial4 text time without zone with timetz timestamp timestamptz tsquery tsvector txid_snapshot uuid xml\"),\n    atoms: set(\"false true null unknown\"),\n    operatorChars: /^[*+\\-%<>!=&|^\\/#@?~]/,\n    dateSQL: set(\"date time timestamp\"),\n    support: set(\"ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber nCharCast charsetCast\")\n  });\n\n  // Spark SQL\n  CodeMirror.defineMIME(\"text/x-sparksql\", {\n    name: \"sql\",\n    keywords: set(\"add after all alter analyze and anti archive array as asc at between bucket buckets by cache cascade case cast change clear cluster clustered codegen collection column columns comment commit compact compactions compute concatenate cost create cross cube current current_date current_timestamp database databases datata dbproperties defined delete delimited deny desc describe dfs directories distinct distribute drop else end escaped except exchange exists explain export extended external false fields fileformat first following for format formatted from full function functions global grant group grouping having if ignore import in index indexes inner inpath inputformat insert intersect interval into is items join keys last lateral lazy left like limit lines list load local location lock locks logical macro map minus msck natural no not null nulls of on optimize option options or order out outer outputformat over overwrite partition partitioned partitions percent preceding principals purge range recordreader recordwriter recover reduce refresh regexp rename repair replace reset restrict revoke right rlike role roles rollback rollup row rows schema schemas select semi separated serde serdeproperties set sets show skewed sort sorted start statistics stored stratify struct table tables tablesample tblproperties temp temporary terminated then to touch transaction transactions transform true truncate unarchive unbounded uncache union unlock unset use using values view when where window with\"),\n    builtin: set(\"tinyint smallint int bigint boolean float double string binary timestamp decimal array map struct uniontype delimited serde sequencefile textfile rcfile inputformat outputformat\"),\n    atoms: set(\"false true null\"),\n    operatorChars: /^[*\\/+\\-%<>!=~&|^]/,\n    dateSQL: set(\"date time timestamp\"),\n    support: set(\"ODBCdotTable doubleQuote zerolessFloat\")\n  });\n\n  // Esper\n  CodeMirror.defineMIME(\"text/x-esper\", {\n    name: \"sql\",\n    client: set(\"source\"),\n    // http://www.espertech.com/esper/release-5.5.0/esper-reference/html/appendix_keywords.html\n    keywords: set(\"alter and as asc between by count create delete desc distinct drop from group having in insert into is join like not on or order select set table union update values where limit after all and as at asc avedev avg between by case cast coalesce count create current_timestamp day days delete define desc distinct else end escape events every exists false first from full group having hour hours in inner insert instanceof into irstream is istream join last lastweekday left limit like max match_recognize matches median measures metadatasql min minute minutes msec millisecond milliseconds not null offset on or order outer output partition pattern prev prior regexp retain-union retain-intersection right rstream sec second seconds select set some snapshot sql stddev sum then true unidirectional until update variable weekday when where window\"),\n    builtin: {},\n    atoms: set(\"false true null\"),\n    operatorChars: /^[*+\\-%<>!=&|^\\/#@?~]/,\n    dateSQL: set(\"time\"),\n    support: set(\"decimallessFloat zerolessFloat binaryNumber hexNumber\")\n  });\n});\n\n/*\n  How Properties of Mime Types are used by SQL Mode\n  =================================================\n\n  keywords:\n    A list of keywords you want to be highlighted.\n  builtin:\n    A list of builtin types you want to be highlighted (if you want types to be of class \"builtin\" instead of \"keyword\").\n  operatorChars:\n    All characters that must be handled as operators.\n  client:\n    Commands parsed and executed by the client (not the server).\n  support:\n    A list of supported syntaxes which are not common, but are supported by more than 1 DBMS.\n    * ODBCdotTable: .tableName\n    * zerolessFloat: .1\n    * doubleQuote\n    * nCharCast: N'string'\n    * charsetCast: _utf8'string'\n    * commentHash: use # char for comments\n    * commentSlashSlash: use // for comments\n    * commentSpaceRequired: require a space after -- for comments\n  atoms:\n    Keywords that must be highlighted as atoms,. Some DBMS's support more atoms than others:\n    UNKNOWN, INFINITY, UNDERFLOW, NaN...\n  dateSQL:\n    Used for date/time SQL standard syntax, because not all DBMS's support same temporal types.\n*/\n"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/common.js",
    "content": "function getQueryString(name) {\r\n    var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');\r\n    var r = window.location.search.substr(1).match(reg);\r\n    if (r != null) {\r\n        return unescape(r[2]);\r\n    }\r\n    return null;\r\n}\r\nDate.prototype.format = function(b) {\r\n\tvar a = this;\r\n\tvar c = {\r\n\t\t\"M+\": a.getMonth() + 1,\r\n\t\t\"d+\": a.getDate(),\r\n\t\t\"h+\": a.getHours(),\r\n\t\t\"m+\": a.getMinutes(),\r\n\t\t\"s+\": a.getSeconds(),\r\n\t\t\"q+\": Math.floor((a.getMonth() + 3) / 3),\r\n\t\tS: a.getMilliseconds()\r\n\t};\r\n\t/(y+)/.test(b) && (b = b.replace(RegExp.$1, (a.getFullYear() + \"\").substr(4 - RegExp.$1.length)));\r\n\tfor(var d in c) new RegExp(\"(\" + d + \")\").test(b) && (b = b.replace(RegExp.$1, 1 == RegExp.$1.length ? c[d] : (\"00\" + c[d]).substr((\"\" + c[d]).length)));\r\n\treturn b\r\n}\r\nvar sf = {};\r\nsf.ajax = function(options){\r\n\tvar loading;\r\n\tvar loadingInterval;\r\n\tvar closeClear = function(){\r\n\t\tlayui.layer.close(loading);\r\n\t\tclearInterval(loadingInterval);\r\n\t}\r\n\tvar beginTime = +new Date();\r\n\tvar url = options.url;\r\n\tvar type = options.type;\r\n\tvar data = options.data;\r\n\tvar success = options.success;\r\n\tvar error = options.error;\r\n\t$.ajax({\r\n\t\turl:url,\r\n\t\ttype:type,\r\n\t\tdata:data,\r\n\t\tsuccess:function(result){\r\n\t\t\tcloseClear();\r\n\t\t\tsuccess && success(result);\r\n\t\t},\r\n\t\terror:function(errorInfo){\r\n\t\t\tcloseClear();\r\n\t\t\terror && error(errorInfo);\r\n\t\t},\r\n\t\tbeforeSend:function(){\r\n\t\t\tloadingInterval = setInterval(function(){\r\n\t\t\t\tvar endTime = +new Date();\r\n\t\t\t\tif((endTime-beginTime) > 500){\r\n\t\t\t\t\tloading = layui.layer.load(1, {\r\n\t\t\t\t\t\tshade: [0.1,'#fff']\r\n\t\t\t\t\t});\r\n\t\t\t\t\tclearInterval(loadingInterval);\r\n\t\t\t\t}\r\n\t\t\t},100);\r\n\t\t}\r\n\t})\r\n}"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/cron/cron.js",
    "content": "﻿function btnFan() {\r\n\t// 获取参数中表达式的值\r\n\tvar txt = $(\"#cron\").val();\r\n\tif (txt) {\r\n\t\tvar regs = txt.split(' ');\r\n\t\t$(\"input[name=v_second]\").val(regs[0]);\r\n\t\t$(\"input[name=v_min]\").val(regs[1]);\r\n\t\t$(\"input[name=v_hour]\").val(regs[2]);\r\n\t\t$(\"input[name=v_day]\").val(regs[3]);\r\n\t\t$(\"input[name=v_mouth]\").val(regs[4]);\r\n\t\t$(\"input[name=v_week]\").val(regs[5]);\r\n\r\n\t\tinitObj(regs[0], \"second\");\r\n\t\tinitObj(regs[1], \"min\");\r\n\t\tinitObj(regs[2], \"hour\");\r\n\t\tinitDay(regs[3]);\r\n\t\tinitMonth(regs[4]);\r\n\t\tinitWeek(regs[5]);\r\n\r\n\t\tif (regs.length > 6) {\r\n\t\t\t$(\"input[name=v_year]\").val(regs[6]);\r\n\t\t\tinitYear(regs[6]);\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction initObj(strVal, strid) {\r\n\tvar ary = null;\r\n\tvar objRadio = $(\"input[name='\" + strid + \"'\");\r\n\tif (strVal == \"*\") {\r\n\t\tobjRadio.eq(0).attr(\"checked\", \"checked\");\r\n\t} else if (strVal.split('-').length > 1) {\r\n\t\tary = strVal.split('-');\r\n\t\tobjRadio.eq(1).attr(\"checked\", \"checked\");\r\n\t\t$(\"#\" + strid + \"Start_0\").numberspinner('setValue', ary[0]);\r\n\t\t$(\"#\" + strid + \"End_0\").numberspinner('setValue', ary[1]);\r\n\t} else if (strVal.split('/').length > 1) {\r\n\t\tary = strVal.split('/');\r\n\t\tobjRadio.eq(2).attr(\"checked\", \"checked\");\r\n\t\t$(\"#\" + strid + \"Start_1\").numberspinner('setValue', ary[0]);\r\n\t\t$(\"#\" + strid + \"End_1\").numberspinner('setValue', ary[1]);\r\n\t} else {\r\n\t\tobjRadio.eq(3).attr(\"checked\", \"checked\");\r\n\t\tif (strVal != \"?\") {\r\n\t\t\tary = strVal.split(\",\");\r\n\t\t\tfor (var i = 0; i < ary.length; i++) {\r\n\t\t\t\t$(\".\" + strid + \"List input[value='\" + ary[i] + \"']\").attr(\r\n\t\t\t\t\t\t\"checked\", \"checked\");\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction initDay(strVal) {\r\n\tvar ary = null;\r\n\tvar objRadio = $(\"input[name='day'\");\r\n\tif (strVal == \"*\") {\r\n\t\tobjRadio.eq(0).attr(\"checked\", \"checked\");\r\n\t} else if (strVal == \"?\") {\r\n\t\tobjRadio.eq(1).attr(\"checked\", \"checked\");\r\n\t} else if (strVal.split('-').length > 1) {\r\n\t\tary = strVal.split('-');\r\n\t\tobjRadio.eq(2).attr(\"checked\", \"checked\");\r\n\t\t$(\"#dayStart_0\").numberspinner('setValue', ary[0]);\r\n\t\t$(\"#dayEnd_0\").numberspinner('setValue', ary[1]);\r\n\t} else if (strVal.split('/').length > 1) {\r\n\t\tary = strVal.split('/');\r\n\t\tobjRadio.eq(3).attr(\"checked\", \"checked\");\r\n\t\t$(\"#dayStart_1\").numberspinner('setValue', ary[0]);\r\n\t\t$(\"#dayEnd_1\").numberspinner('setValue', ary[1]);\r\n\t} else if (strVal.split('W').length > 1) {\r\n\t\tary = strVal.split('W');\r\n\t\tobjRadio.eq(4).attr(\"checked\", \"checked\");\r\n\t\t$(\"#dayStart_2\").numberspinner('setValue', ary[0]);\r\n\t} else if (strVal == \"L\") {\r\n\t\tobjRadio.eq(5).attr(\"checked\", \"checked\");\r\n\t} else {\r\n\t\tobjRadio.eq(6).attr(\"checked\", \"checked\");\r\n\t\tary = strVal.split(\",\");\r\n\t\tfor (var i = 0; i < ary.length; i++) {\r\n\t\t\t$(\".dayList input[value='\" + ary[i] + \"']\").attr(\"checked\",\r\n\t\t\t\t\t\"checked\");\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction initMonth(strVal) {\r\n\tvar ary = null;\r\n\tvar objRadio = $(\"input[name='mouth'\");\r\n\tif (strVal == \"*\") {\r\n\t\tobjRadio.eq(0).attr(\"checked\", \"checked\");\r\n\t} else if (strVal == \"?\") {\r\n\t\tobjRadio.eq(1).attr(\"checked\", \"checked\");\r\n\t} else if (strVal.split('-').length > 1) {\r\n\t\tary = strVal.split('-');\r\n\t\tobjRadio.eq(2).attr(\"checked\", \"checked\");\r\n\t\t$(\"#mouthStart_0\").numberspinner('setValue', ary[0]);\r\n\t\t$(\"#mouthEnd_0\").numberspinner('setValue', ary[1]);\r\n\t} else if (strVal.split('/').length > 1) {\r\n\t\tary = strVal.split('/');\r\n\t\tobjRadio.eq(3).attr(\"checked\", \"checked\");\r\n\t\t$(\"#mouthStart_1\").numberspinner('setValue', ary[0]);\r\n\t\t$(\"#mouthEnd_1\").numberspinner('setValue', ary[1]);\r\n\r\n\t} else {\r\n\t\tobjRadio.eq(4).attr(\"checked\", \"checked\");\r\n\r\n\t\tary = strVal.split(\",\");\r\n\t\tfor (var i = 0; i < ary.length; i++) {\r\n\t\t\t$(\".mouthList input[value='\" + ary[i] + \"']\").attr(\"checked\",\r\n\t\t\t\t\t\"checked\");\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction initWeek(strVal) {\r\n\tvar ary = null;\r\n\tvar objRadio = $(\"input[name='week'\");\r\n\tif (strVal == \"*\") {\r\n\t\tobjRadio.eq(0).attr(\"checked\", \"checked\");\r\n\t} else if (strVal == \"?\") {\r\n\t\tobjRadio.eq(1).attr(\"checked\", \"checked\");\r\n\t} else if (strVal.split('/').length > 1) {\r\n\t\tary = strVal.split('/');\r\n\t\tobjRadio.eq(2).attr(\"checked\", \"checked\");\r\n\t\t$(\"#weekStart_0\").numberspinner('setValue', ary[0]);\r\n\t\t$(\"#weekEnd_0\").numberspinner('setValue', ary[1]);\r\n\t} else if (strVal.split('-').length > 1) {\r\n\t\tary = strVal.split('-');\r\n\t\tobjRadio.eq(3).attr(\"checked\", \"checked\");\r\n\t\t$(\"#weekStart_1\").numberspinner('setValue', ary[0]);\r\n\t\t$(\"#weekEnd_1\").numberspinner('setValue', ary[1]);\r\n\t} else if (strVal.split('L').length > 1) {\r\n\t\tary = strVal.split('L');\r\n\t\tobjRadio.eq(4).attr(\"checked\", \"checked\");\r\n\t\t$(\"#weekStart_2\").numberspinner('setValue', ary[0]);\r\n\t} else {\r\n\t\tobjRadio.eq(5).attr(\"checked\", \"checked\");\r\n\t\tary = strVal.split(\",\");\r\n\t\tfor (var i = 0; i < ary.length; i++) {\r\n\t\t\t$(\".weekList input[value='\" + ary[i] + \"']\").attr(\"checked\",\r\n\t\t\t\t\t\"checked\");\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction initYear(strVal) {\r\n\tvar ary = null;\r\n\tvar objRadio = $(\"input[name='year'\");\r\n\tif (strVal == \"*\") {\r\n\t\tobjRadio.eq(1).attr(\"checked\", \"checked\");\r\n\t} else if (strVal.split('-').length > 1) {\r\n\t\tary = strVal.split('-');\r\n\t\tobjRadio.eq(2).attr(\"checked\", \"checked\");\r\n\t\t$(\"#yearStart_0\").numberspinner('setValue', ary[0]);\r\n\t\t$(\"#yearEnd_0\").numberspinner('setValue', ary[1]);\r\n\t}\r\n}\r\n/**\r\n * 每周期\r\n */\r\nfunction everyTime(dom) {\r\n\tvar item = $(\"input[name=v_\" + dom.name + \"]\");\r\n\titem.val(\"*\");\r\n\titem.change();\r\n}\r\n\r\n/**\r\n * 不指定\r\n */\r\nfunction unAppoint(dom) {\r\n\tvar name = dom.name;\r\n\tvar val = \"?\";\r\n\tif (name == \"year\")\r\n\t\tval = \"\";\r\n\tvar item = $(\"input[name=v_\" + name + \"]\");\r\n\titem.val(val);\r\n\titem.change();\r\n}\r\n\r\nfunction appoint(dom) {\r\n\r\n}\r\n\r\n/**\r\n * 周期\r\n */\r\nfunction cycle(dom) {\r\n\tvar name = dom.name;\r\n\tvar ns = $(dom).parent().find(\".numberspinner\");\r\n\tvar start = ns.eq(0).numberspinner(\"getValue\");\r\n\tvar end = ns.eq(1).numberspinner(\"getValue\");\r\n\tvar item = $(\"input[name=v_\" + name + \"]\");\r\n\titem.val(start + \"-\" + end);\r\n\titem.change();\r\n}\r\n\r\n/**\r\n * 从开始\r\n */\r\nfunction startOn(dom) {\r\n\tvar name = dom.name;\r\n\tvar ns = $(dom).parent().find(\".numberspinner\");\r\n\tvar start = ns.eq(0).numberspinner(\"getValue\");\r\n\tvar end = ns.eq(1).numberspinner(\"getValue\");\r\n\tvar item = $(\"input[name=v_\" + name + \"]\");\r\n\titem.val(start + \"/\" + end);\r\n\titem.change();\r\n}\r\n\r\nfunction lastDay(dom) {\r\n\tvar item = $(\"input[name=v_\" + dom.name + \"]\");\r\n\titem.val(\"L\");\r\n\titem.change();\r\n}\r\n\r\nfunction weekOfDay(dom) {\r\n\tvar name = dom.name;\r\n\tvar ns = $(dom).parent().find(\".numberspinner\");\r\n\tvar start = ns.eq(0).numberspinner(\"getValue\");\r\n\tvar end = ns.eq(1).numberspinner(\"getValue\");\r\n\tvar item = $(\"input[name=v_\" + name + \"]\");\r\n\titem.val(start + \"#\" + end);\r\n\titem.change();\r\n}\r\n\r\nfunction lastWeek(dom) {\r\n\tvar item = $(\"input[name=v_\" + dom.name + \"]\");\r\n\tvar ns = $(dom).parent().find(\".numberspinner\");\r\n\tvar start = ns.eq(0).numberspinner(\"getValue\");\r\n\titem.val(start + \"L\");\r\n\titem.change();\r\n}\r\n\r\nfunction workDay(dom) {\r\n\tvar name = dom.name;\r\n\tvar ns = $(dom).parent().find(\".numberspinner\");\r\n\tvar start = ns.eq(0).numberspinner(\"getValue\");\r\n\tvar item = $(\"input[name=v_\" + name + \"]\");\r\n\titem.val(start + \"W\");\r\n\titem.change();\r\n}\r\n$(function() {\r\n\t$(\".numberspinner\").numberspinner({\r\n\t\tonChange : function() {\r\n\t\t\t$(this).closest(\"div.line\").children().eq(0).click();\r\n\t\t}\r\n\t});\r\n\r\n\tvar vals = $(\"input[name^='v_']\");\r\n\tvar cron = $(\"#cron\");\r\n\tvals.change(function() {\r\n\t\tvar item = [];\r\n\t\tvals.each(function() {\r\n\t\t\titem.push(this.value);\r\n\t\t});\r\n\t\t// 修复表达式错误BUG，如果后一项不为* 那么前一项肯定不为为*，要不然就成了每秒执行了\r\n\t\t// 获取当前选中tab\r\n\t\tvar currentIndex = 0;\r\n\t\t$(\".tabs>li\").each(function(i, item) {\r\n\t\t\tif ($(item).hasClass(\"tabs-selected\")) {\r\n\t\t\t\tcurrentIndex = i;\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\r\n\t\t});\r\n\t\t// 当前选中项之前的如果为*，则都设置成0\r\n\t\tfor (var i = currentIndex; i >= 1; i--) {\r\n\t\t\tif (item[i] != \"*\" && item[i - 1] == \"*\") {\r\n\t\t\t\titem[i - 1] = \"0\";\r\n\t\t\t}\r\n\t\t}\r\n\t\t// 当前选中项之后的如果不为*则都设置成*\r\n\t\tif (item[currentIndex] == \"*\") {\r\n\t\t\tfor (var i = currentIndex + 1; i < item.length; i++) {\r\n\t\t\t\tif (i == 5) {\r\n\t\t\t\t\titem[i] = \"?\";\r\n\t\t\t\t} else {\r\n\t\t\t\t\titem[i] = \"*\";\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tcron.val(item.join(\" \")).change();\r\n\t});\r\n\r\n\tcron.change(function() {\r\n\t\t// 获取参数中表达式的值\r\n\t\t\tbtnFan();\r\n\t\t\t// 设置最近五次运行时间\r\n\t\t\t$.ajax({\r\n\t\t\t\ttype : 'get',\r\n\t\t\t\turl : \"/spider/recent5TriggerTime\",\r\n\t\t\t\tdataType : \"json\",\r\n\t\t\t\tdata : {\r\n\t\t\t\t\t\"cron\" : $(\"#cron\").val()\r\n\t\t\t\t},\r\n\t\t\t\tsuccess : function(data) {\r\n\t\t\t\t\tif (data && data.length == 5) {\r\n\t\t\t\t\t\tvar strHTML = \"<ul>\";\r\n\t\t\t\t\t\tfor (var i = 0; i < data.length; i++) {\r\n\t\t\t\t\t\t\tstrHTML += \"<li>\" + data[i] + \"</li>\";\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tstrHTML += \"</ul>\"\r\n\t\t\t\t\t\t$(\"#runTime\").html(strHTML);\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\t$(\"#runTime\").html(\"\");\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t});\r\n\r\n\tvar secondList = $(\".secondList\").children();\r\n\t$(\"#sencond_appoint\").click(function() {\r\n\t\tif (this.checked) {\r\n\t\t\tif ($(secondList).filter(\":checked\").length == 0) {\r\n\t\t\t\t$(secondList.eq(0)).attr(\"checked\", true);\r\n\t\t\t}\r\n\t\t\tsecondList.eq(0).change();\r\n\t\t}\r\n\t});\r\n\r\n\tsecondList.change(function() {\r\n\t\tvar sencond_appoint = $(\"#sencond_appoint\").prop(\"checked\");\r\n\t\tif (sencond_appoint) {\r\n\t\t\tvar vals = [];\r\n\t\t\tsecondList.each(function() {\r\n\t\t\t\tif (this.checked) {\r\n\t\t\t\t\tvals.push(this.value);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\tvar val = \"?\";\r\n\t\t\tif (vals.length > 0 && vals.length < 59) {\r\n\t\t\t\tval = vals.join(\",\");\r\n\t\t\t} else if (vals.length == 59) {\r\n\t\t\t\tval = \"*\";\r\n\t\t\t}\r\n\t\t\tvar item = $(\"input[name=v_second]\");\r\n\t\t\titem.val(val);\r\n\t\t\titem.change();\r\n\t\t}\r\n\t});\r\n\r\n\tvar minList = $(\".minList\").children();\r\n\t$(\"#min_appoint\").click(function() {\r\n\t\tif (this.checked) {\r\n\t\t\tif ($(minList).filter(\":checked\").length == 0) {\r\n\t\t\t\t$(minList.eq(0)).attr(\"checked\", true);\r\n\t\t\t}\r\n\t\t\tminList.eq(0).change();\r\n\t\t}\r\n\t});\r\n\r\n\tminList.change(function() {\r\n\t\tvar min_appoint = $(\"#min_appoint\").prop(\"checked\");\r\n\t\tif (min_appoint) {\r\n\t\t\tvar vals = [];\r\n\t\t\tminList.each(function() {\r\n\t\t\t\tif (this.checked) {\r\n\t\t\t\t\tvals.push(this.value);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\tvar val = \"?\";\r\n\t\t\tif (vals.length > 0 && vals.length < 59) {\r\n\t\t\t\tval = vals.join(\",\");\r\n\t\t\t} else if (vals.length == 59) {\r\n\t\t\t\tval = \"*\";\r\n\t\t\t}\r\n\t\t\tvar item = $(\"input[name=v_min]\");\r\n\t\t\titem.val(val);\r\n\t\t\titem.change();\r\n\t\t}\r\n\t});\r\n\r\n\tvar hourList = $(\".hourList\").children();\r\n\t$(\"#hour_appoint\").click(function() {\r\n\t\tif (this.checked) {\r\n\t\t\tif ($(hourList).filter(\":checked\").length == 0) {\r\n\t\t\t\t$(hourList.eq(0)).attr(\"checked\", true);\r\n\t\t\t}\r\n\t\t\thourList.eq(0).change();\r\n\t\t}\r\n\t});\r\n\r\n\thourList.change(function() {\r\n\t\tvar hour_appoint = $(\"#hour_appoint\").prop(\"checked\");\r\n\t\tif (hour_appoint) {\r\n\t\t\tvar vals = [];\r\n\t\t\thourList.each(function() {\r\n\t\t\t\tif (this.checked) {\r\n\t\t\t\t\tvals.push(this.value);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\tvar val = \"?\";\r\n\t\t\tif (vals.length > 0 && vals.length < 24) {\r\n\t\t\t\tval = vals.join(\",\");\r\n\t\t\t} else if (vals.length == 24) {\r\n\t\t\t\tval = \"*\";\r\n\t\t\t}\r\n\t\t\tvar item = $(\"input[name=v_hour]\");\r\n\t\t\titem.val(val);\r\n\t\t\titem.change();\r\n\t\t}\r\n\t});\r\n\r\n\tvar dayList = $(\".dayList\").children();\r\n\t$(\"#day_appoint\").click(function() {\r\n\t\tif (this.checked) {\r\n\t\t\tif ($(dayList).filter(\":checked\").length == 0) {\r\n\t\t\t\t$(dayList.eq(0)).attr(\"checked\", true);\r\n\t\t\t}\r\n\t\t\tdayList.eq(0).change();\r\n\t\t}\r\n\t});\r\n\r\n\tdayList.change(function() {\r\n\t\tvar day_appoint = $(\"#day_appoint\").prop(\"checked\");\r\n\t\tif (day_appoint) {\r\n\t\t\tvar vals = [];\r\n\t\t\tdayList.each(function() {\r\n\t\t\t\tif (this.checked) {\r\n\t\t\t\t\tvals.push(this.value);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\tvar val = \"?\";\r\n\t\t\tif (vals.length > 0 && vals.length < 31) {\r\n\t\t\t\tval = vals.join(\",\");\r\n\t\t\t} else if (vals.length == 31) {\r\n\t\t\t\tval = \"*\";\r\n\t\t\t}\r\n\t\t\tvar item = $(\"input[name=v_day]\");\r\n\t\t\titem.val(val);\r\n\t\t\titem.change();\r\n\t\t}\r\n\t});\r\n\r\n\tvar mouthList = $(\".mouthList\").children();\r\n\t$(\"#mouth_appoint\").click(function() {\r\n\t\tif (this.checked) {\r\n\t\t\tif ($(mouthList).filter(\":checked\").length == 0) {\r\n\t\t\t\t$(mouthList.eq(0)).attr(\"checked\", true);\r\n\t\t\t}\r\n\t\t\tmouthList.eq(0).change();\r\n\t\t}\r\n\t});\r\n\r\n\tmouthList.change(function() {\r\n\t\tvar mouth_appoint = $(\"#mouth_appoint\").prop(\"checked\");\r\n\t\tif (mouth_appoint) {\r\n\t\t\tvar vals = [];\r\n\t\t\tmouthList.each(function() {\r\n\t\t\t\tif (this.checked) {\r\n\t\t\t\t\tvals.push(this.value);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\tvar val = \"?\";\r\n\t\t\tif (vals.length > 0 && vals.length < 12) {\r\n\t\t\t\tval = vals.join(\",\");\r\n\t\t\t} else if (vals.length == 12) {\r\n\t\t\t\tval = \"*\";\r\n\t\t\t}\r\n\t\t\tvar item = $(\"input[name=v_mouth]\");\r\n\t\t\titem.val(val);\r\n\t\t\titem.change();\r\n\t\t}\r\n\t});\r\n\r\n\tvar weekList = $(\".weekList\").children();\r\n\t$(\"#week_appoint\").click(function() {\r\n\t\tif (this.checked) {\r\n\t\t\tif ($(weekList).filter(\":checked\").length == 0) {\r\n\t\t\t\t$(weekList.eq(0)).attr(\"checked\", true);\r\n\t\t\t}\r\n\t\t\tweekList.eq(0).change();\r\n\t\t}\r\n\t});\r\n\r\n\tweekList.change(function() {\r\n\t\tvar week_appoint = $(\"#week_appoint\").prop(\"checked\");\r\n\t\tif (week_appoint) {\r\n\t\t\tvar vals = [];\r\n\t\t\tweekList.each(function() {\r\n\t\t\t\tif (this.checked) {\r\n\t\t\t\t\tvals.push(this.value);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\tvar val = \"?\";\r\n\t\t\tif (vals.length > 0 && vals.length < 7) {\r\n\t\t\t\tval = vals.join(\",\");\r\n\t\t\t} else if (vals.length == 7) {\r\n\t\t\t\tval = \"*\";\r\n\t\t\t}\r\n\t\t\tvar item = $(\"input[name=v_week]\");\r\n\t\t\titem.val(val);\r\n\t\t\titem.change();\r\n\t\t}\r\n\t});\r\n});"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/editor.js",
    "content": "var $ = layui.$;\nvar editor;\nvar flows;\nvar codeMirrorInstances = {};\nvar socket;\nvar version = 'lastest';\nfunction renderCodeMirror(){\n\tcodeMirrorInstances = {};\n\t$('[codemirror]').each(function(){\n\t\tvar $dom = $(this);\n\t\tif($dom.attr(\"rendered\") == 'true'){\n\t\t\treturn;\n\t\t}\n\t\t$dom.attr(\"rendered\",true)\n\t\tvar cm = CodeMirror(this,{\n\t\t\tmode : 'spiderflow',\t//语法\n\t\t\ttheme : 'idea',\t//设置样式\n\t\t\tplaceholder : $dom.attr(\"placeholder\"),\n\t\t\tvalue : $dom.attr('data-value') || '',\n\t\t\tscrollbarStyle : 'null',\t//隐藏滚动条\n\t\t});\n\t\tinitHint(cm);\n\t\tcodeMirrorInstances[$(this).attr('codemirror')] = cm;\n\t\tcm.on('change',function(){\n\t\t\t$dom.attr('data-value',cm.getValue());\n\t\t\tif($dom.attr('codemirror') == 'condition'){\n\t\t\t\tvar $select = $('select[name=\"exception-flow\"]');\n\t\t\t\t$select.siblings(\"div.layui-form-select\").find('dl dd[lay-value=' + $select.val() + ']').click();\n\t\t\t}\n\t\t\tserializeForm();\n\t\t});\n\t\tcodeMirrorInstances[$(this).attr('codemirror')] = cm;\n\t});\n}\nfunction getCellData(cellId,keys){\n\tvar cell = editor.getModel().getCell(cellId);\n\tvar data = [];\n\tvar object = cell.data.object;\n\tfor(var k in keys){\n\t\tvar key = keys[k];\n\t\tif(Array.isArray(object[key])){\n\t\t\tvar array = object[key];\n\t\t\tfor(var i =0,len = array.length;i<len;i++){\n\t\t\t\tdata[i] = data[i] || {};\n\t\t\t\tdata[i][key] = array[i];\n\t\t\t}\n\t\t}\n\t}\n\treturn data;\n}\nfunction serializeForm(){\n\tvar $container = $(\".properties-container\");\n\tvar _version = $container.data('version');\n\tif(_version && _version != version){\n\t\treturn;\n\t}\n\tvar cellId = $container.attr('data-cellid');\n\tvar model = editor.getModel();\n\tvar cell = model.getCell(cellId);\n\tif(!cell){\n\t\treturn;\n\t}\n\tvar shape = cell.data.get('shape');\n\tcell.data.reset({});\n\t$.each($(\".properties-container form\").serializeArray(),function(index,item){\n\t\tvar name = item.name;\n\t\tvar value = item.value;\n\t\tif($(\".properties-container form *[name=\"+name+\"].array\").length > 0){\n\t\t\tvar array = cell.data.get(name) || [];\n\t\t\tarray.push(value);\n\t\t\tcell.data.set(name,array);\n\t\t}else{\n\t\t\tif(name == 'value'){\n\t\t\t\tif(cell.getValue() != value){\n\t\t\t\t\tmodel.beginUpdate();\n\t\t\t\t\ttry{\n\t\t\t\t\t\tcell.setValue(value);\n\t\t\t\t\t\tmodel.execute(new mxValueChange(model,cell,value));\n\t\t\t\t\t}finally{\n\t\t\t\t\t\tmodel.endUpdate();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif(name == 'lineWidth'){\n\t\t\t\teditor.graph.setCellStyles('strokeWidth',value,[cell]);\n\t\t\t}\n\t\t\tif(name == 'line-style'){\n\t\t\t\teditor.graph.setCellStyles('sharp',undefined,[cell]);\n\t\t\t\teditor.graph.setCellStyles('rounded',undefined,[cell]);\n\t\t\t\teditor.graph.setCellStyles('curved',undefined,[cell]);\n\t\t\t\teditor.graph.setCellStyles(value,1,[cell]);\n\t\t\t}\n\t\t\tcell.data.set(name,value);\n\t\t}\n\t});\n\t$(\".properties-container form [codemirror]\").each(function(){\n\t\tvar $dom = $(this);\n\t\tvar name = $dom.attr('codemirror');\n\t\tvar value = $dom.attr('data-value');\n\t\tif($dom.hasClass(\"array\")){\n\t\t\tvar array = cell.data.get(name) || [];\n\t\t\tarray.push(value);\n\t\t\tcell.data.set(name,array);\n\t\t}else{\n\t\t\tcell.data.set(name,value);\n\t\t}\n\t});\n\t$(\".properties-container form input[type=checkbox]\").each(function(){\n\t\tif(this.value == 'transmit-variable'){\n\t\t\tif($(this).is(\":checked\")){\n\t\t\t\teditor.graph.setCellStyles('dashed',undefined,[cell]);\n\t\t\t}else{\n\t\t\t\teditor.graph.setCellStyles('dashed',1,[cell]);\n\t\t\t}\n\t\t}\n\t\tcell.data.set(this.value,$(this).is(\":checked\") ? '1': '0');\n\t});\n\tcell.data.set('shape',shape);\n}\nfunction resizeSlideBar(){\n\tvar $dom = $(\".sidebar-container\");\n\tvar height = $dom.height();\n\tvar len = $dom.find(\"img\").length;\n\tvar totalHeight = len * 46;\n\tvar w = Math.ceil(totalHeight / height);\n\t$dom.width(w * 50);\n\t$(\".editor-container,.xml-container\").css(\"left\",w * 50 + \"px\");\n}\n\nfunction validXML(callback){\n\tvar cell = editor.valid();\n\tif(cell){\n\t\tlayui.layer.confirm(\"检测到有箭头未连接到节点上，是否处理？\",{\n\t\t\ttitle : '异常处理',\n\t\t\tbtn : ['处理','忽略'],\n\t\t},function(index){\n\t\t\tlayui.layer.close(index);\n\t\t\teditor.selectCell(cell);\n\t\t},function(){\n\t\t\tcallback&&callback();\n\t\t})\n\t}else{\n\t\tcallback&&callback();\n\t}\n}\n$(function(){\n\t$.ajax({\n\t\turl : 'spider/other',\n\t\ttype : 'post',\n\t\tdata : {\n\t\t\tid : getQueryString('id')\n\t\t},\n\t\tdataType : 'json',\n\t\tsuccess : function(others){\n\t\t\tflows = others;\n\t\t}\n\t})\n\t$.ctrl = function(key, callback, args) {\n\t    var isCtrl = false;\n\t    $(document).keydown(function(e) {\n\t        if(!args) args=[];\n\t        if(e.keyCode == 17) isCtrl = true;\n\t        if(e.keyCode == key.charCodeAt(0) && isCtrl) {\n\t            callback.apply(this, args);\n\t            isCtrl = false;\n\t            return false;\n\t        }\n\t    }).keyup(function(e) {\n\t        if(e.keyCode == 17) isCtrl = false;\n\t    });        \n\t};\n\t$.ctrl('S', function() {\n\t\t$('input,textarea').blur();\n\t\tSave();\n\t});\n\t$.ctrl('Q', function() {\n\t\t$('input,textarea').blur();\n\t\t$(\".btn-test\").click();\n\t});\n\tresizeSlideBar();\n\tvar templateCache = {};\n\tfunction loadTemplate(cell,model,callback){\n\t\tserializeForm();\n\t\tvar cells = model.cells;\n\t\tvar template = cell.data.get('shape') || 'root';\n\t\tif(cell.isEdge()){\n\t\t\ttemplate = 'edge';\n\t\t}\n\t\tvar v = version;\n\t\tvar render = function(){\n\t\t\tlayui.laytpl(templateCache[template]).render({\n\t\t\t\tdata : cell.data,\n\t\t\t\tvalue : cell.value,\n\t\t\t\tflows : flows || [],\n\t\t\t\tmodel : model,\n\t\t\t\tcell : cell\n\t\t\t},function(html){\n\t\t\t\t$(\".properties-container\").attr('data-version',v).html(html).attr('data-cellid',cell.id);\n\t\t\t\tlayui.form.render();\n\t\t\t\trenderCodeMirror();\n\t\t\t\tresizeSlideBar();\n\t\t\t\tcallback&&callback();\n\t\t\t})\n\t\t}\n\t\tif(templateCache[template]){\n\t\t\trender();\n\t\t\treturn;\n\t\t}\n\t\t$.ajax({\n\t\t\turl : 'resources/templates/' + template +\".html\",\n\t\t\tasync :false,\n\t\t\tsuccess : function(content){\n\t\t\t\ttemplateCache[template] = content;\n\t\t\t\trender();\n\t\t\t}\n\t\t});\n\t}\n\tif (!mxClient.isBrowserSupported()){\n\t\tlayui.layer.msg('浏览器不支持!!');\n\t}else{\n\t\teditor = new SpiderEditor({\n\t\t\telement : $('.editor-container')[0],\n\t\t\tselectedCellListener : function(cell){\t//选中节点后打开属性面板\n\t\t\t\tloadTemplate(cell,editor.getModel(),serializeForm);\n\t\t\t}\n\t\t});\n\t\t//绑定工具条点击事件\n\t\tbindToolbarClickAction(editor);\n\t\t//加载图形\n\t\tloadShapes(editor,$('.sidebar-container')[0]);\n\t\tlayui.form.on('checkbox',function(e){\n\t\t\tserializeForm();\n\t\t});\n\t\tlayui.table.on('tool',function(obj){\n\t\t\tlayui.layer.confirm('您确定要删除吗？',{\n\t\t\t\ttitle : '删除'\n\t\t\t},function(index) {\n\t\t\t\tobj.del();\n\t\t\t\tserializeForm();\n\t\t\t\trenderCodeMirror();\n\t\t\t\tlayui.layer.close(index);\n\t\t\t});\n\t\t})\n\t\t//节点名称输入框事件\n\t\t$(\"body\").on(\"mousewheel\",\".layui-tab .layui-tab-title\",function(e,delta){\n\t\t\tvar $dom = $(this);\n\t\t\tvar wheel = e.originalEvent.wheelDelta || -e.originalEvent.detail;\n\t\t    var delta = Math.max(-1, Math.min(1, wheel) );\n\t\t    e.preventDefault = function(){}\n\t\t    if(delta > 0){\n\t\t        $dom.scrollLeft($dom.scrollLeft()-60);\n\t\t\t}else{\n\t\t\t\t$dom.scrollLeft($dom.scrollLeft()+60);\n\t\t\t}\n\t\t\treturn false;\n\t\t}).on(\"dblclick\",\".layui-input-block[codemirror]\",function(){\n\t\t\tif($(this).parent().hasClass(\"layui-layer-content\")){\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlayui.layer.open({\n\t\t\t\ttype : 1,\n\t\t\t\ttitle : '请输入'+$(this).prev().html()+'表达式',\n\t\t\t\tcontent : $(this),\n\t\t\t\tskin : 'codemirror',\n\t\t\t\tarea : '800px'\n\t\t\t})\n\t\t}).on(\"blur\",\"input,textarea\",function(){\n\t\t\tserializeForm();\n\t\t}).on(\"click\",\".history-version li\",function(){\n\t\t\tvar timestamp = $(this).data(\"timestamp\");\n\t\t\tlayui.layer.confirm('你确定要恢复到该版本吗？',function(index){\n\t\t\t\tlayui.layer.close(index);\n\t\t\t\tvar layerIndex = layui.layer.load(1);\n\t\t\t\t$.ajax({\n\t\t\t\t\turl : 'spider/history',\n\t\t\t\t\tdata : {\n\t\t\t\t\t\tid : id,\n\t\t\t\t\t\ttimestamp : timestamp\n\t\t\t\t\t},\n\t\t\t\t\tsuccess : function(data){\n\t\t\t\t\t\tif(data.code == 1){\n\t\t\t\t\t\t\tversion = timestamp;\n\t\t\t\t\t\t\teditor.setXML(data.data);\n\t\t\t\t\t\t\tlayui.layer.close(layerIndex);\n\t\t\t\t\t\t\tlayui.layer.msg('恢复成功')\n\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\tlayui.layer.msg(data.message);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t});\n\t\t}).on(\"click\",\".btn-history\",function(){\n\t\t\t$.ajax({\n\t\t\t\turl : 'spider/history',\n\t\t\t\tdata : {\n\t\t\t\t\tid : id\n\t\t\t\t},\n\t\t\t\tsuccess : function(data){\n\t\t\t\t\tif(data.code == 1){\n\t\t\t\t\t\tif(data.data.length > 0){\n\t\t\t\t\t\t\tvar array = [];\n\t\t\t\t\t\t\tfor(var i = data.data.length - 1;i >=0;i--){\n\t\t\t\t\t\t\t\tvar timestamp = Number(data.data[i])\n\t\t\t\t\t\t\t\tarray.push({\n\t\t\t\t\t\t\t\t\ttime : new Date(timestamp).format('yyyy-MM-dd hh:mm:ss'),\n\t\t\t\t\t\t\t\t\ttimestamp : timestamp\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tlayui.laytpl($('#history-version-tmpl').html()).render(array,function(html){\n\t\t\t\t\t\t\t\tlayui.layer.open({\n\t\t\t\t\t\t\t\t\ttype : 1,\n\t\t\t\t\t\t\t\t\ttitle : '历史版本',\n\t\t\t\t\t\t\t\t\tid : 'history-revert',\n\t\t\t\t\t\t\t\t\tshade : 0,\n\t\t\t\t\t\t\t\t\tresize : false,\n\t\t\t\t\t\t\t\t\tcontent : html,\n\t\t\t\t\t\t\t\t\toffset : 'rt'\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\tlayui.layer.msg('暂无历史版本！');\n\t\t\t\t\t\t}\n\t\t\t\t\t}else{\n\t\t\t\t\t\tlayui.layer.msg(data.message);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t}).on(\"click\",\".table-row-add\",function(){\t//添加一行\n\t\t\tserializeForm();\n\t\t\tvar tableId = $(this).attr('for');\n\t\t\tvar $table = $('#' + tableId);\n\t\t\tvar cellId = $table.data('cell');\n\t\t\tvar data = getCellData(cellId,$table.data('keys').split(\",\"));\n\t\t\tdata.push({});\n\t\t\tlayui.table.reload(tableId,{\n\t\t\t\tdata : data\n\t\t\t});\n\t\t\trenderCodeMirror();\n\t\t}).on(\"click\",\".table-row-up\",function(){\t//上移\n\t\t\tvar current = $(this).parent().parent().parent(); //获取当前<tr>\n\t\t\tvar prev = current.prev();  //获取当前<tr>前一个元素\n\t\t\tif (current.index() > 0) {\n\t\t\t\tcurrent.insertBefore(prev); //插入到当前<tr>前一个元素前\n\t\t\t\tserializeForm();\n\t\t\t}\n\t\t\trenderCodeMirror();\n\t\t}).on(\"click\",\".table-row-down\",function(){\t//下移\n\t\t\tvar current = $(this).parent().parent().parent(); //获取当前<tr>\n\t\t\tvar next = current.next(); //获取当前<tr>后面一个元素\n\t\t\tif (next) {\n\t\t\t\tcurrent.insertAfter(next);  //插入到当前<tr>后面一个元素后面\n\t\t\t\tserializeForm();\n\t\t\t}\n\t\t\trenderCodeMirror();\n\t\t}).on(\"click\",\".editor-form-node .function-remove,.editor-form-node .cmd-remove\",function () {\n\t\t\tvar $dom = $(this).parents(\".draggable\");\n\t\t\t$dom.remove();\n\t\t\tserializeForm();\n\t\t}).on(\"click\",\".editor-form-node .cookie-batch\",function(){\n\t\t\tvar tableId = $(this).attr('for');\n\t\t\tvar $table = $('#' + tableId);\n\t\t\tvar cellId = $table.data('cell');\n\t\t\tvar data = getCellData(cellId,$table.data('keys').split(\",\"));\n\t\t\tlayui.layer.open({\n\t\t\t\ttype : 1,\n\t\t\t\ttitle : '请输入Cookie',\n\t\t\t\tcontent : `<textarea id=\"cookies\" name=\"cookies\" placeholder=\"请输入Cookies，分号( ; )分隔Cookie，等于号( = )分隔name和value\" autocomplete=\"off\" class=\"layui-textarea\"  lay-verify=\"required\" style=\"height:250px\"></textarea>`,\n\t\t\t\tarea : '800px',\n\t\t\t\tbtn : ['关闭','设置'],\n\t\t\t\tbtn2 : function(){\n\t\t\t\t\tvar cookieStr = $(\"#cookies\").val();\n\t\t\t\t\tvar cookieArr = cookieStr.split(\";\");\n\t\t\t\t\tvar length = $(\".draggable\").length;\n\t\t\t\t\tserializeForm();\n\t\t\t\t\tfor (var i = 0; i < cookieArr.length; i++) {\n\t\t\t\t\t\tvar cookieItem = cookieArr[i];\n\t\t\t\t\t\tvar index = cookieItem.indexOf(\"=\");\n\t\t\t\t\t\tif (index < 0) {\n\t\t\t\t\t\t\tlayer.alert('cookie数据格式错误');\n\t\t\t\t\t\t\tappendFlag = false;\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tdata.push({\n\t\t\t\t\t\t\t\t'cookie-name' : $.trim(cookieItem.substring(0, index)),\n\t\t\t\t\t\t\t\t'cookie-value' : $.trim(cookieItem.substring(index + 1))\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tlayui.table.reload(tableId,{\n\t\t\t\t\t\tdata : data\n\t\t\t\t\t});\n\t\t\t\t\trenderCodeMirror();\n\t\t\t\t\tserializeForm();\n\t\t\t\t}\n\t\t\t})\n\t\t}).on(\"click\",\".editor-form-node .header-batch\",function(){\n\t\t\tvar tableId = $(this).attr('for');\n\t\t\tvar $table = $('#' + tableId);\n\t\t\tvar cellId = $table.data('cell');\n\t\t\tvar data = getCellData(cellId,$table.data('keys').split(\",\"));\n\t\t\tlayui.layer.open({\n\t\t\t\ttype : 1,\n\t\t\t\ttitle : '请输入Header',\n\t\t\t\tcontent : `<textarea id=\"headers\" name=\"headers\" placeholder=\"请输入Headers，一行一个，冒号( : )分割name和value\" autocomplete=\"off\" class=\"layui-textarea\"  lay-verify=\"required\" style=\"height:250px\"></textarea>`,\n\t\t\t\tarea : '800px',\n\t\t\t\tbtn : ['关闭','设置'],\n\t\t\t\tbtn2 : function(){\n\t\t\t\t\tvar headerStr = $(\"#headers\").val();\n\t\t\t\t\tvar headerArr = headerStr.split(\"\\n\");\n\t\t\t\t\tvar length = $(\".draggable\").length;\n\t\t\t\t\tfor (var i = 0; i < headerArr.length; i++) {\n\t\t\t\t\t\tvar headerItem = headerArr[i];\n\t\t\t\t\t\tvar index = headerItem.indexOf(\":\");\n\t\t\t\t\t\tif (index < 0) {\n\t\t\t\t\t\t\tlayer.alert('header数据格式错误');\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tdata.push({\n\t\t\t\t\t\t\t\t'header-name' : $.trim(headerItem.substring(0, index)),\n\t\t\t\t\t\t\t\t'header-value' : $.trim(headerItem.substring(index + 1))\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tlayui.table.reload(tableId,{\n\t\t\t\t\t\tdata : data\n\t\t\t\t\t});\n\t\t\t\t\trenderCodeMirror();\n\t\t\t\t\tserializeForm();\n\t\t\t\t}\n\t\t\t})\n\t\t}).on(\"click\",\".editor-form-node .parameter-batch\",function () {\n\t\t\tvar tableId = $(this).attr('for');\n\t\t\tvar $table = $('#' + tableId);\n\t\t\tvar cellId = $table.data('cell');\n\t\t\tvar data = getCellData(cellId,$table.data('keys').split(\",\"));\n\t\t\tlayui.layer.open({\n\t\t\t\ttype : 1,\n\t\t\t\ttitle : '请输入参数',\n\t\t\t\tcontent : `<textarea id=\"paramters\" name=\"paramters\" placeholder=\"请输入参数，一行一个，冒号( : )、等号（ = ）、空格（  ）或tab（ \\t ）分割name和value\" autocomplete=\"off\" class=\"layui-textarea\"  lay-verify=\"required\" style=\"height:250px\"></textarea>`,\n\t\t\t\tarea : '800px',\n\t\t\t\tbtn : ['关闭','设置'],\n\t\t\t\tbtn2 : function(){\n\t\t\t\t\tvar paramterStr = $(\"#paramters\").val();\n\t\t\t\t\tvar paramterArr = paramterStr.split(\"\\n\");\n\t\t\t\t\tvar length = $(\".draggable\").length;\n\t\t\t\t\tfor (var i = 0; i < paramterArr.length; i++) {\n\t\t\t\t\t\tvar paramterItem = paramterArr[i];\n\t\t\t\t\t\tvar index = -1;\n\t\t\t\t\t\tvar indexArr = [];\n\t\t\t\t\t\tindexArr.push(paramterItem.indexOf(\":\"));\n\t\t\t\t\t\tindexArr.push(paramterItem.indexOf(\"=\"));\n\t\t\t\t\t\tindexArr.push(paramterItem.indexOf(\" \"));\n\t\t\t\t\t\tindexArr.push(paramterItem.indexOf(\"\\t\"));\n\t\t\t\t\t\tfor (var j = 0; j < indexArr.length; j++) {\n\t\t\t\t\t\t\tif (indexArr[j] >= 0) {\n\t\t\t\t\t\t\t\tif (index < 0) {\n\t\t\t\t\t\t\t\t\tindex = indexArr[j];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tindex = Math.min(index, indexArr[j]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (index < 0) {\n\t\t\t\t\t\t\tlayer.alert('参数数据格式错误');\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tdata.push({\n\t\t\t\t\t\t\t\t'parameter-name' : $.trim(paramterItem.substring(0, index)),\n\t\t\t\t\t\t\t\t'parameter-value' : $.trim(paramterItem.substring(index + 1))\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tlayui.table.reload(tableId,{\n\t\t\t\t\t\tdata : data\n\t\t\t\t\t});\n\t\t\t\t\trenderCodeMirror();\n\t\t\t\t\tserializeForm();\n\t\t\t\t}\n\t\t\t})\n\t\t}).on(\"click\",\".editor-form-node .function-add\",function(){\n\t\t\tvar index = $(\".draggable\").length;\n\t\t\t$(this).parent().parent().before('<div id=\"function' + index + '\" class=\"draggable\" draggable=\"true\" ondragstart=\"drag(event)\" ondrop=\"drop(event)\" ondragover=\"allowDrop(event)\"><div class=\"layui-form-item layui-form-relative\"><i class=\"layui-icon layui-icon-close function-remove\"></i><label class=\"layui-form-label\">执行函数</label><div class=\"layui-input-block array\" codemirror=\"function\" placeholder=\"执行函数\"></div></div></div>');\n\t\t\trenderCodeMirror();\n\t\t}).on(\"click\",\".editor-form-node .cmd-add\",function(){\n\t\t\tvar index = $(\".draggable\").length;\n\t\t\t$(this).parent().parent().before('<div id=\"' + index + '\" class=\"draggable\" draggable=\"true\" ondragstart=\"drag(event)\" ondrop=\"drop(event)\" ondragover=\"allowDrop(event)\"><div class=\"layui-form-item layui-form-relative\"><i class=\"layui-icon layui-icon-close cmd-remove\"></i><label class=\"layui-form-label\">执行命令</label><div class=\"layui-input-block array\" codemirror=\"cmd\" placeholder=\"执行命令\"></div></div></div>');\n\t\t\trenderCodeMirror();\n\t\t});\n\t\tlayui.form.on('select(bodyType)',function(e){\n\t\t\tvar bodyType = $(e.elem).val();\n\t\t\t$(\".form-body-raw,.form-body-form-data\").hide();\n\t\t\tif(bodyType == 'raw'){\n\t\t\t\t$(\".form-body-raw\").show();\n\t\t\t}\n\t\t\tif(bodyType == 'form-data'){\n\t\t\t\t$(\".form-body-form-data\").show();\n\t\t\t}\n\t\t\trenderCodeMirror();\n\t\t\tserializeForm();\n\t\t});\n        layui.form.on('select(targetCheck)', function (data) {\n            var targetDiv = $(data.elem).attr('target-div');\n            var targetValue = $(data.elem).attr('target-value');\n            if (targetDiv != null) {\n                if (data.elem.value == targetValue) {\n                    $(\".\" + targetDiv).show();\n                } else {\n                    $(\".\" + targetDiv).hide();\n                }\n            }\n        });\n\n\t\tlayui.form.on('checkbox(targetCheck)', function (data) {\n\t\t\tvar targetDiv = $(data.elem).attr('target-div');\n\t\t\tif (targetDiv != null) {\n\t\t\t\tif (data.elem.checked) {\n\t\t\t\t\t$(\".\" + targetDiv).show();\n\t\t\t\t} else {\n\t\t\t\t\t$(\".\" + targetDiv).hide();\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tlayui.element.on('tab',function(){\n\t\t\tfor(var key in codeMirrorInstances){\n\t\t\t\tcodeMirrorInstances[key].refresh();\n\t\t\t}\n\t\t})\n\t\tlayui.form.on('select',serializeForm);\n\t\tvar id = getQueryString('id');\n\t\tif(id != null){\n\t\t\t$.ajax({\n\t\t\t\turl : 'spider/xml',\n\t\t\t\tasync : false,\n\t\t\t\tdata : {\n\t\t\t\t\tid : id\n\t\t\t\t},\n\t\t\t\tsuccess : function(xml){\n\t\t\t\t\teditor.setXML(xml);\n\t\t\t\t}\n\t\t\t})\n\t\t\t//editor.importFromUrl('spider/xml?id=' +  id);\n\t\t}\n\t\teditor.onSelectedCell();\n\t}\n\t/**\n\t * 加载各种图形\n\t */\n\tfunction loadShapes(editor,container){\n\t\t//定义图形\n\t\tvar shapes = [{\n\t\t\tname : 'start',\n\t\t\ttitle : '开始',\n\t\t\timage : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABwUlEQVRYR9WXz1XCQBDGv9GD3sSDkpvYAR2IHWAF0oFsBUIFGyoQK1ArECpQK8BjcsMbnj7fRsN7hPDcCRt87nl25jez828FinNq2d0TWAINAR4XhJkbmStUrImK7+WGZeNAMBOgkd8hMUqN9H11lMl5A/x4/1BUsiCOt4mCN0BkOYDgds0L4jIxMqkahX8EEHMGoFX0NOmLtxOVcyCy7EDwXFRA4iM1skzKKs/gRd+M+SJAuwTgKTXSrWI4v/MrQBTzDkCvzAgJkxqJawOILHsQOIDyQ5wnRt5rAWiOeCPEZu+I+8RIaWQ0QGtP4DreocBuCvtSeQDvna4VgGjEaxCDsnLTeOUpO3azJAM4sWzvf3vd8bwcRowYZgBRTFfjuzXuDBNTiSxbELgut/vjIrCpy9VNk4/y0ABZYmnGc2gA966q/lAHwDQx4p3QwQG0a1owADeaAYw/gcFf5cBkQVxpjGetOGQZklDvB6EB1BtSaAD1PyEIAIk3AeLEyFjbQYMAAKiUgHkSBhlG2vpfWUojywkEF9rwrcgTqg5YBGgReBXBUWUIYpgYcduU6ixXsmwXRLaEuk/Ima+WbRLQ2fgC+RzXgT1bPk8AAAAASUVORK5CYII=',\n\t\t\thidden : true,\n\t\t\tdefaultAdd : true\n\t\t},{\n\t\t\tname : 'request',\n\t\t\timage : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACbUlEQVRYR+2XTXLTQBCFX5sF7DALsHaYE+AbxD4ByQlITmDrBCQnGHMClBMkOUHMCRAnIOwU2JhdssCPamnkkmRJM5KTKqpgqryxpqe/7unpH0HHFRhOIXhPYCLARMUJxALEIM6TUFZdjhTfzS8NJ08EBsDUIbP6TYQ/Q4l9zvYCUKspuBBg6HMogbUQRz7ecAKo5QPBta/yHFAhNsTM5QknQLDkNwBjH8tr9twkC3nTJtsKEBgeQ/Cpp/JMjDhJQomazmgFGBleiuDdPgAkrm5DOewFECzJfZTnsslCGg1NPwSGYwqMCOJkLme54GMAvDI8HAjmG+Ljj1AuUwD754VVHCULORkaDp/ZRLOvF+6AeB3KuhRTWdI63rqmct8RBmgMnD5AJCZCLNO4JL7eA1OF2gKoxU+BlQje9lHgK1NUrjKl4OgMQcxsLVj6gFeV7wDYgDyF4IOPRXl0B4YrCA6cMsRZEsppcd/O8wgM/wP8Yx4YGWqiLOZ7rXxe1a9zEAI3yH5ZnSKuZJ902wNg56FImobLKVd7PveTAtAZgPgMYNszbrJesrz+hmeoHrh2JpUeHtgQR1oBXYno0QBAzKqNam2jMDJci+C5ywtdY+COeKEVsNUD+nFkqMVl/pAATa1ZrQfsEPLlIQHq7r+2GqYlOZsD0rGrbXW5Ah3f7olZ6xVUlWv9bqvzXgDEdwhe275hB6LcERUtt/18YBjpMFrnCSeA7fuKvWDVE1uAkqLKMJEeAGiVTC3JVxMAiV8CLIoDSV1DWooBLUqqpCpYVGjTtg4ZY03XJYBsTF8TiKrJJj8jhyAR3oaSNqh/AKendd/nwfiIAAAAAElFTkSuQmCC',\n\t\t\ttitle : '开始抓取',\n\t\t\tdesc : '抓取静态HTML页面或者API接口，抓取结果存为resp变量中。<br/>支持方法参考命令提示。'\n\t\t},{\n\t\t\tname : 'variable',\n\t\t\timage : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAgCAYAAACYTcH3AAACe0lEQVRYR+2WMW5TQRCGv0mEoMMUKK80J0joKJBITgCUVAkniLehjQ+A9GwugLlBboA5QZITYLpnaEwXkZhBs+xu1s8vkhMDMpItudh9szP//PPP7Aor9JMVwsLqgmmV2roD7W9OTpsYe1jqzgWMJk4m9e/FO30W986nnDXZ1P2bPzsT480ws9XTE4EdlNeVk0EesCj1AOE9MKw6smffzPk9oQQOGsAPz5WXOajcP4KB9+cUTrng6SyYUici3M8DxiDRkSpnYyc+o+TcHCpnAhOFlgjb4VwCbuuipxr2R0A7T0DhSR1MT4RDb6Q8qpzYIYpS2wif/bbixk56Ram7CB+D7QyTRanDkDlVR1KMDIz5/wAMfhp4aJvPGTBNQT0DpSaQ58oDo95KdBeslKOxk06eZW6Pslc5GdaYIfrJz811U8zK6jjuyOPgxFhpWzaVkxl9eN1sso2ya3+FHYFWCtIERvlUOdmt66wJTBSqL5UHcVWOlKUH2dcjVToxuCrfTW9+/Vug5mOemUXBhLJ4IavSD473Ub5UTpLosu7y9Z9CL7ZoUWoX4ehPgYkaGfnugFYUbuquUo9FeG7rXKQhmfRtaWZyIadBFoQb10WpA4T9vJu8fjY4ROlmmkmdlrrpJmUK7Zzas0m4Nj03hZO6CEP797MRkUSfgZlrBDt37d1kwTZgYIPMJmWcOXnwYNP1UxtsJtm/a7amKYWOQverk+OQoDF2MIUXTVfO6l6UTZT/y701M9exvWZmzcxNO3F1NbPV18OZt8hNU1vCXn/w9upJmD0tl/B566MqvKo/O+3umHuB3TrC4gdHesmb1dXM4on8Hcs1M//FBP4FX81QGwO29kEAAAAASUVORK5CYII=',\n\t\t\ttitle : '定义变量',\n\t\t\tdesc : '定义流程变量。<br/>定义变量有先后顺序，先定义变量后续可以使用，拖动可以交换变量顺序。'\n\t\t},{\n\t\t\tname : 'loop',\n\t\t\timage : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADO0lEQVRYR9WWS2hTURCGvwnVlVjpzgdWxQeo0IWP7rQuRBQVFayLSlUQH5V2UWhzo4takOTGCF1UlOJCKyJiFxUFSwWxioguRBEV31qhVXeKSI3ajJybGxPTJDdpCtUDIeFmZv7vzpmZc4RxXjLO+vzHAH4tZZhySihzsih8JCTPCs1o/hlo0UlE2QlsA2YD0zOI3QV68dFBUD7kA+MN0KIT+U4zwg5gbkrQn8ArYELac2MyCJzClsNeELkB/DoT4SJQ6QZ6AZzFRw8TeEyr/HCeH9YyhqhEWAzUAbNc+26GaSIir7OBZAcIaCXKbaAEGAIaidJJm5jf2ZelRrwFnO0y6wlCNSF5mskpM0CdTmIyX12HAWLs4Khc90rnX/9bagBO/4FQ1hGW9+kxMgNYesEpNuUtw2zimDwqSDxhbOlupxbMUjoIyz5vAEuN0UnXqYawnB+VeBKiCTjqxjNZ6EmNNzIDlt4AqhDaCUlDUeJJiHNATaaYIwECuhClOp8WyhsuoOtRrjhta8u83BnIO2qBhpaq67EUW+4nvP/OgKWm55cBzdjSVaBEZnOzpcoUBDMlTX3tdVs0ii2rkgBx8a0pUcw2FA9h6QAwzZkHsCjlexBbpscBRoonOIqHCOhGlLNAacrLfUGoJSSXBUurAFP55kTrRylH+YwwBegzaSp6K/y6AnGG0hzgDcouwnIrLmmWXyP4uIk6J912FNP7gwjfxqwbDupiYrTjo56gPM5WhHuADicDMZYTkZdFv71HgPQuMMdtXFRpICztYwJgqSnuOpRDhOWO1yS8CqwFerBlXdEAAZ2PYg6yGUBr+paOnIR+rUXodIXbsKWxKIhkhw3gYwlB+eQ9Cf16HOGAa1iPLcdHBWHpEeCQ67sGW66lx8l+IUmdDUITITlWEERAT6Dsd326sWVLJv/cV7Lk/Da+ffiwCUpvTpD4wWO6aYNr14Ut1dl8vC+lliaKMh5DuYQP08cP8HEP5RcxKlAWIKxOETbWFraEcwF7Azhh1LyR+Szx3AYzQ+AMMTqJyEMv+/wAElEs3QxUACvdA2YqEAWeu/Oj3xG35Z2XcOZJmK/XGNoVloExFP5nMvAbItf8IXnK1DcAAAAASUVORK5CYII=',\n\t\t\ttitle : '循环',\n\t\t\tdesc : ''\n\t\t},{\n\t\t\tname : 'forkJoin',\n\t\t\timage : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADWElEQVRYR8WX34sVZRjHP9+tqJtA+uHMuY2gOwkSgugfCLopSLMNE6tNVDwzq6hYrLvBlhvovCNsucUWhiYaeBV40z8Q1E13UXQVnjlh3XQZ7TdGz7F1d+bMnEPie3ne93m+n3eeX+8R93jpHuszMcDjmZ++Tzwn85f+4dr1I7oxyWUmAoiCdwLnBGvAA8B11pgrZnVlXIixATqZpy0uYBaKVPNbMm95SJwDdlpM97v6ahyIsQDi4N3A+aH4eqEo9wWZaeCNItGXbSFaA0RnvVdrrFaJD8Xi4PPAbps3+6k+bwPRCqCTe8ZmZZT4bYjMq4i9Eu/0uvq0CaIRIA7eDyy3ER+KRZlXJGaAA0Wij0dBjASIgg8J8nHE14VjGdhv6PYTna2DqAXoZJ61OD2J+FCsE5wbDkkc7nV1pgqiEiDOfRSzVCceZU40xS7Mkzedil+8xqV+qrBRJM5dXmIWcazo6qON+5sAOsEnDIt14nHmecTJcl/wY+nQsG34W9kbNkFkXkIcFbzbS/TB+v07AOLgOWChVjz4FeBKkaj6ywUb2FEk+nojRBS8KDgBnCwSvT/cv+2ok/sZm+8b6rzc/6bqlqXDwdd5sUi0vSrecfACMCexvdfVD7eiN1hR5rJ2TxWJttZlbBz8p82BfqpLVWeizLsklotEj1TtP7bkh+9/kN+Bw8PyHBfgD5m3e6muVgl0Mr9s8VmR6NHaS2T+GZFtAmgVgtzfsca1kSGY4oWiq2crQ3DGO5jicmUIbsawKQkHFTAqCW3SynL8b5DNF6nKXBhU8AbUpjKMgq8KXiqT1fDTIJGeGlWGw1ki814v1WJtGd5uow2NKM68B3EQeGJg8yuwVFl+ubsyZYM6XiRaamxEwwP/RyuOg48Bp4AjRaLTVXlx94bRIF8skn5XeV1V3JVx3An+0HAcc7BIVU7F2tUIUFp2Ms9Y7R4kUXAmSAT7eolWRonf0QmbDg46ZdOT7BNgn+GtfqLVJp9jAQz6RO2jNA7+AiirY0/RVfk2bLVahWC9pyj3azIX1w+tOLicDa/KvN5LdbGVcl0jamMc32qp5TPrt0E769jM9hNdbmPf2IjaONkavG0Kni//mv0N395I1Wtj17oRTeJsEpuxc2ASkVE2/wK1YaYwksZPTgAAAABJRU5ErkJggg==',\n\t\t\ttitle : '执行结束',\n\t\t\tdesc : ''\n\t\t},{\n\t\t\tname : 'comment',\n\t\t\timage : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAD/0lEQVRYR82WT2hcdRDHP9+XGL1YpaTZtzGEotX0UISSih78Uy8t2ksrVPBkVVBKafZtbNOjFUGIjdm3W0xVCjagXrSVglT0orQHlVrQIIoYK2hNXkwo0XowJdmR93Y3vt0k+3aDVn+w8Njf/GY+v5n5zYz4j5eate/6thu4C1gHdBjMCS4a/OSInx1x/pc+XWpUb0MA6bz1mvEkEBoPDddbM4gTBqNTGX2dBFIXIJW3TRTZL/H0MoquAt8jihTpQbTVyMwjjgUZ9dWDWBHAzdlWHN7C6IwUGOOI44Jxc/g26NM3ccWdvvUsQI+MjYh+IFXev6gieyb7dW45kGUB3JwdRjy3eMA4utDGi9P7FCS5NNyPYERBxraYjueDrA7Xnl8CkB62+8zhbOzgw0FWHzRiuFbGzdkAYjCm68Egq0/icksAXN9+AG4NhQJPDSVpEpzrm0UyYsJgezw5qwy4eStg7C/H/FCQ1UsrKe8csvZiC58i5lqL3H+pX5dXknVz9hDiTJRKxutTWT1TkV0E6Mrb7fMwhnGDiY+mMtpe72ZVeWIsG9/4eTdnBVS6nMSWyYwulJxSXjGFUw48MOHpuyTXpvL2powrgae9SbLrXjG35SrnEBuAkcDTvkWADQW7/g+Lbn8HcCDw9HKSwtXsuzk7gDgCTAeeOhYB3JztQLxfjv2STF3O2NqCrWkrcjrc+9PYNZvVbBJUeti2mcOHZblHA0/vRCFwc7YXMRJ+z8+xZuaQriQpiwqV+HgV0L+VdR8JPA1UAAYRA8CFwNOWJONl6KYBonO+fQXcCZwIPD1RAvDtJPBI7RNJeAWrAkj7dtzgKeBM4GlHBJDy7ZRg17UAcH0bAp6VOD2Z0c6KB6I/r0UI0r69bfAY4mjYKSseCD/y/3YSlsMdJu5WjINBVkMRQDpvoftPNZPRq3kFbs7WI34s29kdZPVuCcC3bgvLMNzUaCFaDUAqbxkZPjDT4rA5HN3+LsV5G8EIS2pDpbhZgKpSLIaCjA5W9YK0b/caRFNLg82oqWcYa0bzJjZXWnJVO0759p5gZzlGddtxMx6It2MgqoCVGlMF0FGw21TkC8HNoUC9gaRr2NbOO7yKEdDCC0GfplcqXIsDCXweeLonLrd0JIu/iCge/GMjmQMba9v8/28orbgnnGyLMArcXc6JceA1GWNzrXx2uU+/x13ZPmg3trbRi+gFwgyPxnKDWYn+IKM3lgtR4tDp+hbOhVmgtUbBmOB80VhQyWj4q1phbynCsemsvlwpPxIBomaVt02CxzH2AO1127WYoMhJOYxW5r568g0BVBTcUrCuhQXCLF6PQ7eg24zrgF/DMcvg7JSn0mTV4GoKoEGdTYn9Bf4L6DCOEiGKAAAAAElFTkSuQmCC',\n\t\t\ttitle : '注释',\n\t\t\tdesc : '仅仅是注释,毫无作用'\n\t\t},{\n\t\t\tname : 'output',\n\t\t\timage : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAByUlEQVRYR+WXwVHDMBBF/4YD3AgHwEd3QKiA0IE7IOnAqoCkAqWDmAqACggVJJTAzRku4UYOmWVWloMZgi17bGcGdNGMrZWeVtrdL4JtXc3dI8I9gH76rWIfxSENXW0pHehpHoAwdTXMHccYx4pGLnNlAUYg3BojxtjFODPGB+Hmmw1jGCuKiubZCRCHtP1eNIH89zT3QXgy7IwXIlzYjRRC1A7wwTg5BGauELUDiPfkQrtCNAIg7neFaAzAFaJRAIE41dzrJHfieNfFrB2gMGoYz7GibbL7GwAmjQO9vN0zMDGh2YQHCt2eJKsZCFf/F+A3L9VyCV2OQMLxgKA3DPWmaJHatAbgaTbVloHFmnG9UrQSiDYBfFlcEpL0y5AuWwWQxc40B51EdUkzyqmUB6xs0wB8l3PfNYaBHgFdqx1UKYCs8KgKkLVjYFUKwJbYKN1BFQjjga/CNC4FUGXBrI0NxbmtinexokFrAJ5mnwlz8Z7oxjXQl1BsEyDJA4z3NeC3ngeM+4HJBgj3kgn3XgvONRs9IOe/VBTuoxa46wEQnN51pcKSMTAZNEcR1fc4zSFjxuNSUfDjCKyuezCyqanGeN0AQTYKPgFOSUMAph/CYQAAAABJRU5ErkJggg==',\n\t\t\ttitle : '输出',\n\t\t\tdesc : '输出流程中的变量结果（仅测试下有用）'\n\t\t},{\n\t\t\tname : 'executeSql',\n\t\t\timage : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAD1ElEQVRYR7WXUXIaRxCGv4YqR2+WH2zxZukEUU5gdAKjExhfILAnMD7BohMYn8D4BEYnsHQCobfd5CHkDVIFnerZmc0sLAJjMlVUSbszPX93//13r7DnOk319JcGb0TpAOfRzyxMw0+F8WLF7SyR2T6mZdemVqrnCB+A7sZe5dE9E17X2BmhfMwSMXBb11YAzmPhg0DfnVYeFcYKkz8SGddZfJVqR6At0ClBCYP5ipttEakF4C//JnCpyt/AIE9kuCta8fuzVA34QITnCncL5aoOxAaAl6leNgS7/FSVrwvo7pvPdYDOERiJ8FZhtlKu/kzkLt5XAeA9f/CX3+SJFOH/yXWW6lCEnoFYKBexQyWAtbAf7fKAPQJRSUcJoNygfM0TsVI7+jpLdWzp8NUxKAoI8KX2YIRbwPmhOd+F2HNiasREubASDQBGCO9USdbZ3kq1TRPNfpfb9Qsc8CavWfIY17sRudnk+XzJ/bozrVQHTleUz1kiXTFUJ8JfVudZIqZwbjkjwhevePZoulSuA4tLQ36/wjDvS+IjOkF4g3KVJTKpAT41nZgrL8TEoyF8UaVCvFaqbpOVooEQ4Ver57wvv7moCN8sZVKIU8eH9X2WyKiV6i4ALuIr5VpaqZb/BIWLOHGfJ3LpvXKAsr7YGRdGM2Bn1p3YBSDstzSYMYfWDIdQBQ9RbrNE2h6A9YLzLJFBlEcX4vX9uwA4e0NVs28ASs/iXLkN1uWKPFYayrEAWErFI6kQ0IAEXbC/rQmJchMIdRQA3nEHIJCrhq0mFt2ysxXt9TgpKAFsSUElHakaiE+m5XlfXhwlAua4cl9LwjpFi8l6LACBhBtl6EUo9Wo1isUlLkMj6BJmTaHnUuWrZlcVVMqwTog8gO+BG14tv7syjHTApNuGFhMVF7X/OLK/EJVSDNOsLxdR+7wz9aukw+u3CZUrIWsq0QqTzwmMnRRXX8aaUpb+1mbkgMHQZNa3zfEc+qG5mPiY/juJLuR6YvPjEjoN6LrIrIGzAWejGfn82uT7UDex1BHy0GfxxFVpxx6E03cTnbwv14de8tS5iJxOT3xkiyN+WJj4kP5/I5lyv4B2SOXmUApuYon7+89GIxr3Niau+rHcCFWAGC+U94eOaD7nn+xDxWaHFbSfHMuDp5V0wAxhkPfk5kciYWxXoedH/ErYYztPfpqd2GdZ8V1oa2oRQZjkPbGy21he1N4CNkMU453ycQ7DH/o0iy276QisQgq1q64wJ5SzZPla+WznDv44Xb/J0vIM2o1CmEwJT4NSuq5mqYLpCsb/wGRf3vwLgODoY+vqQ1gAAAAASUVORK5CYII=',\n\t\t\ttitle : '执行SQL',\n\t\t\tdesc : '执行sql，需配置数据源，sql执行结果存于变量rs中。<br/>语句类型为：select，返回:List&lt;Map&lt;String,Object&gt;&gt;<br/>语句类型为：selectOne，返回:Map&lt;String,Object&gt;<br/>语句类型为：selectInt，返回:Integer<br/>语句类型为：insert、update、delete，返回:int，批量操作返回int数组<br/>sql中变量必须用 # # 包裹，如：#${title}#'\n\t\t},{\n\t\t\tname : 'function',\n\t\t\timage : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACkAAAAgCAYAAACPb1E+AAAC9UlEQVRYR+2YP0zUUBjAf9+RKJPgQK6THptxERPiZOTUxEUj/tlMjCS62xoT3YARl5bJTXByBGcHISQmxhjQ6A6TPRg8JrnE62de6Z0Htne9XhMw4W1N3/u+X7//rwJgzeokygRQMs+HYSksBsr0liNrYrk6gTB3GMBiGNZ9W4al6OqiCOOqfFGYKkD1oIEVRkRwQw5l2FhyCWEMZdp3ZOqgARv6LU/1CDIPbxxZMg8rhmUxj5gcdHWw6khiJSh6+lVgAGXMd2S90/79H9czpOXpd+CsKk7FEe8fBab2whwCdeF6n/ISOFUPuLT1RFbSWLsnSMvTOVUmREJVM74tz1uVGov1C6tR9/rs2zJqefoL6FfYDpSy6SKdQDNDGkAI2yeqvKk4ci/GilMIk40aZ1w95OpIQVgWOKFQDZTLnUAzQbYCorz2HQlhW5flakmFVYFBVWYrjtiN9yEoLIkwkAa0a0jL0x8m4SLrxAKGGenqPMIDVbZrUNqfWE1Qk1AFqAc83HLkVZzrs0AGgKhQDwJG41xluVpGeB99SGKLtTx9i3LTJFUAC5u23MkFcsjTZwWYFjie5CrLUwNYRtnwHYkd+fbENHyr/eZG9als5AJphLSLqdZxL1BubzqyGFP3mkmXFNN74jtrMW8FRdgB7u4EfGiWHGXZd6QcA7iCcBEz1yQkXa7F3ID2wUeEY0HATJ9wRoVxo6SunI+N18gqCu8qtlzrVCNzaYtFVx8BVwJ4UYB5Ec4pfKrYciEOoOiqjXC1ptxv10pzcXccQBQC5VqdhaQkSGO5XN2dRWGWM13XySxKej1zBNmrBRvnYy25fyDIS1kWOdG49zNqseG92xPhcSRsqWuhgoXSn3jOFH3F71Kuaatha91RTpp7d0lhzYxQXQra3W66yO4AHL86vW+nNOpOofgI9JaZAzOAmt80p9tAmgFivlu5Aaw1ZoB2Nkglt/kHJNmSsf08lfBoU2pIy9W/14JuNCTs9e3olpRC1n8B+Qd0Dhp9ddQMugAAAABJRU5ErkJggg==',\n\t\t\ttitle : '执行函数',\n\t\t\tdesc : '单独执行函数方法，结果不保存为变量'\n\t\t},{\n\t\t\tname : 'process',\n\t\t\timage : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACMklEQVRYR+2WwXHTQBSGv+fMkNwIB7BvMRVgV0DoIFRAUoGlCpJUsKICnApCB4QKcCrAvslwwEebGfyYp2g1QgmytBmPOaCjtLv/t+/9+neFHT+yY31aAxw6PdwXzgVOgH5lA1OEcTqSy6Ybaw3QdZqIMKoTUOX9PJaoCUQIwEKEp7+U4fdYJmWR504He8IXYJpG8tK+9RJVP0ZhIkKSjuTKv9sOgDJLY8naUwYog6yUN4tYFq0Bek4vEM5ry6tcprFclMeYd57AcQcShCOrxjySYWsAWzTzgZlQOPoDRJkB46p4FeQAJtlc5SwIoIm56sa8cHrSEa4zTzx2sZD51o4D4YfN3QlAz2kf4evOAIoWKLetKxCShD2np2ksY9txXn7Lin6QCdsmoYkjfFgqz/Y7vBYlMXFVbuexDB6sQNdphPBOYOBNlkaSje06bZyEXrxqVBNfwfG9IMrL+6ksHASQJ2EVwIQFEt+OeybsOrWsfoUyW0P0E26MsryDtknoIXwFq9UoWmBlF8GZ+BIGVeHyxLZJWDZhHUC2+7Xy9lssH0MCJmROUQF/aplb63YfIlI3598B8AbcWQsKE8J0qQx9G3pO7Xg99WUMScJGLchD5u43hKkK0WrN5/zUGqeRnBV3gW3dCbPdwU0OUQXPINokYRPDPhjF9t8qWC5YNe4e5cpa0QigdCfcBLHxNMx+z1zcFmubhI8HqJiw8EHgnfCvSbiJdFvfN7ZgW8J+3f8AvwFIqFFyobd5IgAAAABJRU5ErkJggg==',\n\t\t\ttitle : '子流程',\n\t\t\tdesc : '执行其他spiderFlow流程，父子流程变量共享'\n\t\t}];\n\t\tvar addShape = function(shape){\n\t\t\tvar image = new Image();\n\t\t\timage.src = shape.image;\n\t\t\timage.title = shape.title;\n\t\t\timage.id = shape.name;\n\t\t\timage.onclick = function (ev) {\n\t\t\t\tif(shape.desc){\n\t\t\t\t\tlayer.tips(\"(\" + shape.name + \")\" + shape.title + \"<hr/>\" + shape.desc, '#' + shape.name,{\n\t\t\t\t\t\ttips: [1, '#3595CC'],\n\t\t\t\t\t\tarea: ['auto', 'auto'],\n\t\t\t\t\t\ttime: 4000\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\tif(!shape.hidden){\n\t\t\t\tcontainer.appendChild(image);\n\t\t\t}\n\t\t\t\n\t\t\tif(!shape.disabled){\n\t\t\t\teditor.addShape(shape.name,shape.title || 'Label',image,shape.defaultAdd);\n\t\t\t}\n\t\t}\n\t\tfor(var i =0,len = shapes.length;i<len;i++){\n\t\t\taddShape(shapes[i]);\n\t\t}\n\t\t$.ajax({\n\t\t\turl : 'spider/shapes',\n\t\t\ttype : 'post',\n\t\t\tdataType : 'json',\n\t\t\tasync :false,\n\t\t\tsuccess : function(shapeExts){\n\t\t\t\tfor(var i =0,len = shapeExts.length;i<len;i++){\n\t\t\t\t\tvar shape = shapeExts[i];\n\t\t\t\t\taddShape(shape);\n\t\t\t\t\tvar image = new Image();\n\t\t\t\t\timage.src = shape.image;\n\t\t\t\t\timage.title = shape.title;\n\t\t\t\t\teditor.addShape(shape.name,shape.title || 'Label',image,false);\n\t\t\t\t\tresizeSlideBar();\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n});\n\n/**\n * 绑定工具条点击事件\n */\nfunction bindToolbarClickAction(editor){\n\t$(\".xml-container textarea\").bind('input propertychange',function(){\n\t\teditor.setXML($(this).val());\n\t\teditor.onSelectedCell();\n\t})\n\t$(\".toolbar-container\").on('click','.btn-delete',function(){\n\t\teditor.deleteSelectCells();\n\t}).on(\"click\",\".btn-selectAll\",function(){\n\t\teditor.execute('selectAll');\n\t}).on('click',\".btn-undo\",function(){\n\t\teditor.execute('undo');\n\t}).on('click',\".btn-redo\",function(){\n\t\teditor.execute('redo');\n\t}).on('click',\".btn-cut\",function(){\n\t\teditor.execute('cut');\n\t}).on('click',\".btn-copy\",function(){\n\t\teditor.execute('copy');\n\t}).on('click',\".btn-paste\",function(){\n\t\teditor.execute('paste');\n\t}).on('click',\".btn-console-xml\",function(){\n\t\tconsole.log(editor.getXML());\n\t}).on('click',\".btn-edit-xml\",function(){\n\t\t$(\".editor-container\").hide();\n\t\t$(\".xml-container textarea\").val(editor.getXML());\n\t\t$(\".xml-container\").show();\n\t\t$(this).removeClass('btn-edit-xml').addClass('btn-graphical-xml');\n\t}).on('click',\".btn-graphical-xml\",function(){\n\t\t$(\".editor-container\").show();\n\t\t$(\".xml-container\").hide();\n\t\t$(this).removeClass('btn-graphical-xml').addClass('btn-edit-xml');\n//\t\teditor.setXML($(\".xml-container textarea\").val());\n//\t\teditor.onSelectedCell();\n\t}).on('click','.btn-stop:not(.disabled)',function () {\n\t\tsocket.send(JSON.stringify({\n\t\t\teventType : 'stop'\n\t\t}));\n\t}).on('click','.btn-resume:not(.disabled)',function () {\n\t\t$(this).addClass('disabled')\n\t\t$(\".spiderflow-debug-tooltip\").remove();\n\t\tsocket.send(JSON.stringify({\n\t\t\teventType : 'resume'\n\t\t}));\n\t}).on('click','.btn-test',function(){\n\t\trunSpider(false);\n\t}).on('click','.btn-debug',function(){\n\t\trunSpider(true);\n\t}).on('click',\".btn-return\",function(){\n\t\tparent.openTab('爬虫列表','welcome','spiderList.html')\n\t}).on('click','.btn-save',function(){\n\t\tSave();\n\t}).on('click','.btn-dock-right',function(){\n\t\t$('.main-container').addClass('right');\n\t\t$('.main-container .properties-container').width('40%');\n\t\t$('.main-container .resize-container').attr('style','top:0px;left:auto;');\n\t\tlayui.table.resize('spider-variable');\n\t\tvar resize = $('.resize-container')[0]\n\t\tresize.onmousedown = function(e){\n\t\t\tvar startX = e.clientX;\n\t\t\tresize.left = resize.offsetLeft;\n\t\t\tvar box = $(\"body\")[0];\n\t\t\tdocument.onmousemove = function(e){\n\t\t\t\tlayui.table.resize('spider-variable');\n\t\t\t\tvar endX = e.clientX;\n\t\t\t\tvar moveLen = resize.left + (endX - startX);\n\t\t\t\tvar maxT = box.clientWidth - resize.offsetWidth;\n\t\t\t\tif(moveLen<150) moveLen = 150;\n\t\t\t\tif(moveLen>maxT-150) moveLen = maxT-150;\n\t\t\t\tif(box.clientWidth - moveLen < 400 || box.clientWidth - moveLen > 800){\n\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tresize.style.left = moveLen + 'px';\n\t\t\t\t$(\".editor-container\").css('right',($('body').width() - moveLen) + 'px')\n\t\t\t\t$(\".properties-container\").width(box.clientWidth - moveLen - 5);\n\t\t\t\t$(\".xml-container\").width($(\".main-container\").width() - $(\".properties-container\").width() - $(\".sidebar-container\").width() + 8);\n\t\t\t}\n\t\t\tdocument.onmouseup = function(evt){\n\t\t\t\tdocument.onmousemove = null;\n\t\t\t\tdocument.onmouseup = null;\n\t\t\t\tresize.releaseCapture && resize.releaseCapture();\n\t\t\t}\n\t\t\tresize.setCapture && resize.setCapture();\n\t\t\treturn false;\n\t\t}\n\t}).on('click','.btn-dock-bottom',function(){\n\t\tresizeSlideBar();\n\t\t$('.main-container').removeClass('right');\n\t\t$('.properties-container').height(200).width('100%');\n\t\t$('.sidebar-container').css('bottom','200px');\n\t\t$('.editor-container').css('bottom','200px');\n\t\t$('.main-container .resize-container').attr('style','left:0px;top:auto;bottom:190px');\n\t\tvar resize = $('.resize-container')[0]\n\t\tresize.onmousedown = function(e){\n\t\t\tvar startY = e.clientY;\n\t\t\tresize.top = resize.offsetTop;\n\t\t\tvar box = $(\"body\")[0];\n\t\t\tvar maxT = box.clientHeight;\n\t\t\tdocument.onmousemove = function(e){\n\t\t\t  var moveLen = e.clientY;\n\t\t\t  if(moveLen<250) moveLen = 250;\n\t\t\t  if(moveLen>maxT-150) moveLen = maxT-150;\n\t\t\t  resize.style.top = moveLen + 'px';\n\t\t\t  resizeSlideBar();\n\t\t\t  $(\".editor-container,.sidebar-container,.xml-container\").css('bottom',($('body').height() - moveLen) + 'px');\n\t\t\t  $(\".properties-container\").height(box.clientHeight - moveLen - 5);\n\t\t\t}\n\t\t\tdocument.onmouseup = function(evt){\n\t\t\t\tdocument.onmousemove = null;\n\t\t\t\tdocument.onmouseup = null;\n\t\t\t\tresize.releaseCapture && resize.releaseCapture();\n\t\t\t}\n\t\t\tresize.setCapture && resize.setCapture();\n\t\t\treturn false;\n\t\t}\n\t})\n\t$('.btn-dock-bottom').click();\n}\nfunction runSpider(debug){\n\tvalidXML(function(){\n\t\t$(\".btn-debug,.btn-test,.btn-resume\").addClass('disabled');\n\t\t$(\".btn-stop\").removeClass('disabled');\n\t\tvar LogViewer;\n\t\tvar tableMap = {};\n\t\tvar first = true;\n\t\tvar filterText = '';\n\t\tvar testWindowIndex = layui.layer.open({\n\t\t\tid : 'test-window',\n\t\t\ttype : 1,\n\t\t\tskin : 'layer-test',\n\t\t\tcontent : '<div class=\"test-window-container\"><div class=\"output-container\"><div class=\"layui-tab layui-tab-fixed layui-tab-brief\"><ul class=\"layui-tab-title\"></ul><div class=\"layui-tab-content\"></div></div></div><canvas class=\"log-container\" width=\"677\" height=\"100\"></canvas></div>',\n\t\t\tarea : [\"680px\",\"400px\"],\n\t\t\tshade : 0,\n\t\t\toffset : 'rt',\n\t\t\tmaxmin :true,\n\t\t\tmaxWidth : 700,\n\t\t\tmaxHeight : 400,\n\t\t\ttitle : '测试窗口',\n\t\t\tbtn : ['关闭','显示/隐藏输出','显示/隐藏日志','停止'],\n\t\t\tbtn2 : function(){\n\t\t\t\tvar $output = $(\".test-window-container .output-container\");\n\t\t\t\tvar $log = $(\".test-window-container .log-container\");\n\t\t\t\tif($output.is(\":hidden\")){\n\t\t\t\t\t$output.show();\n\t\t\t\t\t$output.find(\"canvas\").each(function(){\n\t\t\t\t\t\tif($log.is(\":hidden\")){\n\t\t\t\t\t\t\tthis.height = 290;\n\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\tthis.height = 200;\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t$log.attr('height',100)\n\t\t\t\t\tLogViewer.resize();\n\t\t\t\t\tfor(var tableId in tableMap){\n\t\t\t\t\t\ttableMap[tableId].instance.resize();\n\t\t\t\t\t}\n\t\t\t\t}else{\n\t\t\t\t\t$output.hide();\n\t\t\t\t\t$log.attr('height',320);\n\t\t\t\t\tLogViewer.resize();\n\t\t\t\t\tfor(var tableId in tableMap){\n\t\t\t\t\t\ttableMap[tableId].instance.resize();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t},\n\t\t\tbtn3 : function(){\n\t\t\t\tvar $output = $(\".test-window-container .output-container\");\n\t\t\t\tvar $log = $(\".test-window-container .log-container\");\n\t\t\t\tif($log.is(\":hidden\")){\n\t\t\t\t\t$log.show();\n\t\t\t\t\t$log.attr('height',$output.is(\":hidden\") ? 320 : 100)\n\t\t\t\t\t$output.find(\"canvas\").each(function(){\n\t\t\t\t\t\tthis.height = 200;\n\t\t\t\t\t});\n\t\t\t\t\tLogViewer.resize();\n\t\t\t\t\tfor(var tableId in tableMap){\n\t\t\t\t\t\ttableMap[tableId].instance.resize();\n\t\t\t\t\t}\n\t\t\t\t}else{\n\t\t\t\t\t$log.hide();\n\t\t\t\t\t$output.find(\"canvas\").each(function(){\n\t\t\t\t\t\tthis.height = 320;\n\t\t\t\t\t});\n\t\t\t\t\tLogViewer.resize();\n\t\t\t\t\tfor(var tableId in tableMap){\n\t\t\t\t\t\ttableMap[tableId].instance.resize();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t},\n\t\t\tbtn4 : function(){\n\t\t\t\tvar $btn = $(\"#layui-layer\" + testWindowIndex).find('.layui-layer-btn3');\n\t\t\t\tif($btn.html() == '停止'){\n\t\t\t\t\tsocket.send(JSON.stringify({\n\t\t\t\t\t\teventType : 'stop'\n\t\t\t\t\t}));\n\t\t\t\t}else{\n\t\t\t\t\t$(\".btn-debug,.btn-test,.btn-resume\").addClass('disabled');\n\t\t\t\t\t$(\".btn-stop\").removeClass('disabled');\n\t\t\t\t\tsocket.send(JSON.stringify({\n\t\t\t\t\t\teventType : debug ? 'debug' : 'test',\n\t\t\t\t\t\tmessage : editor.getXML()\n\t\t\t\t\t}));\n\t\t\t\t\t$btn.html('停止');\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t},\n\t\t\tend : function(){\n\t\t\t\tif(socket){\n\t\t\t\t\tsocket.close();\n\t\t\t\t\t$(\".spiderflow-debug-tooltip\").remove();\n\t\t\t\t\t$(\".btn-stop,.btn-resume\").addClass('disabled');\n\t\t\t\t\t$(\".btn-test,.btn-debug\").removeClass('disabled')\n\t\t\t\t}\n\t\t\t\tif(LogViewer){\n\t\t\t\t\tLogViewer.destory();\n\t\t\t\t}\n\t\t\t\tfor(var tableId in tableMap){\n\t\t\t\t\ttableMap[tableId].instance.destory();\n\t\t\t\t}\n\t\t\t},\n\t\t\tsuccess : function(layero,index){\n\t\t\t\tvar logElement = $(\".test-window-container .log-container\")[0];\n\t\t\t\tvar colors = {\n\t\t\t\t\t'array' : '#2a00ff',\n\t\t\t\t\t'object' : '#2a00ff',\n\t\t\t\t\t'boolean' : '#600100',\n\t\t\t\t\t'number' : '#000E59'\n\t\t\t\t}\n\t\t\t\tLogViewer = new CanvasViewer({\n\t\t\t\t\telement : logElement,\n\t\t\t\t\tonClick : function(e){\n\t\t\t\t\t\tonCanvasViewerClick(e,'日志');\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\t$(layero).find(\".layui-layer-btn\")\n\t\t\t\t\t.append('<div class=\"layui-inline\"><input type=\"text\" class=\"layui-input\" placeholder=\"输入关键字过滤日志\"/></div>')\n\t\t\t\t\t.on(\"keyup\",\"input\",function(){\n\t\t\t\t\t\tLogViewer.filter(this.value);\n\t\t\t\t\t});\n\t\t\t\tsocket = createWebSocket({\n\t\t\t\t\tonopen : function(){\n\t\t\t\t\t\tsocket.send(JSON.stringify({\n\t\t\t\t\t\t\teventType : debug ? 'debug' : 'test',\n\t\t\t\t\t\t\tmessage : editor.getXML()\n\t\t\t\t\t\t}));\n\t\t\t\t\t},\n\t\t\t\t\tonmessage : function(e){\n\t\t\t\t\t\tvar event = JSON.parse(e.data);\n\t\t\t\t\t\tvar eventType = event.eventType;\n\t\t\t\t\t\tvar message = event.message;\n\t\t\t\t\t\tif(eventType == 'finish'){\n\t\t\t\t\t\t\t$(\".spiderflow-debug-tooltip\").remove();\n\t\t\t\t\t\t\t$(\"#layui-layer\" + testWindowIndex).find('.layui-layer-btn3').html('重新开始');\n\t\t\t\t\t\t\t$(\".btn-stop,.btn-resume\").addClass('disabled');\n\t\t\t\t\t\t\t$(\".btn-test,.btn-debug\").removeClass('disabled')\n\t\t\t\t\t\t}else if(eventType == 'output'){\n\t\t\t\t\t\t\tvar tableId = 'output-' + message.nodeId;\n\t\t\t\t\t\t\tvar $table = $('#' + tableId);\n\t\t\t\t\t\t\tif($table.length == 0){\n\t\t\t\t\t\t\t\ttableMap[tableId] = {\n\t\t\t\t\t\t\t\t\tindex : 0\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tvar $tab = $(\".test-window-container .output-container .layui-tab\")\n\t\t\t\t\t\t\t\tvar outputTitle = '输出-'+tableId;\n\t\t\t\t\t\t\t\tvar cell = editor.getModel().cells[message.nodeId];\n\t\t\t\t\t\t\t\tif(cell){\n\t\t\t\t\t\t\t\t\toutputTitle = cell.value;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif(first){\n\t\t\t\t\t\t\t\t\t$tab.find(\".layui-tab-title\").append('<li  class=\"layui-this\">' + outputTitle + '</li>');\n\t\t\t\t\t\t\t\t\t$tab.find(\".layui-tab-content\").append('<div class=\"layui-tab-item layui-show\" data-output=\"'+tableId+'\"></div>');\n\t\t\t\t\t\t\t\t\tfirst = false;\n\t\t\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\t\t\t$tab.find(\".layui-tab-title\").append('<li>' + outputTitle + '</li>');\n\t\t\t\t\t\t\t\t\t$tab.find(\".layui-tab-content\").append('<div class=\"layui-tab-item\" data-output=\"'+tableId+'\"></div>');\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t$table = $('<canvas width=\"677\" height=\"200\"/>').appendTo($(\".test-window-container .output-container .layui-tab-item[data-output=\"+tableId+\"]\"));\n\t\t\t\t\t\t\t\t$table.attr('id',tableId);\n\t\t\t\t\t\t\t\ttableMap[tableId].instance = new CanvasViewer({\n\t\t\t\t\t\t\t\t\telement : document.getElementById(tableId),\n\t\t\t\t\t\t\t\t\tgrid : true,\n\t\t\t\t\t\t\t\t\theader : true,\n\t\t\t\t\t\t\t\t\tstyle : {\n\t\t\t\t\t\t\t\t\t\tfont : 'bold 13px Consolas'\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tonClick : function(e){\n\t\t\t\t\t\t\t\t\t\tonCanvasViewerClick(e,'表格');\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\tvar cols = [];\n\t\t\t\t\t\t\t\tvar texts = [new CanvasText({\n\t\t\t\t\t\t\t\t\ttext : '序号',\n\t\t\t\t\t\t\t\t\tmaxWidth : 100\n\t\t\t\t\t\t\t\t})];\n\t\t\t\t\t\t\t\tfor(var i =0,len = message.outputNames.length;i<len;i++){\n\t\t\t\t\t\t\t\t\ttexts.push(new CanvasText({\n\t\t\t\t\t\t\t\t\t\ttext : message.outputNames[i],\n\t\t\t\t\t\t\t\t\t\tmaxWidth : 200,\n\t\t\t\t\t\t\t\t\t\tclick : true\n\t\t\t\t\t\t\t\t\t}));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\ttableMap[tableId].instance.append(texts);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tvar texts = [new CanvasText({\n\t\t\t\t\t\t\t\ttext : ++tableMap[tableId].index,\n\t\t\t\t\t\t\t\tmaxWidth : 200,\n\t\t\t\t\t\t\t\tclick : true\n\t\t\t\t\t\t\t})];\n\t\t\t\t\t\t\tfor(var i =0,len = message.outputNames.length;i<len;i++){\n\t\t\t\t\t\t\t\tvar displayText = message.values[i];\n\t\t\t\t\t\t\t\tvar variableType = 'string';\n\t\t\t\t\t\t\t\tif(Array.isArray(displayText)){\n\t\t\t\t\t\t\t\t\tvariableType = 'array';\n\t\t\t\t\t\t\t\t\tdisplayText = JSON.stringify(displayText);\n\t\t\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\t\t\tvariableType = typeof displayText;\n\t\t\t\t\t\t\t\t\tif(variableType == 'object'){\n\t\t\t\t\t\t\t\t\t\tdisplayText = JSON.stringify(displayText);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\ttexts.push(new CanvasText({\n\t\t\t\t\t\t\t\t\ttext : displayText,\n\t\t\t\t\t\t\t\t\tmaxWidth : 200,\n\t\t\t\t\t\t\t\t\tcolor : colors[variableType] || 'black',\n\t\t\t\t\t\t\t\t\tclick : true\n\t\t\t\t\t\t\t\t}));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ttableMap[tableId].instance.append(texts);\n\t\t\t\t\t\t\ttableMap[tableId].instance.scrollTo(-1);\n\t\t\t\t\t\t}else if(eventType == 'log'){\n\t\t\t\t\t\t\tvar texts = [];\n\t\t\t\t\t\t\tvar defaultColor = message.level == 'error' ? 'red' : '';\n\t\t\t\t\t\t\ttexts.push(new CanvasText({\n\t\t\t\t\t\t\t\ttext : message.level,\n\t\t\t\t\t\t\t\tcolor : defaultColor\n\t\t\t\t\t\t\t}));\n\t\t\t\t\t\t\ttexts.push(new CanvasText({\n\t\t\t\t\t\t\t\ttext : event.timestamp,\n\t\t\t\t\t\t\t\tcolor : defaultColor\n\t\t\t\t\t\t\t}));\n\t\t\t\t\t\t\tvar temp = message.message.split(\"{}\");\n\t\t\t\t\t\t\tmessage.variables = message.variables || [];\n\t\t\t\t\t\t\tfor(var i=0,len=temp.length;i<len;i++){\n\t\t\t\t\t\t\t\tif(temp[i]!=''){\n\t\t\t\t\t\t\t\t\ttexts.push(new CanvasText({\n\t\t\t\t\t\t\t\t\t\ttext : temp[i],\n\t\t\t\t\t\t\t\t\t\tcolor : defaultColor\n\t\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tvar object = message.variables[i];\n\t\t\t\t\t\t\t\tif(object != undefined){\n\t\t\t\t\t\t\t\t\tvar variableType = '';\n\t\t\t\t\t\t\t\t\tvar displayText = object;\n\t\t\t\t\t\t\t\t\tif(Array.isArray(object)){\n\t\t\t\t\t\t\t\t\t\tvariableType = 'array';\n\t\t\t\t\t\t\t\t\t\tdisplayText = JSON.stringify(displayText);\n\t\t\t\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\t\t\t\tvariableType = typeof object;\n\t\t\t\t\t\t\t\t\t\tif(variableType == 'object'){\n\t\t\t\t\t\t\t\t\t\t\tdisplayText = JSON.stringify(displayText);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\ttexts.push(new CanvasText({\n\t\t\t\t\t\t\t\t\t\ttext : displayText,\n\t\t\t\t\t\t\t\t\t\tmaxWidth : 330,\n\t\t\t\t\t\t\t\t\t\tcolor : colors[variableType] || '#025900',\n\t\t\t\t\t\t\t\t\t\tclick : true\n\t\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tLogViewer.append(texts);\n\t\t\t\t\t\t\tLogViewer.scrollTo(-1);\n\t\t\t\t\t\t}else if(eventType == 'debug'){\n\t\t\t\t\t\t\t$(\".btn-resume\").removeClass('disabled');\n\t\t\t\t\t\t\tvar type = message.event;\n\t\t\t\t\t\t\teditor.selectCell(editor.graph.model.cells[message.nodeId]);\n\t\t\t\t\t\t\tvar selector;\n\t\t\t\t\t\t\tif(type == 'request-parameter'){\n\t\t\t\t\t\t\t\t$('.layui-tab-title li:eq(1)').click();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif(type == 'request-cookie'){\n\t\t\t\t\t\t\t\t$('.layui-tab-title li:eq(2)').click();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif(type == 'request-header'){\n\t\t\t\t\t\t\t\t$('.layui-tab-title li:eq(3)').click();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif(type == 'request-body'){\n\t\t\t\t\t\t\t\t$('.layui-tab-title li:eq(4)').click();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif(type == 'common' || type == 'request-parameter' || type == 'request-header' || type == 'request-cookie'){\n\t\t\t\t\t\t\t\tselector = '.layui-table-cell input[value='+message.key+']';\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif($(selector).length == 0){\n\t\t\t\t\t\t\t\tselector = '.properties-container input[name='+message.key+']';\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif($(selector).length == 0){\n\t\t\t\t\t\t\t\tselector = '.properties-container [codemirror='+message.key+']';\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tvar o1 = $(selector).offset();\n\t\t\t\t\t\t\tvar $parent = $(\".properties-container\");\n\t\t\t\t\t\t\tvar o2 = $parent.offset();\n\t\t\t\t\t\t\tif(o1.top > o2.top + $parent.height()){\n\t\t\t\t\t\t\t\t$parent[0].scrollTop = o1.top - o2.top;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tvar msg = message.value;\n\t\t\t\t\t\t\tvar isJson = Array.isArray(msg) || typeof msg == 'object';\n\t\t\t\t\t\t\tif(!isJson){\n\t\t\t\t\t\t\t\tvar temp = document.createElement(\"div\");\n\t\t\t\t\t\t\t\t(temp.textContent != null) ? (temp.textContent = msg) : (temp.innerText = msg);\n\t\t\t\t\t\t\t\tmsg = temp.innerHTML;\n\t\t\t\t\t\t\t\ttemp = null;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tvar content = '<div class=\"message-content\" style=\"padding:10px;'+(isJson ? '':'font-weight: bold;font-family:Consolas;font-size:12px;')+'\">'+(isJson ? '' : msg.replace(/\\n/g,'<br>')).replace(/ /g,'&nbsp;').replace(/\\t/g,'&nbsp;&nbsp;&nbsp;&nbsp;')+'</div>';\n\t\t\t\t\t\t\tvar tooltip = bindTooltip(content,selector);\n\t\t\t\t\t\t\tif(isJson){\n\t\t\t\t\t\t\t\tvar $dom = $(tooltip.dom).find(\".message-content\");\n\t\t\t\t\t\t\t\tjsonTree.create(msg,$dom[0]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t})\n\t});\n}\nfunction bindTooltip(content,selector){\n\tvar dom = document.createElement('div');\n\tvar $target = $(selector);\n\tvar offset = $target.offset();\n\tdom.className = 'spiderflow-debug-tooltip';\n\tdom.style.bottom = ($(\"body\").height() - offset.top) + 'px';\n\tdom.style.left = (offset.left + $target.width() / 2) + 'px';\n\tdom.innerHTML = '<div class=\"content\">' + content + '</div>';\n\tdocument.body.appendChild(dom);\n\t$(selector).offset();\n\treturn {\n\t\tdom : dom,\n\t\tclose : function(){\n\t\t\tdocument.body.removeChild(dom);\n\t\t}\n\t}\n\n}\n//最近点击打开的弹窗\nvar index;\nfunction onCanvasViewerClick(e,source){\n\tvar msg = e.text;\n\tvar json;\n\ttry{\n\t\tjson = JSON.parse(msg);\n\t\tif(!(Array.isArray(json) || typeof json == 'object')){\n\t\t\tjson = null;\n\t\t}\n\t}catch(e){\n\t\t\n\t}\n\tif(!json){\n\t\tvar temp = document.createElement(\"div\");\n\t\t(temp.textContent != null) ? (temp.textContent = msg) : (temp.innerText = msg);\n\t\tmsg = temp.innerHTML;\n\t\ttemp = null;\n\t}\n\tlayer.close(index);\n\tindex = layer.open({\n\t  type : 1,\n\t  title : source +'内容',\n\t  content: '<div class=\"message-content\" style=\"padding:10px;'+(json ? '':'font-weight: bold;font-family:Consolas;font-size:12px;')+'\">'+(json ? '' : msg.replace(/\\n/g,'<br>')).replace(/ /g,'&nbsp;').replace(/\\t/g,'&nbsp;&nbsp;&nbsp;&nbsp;')+'</div>',\n\t  shade : 0,\n\t  area : json ? ['700px','500px'] : 'auto',\n\t  maxmin : true,\n\t  maxWidth : (json ? undefined : 700),\n\t  maxHeight : (json ? undefined : 400),\n\t  success : function(dom,index){\n\t\t var $dom = $(dom).find(\".message-content\");\n\t\t if(json){\n\t\t\t jsonTree.create(json,$dom[0]);\n\t\t }\n\t  }\n\t}); \n}\nfunction createWebSocket(options){\n\toptions = options || {};\n\tvar socket;\n\tif(location.host === 'demo.spiderflow.org'){\n\t\tsocket = new WebSocket(options.url || 'ws://49.233.182.130:8088/ws');\n\t}else{\n\t\tsocket = new WebSocket(options.url || (location.origin.replace(\"http\",'ws') + '/ws'));\n\t}\n\tsocket.onopen = options.onopen;\n\tsocket.onmessage = options.onmessage;\n\tsocket.onerror = options.onerror || function(){\n\t\tlayer.layer.msg('WebSocket错误');\n\t}\n\treturn socket;\n}\n\nvar flowId;\nfunction Save(){\n\tvalidXML(function(){\n\t\t$.ajax({\n\t\t\turl : 'spider/save',\n\t\t\ttype : 'post',\n\t\t\tdata : {\n\t\t\t\tid : getQueryString('id') || flowId,\n\t\t\t\txml : editor.getXML(),\n\t\t\t\tname : editor.graph.getModel().getRoot().data.get('spiderName') || '未定义名称',\n\t\t\t},\n\t\t\tsuccess : function(id) {\n\t\t\t\tflowId = id;\n\t\t\t\tlayui.layer.msg('保存成功', {\n\t\t\t\t\ttime : 800\n\t\t\t\t}, function() {\n\t\t\t\t\t// location.href = \"spiderList.html\";\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\t});\n}\n\nfunction allowDrop(ev){\n\tev.preventDefault();\n}\n\nfunction drag(ev){\n\tev.dataTransfer.setData(\"moverTarget\", ev.target.id);\n}\n\nfunction drop(ev){\n\tvar moverTargetId = ev.dataTransfer.getData(\"moverTarget\");\n\t$(ev.target).parents(\".draggable\").before($(\"#\" + moverTargetId));\n\tev.preventDefault();\n\tserializeForm();\n}"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/index.js",
    "content": "var $ = layui.$;\r\nfunction setCookie(name,value){\r\n\tvar Days = 30;\r\n\tvar exp = new Date();\r\n\texp.setTime(exp.getTime() + Days*24*60*60*1000);\r\n\tdocument.cookie = name + \"=\"+ escape (value) + \";expires=\" + exp.toGMTString();\r\n}\r\nfunction getCookie(name){\r\n\tvar arr,reg=new RegExp(\"(^| )\"+name+\"=([^;]*)(;|$)\");\r\n\tif(arr=document.cookie.match(reg))\r\n\t\treturn unescape(arr[2]);\r\n\telse\r\n\t\treturn null;\r\n}\r\nfunction setTheSkin(value){\r\n\tif(!value){\r\n\t\tvalue = \"layui-blue\";\r\n\t}\r\n\tdocument.querySelector('#theSkin').setAttribute('href','css/'+value+'.css');\r\n}\r\nsetTheSkin(getCookie('theSkin'));\r\nfunction openTab(title,id,href){\r\n\tif($(\".layui-tab[lay-filter=admin-tab]\").find(\"[lay-id=\"+id+\"]\").length > 0){\t//判断是否已打开\r\n\t\tvar $dom =  $(\".layui-tab[lay-filter=admin-tab]\");\r\n\t\tvar index = $dom.find(\"[lay-id=\"+id+\"]\").index();\r\n\t\t$dom.find(\".layui-tab-content .layui-tab-item\").eq(index).find(\"iframe\").attr(\"src\",href);\r\n\t}else{\r\n\t\tvar html  = '<iframe src=\"'+href+'\" width=\"100%\" height=\"100%\" scrolling=\"yes\" frameborder=\"0\"></iframe>';\r\n\t\tlayui.element.tabAdd('admin-tab',{\r\n\t\t\ttitle:title,\r\n\t\t\tcontent:html,\r\n\t\t\tid:id,\r\n\t\t});\r\n\t}\r\n\tlayui.element.tabChange(\"admin-tab\",id);\r\n}\r\n$(function(){\r\n\t$.ajax({\r\n\t\turl:'spider/pluginConfigs',\r\n\t\tsuccess:function(data){\r\n\t\t\tfor(var i =0;i<data.length;i++){\r\n\t\t\t\t$(\".menu-list .layui-nav-tree\").append('<li class=\"layui-nav-item layui-nav-itemed\"><a data-link=\"'+data[i].url+'\" title=\"'+data[i].name+'\">'+data[i].name+'</a></li>');\r\n\t\t\t}\r\n\t\t\tlayui.element.init();\r\n\t\t\tinitMenu();\r\n\t\t}\r\n\t})\r\n});\r\nfunction initMenu() {\r\n\t$(\"body\").on('click','.menu-list li a',function(){\r\n\t\t$(this).parents(\"ul\").siblings().find(\"li.layui-this,dd.layui-this\").removeClass('layui-this')\r\n\t}).on('click','.menu-list > ul',function(){\r\n\t\t$(this).siblings().find('.layui-nav-itemed').removeClass('layui-nav-itemed')\r\n\t}).on('click','.menu-list a',function(){\r\n\t\tvar href = $(this).data('link');\r\n\t\tif(href){\r\n\t\t\tvar title = $(this).html();\r\n\t\t\topenTab(title, title, href);\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}).on('click','.layui-layout-right .layui-nav-child a',function(e){\r\n\t\te.preventDefault();\r\n\t\tvar value = $(this).data('value');\r\n\t\tsetTheSkin(value);\r\n\t\tsetCookie('theSkin',value);\r\n\t});\r\n}"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/jsontree/jsontree.css",
    "content": "/*\r\n * JSON Tree Viewer\r\n * http://github.com/summerstyle/jsonTreeViewer\r\n *\r\n * Copyright 2017 Vera Lobacheva (http://iamvera.com)\r\n * Released under the MIT license (LICENSE.txt)\r\n */\r\n\r\n/* Background for the tree. May use for <body> element */\r\n.jsontree_bg {\r\n    background: #FFF;\r\n}\r\n\r\n/* Styles for the container of the tree (e.g. fonts, margins etc.) */\r\n.jsontree_tree {\r\n    margin-left: 30px;\r\n    font-family: 'PT Mono', monospace;\r\n    font-size: 14px;\r\n}\r\n\r\n/* Styles for a list of child nodes */\r\n.jsontree_child-nodes {\r\n    display: none;\r\n    margin-left: 35px; \r\n    margin-bottom: 5px;\r\n    line-height: 2;\r\n}\r\n.jsontree_node_expanded > .jsontree_value-wrapper > .jsontree_value > .jsontree_child-nodes {\r\n    display: block;\r\n}\r\n\r\n/* Styles for labels */\r\n.jsontree_label-wrapper {\r\n    float: left;\r\n    margin-right: 8px;\r\n}\r\n.jsontree_label {\r\n    font-weight: normal;\r\n    vertical-align: top;\r\n    color: #000;\r\n    position: relative;\r\n    padding: 1px;\r\n    border-radius: 4px;\r\n    cursor: default;\r\n}\r\n.jsontree_node_marked > .jsontree_label-wrapper > .jsontree_label {\r\n    background: #fff2aa;\r\n}\r\n\r\n/* Styles for values */\r\n.jsontree_value-wrapper {\r\n    display: block;\r\n    overflow: hidden;\r\n}\r\n.jsontree_node_complex > .jsontree_value-wrapper {\r\n    overflow: inherit;\r\n}\r\n.jsontree_value { \r\n    vertical-align: top;\r\n    display: inline;\r\n}\r\n.jsontree_value_null {\r\n    color: #777;\r\n    font-weight: bold;\r\n}\r\n.jsontree_value_string {\r\n    color: #025900;\r\n    font-weight: bold;\r\n}\r\n.jsontree_value_number {\r\n    color: #000E59;\r\n    font-weight: bold;\r\n}\r\n.jsontree_value_boolean {\r\n    color: #600100;\r\n    font-weight: bold;\r\n}\r\n\r\n/* Styles for active elements */\r\n.jsontree_expand-button {\r\n    position: absolute;\r\n    top: 3px;\r\n    left: -15px;\r\n    display: block;\r\n    width: 11px;\r\n    height: 11px;\r\n    background-image: url('icons.svg');\r\n}\r\n.jsontree_node_expanded > .jsontree_label-wrapper > .jsontree_label > .jsontree_expand-button {\r\n    background-position: 0 -11px;\r\n}\r\n.jsontree_show-more {\r\n    cursor: pointer;\r\n}\r\n.jsontree_node_expanded > .jsontree_value-wrapper > .jsontree_value > .jsontree_show-more {\r\n    display: none;\r\n}\r\n.jsontree_node_empty > .jsontree_label-wrapper > .jsontree_label > .jsontree_expand-button,\r\n.jsontree_node_empty > .jsontree_value-wrapper > .jsontree_value > .jsontree_show-more {\r\n    display: none !important;\r\n}\r\n.jsontree_node_complex > .jsontree_label-wrapper > .jsontree_label {\r\n    cursor: pointer;\r\n}\r\n.jsontree_node_empty > .jsontree_label-wrapper > .jsontree_label {\r\n    cursor: default !important;\r\n}"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/jsontree/jsontree.js",
    "content": "/**\r\n * JSON Tree library (a part of jsonTreeViewer)\r\n * http://github.com/summerstyle/jsonTreeViewer\r\n *\r\n * Copyright 2017 Vera Lobacheva (http://iamvera.com)\r\n * Released under the MIT license (LICENSE.txt)\r\n */\r\n\r\nvar jsonTree = (function() {\r\n    \r\n    /* ---------- Utilities ---------- */\r\n    var utils = {\r\n        \r\n        /*\r\n         * Returns js-\"class\" of value\r\n         * \r\n         * @param val {any type} - value\r\n         * @returns {string} - for example, \"[object Function]\"\r\n         */\r\n        getClass : function(val) {\r\n            return Object.prototype.toString.call(val);\r\n        },\r\n        \r\n        /**\r\n         * Checks for a type of value (for valid JSON data types).\r\n         * In other cases - throws an exception\r\n         * \r\n         * @param val {any type} - the value for new node\r\n         * @returns {string} (\"object\" | \"array\" | \"null\" | \"boolean\" | \"number\" | \"string\")\r\n         */\r\n        getType : function(val) {\r\n            if (val === null) {\r\n                return 'null';\r\n            }\r\n            \r\n            switch (typeof val) {\r\n                case 'number':\r\n                    return 'number';\r\n                \r\n                case 'string':\r\n                    return 'string';\r\n                \r\n                case 'boolean':\r\n                    return 'boolean';\r\n            }\r\n            \r\n            switch(utils.getClass(val)) {\r\n                case '[object Array]':\r\n                    return 'array';\r\n                \r\n                case '[object Object]':\r\n                    return 'object';\r\n            }\r\n            \r\n            throw new Error('Bad type: ' + utils.getClass(val));\r\n        },\r\n        \r\n        /**\r\n         * Applies for each item of list some function\r\n         * and checks for last element of the list\r\n         * \r\n         * @param obj {Object | Array} - a list or a dict with child nodes\r\n         * @param func {Function} - the function for each item\r\n         */\r\n        forEachNode : function(obj, func) {\r\n            var type = utils.getType(obj),\r\n                isLast;\r\n        \r\n            switch (type) {\r\n                case 'array':\r\n                    isLast = obj.length - 1;\r\n                    \r\n                    obj.forEach(function(item, i) {\r\n                        func(i, item, i === isLast);\r\n                    });\r\n                    \r\n                    break;\r\n                \r\n                case 'object':\r\n                    var keys = Object.keys(obj).sort();\r\n                    \r\n                    isLast = keys.length - 1;\r\n                    \r\n                    keys.forEach(function(item, i) {\r\n                        func(item, obj[item], i === isLast);\r\n                    });\r\n                    \r\n                    break;\r\n            }\r\n            \r\n        },\r\n        \r\n        /**\r\n         * Implements the kind of an inheritance by\r\n         * using parent prototype and\r\n         * creating intermediate constructor\r\n         * \r\n         * @param Child {Function} - a child constructor\r\n         * @param Parent {Function} - a parent constructor\r\n         */\r\n        inherits : (function() {\r\n            var F = function() {};\r\n            \r\n            return function(Child, Parent) {\r\n                F.prototype = Parent.prototype;\r\n                Child.prototype = new F();\r\n                Child.prototype.constructor = Child;\r\n            };\r\n        })(),\r\n        \r\n        /*\r\n         * Checks for a valid type of root node*\r\n         *\r\n         * @param {any type} jsonObj - a value for root node\r\n         * @returns {boolean} - true for an object or an array, false otherwise\r\n         */\r\n        isValidRoot : function(jsonObj) {\r\n            switch (utils.getType(jsonObj)) {\r\n                case 'object':\r\n                case 'array':\r\n                    return true;\r\n                default:\r\n                    return false;\r\n            }\r\n        },\r\n\r\n        /**\r\n         * Extends some object\r\n         */\r\n        extend : function(targetObj, sourceObj) {\r\n            for (var prop in sourceObj) {\r\n                if (sourceObj.hasOwnProperty(prop)) {\r\n                    targetObj[prop] = sourceObj[prop];\r\n                }\r\n            }\r\n        }\r\n    };\r\n    \r\n    \r\n    /* ---------- Node constructors ---------- */\r\n    \r\n    /**\r\n     * The factory for creating nodes of defined type.\r\n     * \r\n     * ~~~ Node ~~~ is a structure element of an onject or an array\r\n     * with own label (a key of an object or an index of an array)\r\n     * and value of any json data type. The root object or array\r\n     * is a node without label.\r\n     * {...\r\n     * [+] \"label\": value,\r\n     * ...}\r\n     * \r\n     * Markup:\r\n     * <li class=\"jsontree_node [jsontree_node_expanded]\">\r\n     *     <span class=\"jsontree_label-wrapper\">\r\n     *         <span class=\"jsontree_label\">\r\n     *             <span class=\"jsontree_expand-button\" />\r\n     *             \"label\"\r\n     *         </span>\r\n     *         :\r\n     *     </span>\r\n     *     <(div|span) class=\"jsontree_value jsontree_value_(object|array|boolean|null|number|string)\">\r\n     *         ...\r\n     *     </(div|span)>\r\n     * </li>\r\n     *\r\n     * @param label {string} - key name\r\n     * @param val {Object | Array | string | number | boolean | null} - a value of node\r\n     * @param isLast {boolean} - true if node is last in list of siblings\r\n     * \r\n     * @return {Node}\r\n     */\r\n    function Node(label, val, isLast) {\r\n        var nodeType = utils.getType(val);\r\n        \r\n        if (nodeType in Node.CONSTRUCTORS) {\r\n            return new Node.CONSTRUCTORS[nodeType](label, val, isLast);\r\n        } else {\r\n            throw new Error('Bad type: ' + utils.getClass(val));\r\n        }\r\n    }\r\n    \r\n    Node.CONSTRUCTORS = {\r\n        'boolean' : NodeBoolean,\r\n        'number'  : NodeNumber,\r\n        'string'  : NodeString,\r\n        'null'    : NodeNull,\r\n        'object'  : NodeObject,\r\n        'array'   : NodeArray  \r\n    };\r\n    \r\n    \r\n    /*\r\n     * The constructor for simple types (string, number, boolean, null)\r\n     * {...\r\n     * [+] \"label\": value,\r\n     * ...}\r\n     * value = string || number || boolean || null\r\n     *\r\n     * Markup:\r\n     * <li class=\"jsontree_node\">\r\n     *     <span class=\"jsontree_label-wrapper\">\r\n     *         <span class=\"jsontree_label\">\"age\"</span>\r\n     *         :\r\n     *     </span>\r\n     *     <span class=\"jsontree_value jsontree_value_(number|boolean|string|null)\">25</span>\r\n     *     ,\r\n     * </li>\r\n     *\r\n     * @abstract\r\n     * @param label {string} - key name\r\n     * @param val {string | number | boolean | null} - a value of simple types\r\n     * @param isLast {boolean} - true if node is last in list of parent childNodes\r\n     */\r\n    function _NodeSimple(label, val, isLast) {\r\n        if (this.constructor === _NodeSimple) {\r\n            throw new Error('This is abstract class');\r\n        }\r\n        \r\n        var self = this,\r\n            el = document.createElement('li'),\r\n            labelEl,\r\n            template = function(label, val) {\r\n                var str = '\\\r\n                    <span class=\"jsontree_label-wrapper\">\\\r\n                        <span class=\"jsontree_label\">\"' +\r\n                            label +\r\n                        '\"</span> : \\\r\n                    </span>\\\r\n                    <span class=\"jsontree_value-wrapper\">\\\r\n                        <span class=\"jsontree_value jsontree_value_' + self.type + '\">' +\r\n                            val +\r\n                        '</span>' +\r\n                        (!isLast ? ',' : '') + \r\n                    '</span>';\r\n    \r\n                return str;\r\n            };\r\n            \r\n        self.label = label;\r\n        self.isComplex = false;\r\n    \r\n        el.classList.add('jsontree_node');\r\n        el.innerHTML = template(label, val);\r\n    \r\n        self.el = el;\r\n\r\n        labelEl = el.querySelector('.jsontree_label');\r\n    \r\n        labelEl.addEventListener('click', function(e) {\r\n            if (e.altKey) {\r\n                self.toggleMarked();\r\n                return;\r\n            }\r\n\r\n            if (e.shiftKey) {\r\n                document.getSelection().removeAllRanges();\r\n                alert(self.getJSONPath());\r\n                return;\r\n            }\r\n        }, false);\r\n    }\r\n\r\n    _NodeSimple.prototype = {\r\n        constructor : _NodeSimple,\r\n\r\n        /**\r\n         * Mark node\r\n         */\r\n        mark : function() {\r\n            this.el.classList.add('jsontree_node_marked');    \r\n        },\r\n\r\n        /**\r\n         * Unmark node\r\n         */\r\n        unmark : function() {\r\n            this.el.classList.remove('jsontree_node_marked');    \r\n        },\r\n\r\n        /**\r\n         * Mark or unmark node\r\n         */\r\n        toggleMarked : function() {\r\n            this.el.classList.toggle('jsontree_node_marked');    \r\n        },\r\n\r\n        /**\r\n         * Expands parent node of this node\r\n         *\r\n         * @param isRecursive {boolean} - if true, expands all parent nodes\r\n         *                                (from node to root)\r\n         */\r\n        expandParent : function(isRecursive) {\r\n            if (!this.parent) {\r\n                return;\r\n            }\r\n               \r\n            this.parent.expand(); \r\n            this.parent.expandParent(isRecursive);\r\n        },\r\n\r\n        /**\r\n         * Returns JSON-path of this \r\n         * \r\n         * @param isInDotNotation {boolean} - kind of notation for returned json-path\r\n         *                                    (by default, in bracket notation)\r\n         * @returns {string}\r\n         */\r\n        getJSONPath : function(isInDotNotation) {\r\n            if (this.isRoot) {\r\n                return \"$\";\r\n            }\r\n\r\n            var currentPath;\r\n\r\n            if (this.parent.type === 'array') {\r\n                currentPath = \"[\" + this.label + \"]\";\r\n            } else {\r\n                currentPath = isInDotNotation ? \".\" + this.label : \"['\" + this.label + \"']\";\r\n            }\r\n\r\n            return this.parent.getJSONPath(isInDotNotation) + currentPath; \r\n        }\r\n    };\r\n    \r\n    \r\n    /*\r\n     * The constructor for boolean values\r\n     * {...\r\n     * [+] \"label\": boolean,\r\n     * ...}\r\n     * boolean = true || false\r\n     *\r\n     * @constructor\r\n     * @param label {string} - key name\r\n     * @param val {boolean} - value of boolean type, true or false\r\n     * @param isLast {boolean} - true if node is last in list of parent childNodes\r\n     */\r\n    function NodeBoolean(label, val, isLast) {\r\n        this.type = \"boolean\";\r\n    \r\n        _NodeSimple.call(this, label, val, isLast);\r\n    }\r\n    utils.inherits(NodeBoolean,_NodeSimple);\r\n    \r\n    \r\n    /*\r\n     * The constructor for number values\r\n     * {...\r\n     * [+] \"label\": number,\r\n     * ...}\r\n     * number = 123\r\n     *\r\n     * @constructor\r\n     * @param label {string} - key name\r\n     * @param val {number} - value of number type, for example 123\r\n     * @param isLast {boolean} - true if node is last in list of parent childNodes\r\n     */\r\n    function NodeNumber(label, val, isLast) {\r\n        this.type = \"number\";\r\n    \r\n        _NodeSimple.call(this, label, val, isLast);\r\n    }\r\n    utils.inherits(NodeNumber,_NodeSimple);\r\n    \r\n    \r\n    /*\r\n     * The constructor for string values\r\n     * {...\r\n     * [+] \"label\": string,\r\n     * ...}\r\n     * string = \"abc\"\r\n     *\r\n     * @constructor\r\n     * @param label {string} - key name\r\n     * @param val {string} - value of string type, for example \"abc\"\r\n     * @param isLast {boolean} - true if node is last in list of parent childNodes\r\n     */\r\n    function NodeString(label, val, isLast) {\r\n        this.type = \"string\";\r\n    \r\n        _NodeSimple.call(this, label, '\"' + val + '\"', isLast);\r\n    }\r\n    utils.inherits(NodeString,_NodeSimple);\r\n    \r\n    \r\n    /*\r\n     * The constructor for null values\r\n     * {...\r\n     * [+] \"label\": null,\r\n     * ...}\r\n     *\r\n     * @constructor\r\n     * @param label {string} - key name\r\n     * @param val {null} - value (only null)\r\n     * @param isLast {boolean} - true if node is last in list of parent childNodes\r\n     */\r\n    function NodeNull(label, val, isLast) {\r\n        this.type = \"null\";\r\n    \r\n        _NodeSimple.call(this, label, val, isLast);\r\n    }\r\n    utils.inherits(NodeNull,_NodeSimple);\r\n    \r\n    \r\n    /*\r\n     * The constructor for complex types (object, array)\r\n     * {...\r\n     * [+] \"label\": value,\r\n     * ...}\r\n     * value = object || array\r\n     *\r\n     * Markup:\r\n     * <li class=\"jsontree_node jsontree_node_(object|array) [expanded]\">\r\n     *     <span class=\"jsontree_label-wrapper\">\r\n     *         <span class=\"jsontree_label\">\r\n     *             <span class=\"jsontree_expand-button\" />\r\n     *             \"label\"\r\n     *         </span>\r\n     *         :\r\n     *     </span>\r\n     *     <div class=\"jsontree_value\">\r\n     *         <b>{</b>\r\n     *         <ul class=\"jsontree_child-nodes\" />\r\n     *         <b>}</b>\r\n     *         ,\r\n     *     </div>\r\n     * </li>\r\n     *\r\n     * @abstract\r\n     * @param label {string} - key name\r\n     * @param val {Object | Array} - a value of complex types, object or array\r\n     * @param isLast {boolean} - true if node is last in list of parent childNodes\r\n     */\r\n    function _NodeComplex(label, val, isLast) {\r\n        if (this.constructor === _NodeComplex) {\r\n            throw new Error('This is abstract class');\r\n        }\r\n        \r\n        var self = this,\r\n            el = document.createElement('li'),\r\n            template = function(label, sym) {\r\n                var comma = (!isLast) ? ',' : '',\r\n                    str = '\\\r\n                        <div class=\"jsontree_value-wrapper\">\\\r\n                            <div class=\"jsontree_value jsontree_value_' + self.type + '\">\\\r\n                                <b>' + sym[0] + '</b>\\\r\n                                <span class=\"jsontree_show-more\">&hellip;</span>\\\r\n                                <ul class=\"jsontree_child-nodes\"></ul>\\\r\n                                <b>' + sym[1] + '</b>' +\r\n                            '</div>' + comma +\r\n                        '</div>';\r\n    \r\n                if (label !== null) {\r\n                    str = '\\\r\n                        <span class=\"jsontree_label-wrapper\">\\\r\n                            <span class=\"jsontree_label\">' +\r\n                                '<span class=\"jsontree_expand-button\"></span>' +\r\n                                '\"' + label +\r\n                            '\"</span> : \\\r\n                        </span>' + str;\r\n                }\r\n    \r\n                return str;\r\n            },\r\n            childNodesUl,\r\n            labelEl,\r\n            moreContentEl,\r\n            childNodes = [];\r\n    \r\n        self.label = label;\r\n        self.isComplex = true;\r\n    \r\n        el.classList.add('jsontree_node');\r\n        el.classList.add('jsontree_node_complex');\r\n        el.innerHTML = template(label, self.sym);\r\n    \r\n        childNodesUl = el.querySelector('.jsontree_child-nodes');\r\n    \r\n        if (label !== null) {\r\n            labelEl = el.querySelector('.jsontree_label');\r\n            moreContentEl = el.querySelector('.jsontree_show-more');\r\n    \r\n            labelEl.addEventListener('click', function(e) {\r\n                if (e.altKey) {\r\n                    self.toggleMarked();\r\n                    return;\r\n                }\r\n\r\n                if (e.shiftKey) {\r\n                    document.getSelection().removeAllRanges();\r\n                    alert(self.getJSONPath());\r\n                    return;\r\n                }\r\n\r\n                self.toggle(e.ctrlKey || e.metaKey);\r\n            }, false);\r\n            \r\n            moreContentEl.addEventListener('click', function(e) {\r\n                self.toggle(e.ctrlKey || e.metaKey);\r\n            }, false);\r\n    \r\n            self.isRoot = false;\r\n        } else {\r\n            self.isRoot = true;\r\n            self.parent = null;\r\n    \r\n            el.classList.add('jsontree_node_expanded');\r\n        }\r\n    \r\n        self.el = el;\r\n        self.childNodes = childNodes;\r\n        self.childNodesUl = childNodesUl;\r\n    \r\n        utils.forEachNode(val, function(label, node, isLast) {\r\n            self.addChild(new Node(label, node, isLast));\r\n        });\r\n    \r\n        self.isEmpty = !Boolean(childNodes.length);\r\n        if (self.isEmpty) {\r\n            el.classList.add('jsontree_node_empty');\r\n        }\r\n    }\r\n\r\n    utils.inherits(_NodeComplex, _NodeSimple);\r\n    \r\n    utils.extend(_NodeComplex.prototype, {\r\n        constructor : _NodeComplex,\r\n        \r\n        /*\r\n         * Add child node to list of child nodes\r\n         *\r\n         * @param child {Node} - child node\r\n         */\r\n        addChild : function(child) {\r\n            this.childNodes.push(child);\r\n            this.childNodesUl.appendChild(child.el);\r\n            child.parent = this;\r\n        },\r\n    \r\n        /*\r\n         * Expands this list of node child nodes\r\n         *\r\n         * @param isRecursive {boolean} - if true, expands all child nodes\r\n         */\r\n        expand : function(isRecursive){\r\n            if (this.isEmpty) {\r\n                return;\r\n            }\r\n            \r\n            if (!this.isRoot) {\r\n                this.el.classList.add('jsontree_node_expanded');\r\n            }\r\n    \r\n            if (isRecursive) {\r\n                this.childNodes.forEach(function(item, i) {\r\n                    if (item.isComplex) {\r\n                        item.expand(isRecursive);\r\n                    }\r\n                });\r\n            }\r\n        },\r\n    \r\n        /*\r\n         * Collapses this list of node child nodes\r\n         *\r\n         * @param isRecursive {boolean} - if true, collapses all child nodes\r\n         */\r\n        collapse : function(isRecursive) {\r\n            if (this.isEmpty) {\r\n                return;\r\n            }\r\n            \r\n            if (!this.isRoot) {\r\n                this.el.classList.remove('jsontree_node_expanded');\r\n            }\r\n    \r\n            if (isRecursive) {\r\n                this.childNodes.forEach(function(item, i) {\r\n                    if (item.isComplex) {\r\n                        item.collapse(isRecursive);\r\n                    }\r\n                });\r\n            }\r\n        },\r\n    \r\n        /*\r\n         * Expands collapsed or collapses expanded node\r\n         *\r\n         * @param {boolean} isRecursive - Expand all child nodes if this node is expanded\r\n         *                                and collapse it otherwise\r\n         */\r\n        toggle : function(isRecursive) {\r\n            if (this.isEmpty) {\r\n                return;\r\n            }\r\n            \r\n            this.el.classList.toggle('jsontree_node_expanded');\r\n            \r\n            if (isRecursive) {\r\n                var isExpanded = this.el.classList.contains('jsontree_node_expanded');\r\n                \r\n                this.childNodes.forEach(function(item, i) {\r\n                    if (item.isComplex) {\r\n                        item[isExpanded ? 'expand' : 'collapse'](isRecursive);\r\n                    }\r\n                });\r\n            }\r\n        },\r\n\r\n        /**\r\n         * Find child nodes that match some conditions and handle it\r\n         * \r\n         * @param {Function} matcher\r\n         * @param {Function} handler\r\n         * @param {boolean} isRecursive\r\n         */\r\n        findChildren : function(matcher, handler, isRecursive) {\r\n            if (this.isEmpty) {\r\n                return;\r\n            }\r\n            \r\n            this.childNodes.forEach(function(item, i) {\r\n                if (matcher(item)) {\r\n                    handler(item);\r\n                }\r\n\r\n                if (item.isComplex && isRecursive) {\r\n                    item.findChildren(matcher, handler, isRecursive);\r\n                }\r\n            });\r\n        }\r\n    });\r\n    \r\n    \r\n    /*\r\n     * The constructor for object values\r\n     * {...\r\n     * [+] \"label\": object,\r\n     * ...}\r\n     * object = {\"abc\": \"def\"}\r\n     *\r\n     * @constructor\r\n     * @param label {string} - key name\r\n     * @param val {Object} - value of object type, {\"abc\": \"def\"}\r\n     * @param isLast {boolean} - true if node is last in list of siblings\r\n     */\r\n    function NodeObject(label, val, isLast) {\r\n        this.sym = ['{', '}'];\r\n        this.type = \"object\";\r\n    \r\n        _NodeComplex.call(this, label, val, isLast);\r\n    }\r\n    utils.inherits(NodeObject,_NodeComplex);\r\n    \r\n    \r\n    /*\r\n     * The constructor for array values\r\n     * {...\r\n     * [+] \"label\": array,\r\n     * ...}\r\n     * array = [1,2,3]\r\n     *\r\n     * @constructor\r\n     * @param label {string} - key name\r\n     * @param val {Array} - value of array type, [1,2,3]\r\n     * @param isLast {boolean} - true if node is last in list of siblings\r\n     */\r\n    function NodeArray(label, val, isLast) {\r\n        this.sym = ['[', ']'];\r\n        this.type = \"array\";\r\n    \r\n        _NodeComplex.call(this, label, val, isLast);\r\n    }\r\n    utils.inherits(NodeArray, _NodeComplex);\r\n    \r\n    \r\n    /* ---------- The tree constructor ---------- */\r\n    \r\n    /*\r\n     * The constructor for json tree.\r\n     * It contains only one Node (Array or Object), without property name.\r\n     * CSS-styles of .tree define main tree styles like font-family,\r\n     * font-size and own margins.\r\n     *\r\n     * Markup:\r\n     * <ul class=\"jsontree_tree clearfix\">\r\n     *     {Node}\r\n     * </ul>\r\n     *\r\n     * @constructor\r\n     * @param jsonObj {Object | Array} - data for tree\r\n     * @param domEl {DOMElement} - DOM-element, wrapper for tree\r\n     */\r\n    function Tree(jsonObj, domEl) {\r\n        this.wrapper = document.createElement('ul');\r\n        this.wrapper.className = 'jsontree_tree clearfix';\r\n        \r\n        this.rootNode = null;\r\n        \r\n        this.sourceJSONObj = jsonObj;\r\n\r\n        this.loadData(jsonObj);\r\n        this.appendTo(domEl);\r\n    }\r\n    \r\n    Tree.prototype = {\r\n        constructor : Tree,\r\n        \r\n        /**\r\n         * Fill new data in current json tree\r\n         *\r\n         * @param {Object | Array} jsonObj - json-data\r\n         */\r\n        loadData : function(jsonObj) {\r\n            if (!utils.isValidRoot(jsonObj)) {\r\n                alert('The root should be an object or an array');\r\n                return;\r\n            }\r\n\r\n            this.sourceJSONObj = jsonObj;\r\n            \r\n            this.rootNode = new Node(null, jsonObj, 'last');\r\n            this.wrapper.innerHTML = '';\r\n            this.wrapper.appendChild(this.rootNode.el);\r\n        },\r\n        \r\n        /**\r\n         * Appends tree to DOM-element (or move it to new place)\r\n         *\r\n         * @param {DOMElement} domEl \r\n         */\r\n        appendTo : function(domEl) {\r\n            domEl.appendChild(this.wrapper);\r\n        },\r\n        \r\n        /**\r\n         * Expands all tree nodes (objects or arrays) recursively\r\n         *\r\n         * @param {Function} filterFunc - 'true' if this node should be expanded\r\n         */\r\n        expand : function(filterFunc) {\r\n            if (this.rootNode.isComplex) {\r\n                if (typeof filterFunc == 'function') {\r\n                    this.rootNode.childNodes.forEach(function(item, i) {\r\n                        if (item.isComplex && filterFunc(item)) {\r\n                            item.expand();\r\n                        }\r\n                    });\r\n                } else {\r\n                    this.rootNode.expand('recursive');\r\n                }\r\n            }\r\n        },\r\n       \r\n        /**\r\n         * Collapses all tree nodes (objects or arrays) recursively\r\n         */\r\n        collapse : function() {\r\n            if (typeof this.rootNode.collapse === 'function') {\r\n                this.rootNode.collapse('recursive');\r\n            }\r\n        },\r\n\r\n        /**\r\n         * Returns the source json-string (pretty-printed)\r\n         * \r\n         * @param {boolean} isPrettyPrinted - 'true' for pretty-printed string\r\n         * @returns {string} - for exemple, '{\"a\":2,\"b\":3}'\r\n         */\r\n        toSourceJSON : function(isPrettyPrinted) {\r\n            if (!isPrettyPrinted) {\r\n                return JSON.stringify(this.sourceJSONObj);\r\n            }\r\n\r\n            var DELIMETER = \"[%^$#$%^%]\",\r\n                jsonStr = JSON.stringify(this.sourceJSONObj, null, DELIMETER);\r\n\r\n            jsonStr = jsonStr.split(\"\\n\").join(\"<br />\");\r\n            jsonStr = jsonStr.split(DELIMETER).join(\"&nbsp;&nbsp;&nbsp;&nbsp;\");\r\n\r\n            return jsonStr;\r\n        },\r\n\r\n        /**\r\n         * Find all nodes that match some conditions and handle it\r\n         */\r\n        findAndHandle : function(matcher, handler) {\r\n            this.rootNode.findChildren(matcher, handler, 'isRecursive');\r\n        },\r\n\r\n        /**\r\n         * Unmark all nodes\r\n         */\r\n        unmarkAll : function() {\r\n            this.rootNode.findChildren(function(node) {\r\n                return true;\r\n            }, function(node) {\r\n                node.unmark();\r\n            }, 'isRecursive');\r\n        }\r\n    };\r\n\r\n    \r\n    /* ---------- Public methods ---------- */\r\n    return {\r\n        /**\r\n         * Creates new tree by data and appends it to the DOM-element\r\n         * \r\n         * @param jsonObj {Object | Array} - json-data\r\n         * @param domEl {DOMElement} - the wrapper element\r\n         * @returns {Tree}\r\n         */\r\n        create : function(jsonObj, domEl) {\r\n            return new Tree(jsonObj, domEl);\r\n        }\r\n    };\r\n})();"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/layui/css/layui.css",
    "content": "/** layui-v2.4.5 MIT License By https://www.layui.com */\n .layui-inline,img{display:inline-block;vertical-align:middle}h1,h2,h3,h4,h5,h6{font-weight:400}.layui-edge,.layui-header,.layui-inline,.layui-main{position:relative}.layui-elip,.layui-form-checkbox span,.layui-form-pane .layui-form-label{text-overflow:ellipsis;white-space:nowrap}.layui-btn,.layui-edge,.layui-inline,img{vertical-align:middle}.layui-btn,.layui-disabled,.layui-icon,.layui-unselect{-webkit-user-select:none;-ms-user-select:none;-moz-user-select:none}blockquote,body,button,dd,div,dl,dt,form,h1,h2,h3,h4,h5,h6,input,li,ol,p,pre,td,textarea,th,ul{margin:0;padding:0;-webkit-tap-highlight-color:rgba(0,0,0,0)}a:active,a:hover{outline:0}img{border:none}li{list-style:none}table{border-collapse:collapse;border-spacing:0}h4,h5,h6{font-size:100%}button,input,optgroup,option,select,textarea{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;outline:0}pre{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word}body{line-height:24px;font:14px Helvetica Neue,Helvetica,PingFang SC,Tahoma,Arial,sans-serif}hr{height:1px;margin:10px 0;border:0;clear:both}a{color:#333;text-decoration:none}a:hover{color:#777}a cite{font-style:normal;*cursor:pointer}.layui-border-box,.layui-border-box *{box-sizing:border-box}.layui-box,.layui-box *{box-sizing:content-box}.layui-clear{clear:both;*zoom:1}.layui-clear:after{content:'\\20';clear:both;*zoom:1;display:block;height:0}.layui-inline{*display:inline;*zoom:1}.layui-edge{display:inline-block;width:0;height:0;border-width:6px;border-style:dashed;border-color:transparent;overflow:hidden}.layui-edge-top{top:-4px;border-bottom-color:#999;border-bottom-style:solid}.layui-edge-right{border-left-color:#999;border-left-style:solid}.layui-edge-bottom{top:2px;border-top-color:#999;border-top-style:solid}.layui-edge-left{border-right-color:#999;border-right-style:solid}.layui-elip{overflow:hidden}.layui-disabled,.layui-disabled:hover{color:#d2d2d2!important;cursor:not-allowed!important}.layui-circle{border-radius:100%}.layui-show{display:block!important}.layui-hide{display:none!important}@font-face{font-family:layui-icon;src:url(../font/iconfont.eot?v=240);src:url(../font/iconfont.eot?v=240#iefix) format('embedded-opentype'),url(../font/iconfont.svg?v=240#iconfont) format('svg'),url(../font/iconfont.woff?v=240) format('woff'),url(../font/iconfont.ttf?v=240) format('truetype')}.layui-icon{font-family:layui-icon!important;font-size:16px;font-style:normal;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.layui-icon-reply-fill:before{content:\"\\e611\"}.layui-icon-set-fill:before{content:\"\\e614\"}.layui-icon-menu-fill:before{content:\"\\e60f\"}.layui-icon-search:before{content:\"\\e615\"}.layui-icon-share:before{content:\"\\e641\"}.layui-icon-set-sm:before{content:\"\\e620\"}.layui-icon-engine:before{content:\"\\e628\"}.layui-icon-close:before{content:\"\\1006\"}.layui-icon-close-fill:before{content:\"\\1007\"}.layui-icon-chart-screen:before{content:\"\\e629\"}.layui-icon-star:before{content:\"\\e600\"}.layui-icon-circle-dot:before{content:\"\\e617\"}.layui-icon-chat:before{content:\"\\e606\"}.layui-icon-release:before{content:\"\\e609\"}.layui-icon-list:before{content:\"\\e60a\"}.layui-icon-chart:before{content:\"\\e62c\"}.layui-icon-ok-circle:before{content:\"\\1005\"}.layui-icon-layim-theme:before{content:\"\\e61b\"}.layui-icon-table:before{content:\"\\e62d\"}.layui-icon-right:before{content:\"\\e602\"}.layui-icon-left:before{content:\"\\e603\"}.layui-icon-cart-simple:before{content:\"\\e698\"}.layui-icon-face-cry:before{content:\"\\e69c\"}.layui-icon-face-smile:before{content:\"\\e6af\"}.layui-icon-survey:before{content:\"\\e6b2\"}.layui-icon-tree:before{content:\"\\e62e\"}.layui-icon-upload-circle:before{content:\"\\e62f\"}.layui-icon-add-circle:before{content:\"\\e61f\"}.layui-icon-download-circle:before{content:\"\\e601\"}.layui-icon-templeate-1:before{content:\"\\e630\"}.layui-icon-util:before{content:\"\\e631\"}.layui-icon-face-surprised:before{content:\"\\e664\"}.layui-icon-edit:before{content:\"\\e642\"}.layui-icon-speaker:before{content:\"\\e645\"}.layui-icon-down:before{content:\"\\e61a\"}.layui-icon-file:before{content:\"\\e621\"}.layui-icon-layouts:before{content:\"\\e632\"}.layui-icon-rate-half:before{content:\"\\e6c9\"}.layui-icon-add-circle-fine:before{content:\"\\e608\"}.layui-icon-prev-circle:before{content:\"\\e633\"}.layui-icon-read:before{content:\"\\e705\"}.layui-icon-404:before{content:\"\\e61c\"}.layui-icon-carousel:before{content:\"\\e634\"}.layui-icon-help:before{content:\"\\e607\"}.layui-icon-code-circle:before{content:\"\\e635\"}.layui-icon-water:before{content:\"\\e636\"}.layui-icon-username:before{content:\"\\e66f\"}.layui-icon-find-fill:before{content:\"\\e670\"}.layui-icon-about:before{content:\"\\e60b\"}.layui-icon-location:before{content:\"\\e715\"}.layui-icon-up:before{content:\"\\e619\"}.layui-icon-pause:before{content:\"\\e651\"}.layui-icon-date:before{content:\"\\e637\"}.layui-icon-layim-uploadfile:before{content:\"\\e61d\"}.layui-icon-delete:before{content:\"\\e640\"}.layui-icon-play:before{content:\"\\e652\"}.layui-icon-top:before{content:\"\\e604\"}.layui-icon-friends:before{content:\"\\e612\"}.layui-icon-refresh-3:before{content:\"\\e9aa\"}.layui-icon-ok:before{content:\"\\e605\"}.layui-icon-layer:before{content:\"\\e638\"}.layui-icon-face-smile-fine:before{content:\"\\e60c\"}.layui-icon-dollar:before{content:\"\\e659\"}.layui-icon-group:before{content:\"\\e613\"}.layui-icon-layim-download:before{content:\"\\e61e\"}.layui-icon-picture-fine:before{content:\"\\e60d\"}.layui-icon-link:before{content:\"\\e64c\"}.layui-icon-diamond:before{content:\"\\e735\"}.layui-icon-log:before{content:\"\\e60e\"}.layui-icon-rate-solid:before{content:\"\\e67a\"}.layui-icon-fonts-del:before{content:\"\\e64f\"}.layui-icon-unlink:before{content:\"\\e64d\"}.layui-icon-fonts-clear:before{content:\"\\e639\"}.layui-icon-triangle-r:before{content:\"\\e623\"}.layui-icon-circle:before{content:\"\\e63f\"}.layui-icon-radio:before{content:\"\\e643\"}.layui-icon-align-center:before{content:\"\\e647\"}.layui-icon-align-right:before{content:\"\\e648\"}.layui-icon-align-left:before{content:\"\\e649\"}.layui-icon-loading-1:before{content:\"\\e63e\"}.layui-icon-return:before{content:\"\\e65c\"}.layui-icon-fonts-strong:before{content:\"\\e62b\"}.layui-icon-upload:before{content:\"\\e67c\"}.layui-icon-dialogue:before{content:\"\\e63a\"}.layui-icon-video:before{content:\"\\e6ed\"}.layui-icon-headset:before{content:\"\\e6fc\"}.layui-icon-cellphone-fine:before{content:\"\\e63b\"}.layui-icon-add-1:before{content:\"\\e654\"}.layui-icon-face-smile-b:before{content:\"\\e650\"}.layui-icon-fonts-html:before{content:\"\\e64b\"}.layui-icon-form:before{content:\"\\e63c\"}.layui-icon-cart:before{content:\"\\e657\"}.layui-icon-camera-fill:before{content:\"\\e65d\"}.layui-icon-tabs:before{content:\"\\e62a\"}.layui-icon-fonts-code:before{content:\"\\e64e\"}.layui-icon-fire:before{content:\"\\e756\"}.layui-icon-set:before{content:\"\\e716\"}.layui-icon-fonts-u:before{content:\"\\e646\"}.layui-icon-triangle-d:before{content:\"\\e625\"}.layui-icon-tips:before{content:\"\\e702\"}.layui-icon-picture:before{content:\"\\e64a\"}.layui-icon-more-vertical:before{content:\"\\e671\"}.layui-icon-flag:before{content:\"\\e66c\"}.layui-icon-loading:before{content:\"\\e63d\"}.layui-icon-fonts-i:before{content:\"\\e644\"}.layui-icon-refresh-1:before{content:\"\\e666\"}.layui-icon-rmb:before{content:\"\\e65e\"}.layui-icon-home:before{content:\"\\e68e\"}.layui-icon-user:before{content:\"\\e770\"}.layui-icon-notice:before{content:\"\\e667\"}.layui-icon-login-weibo:before{content:\"\\e675\"}.layui-icon-voice:before{content:\"\\e688\"}.layui-icon-upload-drag:before{content:\"\\e681\"}.layui-icon-login-qq:before{content:\"\\e676\"}.layui-icon-snowflake:before{content:\"\\e6b1\"}.layui-icon-file-b:before{content:\"\\e655\"}.layui-icon-template:before{content:\"\\e663\"}.layui-icon-auz:before{content:\"\\e672\"}.layui-icon-console:before{content:\"\\e665\"}.layui-icon-app:before{content:\"\\e653\"}.layui-icon-prev:before{content:\"\\e65a\"}.layui-icon-website:before{content:\"\\e7ae\"}.layui-icon-next:before{content:\"\\e65b\"}.layui-icon-component:before{content:\"\\e857\"}.layui-icon-more:before{content:\"\\e65f\"}.layui-icon-login-wechat:before{content:\"\\e677\"}.layui-icon-shrink-right:before{content:\"\\e668\"}.layui-icon-spread-left:before{content:\"\\e66b\"}.layui-icon-camera:before{content:\"\\e660\"}.layui-icon-note:before{content:\"\\e66e\"}.layui-icon-refresh:before{content:\"\\e669\"}.layui-icon-female:before{content:\"\\e661\"}.layui-icon-male:before{content:\"\\e662\"}.layui-icon-password:before{content:\"\\e673\"}.layui-icon-senior:before{content:\"\\e674\"}.layui-icon-theme:before{content:\"\\e66a\"}.layui-icon-tread:before{content:\"\\e6c5\"}.layui-icon-praise:before{content:\"\\e6c6\"}.layui-icon-star-fill:before{content:\"\\e658\"}.layui-icon-rate:before{content:\"\\e67b\"}.layui-icon-template-1:before{content:\"\\e656\"}.layui-icon-vercode:before{content:\"\\e679\"}.layui-icon-cellphone:before{content:\"\\e678\"}.layui-icon-screen-full:before{content:\"\\e622\"}.layui-icon-screen-restore:before{content:\"\\e758\"}.layui-icon-cols:before{content:\"\\e610\"}.layui-icon-export:before{content:\"\\e67d\"}.layui-icon-print:before{content:\"\\e66d\"}.layui-icon-slider:before{content:\"\\e714\"}.layui-main{width:1140px;margin:0 auto}.layui-header{z-index:1000;height:60px}.layui-header a:hover{transition:all .5s;-webkit-transition:all .5s}.layui-side{position:fixed;left:0;top:0;bottom:0;z-index:999;width:200px;overflow-x:hidden}.layui-side-scroll{position:relative;width:220px;height:100%;overflow-x:hidden}.layui-body{position:absolute;left:200px;right:0;top:0;bottom:0;z-index:998;width:auto;overflow:hidden;overflow-y:auto;box-sizing:border-box}.layui-layout-body{overflow:hidden}.layui-layout-admin .layui-header{background-color:#23262E}.layui-layout-admin .layui-side{top:60px;width:200px;overflow-x:hidden}.layui-layout-admin .layui-body{top:60px;bottom:44px}.layui-layout-admin .layui-main{width:auto;margin:0 15px}.layui-layout-admin .layui-footer{position:fixed;left:200px;right:0;bottom:0;height:44px;line-height:44px;padding:0 15px;background-color:#eee}.layui-layout-admin .layui-logo{position:absolute;left:0;top:0;width:200px;height:100%;line-height:60px;text-align:center;color:#009688;font-size:16px}.layui-layout-admin .layui-header .layui-nav{background:0 0}.layui-layout-left{position:absolute!important;left:200px;top:0}.layui-layout-right{position:absolute!important;right:0;top:0}.layui-container{position:relative;margin:0 auto;padding:0 15px;box-sizing:border-box}.layui-fluid{position:relative;margin:0 auto;padding:0 15px}.layui-row:after,.layui-row:before{content:'';display:block;clear:both}.layui-col-lg1,.layui-col-lg10,.layui-col-lg11,.layui-col-lg12,.layui-col-lg2,.layui-col-lg3,.layui-col-lg4,.layui-col-lg5,.layui-col-lg6,.layui-col-lg7,.layui-col-lg8,.layui-col-lg9,.layui-col-md1,.layui-col-md10,.layui-col-md11,.layui-col-md12,.layui-col-md2,.layui-col-md3,.layui-col-md4,.layui-col-md5,.layui-col-md6,.layui-col-md7,.layui-col-md8,.layui-col-md9,.layui-col-sm1,.layui-col-sm10,.layui-col-sm11,.layui-col-sm12,.layui-col-sm2,.layui-col-sm3,.layui-col-sm4,.layui-col-sm5,.layui-col-sm6,.layui-col-sm7,.layui-col-sm8,.layui-col-sm9,.layui-col-xs1,.layui-col-xs10,.layui-col-xs11,.layui-col-xs12,.layui-col-xs2,.layui-col-xs3,.layui-col-xs4,.layui-col-xs5,.layui-col-xs6,.layui-col-xs7,.layui-col-xs8,.layui-col-xs9{position:relative;display:block;box-sizing:border-box}.layui-col-xs1,.layui-col-xs10,.layui-col-xs11,.layui-col-xs12,.layui-col-xs2,.layui-col-xs3,.layui-col-xs4,.layui-col-xs5,.layui-col-xs6,.layui-col-xs7,.layui-col-xs8,.layui-col-xs9{float:left}.layui-col-xs1{width:8.33333333%}.layui-col-xs2{width:16.66666667%}.layui-col-xs3{width:25%}.layui-col-xs4{width:33.33333333%}.layui-col-xs5{width:41.66666667%}.layui-col-xs6{width:50%}.layui-col-xs7{width:58.33333333%}.layui-col-xs8{width:66.66666667%}.layui-col-xs9{width:75%}.layui-col-xs10{width:83.33333333%}.layui-col-xs11{width:91.66666667%}.layui-col-xs12{width:100%}.layui-col-xs-offset1{margin-left:8.33333333%}.layui-col-xs-offset2{margin-left:16.66666667%}.layui-col-xs-offset3{margin-left:25%}.layui-col-xs-offset4{margin-left:33.33333333%}.layui-col-xs-offset5{margin-left:41.66666667%}.layui-col-xs-offset6{margin-left:50%}.layui-col-xs-offset7{margin-left:58.33333333%}.layui-col-xs-offset8{margin-left:66.66666667%}.layui-col-xs-offset9{margin-left:75%}.layui-col-xs-offset10{margin-left:83.33333333%}.layui-col-xs-offset11{margin-left:91.66666667%}.layui-col-xs-offset12{margin-left:100%}@media screen and (max-width:768px){.layui-hide-xs{display:none!important}.layui-show-xs-block{display:block!important}.layui-show-xs-inline{display:inline!important}.layui-show-xs-inline-block{display:inline-block!important}}@media screen and (min-width:768px){.layui-container{width:750px}.layui-hide-sm{display:none!important}.layui-show-sm-block{display:block!important}.layui-show-sm-inline{display:inline!important}.layui-show-sm-inline-block{display:inline-block!important}.layui-col-sm1,.layui-col-sm10,.layui-col-sm11,.layui-col-sm12,.layui-col-sm2,.layui-col-sm3,.layui-col-sm4,.layui-col-sm5,.layui-col-sm6,.layui-col-sm7,.layui-col-sm8,.layui-col-sm9{float:left}.layui-col-sm1{width:8.33333333%}.layui-col-sm2{width:16.66666667%}.layui-col-sm3{width:25%}.layui-col-sm4{width:33.33333333%}.layui-col-sm5{width:41.66666667%}.layui-col-sm6{width:50%}.layui-col-sm7{width:58.33333333%}.layui-col-sm8{width:66.66666667%}.layui-col-sm9{width:75%}.layui-col-sm10{width:83.33333333%}.layui-col-sm11{width:91.66666667%}.layui-col-sm12{width:100%}.layui-col-sm-offset1{margin-left:8.33333333%}.layui-col-sm-offset2{margin-left:16.66666667%}.layui-col-sm-offset3{margin-left:25%}.layui-col-sm-offset4{margin-left:33.33333333%}.layui-col-sm-offset5{margin-left:41.66666667%}.layui-col-sm-offset6{margin-left:50%}.layui-col-sm-offset7{margin-left:58.33333333%}.layui-col-sm-offset8{margin-left:66.66666667%}.layui-col-sm-offset9{margin-left:75%}.layui-col-sm-offset10{margin-left:83.33333333%}.layui-col-sm-offset11{margin-left:91.66666667%}.layui-col-sm-offset12{margin-left:100%}}@media screen and (min-width:992px){.layui-container{width:970px}.layui-hide-md{display:none!important}.layui-show-md-block{display:block!important}.layui-show-md-inline{display:inline!important}.layui-show-md-inline-block{display:inline-block!important}.layui-col-md1,.layui-col-md10,.layui-col-md11,.layui-col-md12,.layui-col-md2,.layui-col-md3,.layui-col-md4,.layui-col-md5,.layui-col-md6,.layui-col-md7,.layui-col-md8,.layui-col-md9{float:left}.layui-col-md1{width:8.33333333%}.layui-col-md2{width:16.66666667%}.layui-col-md3{width:25%}.layui-col-md4{width:33.33333333%}.layui-col-md5{width:41.66666667%}.layui-col-md6{width:50%}.layui-col-md7{width:58.33333333%}.layui-col-md8{width:66.66666667%}.layui-col-md9{width:75%}.layui-col-md10{width:83.33333333%}.layui-col-md11{width:91.66666667%}.layui-col-md12{width:100%}.layui-col-md-offset1{margin-left:8.33333333%}.layui-col-md-offset2{margin-left:16.66666667%}.layui-col-md-offset3{margin-left:25%}.layui-col-md-offset4{margin-left:33.33333333%}.layui-col-md-offset5{margin-left:41.66666667%}.layui-col-md-offset6{margin-left:50%}.layui-col-md-offset7{margin-left:58.33333333%}.layui-col-md-offset8{margin-left:66.66666667%}.layui-col-md-offset9{margin-left:75%}.layui-col-md-offset10{margin-left:83.33333333%}.layui-col-md-offset11{margin-left:91.66666667%}.layui-col-md-offset12{margin-left:100%}}@media screen and (min-width:1200px){.layui-container{width:1170px}.layui-hide-lg{display:none!important}.layui-show-lg-block{display:block!important}.layui-show-lg-inline{display:inline!important}.layui-show-lg-inline-block{display:inline-block!important}.layui-col-lg1,.layui-col-lg10,.layui-col-lg11,.layui-col-lg12,.layui-col-lg2,.layui-col-lg3,.layui-col-lg4,.layui-col-lg5,.layui-col-lg6,.layui-col-lg7,.layui-col-lg8,.layui-col-lg9{float:left}.layui-col-lg1{width:8.33333333%}.layui-col-lg2{width:16.66666667%}.layui-col-lg3{width:25%}.layui-col-lg4{width:33.33333333%}.layui-col-lg5{width:41.66666667%}.layui-col-lg6{width:50%}.layui-col-lg7{width:58.33333333%}.layui-col-lg8{width:66.66666667%}.layui-col-lg9{width:75%}.layui-col-lg10{width:83.33333333%}.layui-col-lg11{width:91.66666667%}.layui-col-lg12{width:100%}.layui-col-lg-offset1{margin-left:8.33333333%}.layui-col-lg-offset2{margin-left:16.66666667%}.layui-col-lg-offset3{margin-left:25%}.layui-col-lg-offset4{margin-left:33.33333333%}.layui-col-lg-offset5{margin-left:41.66666667%}.layui-col-lg-offset6{margin-left:50%}.layui-col-lg-offset7{margin-left:58.33333333%}.layui-col-lg-offset8{margin-left:66.66666667%}.layui-col-lg-offset9{margin-left:75%}.layui-col-lg-offset10{margin-left:83.33333333%}.layui-col-lg-offset11{margin-left:91.66666667%}.layui-col-lg-offset12{margin-left:100%}}.layui-col-space1{margin:-.5px}.layui-col-space1>*{padding:.5px}.layui-col-space3{margin:-1.5px}.layui-col-space3>*{padding:1.5px}.layui-col-space5{margin:-2.5px}.layui-col-space5>*{padding:2.5px}.layui-col-space8{margin:-3.5px}.layui-col-space8>*{padding:3.5px}.layui-col-space10{margin:-5px}.layui-col-space10>*{padding:5px}.layui-col-space12{margin:-6px}.layui-col-space12>*{padding:6px}.layui-col-space15{margin:-7.5px}.layui-col-space15>*{padding:7.5px}.layui-col-space18{margin:-9px}.layui-col-space18>*{padding:9px}.layui-col-space20{margin:-10px}.layui-col-space20>*{padding:10px}.layui-col-space22{margin:-11px}.layui-col-space22>*{padding:11px}.layui-col-space25{margin:-12.5px}.layui-col-space25>*{padding:12.5px}.layui-col-space30{margin:-15px}.layui-col-space30>*{padding:15px}.layui-btn,.layui-input,.layui-select,.layui-textarea,.layui-upload-button{outline:0;-webkit-appearance:none;transition:all .3s;-webkit-transition:all .3s;box-sizing:border-box}.layui-elem-quote{margin-bottom:10px;padding:15px;line-height:22px;border-left:5px solid #009688;border-radius:0 2px 2px 0;background-color:#f2f2f2}.layui-quote-nm{border-style:solid;border-width:1px 1px 1px 5px;background:0 0}.layui-elem-field{margin-bottom:10px;padding:0;border-width:1px;border-style:solid}.layui-elem-field legend{margin-left:20px;padding:0 10px;font-size:20px;font-weight:300}.layui-field-title{margin:10px 0 20px;border-width:1px 0 0}.layui-field-box{padding:10px 15px}.layui-field-title .layui-field-box{padding:10px 0}.layui-progress{position:relative;height:6px;border-radius:20px;background-color:#e2e2e2}.layui-progress-bar{position:absolute;left:0;top:0;width:0;max-width:100%;height:6px;border-radius:20px;text-align:right;background-color:#5FB878;transition:all .3s;-webkit-transition:all .3s}.layui-progress-big,.layui-progress-big .layui-progress-bar{height:18px;line-height:18px}.layui-progress-text{position:relative;top:-20px;line-height:18px;font-size:12px;color:#666}.layui-progress-big .layui-progress-text{position:static;padding:0 10px;color:#fff}.layui-collapse{border-width:1px;border-style:solid;border-radius:2px}.layui-colla-content,.layui-colla-item{border-top-width:1px;border-top-style:solid}.layui-colla-item:first-child{border-top:none}.layui-colla-title{position:relative;height:42px;line-height:42px;padding:0 15px 0 35px;color:#333;background-color:#f2f2f2;cursor:pointer;font-size:14px;overflow:hidden}.layui-colla-content{display:none;padding:10px 15px;line-height:22px;color:#666}.layui-colla-icon{position:absolute;left:15px;top:0;font-size:14px}.layui-card{margin-bottom:15px;border-radius:2px;background-color:#fff;box-shadow:0 1px 2px 0 rgba(0,0,0,.05)}.layui-card:last-child{margin-bottom:0}.layui-card-header{position:relative;height:42px;line-height:42px;padding:0 15px;border-bottom:1px solid #f6f6f6;color:#333;border-radius:2px 2px 0 0;font-size:14px}.layui-bg-black,.layui-bg-blue,.layui-bg-cyan,.layui-bg-green,.layui-bg-orange,.layui-bg-red{color:#fff!important}.layui-card-body{position:relative;padding:10px 15px;line-height:24px}.layui-card-body[pad15]{padding:15px}.layui-card-body[pad20]{padding:20px}.layui-card-body .layui-table{margin:5px 0}.layui-card .layui-tab{margin:0}.layui-panel-window{position:relative;padding:15px;border-radius:0;border-top:5px solid #E6E6E6;background-color:#fff}.layui-auxiliar-moving{position:fixed;left:0;right:0;top:0;bottom:0;width:100%;height:100%;background:0 0;z-index:9999999999}.layui-form-label,.layui-form-mid,.layui-form-select,.layui-input-block,.layui-input-inline,.layui-textarea{position:relative}.layui-bg-red{background-color:#FF5722!important}.layui-bg-orange{background-color:#FFB800!important}.layui-bg-green{background-color:#009688!important}.layui-bg-cyan{background-color:#2F4056!important}.layui-bg-blue{background-color:#1E9FFF!important}.layui-bg-black{background-color:#393D49!important}.layui-bg-gray{background-color:#eee!important;color:#666!important}.layui-badge-rim,.layui-colla-content,.layui-colla-item,.layui-collapse,.layui-elem-field,.layui-form-pane .layui-form-item[pane],.layui-form-pane .layui-form-label,.layui-input,.layui-layedit,.layui-layedit-tool,.layui-quote-nm,.layui-select,.layui-tab-bar,.layui-tab-card,.layui-tab-title,.layui-tab-title .layui-this:after,.layui-textarea{border-color:#e6e6e6}.layui-timeline-item:before,hr{background-color:#e6e6e6}.layui-text{line-height:22px;font-size:14px;color:#666}.layui-text h1,.layui-text h2,.layui-text h3{font-weight:500;color:#333}.layui-text h1{font-size:30px}.layui-text h2{font-size:24px}.layui-text h3{font-size:18px}.layui-text a:not(.layui-btn){color:#01AAED}.layui-text a:not(.layui-btn):hover{text-decoration:underline}.layui-text ul{padding:5px 0 5px 15px}.layui-text ul li{margin-top:5px;list-style-type:disc}.layui-text em,.layui-word-aux{color:#999!important;padding:0 5px!important}.layui-btn{display:inline-block;height:38px;line-height:38px;padding:0 18px;background-color:#009688;color:#fff;white-space:nowrap;text-align:center;font-size:14px;border:none;border-radius:2px;cursor:pointer}.layui-btn:hover{opacity:.8;filter:alpha(opacity=80);color:#fff}.layui-btn:active{opacity:1;filter:alpha(opacity=100)}.layui-btn+.layui-btn{margin-left:10px}.layui-btn-container{font-size:0}.layui-btn-container .layui-btn{margin-right:10px;margin-bottom:10px}.layui-btn-container .layui-btn+.layui-btn{margin-left:0}.layui-table .layui-btn-container .layui-btn{margin-bottom:9px}.layui-btn-radius{border-radius:100px}.layui-btn .layui-icon{margin-right:3px;font-size:18px;vertical-align:bottom;vertical-align:middle\\9}.layui-btn-primary{border:1px solid #C9C9C9;background-color:#fff;color:#555}.layui-btn-primary:hover{border-color:#009688;color:#333}.layui-btn-normal{background-color:#1E9FFF}.layui-btn-warm{background-color:#FFB800}.layui-btn-danger{background-color:#FF5722}.layui-btn-disabled,.layui-btn-disabled:active,.layui-btn-disabled:hover{border:1px solid #e6e6e6;background-color:#FBFBFB;color:#C9C9C9;cursor:not-allowed;opacity:1}.layui-btn-lg{height:44px;line-height:44px;padding:0 25px;font-size:16px}.layui-btn-sm{height:30px;line-height:30px;padding:0 10px;font-size:12px}.layui-btn-sm i{font-size:16px!important}.layui-btn-xs{height:22px;line-height:22px;padding:0 5px;font-size:12px}.layui-btn-xs i{font-size:14px!important}.layui-btn-group{display:inline-block;vertical-align:middle;font-size:0}.layui-btn-group .layui-btn{margin-left:0!important;margin-right:0!important;border-left:1px solid rgba(255,255,255,.5);border-radius:0}.layui-btn-group .layui-btn-primary{border-left:none}.layui-btn-group .layui-btn-primary:hover{border-color:#C9C9C9;color:#009688}.layui-btn-group .layui-btn:first-child{border-left:none;border-radius:2px 0 0 2px}.layui-btn-group .layui-btn-primary:first-child{border-left:1px solid #c9c9c9}.layui-btn-group .layui-btn:last-child{border-radius:0 2px 2px 0}.layui-btn-group .layui-btn+.layui-btn{margin-left:0}.layui-btn-group+.layui-btn-group{margin-left:10px}.layui-btn-fluid{width:100%}.layui-input,.layui-select,.layui-textarea{height:38px;line-height:1.3;line-height:38px\\9;border-width:1px;border-style:solid;background-color:#fff;border-radius:2px}.layui-input::-webkit-input-placeholder,.layui-select::-webkit-input-placeholder,.layui-textarea::-webkit-input-placeholder{line-height:1.3}.layui-input,.layui-textarea{display:block;width:100%;padding-left:10px}.layui-input:hover,.layui-textarea:hover{border-color:#D2D2D2!important}.layui-input:focus,.layui-textarea:focus{border-color:#C9C9C9!important}.layui-textarea{min-height:100px;height:auto;line-height:20px;padding:6px 10px;resize:vertical}.layui-select{padding:0 10px}.layui-form input[type=checkbox],.layui-form input[type=radio],.layui-form select{display:none}.layui-form [lay-ignore]{display:initial}.layui-form-item{margin-bottom:15px;clear:both;*zoom:1}.layui-form-item:after{content:'\\20';clear:both;*zoom:1;display:block;height:0}.layui-form-label{float:left;display:block;padding:9px 15px;width:80px;font-weight:400;line-height:20px;text-align:right}.layui-form-label-col{display:block;float:none;padding:9px 0;line-height:20px;text-align:left}.layui-form-item .layui-inline{margin-bottom:5px;margin-right:10px}.layui-input-block{margin-left:110px;min-height:36px}.layui-input-inline{display:inline-block;vertical-align:middle}.layui-form-item .layui-input-inline{float:left;width:190px;margin-right:10px}.layui-form-text .layui-input-inline{width:auto}.layui-form-mid{float:left;display:block;padding:9px 0!important;line-height:20px;margin-right:10px}.layui-form-danger+.layui-form-select .layui-input,.layui-form-danger:focus{border-color:#FF5722!important}.layui-form-select .layui-input{padding-right:30px;cursor:pointer}.layui-form-select .layui-edge{position:absolute;right:10px;top:50%;margin-top:-3px;cursor:pointer;border-width:6px;border-top-color:#c2c2c2;border-top-style:solid;transition:all .3s;-webkit-transition:all .3s}.layui-form-select dl{display:none;position:absolute;left:0;top:42px;padding:5px 0;z-index:899;min-width:100%;border:1px solid #d2d2d2;max-height:300px;overflow-y:auto;background-color:#fff;border-radius:2px;box-shadow:0 2px 4px rgba(0,0,0,.12);box-sizing:border-box}.layui-form-select dl dd,.layui-form-select dl dt{padding:0 10px;line-height:36px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.layui-form-select dl dt{font-size:12px;color:#999}.layui-form-select dl dd{cursor:pointer}.layui-form-select dl dd:hover{background-color:#f2f2f2;-webkit-transition:.5s all;transition:.5s all}.layui-form-select .layui-select-group dd{padding-left:20px}.layui-form-select dl dd.layui-select-tips{padding-left:10px!important;color:#999}.layui-form-select dl dd.layui-this{background-color:#5FB878;color:#fff}.layui-form-checkbox,.layui-form-select dl dd.layui-disabled{background-color:#fff}.layui-form-selected dl{display:block}.layui-form-checkbox,.layui-form-checkbox *,.layui-form-switch{display:inline-block;vertical-align:middle}.layui-form-selected .layui-edge{margin-top:-9px;-webkit-transform:rotate(180deg);transform:rotate(180deg);margin-top:-3px\\9}:root .layui-form-selected .layui-edge{margin-top:-9px\\0/IE9}.layui-form-selectup dl{top:auto;bottom:42px}.layui-select-none{margin:5px 0;text-align:center;color:#999}.layui-select-disabled .layui-disabled{border-color:#eee!important}.layui-select-disabled .layui-edge{border-top-color:#d2d2d2}.layui-form-checkbox{position:relative;height:30px;line-height:30px;margin-right:10px;padding-right:30px;cursor:pointer;font-size:0;-webkit-transition:.1s linear;transition:.1s linear;box-sizing:border-box}.layui-form-checkbox span{padding:0 10px;height:100%;font-size:14px;border-radius:2px 0 0 2px;background-color:#d2d2d2;color:#fff;overflow:hidden}.layui-form-checkbox:hover span{background-color:#c2c2c2}.layui-form-checkbox i{position:absolute;right:0;top:0;width:30px;height:28px;border:1px solid #d2d2d2;border-left:none;border-radius:0 2px 2px 0;color:#fff;font-size:20px;text-align:center}.layui-form-checkbox:hover i{border-color:#c2c2c2;color:#c2c2c2}.layui-form-checked,.layui-form-checked:hover{border-color:#5FB878}.layui-form-checked span,.layui-form-checked:hover span{background-color:#5FB878}.layui-form-checked i,.layui-form-checked:hover i{color:#5FB878}.layui-form-item .layui-form-checkbox{margin-top:4px}.layui-form-checkbox[lay-skin=primary]{height:auto!important;line-height:normal!important;min-width:18px;min-height:18px;border:none!important;margin-right:0;padding-left:28px;padding-right:0;background:0 0}.layui-form-checkbox[lay-skin=primary] span{padding-left:0;padding-right:15px;line-height:18px;background:0 0;color:#666}.layui-form-checkbox[lay-skin=primary] i{right:auto;left:0;width:16px;height:16px;line-height:16px;border:1px solid #d2d2d2;font-size:12px;border-radius:2px;background-color:#fff;-webkit-transition:.1s linear;transition:.1s linear}.layui-form-checkbox[lay-skin=primary]:hover i{border-color:#5FB878;color:#fff}.layui-form-checked[lay-skin=primary] i{border-color:#5FB878;background-color:#5FB878;color:#fff}.layui-checkbox-disbaled[lay-skin=primary] span{background:0 0!important;color:#c2c2c2}.layui-checkbox-disbaled[lay-skin=primary]:hover i{border-color:#d2d2d2}.layui-form-item .layui-form-checkbox[lay-skin=primary]{margin-top:10px}.layui-form-switch{position:relative;height:22px;line-height:22px;min-width:35px;padding:0 5px;margin-top:8px;border:1px solid #d2d2d2;border-radius:20px;cursor:pointer;background-color:#fff;-webkit-transition:.1s linear;transition:.1s linear}.layui-form-switch i{position:absolute;left:5px;top:3px;width:16px;height:16px;border-radius:20px;background-color:#d2d2d2;-webkit-transition:.1s linear;transition:.1s linear}.layui-form-switch em{position:relative;top:0;width:25px;margin-left:21px;padding:0!important;text-align:center!important;color:#999!important;font-style:normal!important;font-size:12px}.layui-form-onswitch{border-color:#5FB878;background-color:#5FB878}.layui-checkbox-disbaled,.layui-checkbox-disbaled i{border-color:#e2e2e2!important}.layui-form-onswitch i{left:100%;margin-left:-21px;background-color:#fff}.layui-form-onswitch em{margin-left:5px;margin-right:21px;color:#fff!important}.layui-checkbox-disbaled span{background-color:#e2e2e2!important}.layui-checkbox-disbaled:hover i{color:#fff!important}[lay-radio]{display:none}.layui-form-radio,.layui-form-radio *{display:inline-block;vertical-align:middle}.layui-form-radio{line-height:28px;margin:6px 10px 0 0;padding-right:10px;cursor:pointer;font-size:0}.layui-form-radio *{font-size:14px}.layui-form-radio>i{margin-right:8px;font-size:22px;color:#c2c2c2}.layui-form-radio>i:hover,.layui-form-radioed>i{color:#5FB878}.layui-radio-disbaled>i{color:#e2e2e2!important}.layui-form-pane .layui-form-label{width:110px;padding:8px 15px;height:38px;line-height:20px;border-width:1px;border-style:solid;border-radius:2px 0 0 2px;text-align:center;background-color:#FBFBFB;overflow:hidden;box-sizing:border-box}.layui-form-pane .layui-input-inline{margin-left:-1px}.layui-form-pane .layui-input-block{margin-left:110px;left:-1px}.layui-form-pane .layui-input{border-radius:0 2px 2px 0}.layui-form-pane .layui-form-text .layui-form-label{float:none;width:100%;border-radius:2px;box-sizing:border-box;text-align:left}.layui-form-pane .layui-form-text .layui-input-inline{display:block;margin:0;top:-1px;clear:both}.layui-form-pane .layui-form-text .layui-input-block{margin:0;left:0;top:-1px}.layui-form-pane .layui-form-text .layui-textarea{min-height:100px;border-radius:0 0 2px 2px}.layui-form-pane .layui-form-checkbox{margin:4px 0 4px 10px}.layui-form-pane .layui-form-radio,.layui-form-pane .layui-form-switch{margin-top:6px;margin-left:10px}.layui-form-pane .layui-form-item[pane]{position:relative;border-width:1px;border-style:solid}.layui-form-pane .layui-form-item[pane] .layui-form-label{position:absolute;left:0;top:0;height:100%;border-width:0 1px 0 0}.layui-form-pane .layui-form-item[pane] .layui-input-inline{margin-left:110px}@media screen and (max-width:450px){.layui-form-item .layui-form-label{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.layui-form-item .layui-inline{display:block;margin-right:0;margin-bottom:20px;clear:both}.layui-form-item .layui-inline:after{content:'\\20';clear:both;display:block;height:0}.layui-form-item .layui-input-inline{display:block;float:none;left:-3px;width:auto;margin:0 0 10px 112px}.layui-form-item .layui-input-inline+.layui-form-mid{margin-left:110px;top:-5px;padding:0}.layui-form-item .layui-form-checkbox{margin-right:5px;margin-bottom:5px}}.layui-layedit{border-width:1px;border-style:solid;border-radius:2px}.layui-layedit-tool{padding:3px 5px;border-bottom-width:1px;border-bottom-style:solid;font-size:0}.layedit-tool-fixed{position:fixed;top:0;border-top:1px solid #e2e2e2}.layui-layedit-tool .layedit-tool-mid,.layui-layedit-tool .layui-icon{display:inline-block;vertical-align:middle;text-align:center;font-size:14px}.layui-layedit-tool .layui-icon{position:relative;width:32px;height:30px;line-height:30px;margin:3px 5px;color:#777;cursor:pointer;border-radius:2px}.layui-layedit-tool .layui-icon:hover{color:#393D49}.layui-layedit-tool .layui-icon:active{color:#000}.layui-layedit-tool .layedit-tool-active{background-color:#e2e2e2;color:#000}.layui-layedit-tool .layui-disabled,.layui-layedit-tool .layui-disabled:hover{color:#d2d2d2;cursor:not-allowed}.layui-layedit-tool .layedit-tool-mid{width:1px;height:18px;margin:0 10px;background-color:#d2d2d2}.layedit-tool-html{width:50px!important;font-size:30px!important}.layedit-tool-b,.layedit-tool-code,.layedit-tool-help{font-size:16px!important}.layedit-tool-d,.layedit-tool-face,.layedit-tool-image,.layedit-tool-unlink{font-size:18px!important}.layedit-tool-image input{position:absolute;font-size:0;left:0;top:0;width:100%;height:100%;opacity:.01;filter:Alpha(opacity=1);cursor:pointer}.layui-layedit-iframe iframe{display:block;width:100%}#LAY_layedit_code{overflow:hidden}.layui-laypage{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;margin:10px 0;font-size:0}.layui-laypage>a:first-child,.layui-laypage>a:first-child em{border-radius:2px 0 0 2px}.layui-laypage>a:last-child,.layui-laypage>a:last-child em{border-radius:0 2px 2px 0}.layui-laypage>:first-child{margin-left:0!important}.layui-laypage>:last-child{margin-right:0!important}.layui-laypage a,.layui-laypage button,.layui-laypage input,.layui-laypage select,.layui-laypage span{border:1px solid #e2e2e2}.layui-laypage a,.layui-laypage span{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;padding:0 15px;height:28px;line-height:28px;margin:0 -1px 5px 0;background-color:#fff;color:#333;font-size:12px}.layui-flow-more a *,.layui-laypage input,.layui-table-view select[lay-ignore]{display:inline-block}.layui-laypage a:hover{color:#009688}.layui-laypage em{font-style:normal}.layui-laypage .layui-laypage-spr{color:#999;font-weight:700}.layui-laypage a{text-decoration:none}.layui-laypage .layui-laypage-curr{position:relative}.layui-laypage .layui-laypage-curr em{position:relative;color:#fff}.layui-laypage .layui-laypage-curr .layui-laypage-em{position:absolute;left:-1px;top:-1px;padding:1px;width:100%;height:100%;background-color:#009688}.layui-laypage-em{border-radius:2px}.layui-laypage-next em,.layui-laypage-prev em{font-family:Sim sun;font-size:16px}.layui-laypage .layui-laypage-count,.layui-laypage .layui-laypage-limits,.layui-laypage .layui-laypage-refresh,.layui-laypage .layui-laypage-skip{margin-left:10px;margin-right:10px;padding:0;border:none}.layui-laypage .layui-laypage-limits,.layui-laypage .layui-laypage-refresh{vertical-align:top}.layui-laypage .layui-laypage-refresh i{font-size:18px;cursor:pointer}.layui-laypage select{height:22px;padding:3px;border-radius:2px;cursor:pointer}.layui-laypage .layui-laypage-skip{height:30px;line-height:30px;color:#999}.layui-laypage button,.layui-laypage input{height:30px;line-height:30px;border-radius:2px;vertical-align:top;background-color:#fff;box-sizing:border-box}.layui-laypage input{width:40px;margin:0 10px;padding:0 3px;text-align:center}.layui-laypage input:focus,.layui-laypage select:focus{border-color:#009688!important}.layui-laypage button{margin-left:10px;padding:0 10px;cursor:pointer}.layui-table,.layui-table-view{margin:10px 0}.layui-flow-more{margin:10px 0;text-align:center;color:#999;font-size:14px}.layui-flow-more a{height:32px;line-height:32px}.layui-flow-more a *{vertical-align:top}.layui-flow-more a cite{padding:0 20px;border-radius:3px;background-color:#eee;color:#333;font-style:normal}.layui-flow-more a cite:hover{opacity:.8}.layui-flow-more a i{font-size:30px;color:#737383}.layui-table{width:100%;background-color:#fff;color:#666}.layui-table tr{transition:all .3s;-webkit-transition:all .3s}.layui-table th{text-align:left;font-weight:400}.layui-table tbody tr:hover,.layui-table thead tr,.layui-table-click,.layui-table-header,.layui-table-hover,.layui-table-mend,.layui-table-patch,.layui-table-tool,.layui-table-total,.layui-table-total tr,.layui-table[lay-even] tr:nth-child(even){background-color:#f2f2f2}.layui-table td,.layui-table th,.layui-table-col-set,.layui-table-fixed-r,.layui-table-grid-down,.layui-table-header,.layui-table-page,.layui-table-tips-main,.layui-table-tool,.layui-table-total,.layui-table-view,.layui-table[lay-skin=line],.layui-table[lay-skin=row]{border-width:1px;border-style:solid;border-color:#e6e6e6}.layui-table td,.layui-table th{position:relative;padding:9px 15px;min-height:20px;line-height:20px;font-size:14px}.layui-table[lay-skin=line] td,.layui-table[lay-skin=line] th{border-width:0 0 1px}.layui-table[lay-skin=row] td,.layui-table[lay-skin=row] th{border-width:0 1px 0 0}.layui-table[lay-skin=nob] td,.layui-table[lay-skin=nob] th{border:none}.layui-table img{max-width:100px}.layui-table[lay-size=lg] td,.layui-table[lay-size=lg] th{padding:15px 30px}.layui-table-view .layui-table[lay-size=lg] .layui-table-cell{height:40px;line-height:40px}.layui-table[lay-size=sm] td,.layui-table[lay-size=sm] th{font-size:12px;padding:5px 10px}.layui-table-view .layui-table[lay-size=sm] .layui-table-cell{height:20px;line-height:20px}.layui-table[lay-data]{display:none}.layui-table-box{position:relative;overflow:hidden}.layui-table-view .layui-table{position:relative;width:auto;margin:0}.layui-table-view .layui-table[lay-skin=line]{border-width:0 1px 0 0}.layui-table-view .layui-table[lay-skin=row]{border-width:0 0 1px}.layui-table-view .layui-table td,.layui-table-view .layui-table th{padding:5px 0;border-top:none;border-left:none}.layui-table-view .layui-table th.layui-unselect .layui-table-cell span{cursor:pointer}.layui-table-view .layui-table td{cursor:default}.layui-table-view .layui-form-checkbox[lay-skin=primary] i{width:18px;height:18px}.layui-table-view .layui-form-radio{line-height:0;padding:0}.layui-table-view .layui-form-radio>i{margin:0;font-size:20px}.layui-table-init{position:absolute;left:0;top:0;width:100%;height:100%;text-align:center;z-index:110}.layui-table-init .layui-icon{position:absolute;left:50%;top:50%;margin:-15px 0 0 -15px;font-size:30px;color:#c2c2c2}.layui-table-header{border-width:0 0 1px;overflow:hidden}.layui-table-header .layui-table{margin-bottom:-1px}.layui-table-tool .layui-inline[lay-event]{position:relative;width:26px;height:26px;padding:5px;line-height:16px;margin-right:10px;text-align:center;color:#333;border:1px solid #ccc;cursor:pointer;-webkit-transition:.5s all;transition:.5s all}.layui-table-tool .layui-inline[lay-event]:hover{border:1px solid #999}.layui-table-tool-temp{padding-right:120px}.layui-table-tool-self{position:absolute;right:17px;top:10px}.layui-table-tool .layui-table-tool-self .layui-inline[lay-event]{margin:0 0 0 10px}.layui-table-tool-panel{position:absolute;top:29px;left:-1px;padding:5px 0;min-width:150px;min-height:40px;border:1px solid #d2d2d2;text-align:left;overflow-y:auto;background-color:#fff;box-shadow:0 2px 4px rgba(0,0,0,.12)}.layui-table-cell,.layui-table-tool-panel li{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.layui-table-tool-panel li{padding:0 10px;line-height:30px;-webkit-transition:.5s all;transition:.5s all}.layui-table-tool-panel li .layui-form-checkbox[lay-skin=primary]{width:100%;padding-left:28px}.layui-table-tool-panel li:hover{background-color:#f2f2f2}.layui-table-tool-panel li .layui-form-checkbox[lay-skin=primary] i{position:absolute;left:0;top:0}.layui-table-tool-panel li .layui-form-checkbox[lay-skin=primary] span{padding:0}.layui-table-tool .layui-table-tool-self .layui-table-tool-panel{left:auto;right:-1px}.layui-table-col-set{position:absolute;right:0;top:0;width:20px;height:100%;border-width:0 0 0 1px;background-color:#fff}.layui-table-sort{width:10px;height:20px;margin-left:5px;cursor:pointer!important}.layui-table-sort .layui-edge{position:absolute;left:5px;border-width:5px}.layui-table-sort .layui-table-sort-asc{top:3px;border-top:none;border-bottom-style:solid;border-bottom-color:#b2b2b2}.layui-table-sort .layui-table-sort-asc:hover{border-bottom-color:#666}.layui-table-sort .layui-table-sort-desc{bottom:5px;border-bottom:none;border-top-style:solid;border-top-color:#b2b2b2}.layui-table-sort .layui-table-sort-desc:hover{border-top-color:#666}.layui-table-sort[lay-sort=asc] .layui-table-sort-asc{border-bottom-color:#000}.layui-table-sort[lay-sort=desc] .layui-table-sort-desc{border-top-color:#000}.layui-table-cell{height:28px;line-height:28px;padding:0 15px;position:relative;box-sizing:border-box}.layui-table-cell .layui-form-checkbox[lay-skin=primary]{top:-1px;padding:0}.layui-table-cell .layui-table-link{color:#01AAED}.laytable-cell-checkbox,.laytable-cell-numbers,.laytable-cell-radio,.laytable-cell-space{padding:0;text-align:center}.layui-table-body{position:relative;overflow:auto;margin-right:-1px;margin-bottom:-1px}.layui-table-body .layui-none{line-height:26px;padding:15px;text-align:center;color:#999}.layui-table-fixed{position:absolute;left:0;top:0;z-index:101}.layui-table-fixed .layui-table-body{overflow:hidden}.layui-table-fixed-l{box-shadow:0 -1px 8px rgba(0,0,0,.08)}.layui-table-fixed-r{left:auto;right:-1px;border-width:0 0 0 1px;box-shadow:-1px 0 8px rgba(0,0,0,.08)}.layui-table-fixed-r .layui-table-header{position:relative;overflow:visible}.layui-table-mend{position:absolute;right:-49px;top:0;height:100%;width:50px}.layui-table-tool{position:relative;z-index:890;width:100%;min-height:50px;line-height:30px;padding:10px 15px;border-width:0 0 1px}.layui-table-tool .layui-btn-container{margin-bottom:-10px}.layui-table-page,.layui-table-total{border-width:1px 0 0;margin-bottom:-1px;overflow:hidden}.layui-table-page{position:relative;width:100%;padding:7px 7px 0;height:41px;font-size:12px;white-space:nowrap}.layui-table-page>div{height:26px}.layui-table-page .layui-laypage{margin:0}.layui-table-page .layui-laypage a,.layui-table-page .layui-laypage span{height:26px;line-height:26px;margin-bottom:10px;border:none;background:0 0}.layui-table-page .layui-laypage a,.layui-table-page .layui-laypage span.layui-laypage-curr{padding:0 12px}.layui-table-page .layui-laypage span{margin-left:0;padding:0}.layui-table-page .layui-laypage .layui-laypage-prev{margin-left:-7px!important}.layui-table-page .layui-laypage .layui-laypage-curr .layui-laypage-em{left:0;top:0;padding:0}.layui-table-page .layui-laypage button,.layui-table-page .layui-laypage input{height:26px;line-height:26px}.layui-table-page .layui-laypage input{width:40px}.layui-table-page .layui-laypage button{padding:0 10px}.layui-table-page select{height:18px}.layui-table-patch .layui-table-cell{padding:0;width:30px}.layui-table-edit{position:absolute;left:0;top:0;width:100%;height:100%;padding:0 14px 1px;border-radius:0;box-shadow:1px 1px 20px rgba(0,0,0,.15)}.layui-table-edit:focus{border-color:#5FB878!important}select.layui-table-edit{padding:0 0 0 10px;border-color:#C9C9C9}.layui-table-view .layui-form-checkbox,.layui-table-view .layui-form-radio,.layui-table-view .layui-form-switch{top:0;margin:0;box-sizing:content-box}.layui-table-view .layui-form-checkbox{top:-1px;height:26px;line-height:26px}.layui-table-view .layui-form-checkbox i{height:26px}.layui-table-grid .layui-table-cell{overflow:visible}.layui-table-grid-down{position:absolute;top:0;right:0;width:26px;height:100%;padding:5px 0;border-width:0 0 0 1px;text-align:center;background-color:#fff;color:#999;cursor:pointer}.layui-table-grid-down .layui-icon{position:absolute;top:50%;left:50%;margin:-8px 0 0 -8px}.layui-table-grid-down:hover{background-color:#fbfbfb}body .layui-table-tips .layui-layer-content{background:0 0;padding:0;box-shadow:0 1px 6px rgba(0,0,0,.12)}.layui-table-tips-main{margin:-44px 0 0 -1px;max-height:150px;padding:8px 15px;font-size:14px;overflow-y:scroll;background-color:#fff;color:#666}.layui-table-tips-c{position:absolute;right:-3px;top:-13px;width:20px;height:20px;padding:3px;cursor:pointer;background-color:#666;border-radius:50%;color:#fff}.layui-table-tips-c:hover{background-color:#777}.layui-table-tips-c:before{position:relative;right:-2px}.layui-upload-file{display:none!important;opacity:.01;filter:Alpha(opacity=1)}.layui-upload-drag,.layui-upload-form,.layui-upload-wrap{display:inline-block}.layui-upload-list{margin:10px 0}.layui-upload-choose{padding:0 10px;color:#999}.layui-upload-drag{position:relative;padding:30px;border:1px dashed #e2e2e2;background-color:#fff;text-align:center;cursor:pointer;color:#999}.layui-upload-drag .layui-icon{font-size:50px;color:#009688}.layui-upload-drag[lay-over]{border-color:#009688}.layui-upload-iframe{position:absolute;width:0;height:0;border:0;visibility:hidden}.layui-upload-wrap{position:relative;vertical-align:middle}.layui-upload-wrap .layui-upload-file{display:block!important;position:absolute;left:0;top:0;z-index:10;font-size:100px;width:100%;height:100%;opacity:.01;filter:Alpha(opacity=1);cursor:pointer}.layui-tree{line-height:26px}.layui-tree li{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.layui-tree li .layui-tree-spread,.layui-tree li a{display:inline-block;vertical-align:top;height:26px;*display:inline;*zoom:1;cursor:pointer}.layui-tree li a{font-size:0}.layui-tree li a i{font-size:16px}.layui-tree li a cite{padding:0 6px;font-size:14px;font-style:normal}.layui-tree li i{padding-left:6px;color:#333;-moz-user-select:none}.layui-tree li .layui-tree-check{font-size:13px}.layui-tree li .layui-tree-check:hover{color:#009E94}.layui-tree li ul{display:none;margin-left:20px}.layui-tree li .layui-tree-enter{line-height:24px;border:1px dotted #000}.layui-tree-drag{display:none;position:absolute;left:-666px;top:-666px;background-color:#f2f2f2;padding:5px 10px;border:1px dotted #000;white-space:nowrap}.layui-tree-drag i{padding-right:5px}.layui-nav{position:relative;padding:0 20px;background-color:#393D49;color:#fff;border-radius:2px;font-size:0;box-sizing:border-box}.layui-nav *{font-size:14px}.layui-nav .layui-nav-item{position:relative;display:inline-block;*display:inline;*zoom:1;vertical-align:middle;line-height:60px}.layui-nav .layui-nav-item a{display:block;padding:0 20px;color:#fff;color:rgba(255,255,255,.7);transition:all .3s;-webkit-transition:all .3s}.layui-nav .layui-this:after,.layui-nav-bar,.layui-nav-tree .layui-nav-itemed:after{position:absolute;left:0;top:0;width:0;height:5px;background-color:#5FB878;transition:all .2s;-webkit-transition:all .2s}.layui-nav-bar{z-index:1000}.layui-nav .layui-nav-item a:hover,.layui-nav .layui-this a{color:#fff}.layui-nav .layui-this:after{content:'';top:auto;bottom:0;width:100%}.layui-nav-img{width:30px;height:30px;margin-right:10px;border-radius:50%}.layui-nav .layui-nav-more{content:'';width:0;height:0;border-style:solid dashed dashed;border-color:#fff transparent transparent;overflow:hidden;cursor:pointer;transition:all .2s;-webkit-transition:all .2s;position:absolute;top:50%;right:3px;margin-top:-3px;border-width:6px;border-top-color:rgba(255,255,255,.7)}.layui-nav .layui-nav-mored,.layui-nav-itemed>a .layui-nav-more{margin-top:-9px;border-style:dashed dashed solid;border-color:transparent transparent #fff}.layui-nav-child{display:none;position:absolute;left:0;top:65px;min-width:100%;line-height:36px;padding:5px 0;box-shadow:0 2px 4px rgba(0,0,0,.12);border:1px solid #d2d2d2;background-color:#fff;z-index:100;border-radius:2px;white-space:nowrap}.layui-nav .layui-nav-child a{color:#333}.layui-nav .layui-nav-child a:hover{background-color:#f2f2f2;color:#000}.layui-nav-child dd{position:relative}.layui-nav .layui-nav-child dd.layui-this a,.layui-nav-child dd.layui-this{background-color:#5FB878;color:#fff}.layui-nav-child dd.layui-this:after{display:none}.layui-nav-tree{width:200px;padding:0}.layui-nav-tree .layui-nav-item{display:block;width:100%;line-height:45px}.layui-nav-tree .layui-nav-item a{position:relative;height:45px;line-height:45px;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.layui-nav-tree .layui-nav-item a:hover{background-color:#4E5465}.layui-nav-tree .layui-nav-bar{width:5px;height:0;background-color:#009688}.layui-nav-tree .layui-nav-child dd.layui-this,.layui-nav-tree .layui-nav-child dd.layui-this a,.layui-nav-tree .layui-this,.layui-nav-tree .layui-this>a,.layui-nav-tree .layui-this>a:hover{background-color:#009688;color:#fff}.layui-nav-tree .layui-this:after{display:none}.layui-nav-itemed>a,.layui-nav-tree .layui-nav-title a,.layui-nav-tree .layui-nav-title a:hover{color:#fff!important}.layui-nav-tree .layui-nav-child{position:relative;z-index:0;top:0;border:none;box-shadow:none}.layui-nav-tree .layui-nav-child a{height:40px;line-height:40px;color:#fff;color:rgba(255,255,255,.7)}.layui-nav-tree .layui-nav-child,.layui-nav-tree .layui-nav-child a:hover{background:0 0;color:#fff}.layui-nav-tree .layui-nav-more{right:10px}.layui-nav-itemed>.layui-nav-child{display:block;padding:0;background-color:rgba(0,0,0,.3)!important}.layui-nav-itemed>.layui-nav-child>.layui-this>.layui-nav-child{display:block}.layui-nav-side{position:fixed;top:0;bottom:0;left:0;overflow-x:hidden;z-index:999}.layui-bg-blue .layui-nav-bar,.layui-bg-blue .layui-nav-itemed:after,.layui-bg-blue .layui-this:after{background-color:#93D1FF}.layui-bg-blue .layui-nav-child dd.layui-this{background-color:#1E9FFF}.layui-bg-blue .layui-nav-itemed>a,.layui-nav-tree.layui-bg-blue .layui-nav-title a,.layui-nav-tree.layui-bg-blue .layui-nav-title a:hover{background-color:#007DDB!important}.layui-breadcrumb{visibility:hidden;font-size:0}.layui-breadcrumb>*{font-size:14px}.layui-breadcrumb a{color:#999!important}.layui-breadcrumb a:hover{color:#5FB878!important}.layui-breadcrumb a cite{color:#666;font-style:normal}.layui-breadcrumb span[lay-separator]{margin:0 10px;color:#999}.layui-tab{margin:10px 0;text-align:left!important}.layui-tab[overflow]>.layui-tab-title{overflow:hidden}.layui-tab-title{position:relative;left:0;height:40px;white-space:nowrap;font-size:0;border-bottom-width:1px;border-bottom-style:solid;transition:all .2s;-webkit-transition:all .2s}.layui-tab-title li{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;font-size:14px;transition:all .2s;-webkit-transition:all .2s;position:relative;line-height:40px;min-width:65px;padding:0 15px;text-align:center;cursor:pointer}.layui-tab-title li a{display:block}.layui-tab-title .layui-this{color:#000}.layui-tab-title .layui-this:after{position:absolute;left:0;top:0;content:'';width:100%;height:41px;border-width:1px;border-style:solid;border-bottom-color:#fff;border-radius:2px 2px 0 0;box-sizing:border-box;pointer-events:none}.layui-tab-bar{position:absolute;right:0;top:0;z-index:10;width:30px;height:39px;line-height:39px;border-width:1px;border-style:solid;border-radius:2px;text-align:center;background-color:#fff;cursor:pointer}.layui-tab-bar .layui-icon{position:relative;display:inline-block;top:3px;transition:all .3s;-webkit-transition:all .3s}.layui-tab-item{display:none}.layui-tab-more{padding-right:30px;height:auto!important;white-space:normal!important}.layui-tab-more li.layui-this:after{border-bottom-color:#e2e2e2;border-radius:2px}.layui-tab-more .layui-tab-bar .layui-icon{top:-2px;top:3px\\9;-webkit-transform:rotate(180deg);transform:rotate(180deg)}:root .layui-tab-more .layui-tab-bar .layui-icon{top:-2px\\0/IE9}.layui-tab-content{padding:10px}.layui-tab-title li .layui-tab-close{position:relative;display:inline-block;width:18px;height:18px;line-height:20px;margin-left:8px;top:1px;text-align:center;font-size:14px;color:#c2c2c2;transition:all .2s;-webkit-transition:all .2s}.layui-tab-title li .layui-tab-close:hover{border-radius:2px;background-color:#FF5722;color:#fff}.layui-tab-brief>.layui-tab-title .layui-this{color:#009688}.layui-tab-brief>.layui-tab-more li.layui-this:after,.layui-tab-brief>.layui-tab-title .layui-this:after{border:none;border-radius:0;border-bottom:2px solid #5FB878}.layui-tab-brief[overflow]>.layui-tab-title .layui-this:after{top:-1px}.layui-tab-card{border-width:1px;border-style:solid;border-radius:2px;box-shadow:0 2px 5px 0 rgba(0,0,0,.1)}.layui-tab-card>.layui-tab-title{background-color:#f2f2f2}.layui-tab-card>.layui-tab-title li{margin-right:-1px;margin-left:-1px}.layui-tab-card>.layui-tab-title .layui-this{background-color:#fff}.layui-tab-card>.layui-tab-title .layui-this:after{border-top:none;border-width:1px;border-bottom-color:#fff}.layui-tab-card>.layui-tab-title .layui-tab-bar{height:40px;line-height:40px;border-radius:0;border-top:none;border-right:none}.layui-tab-card>.layui-tab-more .layui-this{background:0 0;color:#5FB878}.layui-tab-card>.layui-tab-more .layui-this:after{border:none}.layui-timeline{padding-left:5px}.layui-timeline-item{position:relative;padding-bottom:20px}.layui-timeline-axis{position:absolute;left:-5px;top:0;z-index:10;width:20px;height:20px;line-height:20px;background-color:#fff;color:#5FB878;border-radius:50%;text-align:center;cursor:pointer}.layui-timeline-axis:hover{color:#FF5722}.layui-timeline-item:before{content:'';position:absolute;left:5px;top:0;z-index:0;width:1px;height:100%}.layui-timeline-item:last-child:before{display:none}.layui-timeline-item:first-child:before{display:block}.layui-timeline-content{padding-left:25px}.layui-timeline-title{position:relative;margin-bottom:10px}.layui-badge,.layui-badge-dot,.layui-badge-rim{position:relative;display:inline-block;padding:0 6px;font-size:12px;text-align:center;background-color:#FF5722;color:#fff;border-radius:2px}.layui-badge{height:18px;line-height:18px}.layui-badge-dot{width:8px;height:8px;padding:0;border-radius:50%}.layui-badge-rim{height:18px;line-height:18px;border-width:1px;border-style:solid;background-color:#fff;color:#666}.layui-btn .layui-badge,.layui-btn .layui-badge-dot{margin-left:5px}.layui-nav .layui-badge,.layui-nav .layui-badge-dot{position:absolute;top:50%;margin:-8px 6px 0}.layui-tab-title .layui-badge,.layui-tab-title .layui-badge-dot{left:5px;top:-2px}.layui-carousel{position:relative;left:0;top:0;background-color:#f8f8f8}.layui-carousel>[carousel-item]{position:relative;width:100%;height:100%;overflow:hidden}.layui-carousel>[carousel-item]:before{position:absolute;content:'\\e63d';left:50%;top:50%;width:100px;line-height:20px;margin:-10px 0 0 -50px;text-align:center;color:#c2c2c2;font-family:layui-icon!important;font-size:30px;font-style:normal;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.layui-carousel>[carousel-item]>*{display:none;position:absolute;left:0;top:0;width:100%;height:100%;background-color:#f8f8f8;transition-duration:.3s;-webkit-transition-duration:.3s}.layui-carousel-updown>*{-webkit-transition:.3s ease-in-out up;transition:.3s ease-in-out up}.layui-carousel-arrow{display:none\\9;opacity:0;position:absolute;left:10px;top:50%;margin-top:-18px;width:36px;height:36px;line-height:36px;text-align:center;font-size:20px;border:0;border-radius:50%;background-color:rgba(0,0,0,.2);color:#fff;-webkit-transition-duration:.3s;transition-duration:.3s;cursor:pointer}.layui-carousel-arrow[lay-type=add]{left:auto!important;right:10px}.layui-carousel:hover .layui-carousel-arrow[lay-type=add],.layui-carousel[lay-arrow=always] .layui-carousel-arrow[lay-type=add]{right:20px}.layui-carousel[lay-arrow=always] .layui-carousel-arrow{opacity:1;left:20px}.layui-carousel[lay-arrow=none] .layui-carousel-arrow{display:none}.layui-carousel-arrow:hover,.layui-carousel-ind ul:hover{background-color:rgba(0,0,0,.35)}.layui-carousel:hover .layui-carousel-arrow{display:block\\9;opacity:1;left:20px}.layui-carousel-ind{position:relative;top:-35px;width:100%;line-height:0!important;text-align:center;font-size:0}.layui-carousel[lay-indicator=outside]{margin-bottom:30px}.layui-carousel[lay-indicator=outside] .layui-carousel-ind{top:10px}.layui-carousel[lay-indicator=outside] .layui-carousel-ind ul{background-color:rgba(0,0,0,.5)}.layui-carousel[lay-indicator=none] .layui-carousel-ind{display:none}.layui-carousel-ind ul{display:inline-block;padding:5px;background-color:rgba(0,0,0,.2);border-radius:10px;-webkit-transition-duration:.3s;transition-duration:.3s}.layui-carousel-ind li{display:inline-block;width:10px;height:10px;margin:0 3px;font-size:14px;background-color:#e2e2e2;background-color:rgba(255,255,255,.5);border-radius:50%;cursor:pointer;-webkit-transition-duration:.3s;transition-duration:.3s}.layui-carousel-ind li:hover{background-color:rgba(255,255,255,.7)}.layui-carousel-ind li.layui-this{background-color:#fff}.layui-carousel>[carousel-item]>.layui-carousel-next,.layui-carousel>[carousel-item]>.layui-carousel-prev,.layui-carousel>[carousel-item]>.layui-this{display:block}.layui-carousel>[carousel-item]>.layui-this{left:0}.layui-carousel>[carousel-item]>.layui-carousel-prev{left:-100%}.layui-carousel>[carousel-item]>.layui-carousel-next{left:100%}.layui-carousel>[carousel-item]>.layui-carousel-next.layui-carousel-left,.layui-carousel>[carousel-item]>.layui-carousel-prev.layui-carousel-right{left:0}.layui-carousel>[carousel-item]>.layui-this.layui-carousel-left{left:-100%}.layui-carousel>[carousel-item]>.layui-this.layui-carousel-right{left:100%}.layui-carousel[lay-anim=updown] .layui-carousel-arrow{left:50%!important;top:20px;margin:0 0 0 -18px}.layui-carousel[lay-anim=updown]>[carousel-item]>*,.layui-carousel[lay-anim=fade]>[carousel-item]>*{left:0!important}.layui-carousel[lay-anim=updown] .layui-carousel-arrow[lay-type=add]{top:auto!important;bottom:20px}.layui-carousel[lay-anim=updown] .layui-carousel-ind{position:absolute;top:50%;right:20px;width:auto;height:auto}.layui-carousel[lay-anim=updown] .layui-carousel-ind ul{padding:3px 5px}.layui-carousel[lay-anim=updown] .layui-carousel-ind li{display:block;margin:6px 0}.layui-carousel[lay-anim=updown]>[carousel-item]>.layui-this{top:0}.layui-carousel[lay-anim=updown]>[carousel-item]>.layui-carousel-prev{top:-100%}.layui-carousel[lay-anim=updown]>[carousel-item]>.layui-carousel-next{top:100%}.layui-carousel[lay-anim=updown]>[carousel-item]>.layui-carousel-next.layui-carousel-left,.layui-carousel[lay-anim=updown]>[carousel-item]>.layui-carousel-prev.layui-carousel-right{top:0}.layui-carousel[lay-anim=updown]>[carousel-item]>.layui-this.layui-carousel-left{top:-100%}.layui-carousel[lay-anim=updown]>[carousel-item]>.layui-this.layui-carousel-right{top:100%}.layui-carousel[lay-anim=fade]>[carousel-item]>.layui-carousel-next,.layui-carousel[lay-anim=fade]>[carousel-item]>.layui-carousel-prev{opacity:0}.layui-carousel[lay-anim=fade]>[carousel-item]>.layui-carousel-next.layui-carousel-left,.layui-carousel[lay-anim=fade]>[carousel-item]>.layui-carousel-prev.layui-carousel-right{opacity:1}.layui-carousel[lay-anim=fade]>[carousel-item]>.layui-this.layui-carousel-left,.layui-carousel[lay-anim=fade]>[carousel-item]>.layui-this.layui-carousel-right{opacity:0}.layui-fixbar{position:fixed;right:15px;bottom:15px;z-index:999999}.layui-fixbar li{width:50px;height:50px;line-height:50px;margin-bottom:1px;text-align:center;cursor:pointer;font-size:30px;background-color:#9F9F9F;color:#fff;border-radius:2px;opacity:.95}.layui-fixbar li:hover{opacity:.85}.layui-fixbar li:active{opacity:1}.layui-fixbar .layui-fixbar-top{display:none;font-size:40px}body .layui-util-face{border:none;background:0 0}body .layui-util-face .layui-layer-content{padding:0;background-color:#fff;color:#666;box-shadow:none}.layui-util-face .layui-layer-TipsG{display:none}.layui-util-face ul{position:relative;width:372px;padding:10px;border:1px solid #D9D9D9;background-color:#fff;box-shadow:0 0 20px rgba(0,0,0,.2)}.layui-util-face ul li{cursor:pointer;float:left;border:1px solid #e8e8e8;height:22px;width:26px;overflow:hidden;margin:-1px 0 0 -1px;padding:4px 2px;text-align:center}.layui-util-face ul li:hover{position:relative;z-index:2;border:1px solid #eb7350;background:#fff9ec}.layui-code{position:relative;margin:10px 0;padding:15px;line-height:20px;border:1px solid #ddd;border-left-width:6px;background-color:#F2F2F2;color:#333;font-family:Courier New;font-size:12px}.layui-rate,.layui-rate *{display:inline-block;vertical-align:middle}.layui-rate{padding:10px 5px 10px 0;font-size:0}.layui-rate li i.layui-icon{font-size:20px;color:#FFB800;margin-right:5px;transition:all .3s;-webkit-transition:all .3s}.layui-rate li i:hover{cursor:pointer;transform:scale(1.12);-webkit-transform:scale(1.12)}.layui-rate[readonly] li i:hover{cursor:default;transform:scale(1)}.layui-colorpicker{width:26px;height:26px;border:1px solid #e6e6e6;padding:5px;border-radius:2px;line-height:24px;display:inline-block;cursor:pointer;transition:all .3s;-webkit-transition:all .3s}.layui-colorpicker:hover{border-color:#d2d2d2}.layui-colorpicker.layui-colorpicker-lg{width:34px;height:34px;line-height:32px}.layui-colorpicker.layui-colorpicker-sm{width:24px;height:24px;line-height:22px}.layui-colorpicker.layui-colorpicker-xs{width:22px;height:22px;line-height:20px}.layui-colorpicker-trigger-bgcolor{display:block;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);border-radius:2px}.layui-colorpicker-trigger-span{display:block;height:100%;box-sizing:border-box;border:1px solid rgba(0,0,0,.15);border-radius:2px;text-align:center}.layui-colorpicker-trigger-i{display:inline-block;color:#FFF;font-size:12px}.layui-colorpicker-trigger-i.layui-icon-close{color:#999}.layui-colorpicker-main{position:absolute;z-index:66666666;width:280px;padding:7px;background:#FFF;border:1px solid #d2d2d2;border-radius:2px;box-shadow:0 2px 4px rgba(0,0,0,.12)}.layui-colorpicker-main-wrapper{height:180px;position:relative}.layui-colorpicker-basis{width:260px;height:100%;position:relative}.layui-colorpicker-basis-white{width:100%;height:100%;position:absolute;top:0;left:0;background:linear-gradient(90deg,#FFF,hsla(0,0%,100%,0))}.layui-colorpicker-basis-black{width:100%;height:100%;position:absolute;top:0;left:0;background:linear-gradient(0deg,#000,transparent)}.layui-colorpicker-basis-cursor{width:10px;height:10px;border:1px solid #FFF;border-radius:50%;position:absolute;top:-3px;right:-3px;cursor:pointer}.layui-colorpicker-side{position:absolute;top:0;right:0;width:12px;height:100%;background:linear-gradient(red,#FF0,#0F0,#0FF,#00F,#F0F,red)}.layui-colorpicker-side-slider{width:100%;height:5px;box-shadow:0 0 1px #888;box-sizing:border-box;background:#FFF;border-radius:1px;border:1px solid #f0f0f0;cursor:pointer;position:absolute;left:0}.layui-colorpicker-main-alpha{display:none;height:12px;margin-top:7px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==)}.layui-colorpicker-alpha-bgcolor{height:100%;position:relative}.layui-colorpicker-alpha-slider{width:5px;height:100%;box-shadow:0 0 1px #888;box-sizing:border-box;background:#FFF;border-radius:1px;border:1px solid #f0f0f0;cursor:pointer;position:absolute;top:0}.layui-colorpicker-main-pre{padding-top:7px;font-size:0}.layui-colorpicker-pre{width:20px;height:20px;border-radius:2px;display:inline-block;margin-left:6px;margin-bottom:7px;cursor:pointer}.layui-colorpicker-pre:nth-child(11n+1){margin-left:0}.layui-colorpicker-pre-isalpha{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==)}.layui-colorpicker-pre.layui-this{box-shadow:0 0 3px 2px rgba(0,0,0,.15)}.layui-colorpicker-pre>div{height:100%;border-radius:2px}.layui-colorpicker-main-input{text-align:right;padding-top:7px}.layui-colorpicker-main-input .layui-btn-container .layui-btn{margin:0 0 0 10px}.layui-colorpicker-main-input div.layui-inline{float:left;margin-right:10px;font-size:14px}.layui-colorpicker-main-input input.layui-input{width:150px;height:30px;color:#666}.layui-slider{height:4px;background:#e2e2e2;border-radius:3px;position:relative;cursor:pointer}.layui-slider-bar{border-radius:3px;position:absolute;height:100%}.layui-slider-step{position:absolute;top:0;width:4px;height:4px;border-radius:50%;background:#FFF;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.layui-slider-wrap{width:36px;height:36px;position:absolute;top:-16px;-webkit-transform:translateX(-50%);transform:translateX(-50%);z-index:10;text-align:center}.layui-slider-wrap-btn{width:12px;height:12px;border-radius:50%;background:#FFF;display:inline-block;vertical-align:middle;cursor:pointer;transition:.3s}.layui-slider-wrap:after{content:\"\";height:100%;display:inline-block;vertical-align:middle}.layui-slider-wrap-btn.layui-slider-hover,.layui-slider-wrap-btn:hover{transform:scale(1.2)}.layui-slider-wrap-btn.layui-disabled:hover{transform:scale(1)!important}.layui-slider-tips{position:absolute;top:-42px;z-index:66666666;white-space:nowrap;display:none;-webkit-transform:translateX(-50%);transform:translateX(-50%);color:#FFF;background:#000;border-radius:3px;height:25px;line-height:25px;padding:0 10px}.layui-slider-tips:after{content:'';position:absolute;bottom:-12px;left:50%;margin-left:-6px;width:0;height:0;border-width:6px;border-style:solid;border-color:#000 transparent transparent}.layui-slider-input{width:70px;height:32px;border:1px solid #e6e6e6;border-radius:3px;font-size:16px;line-height:32px;position:absolute;right:0;top:-15px}.layui-slider-input-btn{display:none;position:absolute;top:0;right:0;width:20px;height:100%;border-left:1px solid #d2d2d2}.layui-slider-input-btn i{cursor:pointer;position:absolute;right:0;bottom:0;width:20px;height:50%;font-size:12px;line-height:16px;text-align:center;color:#999}.layui-slider-input-btn i:first-child{top:0;border-bottom:1px solid #d2d2d2}.layui-slider-input-txt{height:100%;font-size:14px}.layui-slider-input-txt input{height:100%;border:none}.layui-slider-input-btn i:hover{color:#009688}.layui-slider-vertical{width:4px;margin-left:34px}.layui-slider-vertical .layui-slider-bar{width:4px}.layui-slider-vertical .layui-slider-step{top:auto;left:0;-webkit-transform:translateY(50%);transform:translateY(50%)}.layui-slider-vertical .layui-slider-wrap{top:auto;left:-16px;-webkit-transform:translateY(50%);transform:translateY(50%)}.layui-slider-vertical .layui-slider-tips{top:auto;left:2px}@media \\0screen{.layui-slider-wrap-btn{margin-left:-20px}.layui-slider-vertical .layui-slider-wrap-btn{margin-left:0;margin-bottom:-20px}.layui-slider-vertical .layui-slider-tips{margin-left:-8px}.layui-slider>span{margin-left:8px}}.layui-anim{-webkit-animation-duration:.3s;animation-duration:.3s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.layui-anim.layui-icon{display:inline-block}.layui-anim-loop{-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.layui-trans,.layui-trans a{transition:all .3s;-webkit-transition:all .3s}@-webkit-keyframes layui-rotate{from{-webkit-transform:rotate(0)}to{-webkit-transform:rotate(360deg)}}@keyframes layui-rotate{from{transform:rotate(0)}to{transform:rotate(360deg)}}.layui-anim-rotate{-webkit-animation-name:layui-rotate;animation-name:layui-rotate;-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-timing-function:linear;animation-timing-function:linear}@-webkit-keyframes layui-up{from{-webkit-transform:translate3d(0,100%,0);opacity:.3}to{-webkit-transform:translate3d(0,0,0);opacity:1}}@keyframes layui-up{from{transform:translate3d(0,100%,0);opacity:.3}to{transform:translate3d(0,0,0);opacity:1}}.layui-anim-up{-webkit-animation-name:layui-up;animation-name:layui-up}@-webkit-keyframes layui-upbit{from{-webkit-transform:translate3d(0,30px,0);opacity:.3}to{-webkit-transform:translate3d(0,0,0);opacity:1}}@keyframes layui-upbit{from{transform:translate3d(0,30px,0);opacity:.3}to{transform:translate3d(0,0,0);opacity:1}}.layui-anim-upbit{-webkit-animation-name:layui-upbit;animation-name:layui-upbit}@-webkit-keyframes layui-scale{0%{opacity:.3;-webkit-transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1)}}@keyframes layui-scale{0%{opacity:.3;-ms-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-ms-transform:scale(1);transform:scale(1)}}.layui-anim-scale{-webkit-animation-name:layui-scale;animation-name:layui-scale}@-webkit-keyframes layui-scale-spring{0%{opacity:.5;-webkit-transform:scale(.5)}80%{opacity:.8;-webkit-transform:scale(1.1)}100%{opacity:1;-webkit-transform:scale(1)}}@keyframes layui-scale-spring{0%{opacity:.5;transform:scale(.5)}80%{opacity:.8;transform:scale(1.1)}100%{opacity:1;transform:scale(1)}}.layui-anim-scaleSpring{-webkit-animation-name:layui-scale-spring;animation-name:layui-scale-spring}@-webkit-keyframes layui-fadein{0%{opacity:0}100%{opacity:1}}@keyframes layui-fadein{0%{opacity:0}100%{opacity:1}}.layui-anim-fadein{-webkit-animation-name:layui-fadein;animation-name:layui-fadein}@-webkit-keyframes layui-fadeout{0%{opacity:1}100%{opacity:0}}@keyframes layui-fadeout{0%{opacity:1}100%{opacity:0}}.layui-anim-fadeout{-webkit-animation-name:layui-fadeout;animation-name:layui-fadeout}"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/layui/css/layui.mobile.css",
    "content": "/** layui-v2.4.5 MIT License By https://www.layui.com */\n blockquote,body,button,dd,div,dl,dt,form,h1,h2,h3,h4,h5,h6,input,legend,li,ol,p,td,textarea,th,ul{margin:0;padding:0;-webkit-tap-highlight-color:rgba(0,0,0,0)}html{font:12px 'Helvetica Neue','PingFang SC',STHeitiSC-Light,Helvetica,Arial,sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}a,button,input{-webkit-tap-highlight-color:rgba(255,0,0,0)}a{text-decoration:none;background:0 0}a:active,a:hover{outline:0}table{border-collapse:collapse;border-spacing:0}li{list-style:none}b,strong{font-weight:700}h1,h2,h3,h4,h5,h6{font-weight:500}address,cite,dfn,em,var{font-style:normal}dfn{font-style:italic}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}img{border:0;vertical-align:bottom}.layui-inline,input,label{vertical-align:middle}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0;outline:0}button,select{text-transform:none}select{-webkit-appearance:none;border:none}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}@font-face{font-family:layui-icon;src:url(../font/iconfont.eot?v=1.0.7);src:url(../font/iconfont.eot?v=1.0.7#iefix) format('embedded-opentype'),url(../font/iconfont.woff?v=1.0.7) format('woff'),url(../font/iconfont.ttf?v=1.0.7) format('truetype'),url(../font/iconfont.svg?v=1.0.7#iconfont) format('svg')}.layui-icon{font-family:layui-icon!important;font-size:16px;font-style:normal;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.layui-box,.layui-box *{-webkit-box-sizing:content-box!important;-moz-box-sizing:content-box!important;box-sizing:content-box!important}.layui-border-box,.layui-border-box *{-webkit-box-sizing:border-box!important;-moz-box-sizing:border-box!important;box-sizing:border-box!important}.layui-inline{position:relative;display:inline-block;*display:inline;*zoom:1}.layui-edge,.layui-upload-iframe{position:absolute;width:0;height:0}.layui-edge{border-style:dashed;border-color:transparent;overflow:hidden}.layui-elip{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.layui-unselect{-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.layui-disabled,.layui-disabled:active{background-color:#d2d2d2!important;color:#fff!important;cursor:not-allowed!important}.layui-circle{border-radius:100%}.layui-show{display:block!important}.layui-hide{display:none!important}.layui-upload-iframe{border:0;visibility:hidden}.layui-upload-enter{border:1px solid #009E94;background-color:#009E94;color:#fff;-webkit-transform:scale(1.1);transform:scale(1.1)}@-webkit-keyframes layui-m-anim-scale{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes layui-m-anim-scale{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}.layui-m-anim-scale{animation-name:layui-m-anim-scale;-webkit-animation-name:layui-m-anim-scale}@-webkit-keyframes layui-m-anim-up{0%{opacity:0;-webkit-transform:translateY(800px);transform:translateY(800px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes layui-m-anim-up{0%{opacity:0;-webkit-transform:translateY(800px);transform:translateY(800px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}.layui-m-anim-up{-webkit-animation-name:layui-m-anim-up;animation-name:layui-m-anim-up}@-webkit-keyframes layui-m-anim-left{0%{-webkit-transform:translateX(100%);transform:translateX(100%)}100%{-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes layui-m-anim-left{0%{-webkit-transform:translateX(100%);transform:translateX(100%)}100%{-webkit-transform:translateX(0);transform:translateX(0)}}.layui-m-anim-left{-webkit-animation-name:layui-m-anim-left;animation-name:layui-m-anim-left}@-webkit-keyframes layui-m-anim-right{0%{-webkit-transform:translateX(-100%);transform:translateX(-100%)}100%{-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes layui-m-anim-right{0%{-webkit-transform:translateX(-100%);transform:translateX(-100%)}100%{-webkit-transform:translateX(0);transform:translateX(0)}}.layui-m-anim-right{-webkit-animation-name:layui-m-anim-right;animation-name:layui-m-anim-right}@-webkit-keyframes layui-m-anim-lout{0%{-webkit-transform:translateX(0);transform:translateX(0)}100%{-webkit-transform:translateX(-100%);transform:translateX(-100%)}}@keyframes layui-m-anim-lout{0%{-webkit-transform:translateX(0);transform:translateX(0)}100%{-webkit-transform:translateX(-100%);transform:translateX(-100%)}}.layui-m-anim-lout{-webkit-animation-name:layui-m-anim-lout;animation-name:layui-m-anim-lout}@-webkit-keyframes layui-m-anim-rout{0%{-webkit-transform:translateX(0);transform:translateX(0)}100%{-webkit-transform:translateX(100%);transform:translateX(100%)}}@keyframes layui-m-anim-rout{0%{-webkit-transform:translateX(0);transform:translateX(0)}100%{-webkit-transform:translateX(100%);transform:translateX(100%)}}.layui-m-anim-rout{-webkit-animation-name:layui-m-anim-rout;animation-name:layui-m-anim-rout}.layui-m-layer{position:relative;z-index:19891014}.layui-m-layer *{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.layui-m-layermain,.layui-m-layershade{position:fixed;left:0;top:0;width:100%;height:100%}.layui-m-layershade{background-color:rgba(0,0,0,.7);pointer-events:auto}.layui-m-layermain{display:table;font-family:Helvetica,arial,sans-serif;pointer-events:none}.layui-m-layermain .layui-m-layersection{display:table-cell;vertical-align:middle;text-align:center}.layui-m-layerchild{position:relative;display:inline-block;text-align:left;background-color:#fff;font-size:14px;border-radius:5px;box-shadow:0 0 8px rgba(0,0,0,.1);pointer-events:auto;-webkit-overflow-scrolling:touch;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.2s;animation-duration:.2s}.layui-m-layer0 .layui-m-layerchild{width:90%;max-width:640px}.layui-m-layer1 .layui-m-layerchild{border:none;border-radius:0}.layui-m-layer2 .layui-m-layerchild{width:auto;max-width:260px;min-width:40px;border:none;background:0 0;box-shadow:none;color:#fff}.layui-m-layerchild h3{padding:0 10px;height:60px;line-height:60px;font-size:16px;font-weight:400;border-radius:5px 5px 0 0;text-align:center}.layui-m-layerbtn span,.layui-m-layerchild h3{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.layui-m-layercont{padding:50px 30px;line-height:22px;text-align:center}.layui-m-layer1 .layui-m-layercont{padding:0;text-align:left}.layui-m-layer2 .layui-m-layercont{text-align:center;padding:0;line-height:0}.layui-m-layer2 .layui-m-layercont i{width:25px;height:25px;margin-left:8px;display:inline-block;background-color:#fff;border-radius:100%;-webkit-animation:layui-m-anim-loading 1.4s infinite ease-in-out;animation:layui-m-anim-loading 1.4s infinite ease-in-out;-webkit-animation-fill-mode:both;animation-fill-mode:both}.layui-m-layerbtn,.layui-m-layerbtn span{position:relative;text-align:center;border-radius:0 0 5px 5px}.layui-m-layer2 .layui-m-layercont p{margin-top:20px}@-webkit-keyframes layui-m-anim-loading{0%,100%,80%{transform:scale(0);-webkit-transform:scale(0)}40%{transform:scale(1);-webkit-transform:scale(1)}}@keyframes layui-m-anim-loading{0%,100%,80%{transform:scale(0);-webkit-transform:scale(0)}40%{transform:scale(1);-webkit-transform:scale(1)}}.layui-m-layer2 .layui-m-layercont i:first-child{margin-left:0;-webkit-animation-delay:-.32s;animation-delay:-.32s}.layui-m-layer2 .layui-m-layercont i.layui-m-layerload{-webkit-animation-delay:-.16s;animation-delay:-.16s}.layui-m-layer2 .layui-m-layercont>div{line-height:22px;padding-top:7px;margin-bottom:20px;font-size:14px}.layui-m-layerbtn{display:box;display:-moz-box;display:-webkit-box;width:100%;height:50px;line-height:50px;font-size:0;border-top:1px solid #D0D0D0;background-color:#F2F2F2}.layui-m-layerbtn span{display:block;-moz-box-flex:1;box-flex:1;-webkit-box-flex:1;font-size:14px;cursor:pointer}.layui-m-layerbtn span[yes]{color:#40AFFE}.layui-m-layerbtn span[no]{border-right:1px solid #D0D0D0;border-radius:0 0 0 5px}.layui-m-layerbtn span:active{background-color:#F6F6F6}.layui-m-layerend{position:absolute;right:7px;top:10px;width:30px;height:30px;border:0;font-weight:400;background:0 0;cursor:pointer;-webkit-appearance:none;font-size:30px}.layui-m-layerend::after,.layui-m-layerend::before{position:absolute;left:5px;top:15px;content:'';width:18px;height:1px;background-color:#999;transform:rotate(45deg);-webkit-transform:rotate(45deg);border-radius:3px}.layui-m-layerend::after{transform:rotate(-45deg);-webkit-transform:rotate(-45deg)}body .layui-m-layer .layui-m-layer-footer{position:fixed;width:95%;max-width:100%;margin:0 auto;left:0;right:0;bottom:10px;background:0 0}.layui-m-layer-footer .layui-m-layercont{padding:20px;border-radius:5px 5px 0 0;background-color:rgba(255,255,255,.8)}.layui-m-layer-footer .layui-m-layerbtn{display:block;height:auto;background:0 0;border-top:none}.layui-m-layer-footer .layui-m-layerbtn span{background-color:rgba(255,255,255,.8)}.layui-m-layer-footer .layui-m-layerbtn span[no]{color:#FD482C;border-top:1px solid #c2c2c2;border-radius:0 0 5px 5px}.layui-m-layer-footer .layui-m-layerbtn span[yes]{margin-top:10px;border-radius:5px}body .layui-m-layer .layui-m-layer-msg{width:auto;max-width:90%;margin:0 auto;bottom:-150px;background-color:rgba(0,0,0,.7);color:#fff}.layui-m-layer-msg .layui-m-layercont{padding:10px 20px}"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/layui/css/modules/code.css",
    "content": "/** layui-v2.4.5 MIT License By https://www.layui.com */\n html #layuicss-skincodecss{display:none;position:absolute;width:1989px}.layui-code-h3,.layui-code-view{position:relative;font-size:12px}.layui-code-view{display:block;margin:10px 0;padding:0;border:1px solid #e2e2e2;border-left-width:6px;background-color:#F2F2F2;color:#333;font-family:Courier New}.layui-code-h3{padding:0 10px;height:32px;line-height:32px;border-bottom:1px solid #e2e2e2}.layui-code-h3 a{position:absolute;right:10px;top:0;color:#999}.layui-code-view .layui-code-ol{position:relative;overflow:auto}.layui-code-view .layui-code-ol li{position:relative;margin-left:45px;line-height:20px;padding:0 5px;border-left:1px solid #e2e2e2;list-style-type:decimal-leading-zero;*list-style-type:decimal;background-color:#fff}.layui-code-view pre{margin:0}.layui-code-notepad{border:1px solid #0C0C0C;border-left-color:#3F3F3F;background-color:#0C0C0C;color:#C2BE9E}.layui-code-notepad .layui-code-h3{border-bottom:none}.layui-code-notepad .layui-code-ol li{background-color:#3F3F3F;border-left:none}"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/layui/css/modules/laydate/default/laydate.css",
    "content": "/** layui-v2.4.5 MIT License By https://www.layui.com */\n .laydate-set-ym,.layui-laydate,.layui-laydate *,.layui-laydate-list{box-sizing:border-box}html #layuicss-laydate{display:none;position:absolute;width:1989px}.layui-laydate *{margin:0;padding:0}.layui-laydate{position:absolute;z-index:66666666;margin:5px 0;border-radius:2px;font-size:14px;-webkit-animation-duration:.3s;animation-duration:.3s;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-name:laydate-upbit;animation-name:laydate-upbit}.layui-laydate-main{width:272px}.layui-laydate-content td,.layui-laydate-header *,.layui-laydate-list li{transition-duration:.3s;-webkit-transition-duration:.3s}@-webkit-keyframes laydate-upbit{from{-webkit-transform:translate3d(0,20px,0);opacity:.3}to{-webkit-transform:translate3d(0,0,0);opacity:1}}@keyframes laydate-upbit{from{transform:translate3d(0,20px,0);opacity:.3}to{transform:translate3d(0,0,0);opacity:1}}.layui-laydate-static{position:relative;z-index:0;display:inline-block;margin:0;-webkit-animation:none;animation:none}.laydate-ym-show .laydate-next-m,.laydate-ym-show .laydate-prev-m{display:none!important}.laydate-ym-show .laydate-next-y,.laydate-ym-show .laydate-prev-y{display:inline-block!important}.laydate-time-show .laydate-set-ym span[lay-type=month],.laydate-time-show .laydate-set-ym span[lay-type=year],.laydate-time-show .layui-laydate-header .layui-icon,.laydate-ym-show .laydate-set-ym span[lay-type=month]{display:none!important}.layui-laydate-header{position:relative;line-height:30px;padding:10px 70px 5px}.laydate-set-ym span,.layui-laydate-header i{padding:0 5px;cursor:pointer}.layui-laydate-header *{display:inline-block;vertical-align:bottom}.layui-laydate-header i{position:absolute;top:10px;color:#999;font-size:18px}.layui-laydate-header i.laydate-prev-y{left:15px}.layui-laydate-header i.laydate-prev-m{left:45px}.layui-laydate-header i.laydate-next-y{right:15px}.layui-laydate-header i.laydate-next-m{right:45px}.laydate-set-ym{width:100%;text-align:center;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.laydate-time-text{cursor:default!important}.layui-laydate-content{position:relative;padding:10px;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.layui-laydate-content table{border-collapse:collapse;border-spacing:0}.layui-laydate-content td,.layui-laydate-content th{width:36px;height:30px;padding:5px;text-align:center}.layui-laydate-content td{position:relative;cursor:pointer}.laydate-day-mark{position:absolute;left:0;top:0;width:100%;height:100%;line-height:30px;font-size:12px;overflow:hidden}.laydate-day-mark::after{position:absolute;content:'';right:2px;top:2px;width:5px;height:5px;border-radius:50%}.layui-laydate-footer{position:relative;height:46px;line-height:26px;padding:10px 20px}.layui-laydate-footer span{margin-right:15px;display:inline-block;cursor:pointer;font-size:12px}.layui-laydate-footer span:hover{color:#5FB878}.laydate-footer-btns{position:absolute;right:10px;top:10px}.laydate-footer-btns span{height:26px;line-height:26px;margin:0 0 0 -1px;padding:0 10px;border:1px solid #C9C9C9;background-color:#fff;white-space:nowrap;vertical-align:top;border-radius:2px}.layui-laydate-list>li,.layui-laydate-range .layui-laydate-main{display:inline-block;vertical-align:middle}.layui-laydate-list{position:absolute;left:0;top:0;width:100%;height:100%;padding:10px;background-color:#fff}.layui-laydate-list>li{position:relative;width:33.3%;height:36px;line-height:36px;margin:3px 0;text-align:center;cursor:pointer}.laydate-month-list>li{width:25%;margin:17px 0}.laydate-time-list>li{height:100%;margin:0;line-height:normal;cursor:default}.laydate-time-list p{position:relative;top:-4px;line-height:29px}.laydate-time-list ol{height:181px;overflow:hidden}.laydate-time-list>li:hover ol{overflow-y:auto}.laydate-time-list ol li{width:130%;padding-left:33px;line-height:30px;text-align:left;cursor:pointer}.layui-laydate-hint{position:absolute;top:115px;left:50%;width:250px;margin-left:-125px;line-height:20px;padding:15px;text-align:center;font-size:12px}.layui-laydate-range{width:546px}.layui-laydate-range .laydate-main-list-0 .laydate-next-m,.layui-laydate-range .laydate-main-list-0 .laydate-next-y,.layui-laydate-range .laydate-main-list-1 .laydate-prev-m,.layui-laydate-range .laydate-main-list-1 .laydate-prev-y{display:none}.layui-laydate-range .laydate-main-list-1 .layui-laydate-content{border-left:1px solid #e2e2e2}.layui-laydate,.layui-laydate-hint{border:1px solid #d2d2d2;box-shadow:0 2px 4px rgba(0,0,0,.12);background-color:#fff;color:#666}.layui-laydate-header{border-bottom:1px solid #e2e2e2}.layui-laydate-header i:hover,.layui-laydate-header span:hover{color:#5FB878}.layui-laydate-content{border-top:none 0;border-bottom:none 0}.layui-laydate-content th{font-weight:400;color:#333}.layui-laydate-content td{color:#666}.layui-laydate-content td.laydate-selected{background-color:#00F7DE}.laydate-selected:hover{background-color:#00F7DE!important}.layui-laydate-content td:hover,.layui-laydate-list li:hover{background-color:#eaeaea;color:#333}.laydate-time-list li ol{margin:0;padding:0;border:1px solid #e2e2e2;border-left-width:0}.laydate-time-list li:first-child ol{border-left-width:1px}.laydate-time-list>li:hover{background:0 0}.layui-laydate-content .laydate-day-next,.layui-laydate-content .laydate-day-prev{color:#d2d2d2}.laydate-selected.laydate-day-next,.laydate-selected.laydate-day-prev{background-color:#f8f8f8!important}.layui-laydate-footer{border-top:1px solid #e2e2e2}.layui-laydate-hint{color:#FF5722}.laydate-day-mark::after{background-color:#5FB878}.layui-laydate-content td.layui-this .laydate-day-mark::after{display:none}.layui-laydate-footer span[lay-type=date]{color:#5FB878}.layui-laydate .layui-this{background-color:#009688!important;color:#fff!important}.layui-laydate .laydate-disabled,.layui-laydate .laydate-disabled:hover{background:0 0!important;color:#d2d2d2!important;cursor:not-allowed!important;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.laydate-theme-molv{border:none}.laydate-theme-molv.layui-laydate-range{width:548px}.laydate-theme-molv .layui-laydate-main{width:274px}.laydate-theme-molv .layui-laydate-header{border:none;background-color:#009688}.laydate-theme-molv .layui-laydate-header i,.laydate-theme-molv .layui-laydate-header span{color:#f6f6f6}.laydate-theme-molv .layui-laydate-header i:hover,.laydate-theme-molv .layui-laydate-header span:hover{color:#fff}.laydate-theme-molv .layui-laydate-content{border:1px solid #e2e2e2;border-top:none;border-bottom:none}.laydate-theme-molv .laydate-main-list-1 .layui-laydate-content{border-left:none}.laydate-theme-grid .laydate-month-list>li,.laydate-theme-grid .laydate-year-list>li,.laydate-theme-grid .layui-laydate-content td,.laydate-theme-grid .layui-laydate-content thead,.laydate-theme-molv .layui-laydate-footer{border:1px solid #e2e2e2}.laydate-theme-grid .laydate-selected,.laydate-theme-grid .laydate-selected:hover{background-color:#f2f2f2!important;color:#009688!important}.laydate-theme-grid .laydate-selected.laydate-day-next,.laydate-theme-grid .laydate-selected.laydate-day-prev{color:#d2d2d2!important}.laydate-theme-grid .laydate-month-list,.laydate-theme-grid .laydate-year-list{margin:1px 0 0 1px}.laydate-theme-grid .laydate-month-list>li,.laydate-theme-grid .laydate-year-list>li{margin:0 -1px -1px 0}.laydate-theme-grid .laydate-year-list>li{height:43px;line-height:43px}.laydate-theme-grid .laydate-month-list>li{height:71px;line-height:71px}"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/layui/css/modules/layer/default/layer.css",
    "content": "/** layui-v2.4.5 MIT License By https://www.layui.com */\n .layui-layer-imgbar,.layui-layer-imgtit a,.layui-layer-tab .layui-layer-title span,.layui-layer-title{text-overflow:ellipsis;white-space:nowrap}html #layuicss-layer{display:none;position:absolute;width:1989px}.layui-layer,.layui-layer-shade{position:fixed;_position:absolute;pointer-events:auto}.layui-layer-shade{top:0;left:0;width:100%;height:100%;_height:expression(document.body.offsetHeight+\"px\")}.layui-layer{-webkit-overflow-scrolling:touch;top:150px;left:0;margin:0;padding:0;background-color:#fff;-webkit-background-clip:content;border-radius:2px;box-shadow:1px 1px 50px rgba(0,0,0,.3)}.layui-layer-close{position:absolute}.layui-layer-content{position:relative}.layui-layer-border{border:1px solid #B2B2B2;border:1px solid rgba(0,0,0,.1);box-shadow:1px 1px 5px rgba(0,0,0,.2)}.layui-layer-load{background:url(loading-1.gif) center center no-repeat #eee}.layui-layer-ico{background:url(icon.png) no-repeat}.layui-layer-btn a,.layui-layer-dialog .layui-layer-ico,.layui-layer-setwin a{display:inline-block;*display:inline;*zoom:1;vertical-align:top}.layui-layer-move{display:none;position:fixed;*position:absolute;left:0;top:0;width:100%;height:100%;cursor:move;opacity:0;filter:alpha(opacity=0);background-color:#fff;z-index:2147483647}.layui-layer-resize{position:absolute;width:15px;height:15px;right:0;bottom:0;cursor:se-resize}.layer-anim{-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.3s;animation-duration:.3s}@-webkit-keyframes layer-bounceIn{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes layer-bounceIn{0%{opacity:0;-webkit-transform:scale(.5);-ms-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layer-anim-00{-webkit-animation-name:layer-bounceIn;animation-name:layer-bounceIn}@-webkit-keyframes layer-zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes layer-zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);-ms-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);-ms-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-01{-webkit-animation-name:layer-zoomInDown;animation-name:layer-zoomInDown}@-webkit-keyframes layer-fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes layer-fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);-ms-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}.layer-anim-02{-webkit-animation-name:layer-fadeInUpBig;animation-name:layer-fadeInUpBig}@-webkit-keyframes layer-zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes layer-zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);-ms-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);-ms-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-03{-webkit-animation-name:layer-zoomInLeft;animation-name:layer-zoomInLeft}@-webkit-keyframes layer-rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0) rotate(0);transform:translateX(0) rotate(0)}}@keyframes layer-rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);-ms-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0) rotate(0);-ms-transform:translateX(0) rotate(0);transform:translateX(0) rotate(0)}}.layer-anim-04{-webkit-animation-name:layer-rollIn;animation-name:layer-rollIn}@keyframes layer-fadeIn{0%{opacity:0}100%{opacity:1}}.layer-anim-05{-webkit-animation-name:layer-fadeIn;animation-name:layer-fadeIn}@-webkit-keyframes layer-shake{0%,100%{-webkit-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);transform:translateX(10px)}}@keyframes layer-shake{0%,100%{-webkit-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);-ms-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);-ms-transform:translateX(10px);transform:translateX(10px)}}.layer-anim-06{-webkit-animation-name:layer-shake;animation-name:layer-shake}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}.layui-layer-title{padding:0 80px 0 20px;height:42px;line-height:42px;border-bottom:1px solid #eee;font-size:14px;color:#333;overflow:hidden;background-color:#F8F8F8;border-radius:2px 2px 0 0}.layui-layer-setwin{position:absolute;right:15px;*right:0;top:15px;font-size:0;line-height:initial}.layui-layer-setwin a{position:relative;width:16px;height:16px;margin-left:10px;font-size:12px;_overflow:hidden}.layui-layer-setwin .layui-layer-min cite{position:absolute;width:14px;height:2px;left:0;top:50%;margin-top:-1px;background-color:#2E2D3C;cursor:pointer;_overflow:hidden}.layui-layer-setwin .layui-layer-min:hover cite{background-color:#2D93CA}.layui-layer-setwin .layui-layer-max{background-position:-32px -40px}.layui-layer-setwin .layui-layer-max:hover{background-position:-16px -40px}.layui-layer-setwin .layui-layer-maxmin{background-position:-65px -40px}.layui-layer-setwin .layui-layer-maxmin:hover{background-position:-49px -40px}.layui-layer-setwin .layui-layer-close1{background-position:1px -40px;cursor:pointer}.layui-layer-setwin .layui-layer-close1:hover{opacity:.7}.layui-layer-setwin .layui-layer-close2{position:absolute;right:-28px;top:-28px;width:30px;height:30px;margin-left:0;background-position:-149px -31px;*right:-18px;_display:none}.layui-layer-setwin .layui-layer-close2:hover{background-position:-180px -31px}.layui-layer-btn{text-align:right;padding:0 15px 12px;pointer-events:auto;user-select:none;-webkit-user-select:none}.layui-layer-btn a{height:28px;line-height:28px;margin:5px 5px 0;padding:0 15px;border:1px solid #dedede;background-color:#fff;color:#333;border-radius:2px;font-weight:400;cursor:pointer;text-decoration:none}.layui-layer-btn a:hover{opacity:.9;text-decoration:none}.layui-layer-btn a:active{opacity:.8}.layui-layer-btn .layui-layer-btn0{border-color:#1E9FFF;background-color:#1E9FFF;color:#fff}.layui-layer-btn-l{text-align:left}.layui-layer-btn-c{text-align:center}.layui-layer-dialog{min-width:260px}.layui-layer-dialog .layui-layer-content{position:relative;padding:20px;line-height:24px;word-break:break-all;overflow:hidden;font-size:14px;overflow-x:hidden;overflow-y:auto}.layui-layer-dialog .layui-layer-content .layui-layer-ico{position:absolute;top:16px;left:15px;_left:-40px;width:30px;height:30px}.layui-layer-ico1{background-position:-30px 0}.layui-layer-ico2{background-position:-60px 0}.layui-layer-ico3{background-position:-90px 0}.layui-layer-ico4{background-position:-120px 0}.layui-layer-ico5{background-position:-150px 0}.layui-layer-ico6{background-position:-180px 0}.layui-layer-rim{border:6px solid #8D8D8D;border:6px solid rgba(0,0,0,.3);border-radius:5px;box-shadow:none}.layui-layer-msg{min-width:180px;border:1px solid #D3D4D3;box-shadow:none}.layui-layer-hui{min-width:100px;background-color:#000;filter:alpha(opacity=60);background-color:rgba(0,0,0,.6);color:#fff;border:none}.layui-layer-hui .layui-layer-content{padding:12px 25px;text-align:center}.layui-layer-dialog .layui-layer-padding{padding:20px 20px 20px 55px;text-align:left}.layui-layer-page .layui-layer-content{position:relative;overflow:auto}.layui-layer-iframe .layui-layer-btn,.layui-layer-page .layui-layer-btn{padding-top:10px}.layui-layer-nobg{background:0 0}.layui-layer-iframe iframe{display:block;width:100%}.layui-layer-loading{border-radius:100%;background:0 0;box-shadow:none;border:none}.layui-layer-loading .layui-layer-content{width:60px;height:24px;background:url(loading-0.gif) no-repeat}.layui-layer-loading .layui-layer-loading1{width:37px;height:37px;background:url(loading-1.gif) no-repeat}.layui-layer-ico16,.layui-layer-loading .layui-layer-loading2{width:32px;height:32px;background:url(loading-2.gif) no-repeat}.layui-layer-tips{background:0 0;box-shadow:none;border:none}.layui-layer-tips .layui-layer-content{position:relative;line-height:22px;min-width:12px;padding:8px 15px;font-size:12px;_float:left;border-radius:2px;box-shadow:1px 1px 3px rgba(0,0,0,.2);background-color:#000;color:#fff}.layui-layer-tips .layui-layer-close{right:-2px;top:-1px}.layui-layer-tips i.layui-layer-TipsG{position:absolute;width:0;height:0;border-width:8px;border-color:transparent;border-style:dashed;*overflow:hidden}.layui-layer-tips i.layui-layer-TipsB,.layui-layer-tips i.layui-layer-TipsT{left:5px;border-right-style:solid;border-right-color:#000}.layui-layer-tips i.layui-layer-TipsT{bottom:-8px}.layui-layer-tips i.layui-layer-TipsB{top:-8px}.layui-layer-tips i.layui-layer-TipsL,.layui-layer-tips i.layui-layer-TipsR{top:5px;border-bottom-style:solid;border-bottom-color:#000}.layui-layer-tips i.layui-layer-TipsR{left:-8px}.layui-layer-tips i.layui-layer-TipsL{right:-8px}.layui-layer-lan[type=dialog]{min-width:280px}.layui-layer-lan .layui-layer-title{background:#4476A7;color:#fff;border:none}.layui-layer-lan .layui-layer-btn{padding:5px 10px 10px;text-align:right;border-top:1px solid #E9E7E7}.layui-layer-lan .layui-layer-btn a{background:#fff;border-color:#E9E7E7;color:#333}.layui-layer-lan .layui-layer-btn .layui-layer-btn1{background:#C9C5C5}.layui-layer-molv .layui-layer-title{background:#009f95;color:#fff;border:none}.layui-layer-molv .layui-layer-btn a{background:#009f95;border-color:#009f95}.layui-layer-molv .layui-layer-btn .layui-layer-btn1{background:#92B8B1}.layui-layer-iconext{background:url(icon-ext.png) no-repeat}.layui-layer-prompt .layui-layer-input{display:block;width:230px;height:36px;margin:0 auto;line-height:30px;padding-left:10px;border:1px solid #e6e6e6;color:#333}.layui-layer-prompt textarea.layui-layer-input{width:300px;height:100px;line-height:20px;padding:6px 10px}.layui-layer-prompt .layui-layer-content{padding:20px}.layui-layer-prompt .layui-layer-btn{padding-top:0}.layui-layer-tab{box-shadow:1px 1px 50px rgba(0,0,0,.4)}.layui-layer-tab .layui-layer-title{padding-left:0;overflow:visible}.layui-layer-tab .layui-layer-title span{position:relative;float:left;min-width:80px;max-width:260px;padding:0 20px;text-align:center;overflow:hidden;cursor:pointer}.layui-layer-tab .layui-layer-title span.layui-this{height:43px;border-left:1px solid #eee;border-right:1px solid #eee;background-color:#fff;z-index:10}.layui-layer-tab .layui-layer-title span:first-child{border-left:none}.layui-layer-tabmain{line-height:24px;clear:both}.layui-layer-tabmain .layui-layer-tabli{display:none}.layui-layer-tabmain .layui-layer-tabli.layui-this{display:block}.layui-layer-photos{-webkit-animation-duration:.8s;animation-duration:.8s}.layui-layer-photos .layui-layer-content{overflow:hidden;text-align:center}.layui-layer-photos .layui-layer-phimg img{position:relative;width:100%;display:inline-block;*display:inline;*zoom:1;vertical-align:top}.layui-layer-imgbar,.layui-layer-imguide{display:none}.layui-layer-imgnext,.layui-layer-imgprev{position:absolute;top:50%;width:27px;_width:44px;height:44px;margin-top:-22px;outline:0;blr:expression(this.onFocus=this.blur())}.layui-layer-imgprev{left:10px;background-position:-5px -5px;_background-position:-70px -5px}.layui-layer-imgprev:hover{background-position:-33px -5px;_background-position:-120px -5px}.layui-layer-imgnext{right:10px;_right:8px;background-position:-5px -50px;_background-position:-70px -50px}.layui-layer-imgnext:hover{background-position:-33px -50px;_background-position:-120px -50px}.layui-layer-imgbar{position:absolute;left:0;bottom:0;width:100%;height:32px;line-height:32px;background-color:rgba(0,0,0,.8);background-color:#000\\9;filter:Alpha(opacity=80);color:#fff;overflow:hidden;font-size:0}.layui-layer-imgtit *{display:inline-block;*display:inline;*zoom:1;vertical-align:top;font-size:12px}.layui-layer-imgtit a{max-width:65%;overflow:hidden;color:#fff}.layui-layer-imgtit a:hover{color:#fff;text-decoration:underline}.layui-layer-imgtit em{padding-left:10px;font-style:normal}@-webkit-keyframes layer-bounceOut{100%{opacity:0;-webkit-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.05);transform:scale(1.05)}0%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes layer-bounceOut{100%{opacity:0;-webkit-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.05);-ms-transform:scale(1.05);transform:scale(1.05)}0%{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layer-anim-close{-webkit-animation-name:layer-bounceOut;animation-name:layer-bounceOut;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.2s;animation-duration:.2s}@media screen and (max-width:1100px){.layui-layer-iframe{overflow-y:auto;-webkit-overflow-scrolling:touch}}"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/layui/ext/eleTree/eleTree.css",
    "content": "/* #region tree */\r\n.eleTree{\r\n    position: relative;\r\n}\r\n.eleTree-hide{\r\n    display: none;\r\n}\r\n.eleTree-loadData{\r\n    width: 100%;\r\n    height: 100%;\r\n    position: absolute;\r\n    z-index: 1;\r\n    top: 0px;\r\n}\r\n.eleTree-loadData .layui-icon{\r\n    position: absolute;\r\n    left: 50%;\r\n    top: 50%;\r\n    transform: translateX(-50%)  translateY(-50%);\r\n}\r\n.eleTree-node-content{\r\n    cursor: pointer;\r\n    height: 26px;\r\n    line-height: 1.3;\r\n    white-space: nowrap;\r\n}\r\n.eleTree-node-content:hover,\r\n.eleTree-node-content.eleTree-node-content-active{\r\n    background-color: #eee;\r\n}\r\n.eleTree-node-content-icon .layui-icon{\r\n    padding: 6px 3px;\r\n    color: #c0c4cc;\r\n    font-size: 12px;\r\n    display: inline-block;\r\n    transform: rotate(0deg);\r\n    transition: transform .3s ease-in-out;\r\n}\r\n.eleTree-node-content-icon .layui-icon.icon-rotate{\r\n    transform: rotate(90deg);\r\n}\r\n.eleTree-node-content .layui-form-checkbox[lay-skin=primary] i{\r\n    width: 13px;\r\n    height: 14px;\r\n    line-height: 1.3;\r\n}\r\n.eleTree-node-content-label{\r\n    padding-left: 5px;\r\n}\r\n.eleTree-node-content-input{\r\n    width: 80px;\r\n    border: 1px solid #e6e6e6;\r\n    outline: 0;\r\n    padding: 3px 5px;\r\n    font-size: 12px;\r\n}\r\n\r\n\r\n/* checkbox第三种状态 */\r\ninput.eleTree-hideen[type=checkbox]{\r\n    display: none;\r\n}\r\n.eleTree-checkbox {\r\n    height: auto!important;\r\n    line-height: normal!important;\r\n    min-height: 12px;\r\n    border: none!important;\r\n    margin-right: 0;\r\n    padding-left: 18px;\r\n    position: relative;\r\n    display: inline-block;\r\n}\r\n.eleTree-checkbox i {\r\n    left: 0;\r\n    border: 1px solid #d2d2d2;\r\n    font-size: 12px;\r\n    border-radius: 2px;\r\n    background-color: #fff;\r\n    -webkit-transition: .1s linear;\r\n    transition: .1s linear;\r\n    position: absolute;\r\n    top: 0;\r\n    color: #fff;\r\n    cursor: pointer;\r\n    text-align: center;\r\n    width: 13px;\r\n    height: 14px;\r\n    line-height: 1.3;\r\n}\r\n.eleTree-checkbox i:hover {\r\n    border-color: #5FB878;\r\n}\r\n.eleTree-checkbox-checked i {\r\n    border-color: #5FB878;\r\n    background-color: #5FB878;\r\n    color: #fff;\r\n}\r\n.eleTree-checkbox-line:after{\r\n    content: \"\";\r\n    position: relative;\r\n    width: 8px;\r\n    height: 1px;\r\n    background-color: #fff;\r\n    display: inline-block;\r\n    top: -4px;\r\n}\r\n\r\n.eleTree-checkbox.eleTree-checkbox-disabled i{\r\n    cursor: not-allowed;\r\n    background-color: #f2f6fc;\r\n    border-color: #dcdfe6;\r\n    color: #c2c2c2;\r\n}\r\n.eleTree-checkbox.eleTree-checkbox-disabled i.eleTree-checkbox-line:after{\r\n    background-color: #c2c2c2;\r\n}\r\n.eleTree-checkbox.eleTree-checkbox-disabled i:hover{\r\n    border-color: #dcdfe6;\r\n}\r\n\r\n#tree-menu{\r\n    margin: 0;\r\n    padding: 2px;\r\n    position: absolute;\r\n    background: #f5f5f5;\r\n    border: 1px solid #979797;\r\n    box-shadow: 2px 2px 2px #999;\r\n    display: none;\r\n    z-index: 20181205;\r\n}\r\n#tree-menu li>a{\r\n    display: block;\r\n    padding: 0 1em;\r\n    text-decoration: none;\r\n    width: auto;\r\n    color: #000;\r\n    white-space: nowrap;\r\n    line-height: 2.4em;\r\n    text-shadow: 1px 1px 0 #fff;\r\n    border-radius: 1px;\r\n}\r\n#tree-menu li>a:hover{\r\n    background-color: #e8eff7;\r\n    box-shadow: 0 0 2px #0a6aa1;\r\n}\r\n.tree-menu-bg{\r\n    background-color: #ccc;\r\n}\r\n/* #endregion */"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/layui/ext/eleTree/eleTree.js",
    "content": "/**\r\n * 基于layui的tree重写\r\n * author: hsianglee\r\n * 最近修改时间: 2019/01/07\r\n */\r\n\r\nlayui.define([\"jquery\",\"laytpl\"], function (exports) {\r\n    var $ = layui.jquery;\r\n    var laytpl = layui.laytpl;\r\n    var hint = layui.hint();\r\n\r\n    var MOD_NAME=\"eleTree\";\r\n    \r\n    //外部接口\r\n    var eleTree={\r\n        //事件监听\r\n        on: function(events, callback){\r\n            return layui.onevent.call(this, MOD_NAME, events, callback);\r\n        },\r\n        render: function(options) {\r\n            var inst = new Class(options);\r\n            return thisTree.call(inst);\r\n        }\r\n    }\r\n\r\n    var thisTree=function() {\r\n        var _self=this;\r\n        var options = _self.config;\r\n\r\n        // 暴漏外面的方法\r\n        return {\r\n            // 接收两个参数，1. 节点 key 2. 节点数据的数组\r\n            updateKeyChildren: function(key,data) {\r\n                if(options.data.length===0) return;\r\n                return _self.updateKeyChildren.call(_self,key,data);\r\n            },\r\n            updateKeySelf: function(key,data) {\r\n                if(options.data.length===0) return;\r\n                return _self.updateKeySelf.call(_self,key,data);\r\n            },\r\n            remove: function(key) {\r\n                if(options.data.length===0) return;\r\n                return _self.remove.call(_self,key);\r\n            },\r\n            append: function(key,data) {\r\n                if(options.data.length===0) return;\r\n                return _self.append.call(_self,key,data);\r\n            },\r\n            insertBefore: function(key,data) {\r\n                if(options.data.length===0) return;\r\n                return _self.insertBefore.call(_self,key,data);\r\n            },\r\n            insertAfter: function(key,data) {\r\n                if(options.data.length===0) return;\r\n                return _self.insertAfter.call(_self,key,data);\r\n            },\r\n            // 接收两个 boolean 类型的参数，1. 是否只是叶子节点，默认值为 false 2. 是否包含半选节点，默认值为 false\r\n            getChecked: function(leafOnly, includeHalfChecked) {\r\n                if(options.data.length===0) return;\r\n                return _self.getChecked.call(_self,leafOnly, includeHalfChecked);\r\n            },\r\n            // 接收勾选节点数据的数组\r\n            setChecked: function(data) {\r\n                if(options.data.length===0) return;\r\n                return _self.setChecked.call(_self,data);\r\n            },\r\n            // 取消选中\r\n            unCheckNodes: function() {\r\n                if(options.data.length===0) return;\r\n                return _self.unCheckNodes.call(_self);\r\n            },\r\n            expandAll: function() {\r\n                options.elem.children(\".eleTree-node\").children(\".eleTree-node-group\").empty();\r\n                _self.expandAll.call(_self,options.data,[],1,true);\r\n                _self.unCheckNodes();\r\n                _self.defaultChecked();\r\n            },\r\n            unExpandAll: function() {\r\n                return _self.unExpandAll.call(_self);\r\n            },\r\n            reload: function(options) {\r\n                return _self.reload.call(_self,options);\r\n            },\r\n            search: function(value) {\r\n                return _self.search.call(_self,value);\r\n            }\r\n        }\r\n    }\r\n\r\n    // 模板渲染\r\n    var TPL_ELEM=function(options,floor,parentStatus) {\r\n        return [\r\n            '{{# for(var i=0;i<d.length;i++){ }}',\r\n                '<div class=\"eleTree-node\" data-'+options.request.key+'=\"{{d[i][\"'+options.request.key+'\"]}}\" eletree-floor=\"'+floor+'\" style=\"display: none;\">',\r\n                    '<div class=\"eleTree-node-content\" style=\"padding-left: '+(options.indent*floor)+'px;\">',\r\n                        '<span class=\"eleTree-node-content-icon\">',\r\n                            '<i class=\"layui-icon layui-icon-triangle-r ',\r\n                            function() {\r\n                                if(options.lazy){\r\n                                    var str=[\r\n                                        '{{# if(!d[i][\"'+options.request.isLeaf+'\"]){ }}',\r\n                                            'lazy-icon\" ></i>',\r\n                                        '{{# }else{ }}',\r\n                                            'leaf-icon\" style=\"color: transparent;\" ></i>',\r\n                                        '{{# } }}'\r\n                                    ].join(\"\");\r\n                                    return str;\r\n                                }\r\n                                return ['{{# if(!d[i][\"'+options.request.children+'\"] || d[i][\"'+options.request.children+'\"].length===0){ }}',\r\n                                        'leaf-icon\" style=\"color: transparent;\"',\r\n                                    '{{# } }}',\r\n                                    '\"></i>'\r\n                                ].join(\"\");\r\n                            }(),\r\n                        '</span>',\r\n                        function() {\r\n                            if(options.showCheckbox){\r\n                                var status=\"\";\r\n                                if(parentStatus===\"1\"){\r\n                                    status='\"1\" checked';\r\n                                }else if(parentStatus===\"2\"){\r\n                                    status='\"2\"';\r\n                                }else{\r\n                                    status='\"0\"';\r\n                                }\r\n                                return [\r\n                                    '{{# if(d[i][\"'+options.request.checked+'\"]) { }}',\r\n                                        '<input type=\"checkbox\" name=\"eleTree-node\" eleTree-status=\"1\" checked class=\"eleTree-hideen ',\r\n                                    '{{# }else{ }}',\r\n                                        '<input type=\"checkbox\" name=\"eleTree-node\" eleTree-status='+status+' class=\"eleTree-hideen ',\r\n                                    '{{# } }}',\r\n\r\n                                    '{{# if(d[i][\"'+options.request.disabled+'\"]) { }}',\r\n                                        'eleTree-disabled',\r\n                                    '{{# } }}',\r\n                                    '\" />'\r\n                                ].join(\"\");\r\n                            }\r\n                            return ''\r\n                        }(),\r\n                        '<span class=\"eleTree-node-content-label\">{{d[i][\"'+options.request.name+'\"]}}</span>',\r\n                    '</div>',\r\n                    '<div class=\"eleTree-node-group\">',\r\n                    '</div>',\r\n                '</div>',\r\n            '{{# } }}'\r\n        ].join(\"\");\r\n    }\r\n\r\n    var TPL_NoText=function() {\r\n        return '<h3 class=\"eleTree-noText\" style=\"text-align: center;height: 30px;line-height: 30px;color: #888;\">{{d.emptText}}</h3>';\r\n    }\r\n\r\n    var Class=function(options) {\r\n        options.response=$.extend({}, this.config.response, options.response);\r\n        options.request=$.extend({}, this.config.request, options.request);\r\n        this.config = $.extend({}, this.config, options);\r\n        this.prevClickEle=null;\r\n        this.addKeyIndex=20181201;\r\n        this.nameIndex=1;\r\n        this.render();\r\n    };\r\n\r\n    Class.prototype={\r\n        constructor: Class,\r\n        config: {\r\n            elem: \"\",\r\n            data: [],\r\n            emptText: \"暂无数据\",        // 内容为空的时候展示的文本\r\n            renderAfterExpand: true,    // 是否在第一次展开某个树节点后才渲染其子节点\r\n            highlightCurrent: false,    // 是否高亮当前选中节点，默认值是 false。\r\n            defaultExpandAll: false,    // 是否默认展开所有节点\r\n            expandOnClickNode: true,    // 是否在点击节点的时候展开或者收缩节点， 默认值为 true，如果为 false，则只有点箭头图标的时候才会展开或者收缩节点。\r\n            checkOnClickNode: false,    // 是否在点击节点的时候选中节点，默认值为 false，即只有在点击复选框时才会选中节点。\r\n            defaultExpandedKeys: [],    // 默认展开的节点的 key 的数组\r\n            autoExpandParent: true,     // 展开子节点的时候是否自动展开父节点\r\n            showCheckbox: false,        // 节点是否可被选择\r\n            checkStrictly: false,       // 在显示复选框的情况下，是否严格的遵循父子不互相关联的做法，默认为 false\r\n            defaultCheckedKeys: [],     // 默认勾选的节点的 key 的数组\r\n            accordion: false,           // 是否每次只打开一个同级树节点展开（手风琴效果）\r\n            indent: 16,                 // 相邻级节点间的水平缩进，单位为像素\r\n            lazy: false,                // 是否懒加载子节点，需与 load 方法结合使用\r\n            load: function() {},        // 加载子树数据的方法，仅当 lazy 属性为true 时生效\r\n            draggable: false,           // 是否开启拖拽节点功能\r\n            contextmenuList: [],        // 启用右键菜单，支持的操作有：\"copy\",\"add\",\"edit\",\"remove\"\r\n            searchNodeMethod: null,     // 对树节点进行筛选时执行的方法，返回 true 表示这个节点可以显示，返回 false 则表示这个节点会被隐藏\r\n\r\n            method: \"get\",\r\n            url: \"\",\r\n            contentType: \"\",\r\n            headers: {},\r\n            done: null,\r\n            \r\n            response: {\r\n                statusName: \"code\",\r\n                statusCode: 0,\r\n                dataName: \"data\"\r\n            },\r\n            request: {\r\n                name: \"label\",\r\n                key: \"id\",\r\n                children: \"children\",\r\n                disabled: \"disabled\",\r\n                checked: \"checked\",\r\n                isLeaf: \"isLeaf\"\r\n            }\r\n        },\r\n        render: function() {\r\n            if(this.config.indent>30){\r\n                this.config.indent=30;\r\n            }else if(this.config.indent<10){\r\n                this.config.indent=10;\r\n            }\r\n            var options=this.config;\r\n            options.where=options.where || {};\r\n            if(!options.elem) return hint.error(\"缺少elem参数\");\r\n            options.elem=typeof options.elem === \"string\" ? $(options.elem) : options.elem;\r\n            this.filter=options.elem.attr(\"lay-filter\");\r\n            // load加载框\r\n            options.elem.append('<div class=\"eleTree-loadData\"><i class=\"layui-icon layui-icon-loading layui-icon layui-anim layui-anim-rotate layui-anim-loop\"></i></div>')\r\n            \r\n            // 判断加载方式\r\n            if(options.data.length===0){\r\n                this.ajaxGetData();\r\n            }else{\r\n                this.renderData();\r\n            }\r\n        },\r\n        renderData: function() {\r\n            var options=this.config;\r\n            // 渲染第一层\r\n            laytpl(TPL_ELEM(options,0)).render(options.data, function(string){\r\n                options.elem.html(string).children().show();\r\n            }); \r\n            // 懒加载 > 展开所有 > 初始展开项 > 初始渲染所有子节点 > 初始选中项 > 每次点击只渲染当前层（默认）\r\n            // 判断所有dom是否全部加载\r\n            if(!options.lazy){\r\n                if(!options.renderAfterExpand || options.defaultExpandAll || options.defaultExpandedKeys.length>0 || options.defaultCheckedKeys.length>0){\r\n                    this.expandAll(options.data,[],1);\r\n                }\r\n            }\r\n\r\n            this.eleTreeEvent();\r\n            this.checkboxRender();\r\n            this.checkboxEvent();\r\n            this.defaultChecked();\r\n            this.nodeEvent();\r\n            this.rightClickMenu();\r\n            if(!options.checkStrictly){\r\n                this.checkboxInit();\r\n            }\r\n        },\r\n        ajaxGetData: function() {\r\n            var options=this.config;\r\n            var _self=this;\r\n            if(!options.url) {\r\n                laytpl(TPL_NoText()).render(options, function(string){\r\n                    options.elem.html(string);\r\n                }); \r\n                return;\r\n            }\r\n            var data = $.extend({}, options.where);\r\n            if(options.contentType && options.contentType.indexOf(\"application/json\") == 0){ //提交 json 格式\r\n              data = JSON.stringify(data);\r\n            }\r\n\r\n            $.ajax({\r\n                type: options.method || 'get'\r\n                ,url: options.url\r\n                ,contentType: options.contentType\r\n                ,data: data\r\n                ,dataType: 'json'\r\n                ,headers: options.headers || {}\r\n                ,success: function(res){\r\n                    if(res[options.response.statusName] != options.response.statusCode || !res[options.response.dataName]){\r\n                        hint.error(\"请检查数据格式是否符合规范\");\r\n                        typeof options.done === 'function' && options.done(res);\r\n                        return;\r\n                    }\r\n                    options.data=res[options.response.dataName];\r\n                    _self.renderData();\r\n                    typeof options.done === 'function' && options.done(res);\r\n                }\r\n            });\r\n        },\r\n        reload: function(options) {\r\n            var _self=this;\r\n            if(this.config.data && this.config.data.constructor === Array) this.config.data=[];\r\n            this.config = $.extend({}, this.config, options);\r\n            $(this.config.elem).off();  // 取消事件绑定，防止多次绑定事件\r\n            // reload记录选中的数据\r\n            // this.getChecked().forEach(function(val) {\r\n            //     if($.inArray(val.key,this.config.defaultCheckedKeys)===-1){\r\n            //         this.config.defaultCheckedKeys.push(val.key);\r\n            //     }\r\n            // },this);\r\n            return eleTree.render(this.config)\r\n        },\r\n        // 下拉\r\n        eleTreeEvent: function() {\r\n            var _self=this;\r\n            var options=this.config;\r\n            // 下拉\r\n            var expandOnClickNode=options.expandOnClickNode?\".eleTree-node-content\":\".eleTree-node-content>.eleTree-node-content-icon\";\r\n            options.elem.on(\"click\",expandOnClickNode,function(e) {\r\n                e.stopPropagation();\r\n                var eleTreeNodeContent=$(this).parent(\".eleTree-node\").length===0?$(this).parent(\".eleTree-node-content\"):$(this);\r\n                var eleNode=eleTreeNodeContent.parent(\".eleTree-node\");\r\n                var sibNode=eleTreeNodeContent.siblings(\".eleTree-node-group\");\r\n                var el=eleTreeNodeContent.children(\".eleTree-node-content-icon\").children(\".layui-icon\");\r\n\r\n                // 添加active背景\r\n                if(_self.prevClickEle) _self.prevClickEle.removeClass(\"eleTree-node-content-active\");\r\n                if(options.highlightCurrent) eleTreeNodeContent.addClass(\"eleTree-node-content-active\");\r\n                _self.prevClickEle=eleTreeNodeContent;\r\n\r\n                \r\n\r\n                if(el.hasClass(\"icon-rotate\")){\r\n                    // 合并\r\n                    sibNode.children(\".eleTree-node:not(.eleTree-search-hide)\").hide(\"fast\");\r\n                    el.removeClass(\"icon-rotate\");\r\n                    return;\r\n                }\r\n\r\n                if(sibNode.children(\".eleTree-node\").length===0){\r\n                    var floor=Number(eleNode.attr(\"eletree-floor\"))+1;\r\n\r\n                    var data=_self.reInitData(eleNode);\r\n                    var d=data.currentData;\r\n                    // 是否懒加载\r\n                    if(options.lazy && el.hasClass(\"lazy-icon\")){\r\n                        el.removeClass(\"layui-icon-triangle-r\").addClass(\"layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop\");\r\n                        options.load(d,function(getData) {\r\n                            d[options.request.children]=getData;\r\n                            var eletreeStatus=eleTreeNodeContent.children(\"input.eleTree-hideen\").attr(\"eletree-status\");\r\n                            if(d[options.request.children] && d[options.request.children].length>0){\r\n                                laytpl(TPL_ELEM(options,floor,eletreeStatus)).render(d[options.request.children], function(string){\r\n                                    sibNode.append(string).children().show(\"fast\");\r\n                                });\r\n                            }else{\r\n                                el.css(\"color\",\"transparent\").addClass(\"leaf-icon\");\r\n                            }\r\n                            el.removeClass(\"lazy-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop\").addClass(\"layui-icon-triangle-r icon-rotate\");\r\n                            _self.checkboxRender();\r\n\r\n                            // 懒加载子元素选择祖父（待写）\r\n                        })\r\n                    }else{\r\n                        var eletreeStatus=eleTreeNodeContent.children(\"input.eleTree-hideen\").attr(\"eletree-status\");\r\n                        d[options.request.children] && d[options.request.children].length>0 && laytpl(TPL_ELEM(options,floor,eletreeStatus)).render(d[options.request.children], function(string){\r\n                            sibNode.append(string);\r\n                        });\r\n\r\n                        // 选择祖父\r\n                        var eleNode1=sibNode.children(\".eleTree-node\").eq(0);\r\n                        if(eleNode1.length===0){\r\n                            _self.checkboxRender();\r\n                            return;\r\n                        }\r\n                        var siblingNode1=eleNode1.siblings(\".eleTree-node\");\r\n                        var item1=eleNode1.children(\".eleTree-node-content\").children(\".eleTree-hideen\").get(0);\r\n                        _self.selectParents(item1,eleNode1,siblingNode1);\r\n                        _self.checkboxRender();\r\n                    }\r\n                }\r\n                // 显示隐藏没有搜索类的\r\n                sibNode.children(\".eleTree-node:not(.eleTree-search-hide)\").show(\"fast\");\r\n                el.addClass(\"icon-rotate\");\r\n                // 手风琴效果\r\n                if(options.accordion){\r\n                    var node=eleTreeNodeContent.parent(\".eleTree-node\").siblings(\".eleTree-node\");\r\n                    node.children(\".eleTree-node-group\").children(\".eleTree-node:not(.eleTree-search-hide)\").hide(\"fast\");\r\n                    node.children(\".eleTree-node-content\").children(\".eleTree-node-content-icon\").children(\".layui-icon\").removeClass(\"icon-rotate\");\r\n                }\r\n            })\r\n        },\r\n        // checkbox选中\r\n        checkboxEvent: function() {\r\n            var options=this.config;\r\n            var _self=this;\r\n            var checkOnClickNode=options.checkOnClickNode?\".eleTree-node-content\":\".eleTree-checkbox\";\r\n            // input添加属性eleTree-status：即input的三种状态，\"0\":未选中，\"1\":选中，\"2\":子孙部分选中\r\n            options.elem.on(\"click\",checkOnClickNode,function(e,type) {\r\n                e.stopPropagation();\r\n                var eleTreeNodeContent=$(this).parent(\".eleTree-node\").length===0?$(this).parent(\".eleTree-node-content\"):$(this);\r\n                var checkbox=eleTreeNodeContent.children(\".eleTree-checkbox\");\r\n                if(checkbox.hasClass(\"eleTree-checkbox-disabled\")) return;\r\n                // 获取点击所在数据\r\n                var node=eleTreeNodeContent.parent(\".eleTree-node\");\r\n                // var d=_self.reInitData(node).currentData;\r\n                // 实际的input\r\n                var inp=checkbox.siblings(\".eleTree-hideen\").get(0);\r\n                var childNode=eleTreeNodeContent.siblings(\".eleTree-node-group\").find(\"input[name='eleTree-node']\");\r\n\r\n                // 添加active背景\r\n                if(_self.prevClickEle) _self.prevClickEle.removeClass(\"eleTree-node-content-active\");\r\n                if(options.highlightCurrent) eleTreeNodeContent.addClass(\"eleTree-node-content-active\");\r\n                _self.prevClickEle=eleTreeNodeContent;\r\n                \r\n                if(!inp){\r\n                    return;\r\n                }\r\n\r\n                if(inp.checked){\r\n                    // 反选自身\r\n                    $(inp).prop(\"checked\",false).attr(\"eleTree-status\",\"0\");\r\n                    // 点击祖父层选中子孙层\r\n                    if(!options.checkStrictly){\r\n                        childNode.prop(\"checked\",false);\r\n                        childNode.attr(\"eleTree-status\",\"0\");\r\n                    }\r\n                    \r\n                }else{\r\n                    // 反选自身\r\n                    $(inp).prop(\"checked\",true).attr(\"eleTree-status\",\"1\");\r\n                    // 点击祖父层选中子孙层\r\n                    if(!options.checkStrictly){\r\n                        childNode.prop(\"checked\",true).attr(\"eleTree-status\",\"1\");\r\n                    }\r\n                }\r\n\r\n                var eleNode=eleTreeNodeContent.parent(\".eleTree-node\");\r\n                // 点击子孙层选中祖父层(递归)\r\n                if(!options.checkStrictly){\r\n                    var siblingNode=eleNode.siblings(\".eleTree-node\");\r\n                    // 点击子孙层选中祖父层(递归)\r\n                    _self.selectParents(inp,eleNode,siblingNode);\r\n                }\r\n                \r\n                _self.checkboxRender();\r\n\r\n                if(type===\"default\") return;\r\n                layui.event.call(inp, MOD_NAME, 'nodeChecked('+ _self.filter +')', {\r\n                    node: eleNode,\r\n                    data: _self.reInitData(eleNode),\r\n                    isChecked: inp.checked\r\n                });\r\n            })\r\n        },\r\n        // 对后台数据有 checked:true 的默认选中项渲染父子层\r\n        checkboxInit: function() {\r\n            var options=this.config;\r\n            var _self=this;\r\n            options.elem.find(\"input[eleTree-status='1']\").each(function(index,item) {\r\n                var checkboxEl=$(item).siblings(\".eleTree-checkbox\");\r\n                var childNode=checkboxEl.parent(\".eleTree-node-content\").siblings(\".eleTree-node-group\").find(\"input[name='eleTree-node']\");\r\n                // 选择当前\r\n                checkboxEl.addClass(\"eleTree-checkbox-checked\");\r\n                checkboxEl.children(\"i\").addClass(\"layui-icon-ok\").removeClass(\"eleTree-checkbox-line\");\r\n                // 选择子孙\r\n                childNode.prop(\"checked\",\"checked\").attr(\"eleTree-status\",\"1\");\r\n                childNode.siblings(\".eleTree-checkbox\").addClass(\"eleTree-checkbox-checked\");\r\n                childNode.siblings(\".eleTree-checkbox\").children(\"i\").addClass(\"layui-icon-ok\").removeClass(\"eleTree-checkbox-line\");\r\n                \r\n                // 选择祖父\r\n                var eleNode=checkboxEl.parent(\".eleTree-node-content\").parent(\".eleTree-node\");\r\n                var siblingNode=eleNode.siblings(\".eleTree-node\");\r\n                _self.selectParents(item,eleNode,siblingNode);\r\n            })\r\n            _self.checkboxRender();\r\n        },\r\n        // 通过子元素选中祖父元素\r\n        selectParents: function(inp,eleNode,siblingNode) {\r\n            // inp: 实际input(dom元素)\r\n            // eleNode: input父层类（.eleTree-node）\r\n            // siblingNode: 父层同级兄弟\r\n            while (Number(eleNode.attr(\"eletree-floor\"))!==0) {\r\n                // 同级input状态存入数组\r\n                var arr=[];\r\n                arr.push($(inp).attr(\"eleTree-status\"));\r\n                siblingNode.each(function(index,item) {\r\n                    var siblingIsChecked=$(item).children(\".eleTree-node-content\").children(\"input[name='eleTree-node']\").attr(\"eleTree-status\");\r\n                    arr.push(siblingIsChecked);\r\n                })\r\n                // 父元素的实际input\r\n                var parentInput=eleNode.parent(\".eleTree-node-group\").siblings(\".eleTree-node-content\").children(\"input[name='eleTree-node']\");\r\n                // 父元素的checkbox替代\r\n                var parentCheckbox=parentInput.siblings(\".eleTree-checkbox\");\r\n                // 子都选中则选中父\r\n                if(arr.every(function(val) {\r\n                    return val===\"1\";\r\n                })){\r\n                    parentInput.prop(\"checked\",true).attr(\"eleTree-status\",\"1\");\r\n                }\r\n                // 子有一个未选中则checkbox第三种状态\r\n                if(arr.some(function(val) {\r\n                    return val===\"0\" || val===\"2\";\r\n                })){\r\n                    parentInput.attr(\"eleTree-status\",\"2\");\r\n                }\r\n                // 子全部未选中则取消父选中(并且取消第三种状态)\r\n                if(arr.every(function(val) {\r\n                    return val===\"0\";\r\n                })){\r\n                    parentInput.prop(\"checked\",false);\r\n                    parentInput.attr(\"eleTree-status\",\"0\");\r\n                }\r\n\r\n                var parentNode=eleNode.parents(\"[eletree-floor='\"+(Number(eleNode.attr(\"eletree-floor\"))-1)+\"']\");\r\n                var parentCheckbox=parentNode.children(\".eleTree-node-content\").children(\"input[name='eleTree-node']\").get(0);\r\n                var parentSiblingNode=parentNode.siblings(\".eleTree-node\");\r\n                eleNode=parentNode;\r\n                inp=parentCheckbox;\r\n                siblingNode=parentSiblingNode;\r\n            }\r\n        },\r\n        // 初始展开所有\r\n        expandAll: function(data,arr,floor,isMethodsExpandAll) {\r\n            var options=this.config;\r\n            var _self=this;\r\n            data.forEach(function(val,index) {\r\n                arr.push(index);\r\n                if(val[options.request.children] && val[options.request.children].length>0){\r\n                    var el=options.elem.children(\".eleTree-node\").eq(arr[0]).children(\".eleTree-node-group\");\r\n                    for(var i=1;i<arr.length;i++){\r\n                        el=el.children(\".eleTree-node\").eq(arr[i]).children(\".eleTree-node-group\");\r\n                    }\r\n                    laytpl(TPL_ELEM(options,floor)).render(val[options.request.children], function(string){\r\n                        el.append(string);\r\n                        // 判断是否展开所有\r\n                        if(options.defaultExpandAll || isMethodsExpandAll){\r\n                            el.siblings(\".eleTree-node-content\").children(\".eleTree-node-content-icon\").children(\".layui-icon\").addClass(\"icon-rotate\");\r\n                            el.children().show();\r\n                        }else if(options.defaultExpandedKeys.length>0) {\r\n                            // 展开指定id项\r\n                            var id=el.parent(\".eleTree-node\").attr(\"data-\"+options.request.key);\r\n                            id=isNaN(id) ? id : Number(id);\r\n                            if($.inArray(id,options.defaultExpandedKeys)!==-1){\r\n                                el.siblings(\".eleTree-node-content\").children(\".eleTree-node-content-icon\").children(\".layui-icon\").addClass(\"icon-rotate\");\r\n                                el.children().show();\r\n                                // 展开子项是否继续展开祖父项\r\n                                if(options.autoExpandParent){\r\n                                    var eleP=el.parent(\".eleTree-node[data-\"+options.request.key+\"]\").parents(\".eleTree-node\");\r\n                                    eleP.each(function(i,item) {\r\n                                        if($(item).attr(\"data-\"+options.request.key)){\r\n                                            $(item).children(\".eleTree-node-group\").siblings(\".eleTree-node-content\").children(\".eleTree-node-content-icon\").children(\".layui-icon\").addClass(\"icon-rotate\");\r\n                                            $(item).children(\".eleTree-node-group\").children().show();\r\n                                        }\r\n                                    })\r\n                                }\r\n                            }\r\n                        }\r\n                    });\r\n                    floor++;\r\n                    _self.expandAll(val[options.request.children],arr,floor,isMethodsExpandAll);\r\n                    floor--;\r\n                }\r\n                // 重置数组索引\r\n                arr.pop();\r\n            })\r\n\r\n            \r\n        },\r\n        // 初始默认选中\r\n        defaultChecked: function() {\r\n            var options=this.config;\r\n            if(options.defaultCheckedKeys.length===0){\r\n                return false;\r\n            }\r\n            // 判断是否父子无关\r\n            if(options.checkStrictly){\r\n                options.defaultCheckedKeys.forEach(function(val,index) {\r\n                    var nodeContent=options.elem.find(\"[data-\"+options.request.key+\"='\"+val+\"']\").children(\".eleTree-node-content\");\r\n                    // 如果当前没选中则选中\r\n                    if(nodeContent.children(\".eleTree-hideen\").prop(\"checked\")===false){\r\n                        nodeContent.children(\".eleTree-checkbox\").trigger(\"click\",[\"default\"]);\r\n                    }\r\n                })\r\n                return false;\r\n            }\r\n            // 父元素优先\r\n            var arr=$.extend([],options.defaultCheckedKeys);\r\n            options.defaultCheckedKeys.forEach(function(val,index) {\r\n                options.elem.find(\"[data-\"+options.request.key+\"='\"+val+\"']\").find(\"[data-\"+options.request.key+\"]\").each(function(i,item) {\r\n                    var id=$(item).attr(\"data-\"+options.request.key);\r\n                    id=isNaN(id) ? id : Number(id);\r\n                    var isInArrayIndex=$.inArray(id,arr);\r\n                    if(isInArrayIndex!==-1){\r\n                        arr.splice(isInArrayIndex,1);\r\n                    }\r\n                })\r\n            })\r\n            arr.forEach(function(val,index) {\r\n                var nodeContent=options.elem.find(\"[data-\"+options.request.key+\"='\"+val+\"']\").children(\".eleTree-node-content\");\r\n                // 如果当前没选中则选中\r\n                if(nodeContent.children(\".eleTree-hideen\").prop(\"checked\")===false){\r\n                    nodeContent.children(\".eleTree-checkbox\").trigger(\"click\",[\"default\"]);\r\n                }\r\n            })\r\n        },\r\n        // 自定义checkbox解析\r\n        checkboxRender: function() {\r\n            var options=this.config;\r\n            options.elem.find(\".eleTree-checkbox\").remove();\r\n            options.elem.find(\"input.eleTree-hideen[type=checkbox]\").each(function(index,item){\r\n                if($(item).hasClass(\"eleTree-disabled\")){\r\n                    $(item).after('<div class=\"eleTree-checkbox eleTree-checkbox-disabled\"><i class=\"layui-icon\"></i></div>');\r\n                }else{\r\n                    $(item).after('<div class=\"eleTree-checkbox\"><i class=\"layui-icon\"></i></div>');\r\n                }\r\n\r\n                var checkbox=$(item).siblings(\".eleTree-checkbox\");\r\n                if($(item).attr(\"eletree-status\")===\"1\"){\r\n                    checkbox.addClass(\"eleTree-checkbox-checked\");\r\n                    checkbox.children(\"i\").addClass(\"layui-icon-ok\").removeClass(\"eleTree-checkbox-line\");\r\n                }else if($(item).attr(\"eletree-status\")===\"0\"){\r\n                    checkbox.removeClass(\"eleTree-checkbox-checked\");\r\n                    checkbox.children(\"i\").removeClass(\"layui-icon-ok eleTree-checkbox-line\");\r\n                }else if($(item).attr(\"eletree-status\")===\"2\"){\r\n                    checkbox.addClass(\"eleTree-checkbox-checked\");\r\n                    checkbox.children(\"i\").removeClass(\"layui-icon-ok\").addClass(\"eleTree-checkbox-line\");\r\n                }\r\n                \r\n            })\r\n        },\r\n        // 通过dom节点找对应数据\r\n        reInitData: function(node) {\r\n            var options=this.config;\r\n            var i=node.index();\r\n            var floor=Number(node.attr(\"eletree-floor\"));\r\n            var arr=[];     // 节点对应的index\r\n            while (floor>=0) {\r\n                arr.push(i);\r\n                floor=floor-1;\r\n                node=node.parents(\"[eletree-floor='\"+floor+\"']\");\r\n                i=node.index();\r\n            }\r\n            arr=arr.reverse();\r\n            var oData=this.config.data;\r\n            // 当前节点的父节点数据\r\n            var parentData=oData[arr[0]];\r\n            // 当前节点的data数据\r\n            var d = oData[arr[0]];\r\n            for(var i = 1; i<arr.length; i++){\r\n                d = d[options.request.children]?d[options.request.children][arr[i]]:d;\r\n            }\r\n            for(var i = 1; i<arr.length-1; i++){\r\n                parentData = parentData[options.request.children]?parentData[options.request.children][arr[i]]:parentData;\r\n            }\r\n\r\n            return {\r\n                currentData: d,\r\n                parentData: {\r\n                    data: parentData,\r\n                    childIndex: arr[arr.length-1]\r\n                },\r\n                index: arr\r\n            }\r\n        },\r\n        // 通过key查找数据\r\n        keySearchToOpera: function(key,callback) {\r\n            var options=this.config;\r\n            var _self=this;\r\n            // 查找数据\r\n            var fn=function(data) {\r\n                var obj={\r\n                    i: 0,\r\n                    len: data.length\r\n                }\r\n                for(;obj.i<obj.len;obj.i++){\r\n                    if(data[obj.i][options.request.key]!==key){\r\n                        if(data[obj.i][options.request.children] && data[obj.i][options.request.children].length>0){\r\n                            fn(data[obj.i][options.request.children]);\r\n                        }\r\n                    }else{\r\n                        callback(data,obj);\r\n                    }\r\n                }\r\n            }\r\n            fn(options.data);\r\n        },\r\n        updateKeyChildren: function(key,data) {\r\n            var options=this.config;\r\n            var node=options.elem.find(\"[data-\"+options.request.key+\"='\"+key+\"']\");\r\n            var floor=Number(node.attr(\"eletree-floor\"))+1;\r\n            var _self=this;\r\n            \r\n            this.keySearchToOpera(key,function(d,obj) {\r\n                // 数据更新\r\n                d[obj.i][options.request.children]=data;\r\n                // dom更新\r\n                node.length!==0 && laytpl(TPL_ELEM(options,floor)).render(data, function(string){\r\n                    $(node).children(\".eleTree-node-group\").empty().append(string);\r\n                    options.defaultExpandAll && $(node).children(\".eleTree-node-group\").children().show();\r\n                }); \r\n                _self.unCheckNodes();\r\n                _self.defaultChecked();\r\n            });\r\n        },\r\n        updateKeySelf: function(key,data) {\r\n            var options=this.config;\r\n            var node=options.elem.find(\"[data-\"+options.request.key+\"='\"+key+\"']\").children(\".eleTree-node-content\");\r\n            var floor=Number(node.attr(\"eletree-floor\"))+1;\r\n            data[options.request.name] && node.children(\".eleTree-node-content-label\").text(data[options.request.name]);\r\n            data[options.request.disabled] && node.children(\".eleTree-hideen\").addClass(\"eleTree-disabled\")\r\n                .siblings(\".eleTree-checkbox\").addClass(\"eleTree-checkbox-disabled\");\r\n            // 数据更新\r\n            var getData=this.keySearchToOpera(key,function(d,obj) {\r\n                data[options.request.key]=d[obj.i][options.request.key];\r\n                data[options.request.children]=d[obj.i][options.request.children];\r\n                d[obj.i]=$.extend({},d[obj.i],data);\r\n                console.log(options.data);\r\n            });\r\n        },\r\n        remove: function(key) {\r\n            var options=this.config;\r\n            var node=options.elem.find(\"[data-\"+options.request.key+\"='\"+key+\"']\");\r\n            var pElem=node.parent(\".eleTree-node-group\");\r\n            // 数据删除\r\n            this.keySearchToOpera(key,function(data,obj) {\r\n                data.splice(obj.i,1);\r\n                obj.i--;\r\n                obj.len--;\r\n\r\n                node.length!==0 && options.elem.find(\"[data-\"+options.request.key+\"='\"+key+\"']\").remove();\r\n                if(pElem.children(\".eleTree-node\").length===0){\r\n                    pElem.siblings(\".eleTree-node-content\").children(\".eleTree-node-content-icon\").children(\".layui-icon\").css(\"color\", \"transparent\");\r\n                }\r\n            });\r\n            this.unCheckNodes();\r\n            this.defaultChecked();\r\n        },\r\n        append: function(key,data) {\r\n            var options=this.config;\r\n            var node=options.elem.find(\"[data-\"+options.request.key+\"='\"+key+\"']\");\r\n            var floor=Number(node.attr(\"eletree-floor\"))+1;\r\n            // 数据更新\r\n            this.keySearchToOpera(key,function(d,obj) {\r\n                if(d[obj.i][options.request.children]){\r\n                    d[obj.i][options.request.children].push(data);\r\n                }else{\r\n                    d[obj.i][options.request.children]=[data];\r\n                }\r\n                var arr=d[obj.i][options.request.children];\r\n                // 添加之后长度为1，则原来没有三角，添加三角\r\n                if(arr.length===1){\r\n                    node.children(\".eleTree-node-content\").find(\".eleTree-node-content-icon .layui-icon\").removeAttr(\"style\").addClass(\"icon-rotate\");\r\n                }\r\n                var len=arr.length;\r\n                var eletreeStatus=node.children(\".eleTree-node-content\").children(\"input.eleTree-hideen\").attr(\"eletree-status\");\r\n                eletreeStatus=eletreeStatus===\"2\" ? \"0\" : eletreeStatus;\r\n                node.length!==0 && laytpl(TPL_ELEM(options,floor,eletreeStatus)).render([arr[len-1]], function(string){\r\n                    node.children(\".eleTree-node-group\").append(string).children().show();\r\n                }); \r\n            });\r\n            this.checkboxRender();\r\n        },\r\n        insertBefore: function(key,data) {\r\n            var options=this.config;\r\n            var node=options.elem.find(\"[data-\"+options.request.key+\"='\"+key+\"']\");\r\n            var floor=Number(node.attr(\"eletree-floor\"));\r\n            // 数据更新\r\n            this.keySearchToOpera(key,function(d,obj) {\r\n                d.splice(obj.i,0,data);\r\n                obj.i++;\r\n                obj.len++;\r\n                var eletreeStatus=node.parent(\".eleTree-node-group\").length===0 ? \"0\" : node.parent(\".eleTree-node-group\").parent(\".eleTree-node\")\r\n                .children(\".eleTree-node-content\").children(\"input.eleTree-hideen\").attr(\"eletree-status\");\r\n                eletreeStatus=eletreeStatus===\"2\" ? \"0\" : eletreeStatus;\r\n                node.length!==0 && laytpl(TPL_ELEM(options,floor,eletreeStatus)).render([data], function(string){\r\n                    node.before(string).prev(\".eleTree-node\").show();\r\n                }); \r\n            });\r\n            this.checkboxRender();\r\n        },\r\n        insertAfter: function(key,data) {\r\n            var options=this.config;\r\n            var node=options.elem.find(\"[data-\"+options.request.key+\"='\"+key+\"']\");\r\n            var floor=Number(node.attr(\"eletree-floor\"));\r\n            // 数据更新\r\n            this.keySearchToOpera(key,function(d,obj) {\r\n                d.splice(obj.i+1,0,data);\r\n                obj.i++;\r\n                obj.len++;\r\n                var eletreeStatus=node.parent(\".eleTree-node-group\").length===0 ? \"0\" : node.parent(\".eleTree-node-group\").parent(\".eleTree-node\")\r\n                .children(\".eleTree-node-content\").children(\"input.eleTree-hideen\").attr(\"eletree-status\");\r\n                eletreeStatus=eletreeStatus===\"2\" ? \"0\" : eletreeStatus;\r\n                node.length!==0 && laytpl(TPL_ELEM(options,floor,eletreeStatus)).render([data], function(string){\r\n                    $(node).after(string).next(\".eleTree-node\").show();\r\n                }); \r\n            });\r\n            this.checkboxRender();\r\n            // if(!options.lazy){\r\n            //     if(!options.renderAfterExpand || options.defaultExpandAll || options.defaultExpandedKeys.length>0){\r\n            //         this.expandAll(options.data,[],1);\r\n            //     }\r\n            // }\r\n        },\r\n        getChecked: function(leafOnly, includeHalfChecked) {\r\n            var options=this.config\r\n                ,el\r\n                ,arr=[];\r\n            leafOnly=leafOnly || false;\r\n            includeHalfChecked=includeHalfChecked || false;\r\n            if(leafOnly){\r\n                el=options.elem.find(\".layui-icon.leaf-icon\").parent(\".eleTree-node-content-icon\")\r\n                    .siblings(\"input.eleTree-hideen[eletree-status='1']\");\r\n            }else if(includeHalfChecked){\r\n                el=options.elem.find(\"input.eleTree-hideen[eletree-status='1'],input.eleTree-hideen[eletree-status='2']\");\r\n            }else{\r\n                el=options.elem.find(\"input.eleTree-hideen[eletree-status='1']\");\r\n            }\r\n            el.each(function(index,item) {\r\n                var obj={};\r\n                var id=$(item).parent(\".eleTree-node-content\").parent(\".eleTree-node\").attr(\"data-\"+options.request.key);\r\n                id=isNaN(id) ? id : Number(id);\r\n                obj[options.request.key]=id;\r\n                obj.elem=item;\r\n                obj.othis=$(item).siblings(\".eleTree-checkbox\").get(0)\r\n                arr.push(obj);\r\n            })\r\n            return arr;\r\n        },\r\n        setChecked: function(arr) {\r\n            var options=this.config;\r\n            this.unCheckNodes();\r\n            arr.forEach(function(val) {\r\n                if($.inArray(val,options.defaultCheckedKeys)===-1){\r\n                    options.defaultCheckedKeys.push(val);\r\n                }\r\n            })\r\n            this.defaultChecked();\r\n        },\r\n        unCheckNodes: function() {\r\n            var options=this.config;\r\n            options.elem.find(\"input.eleTree-hideen[eletree-status='1'],input.eleTree-hideen[eletree-status='2']\").each(function(index,item) {\r\n                $(item).attr(\"eletree-status\",\"0\").prop(\"checked\",false);\r\n            });\r\n            this.checkboxRender();\r\n        },\r\n        unExpandAll: function() {\r\n            var options=this.config;\r\n            options.elem.find(\".layui-icon.icon-rotate\").removeClass(\"icon-rotate\")\r\n                .parent(\".eleTree-node-content-icon\").parent(\".eleTree-node-content\")\r\n                .siblings(\".eleTree-node-group\").children(\".eleTree-node\").hide();\r\n        },\r\n        // 节点事件\r\n        nodeEvent: function() {\r\n            var _self=this;\r\n            var options=this.config;\r\n            // 节点被点击的回调事件\r\n            options.elem.on(\"click\",\".eleTree-node-content\",function(e) {\r\n                var eleNode=$(this).parent(\".eleTree-node\");\r\n                $(\"#tree-menu\").hide().remove();\r\n                layui.event.call(eleNode, MOD_NAME, 'nodeClick('+ _self.filter +')', {\r\n                    node: eleNode,\r\n                    data: _self.reInitData(eleNode),\r\n                    event: e\r\n                });\r\n            })\r\n            // 节点右键的回调事件\r\n            options.elem.on(\"contextmenu\",\".eleTree-node-content\",function(e) {\r\n                var eleNode=$(this).parent(\".eleTree-node\");\r\n                layui.event.call(eleNode, MOD_NAME, 'nodeContextmenu('+ _self.filter +')', {\r\n                    node: eleNode,\r\n                    data: _self.reInitData(eleNode),\r\n                    event: e\r\n                });\r\n            })\r\n            // 节点被拖拽的回调事件\r\n            options.draggable && options.elem.on(\"mousedown\",\".eleTree-node-content\",function(e) {\r\n                var time=0;\r\n                var eleNode=$(this).parent(\".eleTree-node\");\r\n                var eleFloor=Number(eleNode.attr(\"eletree-floor\"));\r\n                var groupNode=eleNode.parent(\".eleTree-node-group\");\r\n\r\n                e.stopPropagation();\r\n                options.elem.css(\"user-select\",\"none\");\r\n                var cloneNode=eleNode.clone(true);\r\n                var temNode=eleNode.clone(true);\r\n\r\n                var x=e.clientX-options.elem.offset().left;\r\n                var y=e.clientY-options.elem.offset().top;\r\n                options.elem.append(cloneNode);\r\n                cloneNode.css({\r\n                    \"display\": \"none\",\r\n                    \"opacity\": 0.7,\r\n                    \"position\": \"absolute\",\r\n                    \"background-color\": \"#f5f5f5\",\r\n                    \"width\": \"100%\"\r\n                })\r\n\r\n                var currentData=_self.reInitData(eleNode);\r\n\r\n                var isStop=false;\r\n\r\n                $(document).on(\"mousemove\",function(e) {\r\n                    // t为了区别click事件\r\n                    time++;\r\n                    if(time>2){\r\n                        var xx=e.clientX-options.elem.offset().left+10;\r\n                        var yy=e.clientY-options.elem.offset().top+$(document).scrollTop()-5;   // 加上浏览器滚动高度\r\n\r\n                        cloneNode.css({\r\n                            display: \"block\",\r\n                            left: xx+\"px\",\r\n                            top: yy+\"px\"\r\n                        })\r\n                    }\r\n                }).on(\"mouseup\",function(e) {\r\n                    $(document).off(\"mousemove\").off(\"mouseup\");\r\n                    var target=$(e.target).parents(\".eleTree-node\").eq(0);\r\n                    cloneNode.remove();\r\n                    options.elem.css(\"user-select\",\"auto\");\r\n\r\n                    \r\n                    // 当前点击的是否时最外层\r\n                    var isCurrentOuterMost=eleNode.parent().get(0).isEqualNode(options.elem.get(0))\r\n                    // 目标是否时最外层\r\n                    var isTargetOuterMost=$(e.target).get(0).isEqualNode(options.elem.get(0))\r\n                    if(isTargetOuterMost){\r\n                        target=options.elem;\r\n                    }\r\n                    // 判断是否超出边界\r\n                    if(target.parents(options.elem).length===0 && !isTargetOuterMost){\r\n                        return;\r\n                    }\r\n                    // 判断初始与结束是否是同一个节点\r\n                    if(target.get(0).isEqualNode(eleNode.get(0))){\r\n                        return;\r\n                    }\r\n                    // 判断是否是父节点放到子节点\r\n                    var tFloor=target.attr(\"eletree-floor\");\r\n                    var isInChild=false;\r\n                    eleNode.find(\"[eletree-floor='\"+tFloor+\"']\").each(function() {\r\n                        if(this.isEqualNode(target.get(0))){\r\n                            isInChild=true;\r\n                        }\r\n                    })\r\n                    if(isInChild){\r\n                        return;\r\n                    }\r\n\r\n                    var targetData=_self.reInitData(target);\r\n                    layui.event.call(target, MOD_NAME, 'nodeDrag('+ _self.filter +')', {\r\n                        current: {\r\n                            node: eleNode,\r\n                            data: currentData\r\n                        },\r\n                        target: {\r\n                            node: target,\r\n                            data: targetData\r\n                        },\r\n                        stop: function() {\r\n                            isStop=true;\r\n                        }\r\n                    });\r\n                    // 拖拽是否取消\r\n                    if(isStop){\r\n                        return false;\r\n                    }\r\n\r\n                    // 数据更改\r\n                    var currList=currentData.parentData.data[options.request.children]\r\n                    var currIndex=currentData.parentData.childIndex\r\n                    var currData=currentData.currentData;\r\n                    var tarData=targetData.currentData;\r\n                    // 当前是否是最外层\r\n                    isCurrentOuterMost ? options.data.splice(currIndex,1) : currList.splice(currIndex,1)\r\n                    // 目标是否是最外层\r\n                    isTargetOuterMost ? options.data.push(currData) : (function() {\r\n                        !tarData[options.request.children] ? tarData[options.request.children]=[] : \"\";\r\n                        tarData[options.request.children].push(currData);\r\n                    })()\r\n\r\n                    // dom互换\r\n                    eleNode.remove();\r\n                    // 最外层判断\r\n                    if(isTargetOuterMost){\r\n                        target.append(temNode);\r\n                        var floor=0;\r\n                    }else{\r\n                        target.children(\".eleTree-node-group\").append(temNode);\r\n                        var floor=Number(target.attr(\"eletree-floor\"))+1;\r\n                    }\r\n                    // 加floor和padding\r\n                    temNode.attr(\"eletree-floor\",String(floor));\r\n                    temNode.children(\".eleTree-node-content\").css(\"padding-left\",floor*options.indent+\"px\");\r\n                    // 通过floor差值计算子元素的floor\r\n                    var countFloor=eleFloor-floor;\r\n                    temNode.find(\".eleTree-node\").each(function(index,item) {\r\n                        var f=Number($(item).attr(\"eletree-floor\"))-countFloor;\r\n                        $(item).attr(\"eletree-floor\",String(f));\r\n                        $(item).children(\".eleTree-node-content\").css(\"padding-left\",f*options.indent+\"px\");\r\n                    })\r\n                    // 原dom去三角\r\n                    var leaf=groupNode.children(\".eleTree-node\").length===0;\r\n                        leaf && groupNode.siblings(\".eleTree-node-content\")\r\n                        .children(\".eleTree-node-content-icon\").children(\".layui-icon\")\r\n                        .removeClass(\"icon-rotate\").css(\"color\",\"transparent\");\r\n                    // 当前的增加三角\r\n                    var cLeaf=target.children(\".eleTree-node-group\").children(\".eleTree-node\").length===0;\r\n                        !cLeaf && target.children(\".eleTree-node-content\")\r\n                        .children(\".eleTree-node-content-icon\").children(\".layui-icon\")\r\n                        .addClass(\"icon-rotate\").removeAttr(\"style\");\r\n\r\n                    _self.unCheckNodes();\r\n                    _self.defaultChecked();\r\n\r\n                })\r\n            })\r\n        },\r\n        rightClickMenu: function() {\r\n            var _self=this;\r\n            var options=this.config;\r\n            if(options.contextmenuList.length<=0){\r\n                return;\r\n            }\r\n            $(document).on(\"click\",function() {\r\n                $(\"#tree-menu\").hide().remove();\r\n            });\r\n            var menuStr=['<ul id=\"tree-menu\">'\r\n                ,$.inArray(\"copy\",options.contextmenuList)!==-1?'<li class=\"copy\"><a href=\"javascript:;\">复制</a></li>':''\r\n                ,$.inArray(\"add\",options.contextmenuList)!==-1?'<li class=\"add\"><a href=\"javascript:;\">新增</a></li>'+\r\n                    '<li class=\"insertBefore\"><a href=\"javascript:;\">插入节点前</a></li>'+\r\n                    '<li class=\"insertAfter\"><a href=\"javascript:;\">插入节点后</a></li>'+\r\n                    '<li class=\"append\"><a href=\"javascript:;\">插入子节点</a></li>' : \"\"\r\n                ,$.inArray(\"edit\",options.contextmenuList)!==-1?'<li class=\"edit\"><a href=\"javascript:;\">修改</a></li>':''\r\n                ,$.inArray(\"remove\",options.contextmenuList)!==-1?'<li class=\"remove\"><a href=\"javascript:;\">删除</a></li>':''\r\n            ,'</ul>'].join(\"\");\r\n            this.treeMenu=$(menuStr);\r\n            options.elem.off(\"contextmenu\").on(\"contextmenu\",\".eleTree-node-content\",function(e) {\r\n                var that=this;\r\n                e.stopPropagation();\r\n                e.preventDefault();\r\n                // 添加active背景\r\n                if(_self.prevClickEle) _self.prevClickEle.removeClass(\"eleTree-node-content-active\");\r\n                $(this).addClass(\"eleTree-node-content-active\");\r\n                var eleNode=$(this).parent(\".eleTree-node\");\r\n                var nodeData=_self.reInitData(eleNode);\r\n\r\n                // 菜单位置\r\n                $(document.body).after(_self.treeMenu);\r\n                $(\"#tree-menu li.insertBefore,#tree-menu li.insertAfter,#tree-menu li.append\").hide();\r\n                $(\"#tree-menu li.copy,#tree-menu li.add,#tree-menu li.edit,#tree-menu li.remove\").show();\r\n                $(\"#tree-menu\").css({\r\n                    left: e.pageX,\r\n                    top: e.pageY\r\n                }).show();\r\n                // 复制\r\n                $(\"#tree-menu li.copy\").off().on(\"click\",function() {\r\n                    var el = $(that).children(\".eleTree-node-content-label\").get(0);\r\n                    var selection = window.getSelection();\r\n                    var range = document.createRange();\r\n                    range.selectNodeContents(el);\r\n                    selection.removeAllRanges();\r\n                    selection.addRange(range);\r\n                    document.execCommand('Copy', 'false', null);\r\n                    selection.removeAllRanges();\r\n                });\r\n                // 新增\r\n                $(\"#tree-menu li.add\").off().on(\"click\",function(e) {\r\n                    e.stopPropagation();\r\n                    $(this).hide().siblings(\"li.copy,li.edit,li.remove\").hide();\r\n                    $(this).siblings(\".append,li.insertAfter,li.insertBefore\").show();\r\n                })\r\n                // 添加的默认数据\r\n                var obj={};\r\n                obj[options.request.key]=_self.addKeyIndex;\r\n                obj[options.request.name]=\"未命名\"+_self.nameIndex;\r\n                \r\n                var arr=[\"Append\",\"InsertBefore\",\"InsertAfter\"];\r\n                arr.forEach(function(val) {\r\n                    var s=val[0].toLocaleLowerCase()+val.slice(1,val.length);\r\n                    $(\"#tree-menu li.\"+s).off().on(\"click\",function(e) {\r\n                        var node=$(that).parent(\".eleTree-node\");\r\n                        var key=node.attr(\"data-\"+options.request.key);\r\n                        key=isNaN(key) ? key : Number(key);\r\n                        var isStop=false;\r\n                        var s=val[0].toLocaleLowerCase()+val.slice(1,val.length);\r\n                        layui.event.call(node, MOD_NAME, 'node'+val+'('+ _self.filter +')', {\r\n                            node: node,\r\n                            data: nodeData.currentData,\r\n                            // 重新设置数据\r\n                            setData: function(o) {\r\n                                _self[s](key,$.extend({},obj,o));\r\n                                isStop=true;\r\n                            },\r\n                            // 停止添加\r\n                            stop: function() {\r\n                                isStop=true;\r\n                            }\r\n                        });\r\n                        if(isStop) return;\r\n                        _self[s](key,obj)\r\n                        _self.nameIndex++;\r\n                        _self.addKeyIndex++;\r\n                    })\r\n                })\r\n                \r\n                // 编辑\r\n                $(\"#tree-menu li.edit\").off().on(\"click\",function(e) {\r\n                    e.stopPropagation();\r\n                    $(\"#tree-menu\").hide().remove();\r\n                    var node=$(that).parent(\".eleTree-node\");\r\n                    var key=node.attr(\"data-\"+options.request.key);\r\n                    key=isNaN(key) ? key : Number(key);\r\n                    var label=$(that).children(\".eleTree-node-content-label\").hide();\r\n                    var text=label.text();\r\n                    var inp=\"<input type='text' value='\"+text+\"' class='eleTree-node-content-input' />\";\r\n                    label.after(inp);\r\n                    label.siblings(\".eleTree-node-content-input\").focus().select().off().on(\"blur\",function() {\r\n                        var val=$(this).val();\r\n                        var isStop=false;\r\n                        var inpThis=this;\r\n                        layui.event.call(node, MOD_NAME, 'nodeEdit('+ _self.filter +')', {\r\n                            node: node,\r\n                            value: val,\r\n                            data: nodeData.currentData,\r\n                            // 停止添加\r\n                            stop: function() {\r\n                                isStop=true;\r\n                                $(inpThis).siblings(\".eleTree-node-content-label\").show();\r\n                                $(inpThis).remove();\r\n                            }\r\n                        });\r\n                        if(isStop) return;\r\n                        // 修改数据\r\n                        _self.reInitData(eleNode).currentData[options.request.name]=val;\r\n                        // 修改dom\r\n                        $(this).siblings(\".eleTree-node-content-label\").text(val).show();\r\n                        $(this).remove();\r\n                    }).on(\"mousedown\",function(e) {\r\n                        // 防止input拖拽\r\n                        e.stopPropagation();\r\n                    })\r\n                })\r\n                // 删除\r\n                $(\"#tree-menu li.remove\").off().on(\"click\",function(e) {\r\n                    var node=$(that).parent(\".eleTree-node\");\r\n                    var key=node.attr(\"data-\"+options.request.key);\r\n                    key=isNaN(key) ? key : Number(key);\r\n                    var isStop=false;\r\n                    layui.event.call(node, MOD_NAME, 'nodeRemove('+ _self.filter +')', {\r\n                        node: node,\r\n                        data: nodeData.currentData,\r\n                        // 停止添加\r\n                        stop: function() {\r\n                            isStop=true;\r\n                        }\r\n                    });\r\n                    if(isStop) return;\r\n                    _self.remove(key);\r\n                })\r\n\r\n                _self.prevClickEle=$(this);\r\n            })\r\n        },\r\n        search: function(value) {\r\n            var options=this.config;\r\n            if(!options.searchNodeMethod || typeof options.searchNodeMethod !== \"function\"){\r\n                return;\r\n            }\r\n            var data=options.data;\r\n            // 数据递归\r\n            var traverse=function(data) {\r\n                data.forEach(function(val,index) {\r\n                    // 所有查找到的节点增加属性\r\n                    val.visible=options.searchNodeMethod(value,val);\r\n                    if(val[options.request.children] && val[options.request.children].length>0){\r\n                        traverse(val[options.request.children]);\r\n                    }\r\n                    //如果当前节点属性为隐藏，判断其子节点是否有显示的，如果有，则当前节点改为显示\r\n                    if(!val.visible){\r\n                        let childSomeShow = false;\r\n                        if(val[options.request.children] && val[options.request.children].length>0){\r\n                            childSomeShow=val[options.request.children].some(function(v,i) {\r\n                                return v.visible;\r\n                            })\r\n                        }\r\n                        val.visible = childSomeShow;\r\n                    }\r\n                    // 通过节点的属性，显示隐藏各个节点，并添加删除搜索类\r\n                    var el=options.elem.find(\"[data-\"+options.request.key+\"='\"+val[options.request.key]+\"']\");\r\n                    if(val.visible){\r\n                        el.removeClass(\"eleTree-search-hide\");\r\n                        // 判断父节点是否展开，如果父节点没有展开，则子节点也不要显示\r\n                        var parentEl=el.parent(\".eleTree-node-group\").parent(\".eleTree-node\");\r\n                        var isParentOpen=parentEl.children(\".eleTree-node-content\").children(\".eleTree-node-content-icon\").children(\".layui-icon.layui-icon-triangle-r\").hasClass(\"icon-rotate\")\r\n                        if((parentEl.length>0 && isParentOpen) || parentEl.length===0){\r\n                            el.show();\r\n                        }\r\n                    }else{\r\n                        el.hide().addClass(\"eleTree-search-hide\");\r\n                    }\r\n                    // 删除子层属性\r\n                    if(val[options.request.children] && val[options.request.children].length>0){\r\n                        val[options.request.children].forEach(function(v,i) {\r\n                            delete v.visible;\r\n                        })\r\n                    }\r\n                })\r\n            }\r\n            traverse(data);\r\n            // 删除最外层属性\r\n            var arr=[];\r\n            data.forEach(function(val) {\r\n                arr.push(val.visible);\r\n                delete val.visible;\r\n            })\r\n            // 如果第一层的所有的都隐藏，则显示文本\r\n            if(arr.every(function(v) {\r\n                return v===false;\r\n            })){\r\n                laytpl(TPL_NoText()).render(options, function(string){\r\n                    options.elem.append(string);\r\n                }); \r\n            }else{\r\n                options.elem.children(\".eleTree-noText\").remove();\r\n            }\r\n        }\r\n    }\r\n    \r\n    exports(MOD_NAME,eleTree);\r\n})"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/layui/ext/treeselect/treeselect.js",
    "content": "\r\nlayui.define(['form', 'jquery'], function (exports) { //提示：模块也可以依赖其它模块，如：layui.define('layer', callback);\r\n  var jQuery = layui.jquery,\r\n      $ = jQuery,\r\n      form = layui.form,\r\n      _MOD = 'treeSelect',\r\n      trss = {},\r\n      TreeSelect = function () {\r\n        this.v = '1.0.4';\r\n      };\r\n\r\n\r\n\r\n/*\r\n * JQuery zTree core v3.5.37\r\n * http://treejs.cn/\r\n *\r\n * Copyright (c) 2010 Hunter.z\r\n *\r\n * Licensed same as jquery - MIT License\r\n * http://www.opensource.org/licenses/mit-license.php\r\n *\r\n * email: hunter.z@263.net\r\n * Date: 2018-08-21\r\n */\r\n(function(r){var J,K,L,M,N,O,v,t={},w={},x={},P={treeId:\"\",treeObj:null,view:{addDiyDom:null,autoCancelSelected:!0,dblClickExpand:!0,expandSpeed:\"fast\",fontCss:{},nameIsHTML:!1,selectedMulti:!0,showIcon:!0,showLine:!0,showTitle:!0,txtSelectedEnable:!1},data:{key:{isParent:\"isParent\",children:\"children\",name:\"name\",title:\"\",url:\"url\",icon:\"icon\"},simpleData:{enable:!1,idKey:\"id\",pIdKey:\"pId\",rootPId:null},keep:{parent:!1,leaf:!1}},async:{enable:!1,contentType:\"application/x-www-form-urlencoded\",type:\"post\",\r\ndataType:\"text\",headers:{},xhrFields:{},url:\"\",autoParam:[],otherParam:[],dataFilter:null},callback:{beforeAsync:null,beforeClick:null,beforeDblClick:null,beforeRightClick:null,beforeMouseDown:null,beforeMouseUp:null,beforeExpand:null,beforeCollapse:null,beforeRemove:null,onAsyncError:null,onAsyncSuccess:null,onNodeCreated:null,onClick:null,onDblClick:null,onRightClick:null,onMouseDown:null,onMouseUp:null,onExpand:null,onCollapse:null,onRemove:null}},y=[function(a){var b=a.treeObj,c=g.event;b.bind(c.NODECREATED,\r\nfunction(b,c,i){h.apply(a.callback.onNodeCreated,[b,c,i])});b.bind(c.CLICK,function(b,c,i,e,k){h.apply(a.callback.onClick,[c,i,e,k])});b.bind(c.EXPAND,function(b,c,i){h.apply(a.callback.onExpand,[b,c,i])});b.bind(c.COLLAPSE,function(b,c,i){h.apply(a.callback.onCollapse,[b,c,i])});b.bind(c.ASYNC_SUCCESS,function(b,c,i,e){h.apply(a.callback.onAsyncSuccess,[b,c,i,e])});b.bind(c.ASYNC_ERROR,function(b,c,i,e,k,g){h.apply(a.callback.onAsyncError,[b,c,i,e,k,g])});b.bind(c.REMOVE,function(b,c,i){h.apply(a.callback.onRemove,\r\n[b,c,i])});b.bind(c.SELECTED,function(b,c,i){h.apply(a.callback.onSelected,[c,i])});b.bind(c.UNSELECTED,function(b,c,i){h.apply(a.callback.onUnSelected,[c,i])})}],z=[function(a){var b=g.event;a.treeObj.unbind(b.NODECREATED).unbind(b.CLICK).unbind(b.EXPAND).unbind(b.COLLAPSE).unbind(b.ASYNC_SUCCESS).unbind(b.ASYNC_ERROR).unbind(b.REMOVE).unbind(b.SELECTED).unbind(b.UNSELECTED)}],A=[function(a){var b=e.getCache(a);b||(b={},e.setCache(a,b));b.nodes=[];b.doms=[]}],B=[function(a,b,c,d,f,i){if(c){var m=\r\ne.getRoot(a),k=e.nodeChildren(a,c);c.level=b;c.tId=a.treeId+\"_\"+ ++m.zId;c.parentTId=d?d.tId:null;c.open=typeof c.open==\"string\"?h.eqs(c.open,\"true\"):!!c.open;b=e.nodeIsParent(a,c);h.isArray(k)&&!(b===!1||typeof b==\"string\"&&h.eqs(b,\"false\"))?(e.nodeIsParent(a,c,!0),c.zAsync=!0):(b=e.nodeIsParent(a,c,b),c.open=b&&!a.async.enable?c.open:!1,c.zAsync=!b);c.isFirstNode=f;c.isLastNode=i;c.getParentNode=function(){return e.getNodeCache(a,c.parentTId)};c.getPreNode=function(){return e.getPreNode(a,c)};c.getNextNode=\r\nfunction(){return e.getNextNode(a,c)};c.getIndex=function(){return e.getNodeIndex(a,c)};c.getPath=function(){return e.getNodePath(a,c)};c.isAjaxing=!1;e.fixPIdKeyValue(a,c)}}],u=[function(a){var b=a.target,c=e.getSetting(a.data.treeId),d=\"\",f=null,i=\"\",m=\"\",k=null,j=null,o=null;if(h.eqs(a.type,\"mousedown\"))m=\"mousedown\";else if(h.eqs(a.type,\"mouseup\"))m=\"mouseup\";else if(h.eqs(a.type,\"contextmenu\"))m=\"contextmenu\";else if(h.eqs(a.type,\"click\"))if(h.eqs(b.tagName,\"span\")&&b.getAttribute(\"treeNode\"+\r\ng.id.SWITCH)!==null)d=h.getNodeMainDom(b).id,i=\"switchNode\";else{if(o=h.getMDom(c,b,[{tagName:\"a\",attrName:\"treeNode\"+g.id.A}]))d=h.getNodeMainDom(o).id,i=\"clickNode\"}else if(h.eqs(a.type,\"dblclick\")&&(m=\"dblclick\",o=h.getMDom(c,b,[{tagName:\"a\",attrName:\"treeNode\"+g.id.A}])))d=h.getNodeMainDom(o).id,i=\"switchNode\";if(m.length>0&&d.length==0&&(o=h.getMDom(c,b,[{tagName:\"a\",attrName:\"treeNode\"+g.id.A}])))d=h.getNodeMainDom(o).id;if(d.length>0)switch(f=e.getNodeCache(c,d),i){case \"switchNode\":e.nodeIsParent(c,\r\nf)?h.eqs(a.type,\"click\")||h.eqs(a.type,\"dblclick\")&&h.apply(c.view.dblClickExpand,[c.treeId,f],c.view.dblClickExpand)?k=J:i=\"\":i=\"\";break;case \"clickNode\":k=K}switch(m){case \"mousedown\":j=L;break;case \"mouseup\":j=M;break;case \"dblclick\":j=N;break;case \"contextmenu\":j=O}return{stop:!1,node:f,nodeEventType:i,nodeEventCallback:k,treeEventType:m,treeEventCallback:j}}],C=[function(a){var b=e.getRoot(a);b||(b={},e.setRoot(a,b));e.nodeChildren(a,b,[]);b.expandTriggerFlag=!1;b.curSelectedList=[];b.noSelection=\r\n!0;b.createdNodes=[];b.zId=0;b._ver=(new Date).getTime()}],D=[],E=[],F=[],G=[],H=[],e={addNodeCache:function(a,b){e.getCache(a).nodes[e.getNodeCacheId(b.tId)]=b},getNodeCacheId:function(a){return a.substring(a.lastIndexOf(\"_\")+1)},addAfterA:function(a){E.push(a)},addBeforeA:function(a){D.push(a)},addInnerAfterA:function(a){G.push(a)},addInnerBeforeA:function(a){F.push(a)},addInitBind:function(a){y.push(a)},addInitUnBind:function(a){z.push(a)},addInitCache:function(a){A.push(a)},addInitNode:function(a){B.push(a)},\r\naddInitProxy:function(a,b){b?u.splice(0,0,a):u.push(a)},addInitRoot:function(a){C.push(a)},addNodesData:function(a,b,c,d){var f=e.nodeChildren(a,b);f?c>=f.length&&(c=-1):(f=e.nodeChildren(a,b,[]),c=-1);if(f.length>0&&c===0)f[0].isFirstNode=!1,j.setNodeLineIcos(a,f[0]);else if(f.length>0&&c<0)f[f.length-1].isLastNode=!1,j.setNodeLineIcos(a,f[f.length-1]);e.nodeIsParent(a,b,!0);c<0?e.nodeChildren(a,b,f.concat(d)):(a=[c,0].concat(d),f.splice.apply(f,a))},addSelectedNode:function(a,b){var c=e.getRoot(a);\r\ne.isSelectedNode(a,b)||c.curSelectedList.push(b)},addCreatedNode:function(a,b){(a.callback.onNodeCreated||a.view.addDiyDom)&&e.getRoot(a).createdNodes.push(b)},addZTreeTools:function(a){H.push(a)},exSetting:function(a){r.extend(!0,P,a)},fixPIdKeyValue:function(a,b){a.data.simpleData.enable&&(b[a.data.simpleData.pIdKey]=b.parentTId?b.getParentNode()[a.data.simpleData.idKey]:a.data.simpleData.rootPId)},getAfterA:function(a,b,c){for(var d=0,e=E.length;d<e;d++)E[d].apply(this,arguments)},getBeforeA:function(a,\r\nb,c){for(var d=0,e=D.length;d<e;d++)D[d].apply(this,arguments)},getInnerAfterA:function(a,b,c){for(var d=0,e=G.length;d<e;d++)G[d].apply(this,arguments)},getInnerBeforeA:function(a,b,c){for(var d=0,e=F.length;d<e;d++)F[d].apply(this,arguments)},getCache:function(a){return x[a.treeId]},getNodeIndex:function(a,b){if(!b)return null;for(var c=b.parentTId?b.getParentNode():e.getRoot(a),c=e.nodeChildren(a,c),d=0,f=c.length-1;d<=f;d++)if(c[d]===b)return d;return-1},getNextNode:function(a,b){if(!b)return null;\r\nfor(var c=b.parentTId?b.getParentNode():e.getRoot(a),c=e.nodeChildren(a,c),d=0,f=c.length-1;d<=f;d++)if(c[d]===b)return d==f?null:c[d+1];return null},getNodeByParam:function(a,b,c,d){if(!b||!c)return null;for(var f=0,i=b.length;f<i;f++){var m=b[f];if(m[c]==d)return b[f];m=e.nodeChildren(a,m);if(m=e.getNodeByParam(a,m,c,d))return m}return null},getNodeCache:function(a,b){if(!b)return null;var c=x[a.treeId].nodes[e.getNodeCacheId(b)];return c?c:null},getNodePath:function(a,b){if(!b)return null;var c;\r\n(c=b.parentTId?b.getParentNode().getPath():[])&&c.push(b);return c},getNodes:function(a){return e.nodeChildren(a,e.getRoot(a))},getNodesByParam:function(a,b,c,d){if(!b||!c)return[];for(var f=[],i=0,m=b.length;i<m;i++){var k=b[i];k[c]==d&&f.push(k);k=e.nodeChildren(a,k);f=f.concat(e.getNodesByParam(a,k,c,d))}return f},getNodesByParamFuzzy:function(a,b,c,d){if(!b||!c)return[];for(var f=[],d=d.toLowerCase(),i=0,m=b.length;i<m;i++){var k=b[i];typeof k[c]==\"string\"&&b[i][c].toLowerCase().indexOf(d)>-1&&\r\nf.push(k);k=e.nodeChildren(a,k);f=f.concat(e.getNodesByParamFuzzy(a,k,c,d))}return f},getNodesByFilter:function(a,b,c,d,f){if(!b)return d?null:[];for(var i=d?null:[],m=0,k=b.length;m<k;m++){var g=b[m];if(h.apply(c,[g,f],!1)){if(d)return g;i.push(g)}g=e.nodeChildren(a,g);g=e.getNodesByFilter(a,g,c,d,f);if(d&&g)return g;i=d?g:i.concat(g)}return i},getPreNode:function(a,b){if(!b)return null;for(var c=b.parentTId?b.getParentNode():e.getRoot(a),c=e.nodeChildren(a,c),d=0,f=c.length;d<f;d++)if(c[d]===b)return d==\r\n0?null:c[d-1];return null},getRoot:function(a){return a?w[a.treeId]:null},getRoots:function(){return w},getSetting:function(a){return t[a]},getSettings:function(){return t},getZTreeTools:function(a){return(a=this.getRoot(this.getSetting(a)))?a.treeTools:null},initCache:function(a){for(var b=0,c=A.length;b<c;b++)A[b].apply(this,arguments)},initNode:function(a,b,c,d,e,i){for(var m=0,g=B.length;m<g;m++)B[m].apply(this,arguments)},initRoot:function(a){for(var b=0,c=C.length;b<c;b++)C[b].apply(this,arguments)},\r\nisSelectedNode:function(a,b){for(var c=e.getRoot(a),d=0,f=c.curSelectedList.length;d<f;d++)if(b===c.curSelectedList[d])return!0;return!1},nodeChildren:function(a,b,c){if(!b)return null;a=a.data.key.children;typeof c!==\"undefined\"&&(b[a]=c);return b[a]},nodeIsParent:function(a,b,c){if(!b)return!1;a=a.data.key.isParent;typeof c!==\"undefined\"&&(typeof c===\"string\"&&(c=h.eqs(c,\"true\")),b[a]=!!c);return b[a]},nodeName:function(a,b,c){a=a.data.key.name;typeof c!==\"undefined\"&&(b[a]=c);return\"\"+b[a]},nodeTitle:function(a,\r\nb){return\"\"+b[a.data.key.title===\"\"?a.data.key.name:a.data.key.title]},removeNodeCache:function(a,b){var c=e.nodeChildren(a,b);if(c)for(var d=0,f=c.length;d<f;d++)e.removeNodeCache(a,c[d]);e.getCache(a).nodes[e.getNodeCacheId(b.tId)]=null},removeSelectedNode:function(a,b){for(var c=e.getRoot(a),d=0,f=c.curSelectedList.length;d<f;d++)if(b===c.curSelectedList[d]||!e.getNodeCache(a,c.curSelectedList[d].tId))c.curSelectedList.splice(d,1),a.treeObj.trigger(g.event.UNSELECTED,[a.treeId,b]),d--,f--},setCache:function(a,\r\nb){x[a.treeId]=b},setRoot:function(a,b){w[a.treeId]=b},setZTreeTools:function(a,b){for(var c=0,d=H.length;c<d;c++)H[c].apply(this,arguments)},transformToArrayFormat:function(a,b){function c(b){d.push(b);(b=e.nodeChildren(a,b))&&(d=d.concat(e.transformToArrayFormat(a,b)))}if(!b)return[];var d=[];if(h.isArray(b))for(var f=0,i=b.length;f<i;f++)c(b[f]);else c(b);return d},transformTozTreeFormat:function(a,b){var c,d,f=a.data.simpleData.idKey,i=a.data.simpleData.pIdKey;if(!f||f==\"\"||!b)return[];if(h.isArray(b)){var g=\r\n[],k={};for(c=0,d=b.length;c<d;c++)k[b[c][f]]=b[c];for(c=0,d=b.length;c<d;c++){var j=k[b[c][i]];if(j&&b[c][f]!=b[c][i]){var o=e.nodeChildren(a,j);o||(o=e.nodeChildren(a,j,[]));o.push(b[c])}else g.push(b[c])}return g}else return[b]}},n={bindEvent:function(a){for(var b=0,c=y.length;b<c;b++)y[b].apply(this,arguments)},unbindEvent:function(a){for(var b=0,c=z.length;b<c;b++)z[b].apply(this,arguments)},bindTree:function(a){var b={treeId:a.treeId},c=a.treeObj;a.view.txtSelectedEnable||c.bind(\"selectstart\",\r\nv).css({\"-moz-user-select\":\"-moz-none\"});c.bind(\"click\",b,n.proxy);c.bind(\"dblclick\",b,n.proxy);c.bind(\"mouseover\",b,n.proxy);c.bind(\"mouseout\",b,n.proxy);c.bind(\"mousedown\",b,n.proxy);c.bind(\"mouseup\",b,n.proxy);c.bind(\"contextmenu\",b,n.proxy)},unbindTree:function(a){a.treeObj.unbind(\"selectstart\",v).unbind(\"click\",n.proxy).unbind(\"dblclick\",n.proxy).unbind(\"mouseover\",n.proxy).unbind(\"mouseout\",n.proxy).unbind(\"mousedown\",n.proxy).unbind(\"mouseup\",n.proxy).unbind(\"contextmenu\",n.proxy)},doProxy:function(a){for(var b=\r\n[],c=0,d=u.length;c<d;c++){var e=u[c].apply(this,arguments);b.push(e);if(e.stop)break}return b},proxy:function(a){var b=e.getSetting(a.data.treeId);if(!h.uCanDo(b,a))return!0;for(var b=n.doProxy(a),c=!0,d=0,f=b.length;d<f;d++){var i=b[d];i.nodeEventCallback&&(c=i.nodeEventCallback.apply(i,[a,i.node])&&c);i.treeEventCallback&&(c=i.treeEventCallback.apply(i,[a,i.node])&&c)}return c}};J=function(a,b){var c=e.getSetting(a.data.treeId);if(b.open){if(h.apply(c.callback.beforeCollapse,[c.treeId,b],!0)==\r\n!1)return!0}else if(h.apply(c.callback.beforeExpand,[c.treeId,b],!0)==!1)return!0;e.getRoot(c).expandTriggerFlag=!0;j.switchNode(c,b);return!0};K=function(a,b){var c=e.getSetting(a.data.treeId),d=c.view.autoCancelSelected&&(a.ctrlKey||a.metaKey)&&e.isSelectedNode(c,b)?0:c.view.autoCancelSelected&&(a.ctrlKey||a.metaKey)&&c.view.selectedMulti?2:1;if(h.apply(c.callback.beforeClick,[c.treeId,b,d],!0)==!1)return!0;d===0?j.cancelPreSelectedNode(c,b):j.selectNode(c,b,d===2);c.treeObj.trigger(g.event.CLICK,\r\n[a,c.treeId,b,d]);return!0};L=function(a,b){var c=e.getSetting(a.data.treeId);h.apply(c.callback.beforeMouseDown,[c.treeId,b],!0)&&h.apply(c.callback.onMouseDown,[a,c.treeId,b]);return!0};M=function(a,b){var c=e.getSetting(a.data.treeId);h.apply(c.callback.beforeMouseUp,[c.treeId,b],!0)&&h.apply(c.callback.onMouseUp,[a,c.treeId,b]);return!0};N=function(a,b){var c=e.getSetting(a.data.treeId);h.apply(c.callback.beforeDblClick,[c.treeId,b],!0)&&h.apply(c.callback.onDblClick,[a,c.treeId,b]);return!0};\r\nO=function(a,b){var c=e.getSetting(a.data.treeId);h.apply(c.callback.beforeRightClick,[c.treeId,b],!0)&&h.apply(c.callback.onRightClick,[a,c.treeId,b]);return typeof c.callback.onRightClick!=\"function\"};v=function(a){a=a.originalEvent.srcElement.nodeName.toLowerCase();return a===\"input\"||a===\"textarea\"};var h={apply:function(a,b,c){return typeof a==\"function\"?a.apply(Q,b?b:[]):c},canAsync:function(a,b){var c=e.nodeChildren(a,b),d=e.nodeIsParent(a,b);return a.async.enable&&b&&d&&!(b.zAsync||c&&c.length>\r\n0)},clone:function(a){if(a===null)return null;var b=h.isArray(a)?[]:{},c;for(c in a)b[c]=a[c]instanceof Date?new Date(a[c].getTime()):typeof a[c]===\"object\"?h.clone(a[c]):a[c];return b},eqs:function(a,b){return a.toLowerCase()===b.toLowerCase()},isArray:function(a){return Object.prototype.toString.apply(a)===\"[object Array]\"},isElement:function(a){return typeof HTMLElement===\"object\"?a instanceof HTMLElement:a&&typeof a===\"object\"&&a!==null&&a.nodeType===1&&typeof a.nodeName===\"string\"},$:function(a,\r\nb,c){b&&typeof b!=\"string\"&&(c=b,b=\"\");return typeof a==\"string\"?r(a,c?c.treeObj.get(0).ownerDocument:null):r(\"#\"+a.tId+b,c?c.treeObj:null)},getMDom:function(a,b,c){if(!b)return null;for(;b&&b.id!==a.treeId;){for(var d=0,e=c.length;b.tagName&&d<e;d++)if(h.eqs(b.tagName,c[d].tagName)&&b.getAttribute(c[d].attrName)!==null)return b;b=b.parentNode}return null},getNodeMainDom:function(a){return r(a).parent(\"li\").get(0)||r(a).parentsUntil(\"li\").parent().get(0)},isChildOrSelf:function(a,b){return r(a).closest(\"#\"+\r\nb).length>0},uCanDo:function(){return!0}},j={addNodes:function(a,b,c,d,f){var i=e.nodeIsParent(a,b);if(!a.data.keep.leaf||!b||i)if(h.isArray(d)||(d=[d]),a.data.simpleData.enable&&(d=e.transformTozTreeFormat(a,d)),b){var i=l(b,g.id.SWITCH,a),m=l(b,g.id.ICON,a),k=l(b,g.id.UL,a);if(!b.open)j.replaceSwitchClass(b,i,g.folder.CLOSE),j.replaceIcoClass(b,m,g.folder.CLOSE),b.open=!1,k.css({display:\"none\"});e.addNodesData(a,b,c,d);j.createNodes(a,b.level+1,d,b,c);f||j.expandCollapseParentNode(a,b,!0)}else e.addNodesData(a,\r\ne.getRoot(a),c,d),j.createNodes(a,0,d,null,c)},appendNodes:function(a,b,c,d,f,i,g){if(!c)return[];var k=[],h=d?d:e.getRoot(a),h=e.nodeChildren(a,h),o,l;if(!h||f>=h.length-c.length)f=-1;for(var s=0,n=c.length;s<n;s++){var p=c[s];i&&(o=(f===0||h.length==c.length)&&s==0,l=f<0&&s==c.length-1,e.initNode(a,b,p,d,o,l,g),e.addNodeCache(a,p));o=e.nodeIsParent(a,p);l=[];var I=e.nodeChildren(a,p);I&&I.length>0&&(l=j.appendNodes(a,b+1,I,p,-1,i,g&&p.open));g&&(j.makeDOMNodeMainBefore(k,a,p),j.makeDOMNodeLine(k,\r\na,p),e.getBeforeA(a,p,k),j.makeDOMNodeNameBefore(k,a,p),e.getInnerBeforeA(a,p,k),j.makeDOMNodeIcon(k,a,p),e.getInnerAfterA(a,p,k),j.makeDOMNodeNameAfter(k,a,p),e.getAfterA(a,p,k),o&&p.open&&j.makeUlHtml(a,p,k,l.join(\"\")),j.makeDOMNodeMainAfter(k,a,p),e.addCreatedNode(a,p))}return k},appendParentULDom:function(a,b){var c=[],d=l(b,a);!d.get(0)&&b.parentTId&&(j.appendParentULDom(a,b.getParentNode()),d=l(b,a));var f=l(b,g.id.UL,a);f.get(0)&&f.remove();f=e.nodeChildren(a,b);f=j.appendNodes(a,b.level+1,\r\nf,b,-1,!1,!0);j.makeUlHtml(a,b,c,f.join(\"\"));d.append(c.join(\"\"))},asyncNode:function(a,b,c,d){var f,i;f=e.nodeIsParent(a,b);if(b&&!f)return h.apply(d),!1;else if(b&&b.isAjaxing)return!1;else if(h.apply(a.callback.beforeAsync,[a.treeId,b],!0)==!1)return h.apply(d),!1;if(b)b.isAjaxing=!0,l(b,g.id.ICON,a).attr({style:\"\",\"class\":g.className.BUTTON+\" \"+g.className.ICO_LOADING});var m={},k=h.apply(a.async.autoParam,[a.treeId,b],a.async.autoParam);for(f=0,i=k.length;b&&f<i;f++){var q=k[f].split(\"=\"),o=\r\nq;q.length>1&&(o=q[1],q=q[0]);m[o]=b[q]}k=h.apply(a.async.otherParam,[a.treeId,b],a.async.otherParam);if(h.isArray(k))for(f=0,i=k.length;f<i;f+=2)m[k[f]]=k[f+1];else for(var n in k)m[n]=k[n];var s=e.getRoot(a)._ver;r.ajax({contentType:a.async.contentType,cache:!1,type:a.async.type,url:h.apply(a.async.url,[a.treeId,b],a.async.url),data:a.async.contentType.indexOf(\"application/json\")>-1?JSON.stringify(m):m,dataType:a.async.dataType,headers:a.async.headers,xhrFields:a.async.xhrFields,success:function(i){if(s==\r\ne.getRoot(a)._ver){var f=[];try{f=!i||i.length==0?[]:typeof i==\"string\"?eval(\"(\"+i+\")\"):i}catch(k){f=i}if(b)b.isAjaxing=null,b.zAsync=!0;j.setNodeLineIcos(a,b);f&&f!==\"\"?(f=h.apply(a.async.dataFilter,[a.treeId,b,f],f),j.addNodes(a,b,-1,f?h.clone(f):[],!!c)):j.addNodes(a,b,-1,[],!!c);a.treeObj.trigger(g.event.ASYNC_SUCCESS,[a.treeId,b,i]);h.apply(d)}},error:function(c,d,i){if(s==e.getRoot(a)._ver){if(b)b.isAjaxing=null;j.setNodeLineIcos(a,b);a.treeObj.trigger(g.event.ASYNC_ERROR,[a.treeId,b,c,d,i])}}});\r\nreturn!0},cancelPreSelectedNode:function(a,b,c){var d=e.getRoot(a).curSelectedList,f,i;for(f=d.length-1;f>=0;f--)if(i=d[f],b===i||!b&&(!c||c!==i))if(l(i,g.id.A,a).removeClass(g.node.CURSELECTED),b){e.removeSelectedNode(a,b);break}else d.splice(f,1),a.treeObj.trigger(g.event.UNSELECTED,[a.treeId,i])},createNodeCallback:function(a){if(a.callback.onNodeCreated||a.view.addDiyDom)for(var b=e.getRoot(a);b.createdNodes.length>0;){var c=b.createdNodes.shift();h.apply(a.view.addDiyDom,[a.treeId,c]);a.callback.onNodeCreated&&\r\na.treeObj.trigger(g.event.NODECREATED,[a.treeId,c])}},createNodes:function(a,b,c,d,f){if(c&&c.length!=0){var i=e.getRoot(a),m=!d||d.open||!!l(e.nodeChildren(a,d)[0],a).get(0);i.createdNodes=[];var b=j.appendNodes(a,b,c,d,f,!0,m),k,h;d?(d=l(d,g.id.UL,a),d.get(0)&&(k=d)):k=a.treeObj;k&&(f>=0&&(h=k.children()[f]),f>=0&&h?r(h).before(b.join(\"\")):k.append(b.join(\"\")));j.createNodeCallback(a)}},destroy:function(a){a&&(e.initCache(a),e.initRoot(a),n.unbindTree(a),n.unbindEvent(a),a.treeObj.empty(),delete t[a.treeId])},\r\nexpandCollapseNode:function(a,b,c,d,f){var i=e.getRoot(a),m;if(b){var k=e.nodeChildren(a,b),q=e.nodeIsParent(a,b);if(i.expandTriggerFlag)m=f,f=function(){m&&m();b.open?a.treeObj.trigger(g.event.EXPAND,[a.treeId,b]):a.treeObj.trigger(g.event.COLLAPSE,[a.treeId,b])},i.expandTriggerFlag=!1;if(!b.open&&q&&(!l(b,g.id.UL,a).get(0)||k&&k.length>0&&!l(k[0],a).get(0)))j.appendParentULDom(a,b),j.createNodeCallback(a);if(b.open==c)h.apply(f,[]);else{var c=l(b,g.id.UL,a),i=l(b,g.id.SWITCH,a),o=l(b,g.id.ICON,\r\na);q?(b.open=!b.open,b.iconOpen&&b.iconClose&&o.attr(\"style\",j.makeNodeIcoStyle(a,b)),b.open?(j.replaceSwitchClass(b,i,g.folder.OPEN),j.replaceIcoClass(b,o,g.folder.OPEN),d==!1||a.view.expandSpeed==\"\"?(c.show(),h.apply(f,[])):k&&k.length>0?c.slideDown(a.view.expandSpeed,f):(c.show(),h.apply(f,[]))):(j.replaceSwitchClass(b,i,g.folder.CLOSE),j.replaceIcoClass(b,o,g.folder.CLOSE),d==!1||a.view.expandSpeed==\"\"||!(k&&k.length>0)?(c.hide(),h.apply(f,[])):c.slideUp(a.view.expandSpeed,f))):h.apply(f,[])}}else h.apply(f,\r\n[])},expandCollapseParentNode:function(a,b,c,d,e){b&&(b.parentTId?(j.expandCollapseNode(a,b,c,d),b.parentTId&&j.expandCollapseParentNode(a,b.getParentNode(),c,d,e)):j.expandCollapseNode(a,b,c,d,e))},expandCollapseSonNode:function(a,b,c,d,f){var i=e.getRoot(a),i=b?e.nodeChildren(a,b):e.nodeChildren(a,i),g=b?!1:d,k=e.getRoot(a).expandTriggerFlag;e.getRoot(a).expandTriggerFlag=!1;if(i)for(var h=0,l=i.length;h<l;h++)i[h]&&j.expandCollapseSonNode(a,i[h],c,g);e.getRoot(a).expandTriggerFlag=k;j.expandCollapseNode(a,\r\nb,c,d,f)},isSelectedNode:function(a,b){if(!b)return!1;var c=e.getRoot(a).curSelectedList,d;for(d=c.length-1;d>=0;d--)if(b===c[d])return!0;return!1},makeDOMNodeIcon:function(a,b,c){var d=e.nodeName(b,c),d=b.view.nameIsHTML?d:d.replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\");a.push(\"<span id='\",c.tId,g.id.ICON,\"' title='' treeNode\",g.id.ICON,\" class='\",j.makeNodeIcoClass(b,c),\"' style='\",j.makeNodeIcoStyle(b,c),\"'></span><span id='\",c.tId,g.id.SPAN,\"' class='\",g.className.NAME,\"'>\",\r\nd,\"</span>\")},makeDOMNodeLine:function(a,b,c){a.push(\"<span id='\",c.tId,g.id.SWITCH,\"' title='' class='\",j.makeNodeLineClass(b,c),\"' treeNode\",g.id.SWITCH,\"></span>\")},makeDOMNodeMainAfter:function(a){a.push(\"</li>\")},makeDOMNodeMainBefore:function(a,b,c){a.push(\"<li id='\",c.tId,\"' class='\",g.className.LEVEL,c.level,\"' tabindex='0' hidefocus='true' treenode>\")},makeDOMNodeNameAfter:function(a){a.push(\"</a>\")},makeDOMNodeNameBefore:function(a,b,c){var d=e.nodeTitle(b,c),f=j.makeNodeUrl(b,c),i=j.makeNodeFontCss(b,\r\nc),m=[],k;for(k in i)m.push(k,\":\",i[k],\";\");a.push(\"<a id='\",c.tId,g.id.A,\"' class='\",g.className.LEVEL,c.level,\"' treeNode\",g.id.A,' onclick=\"',c.click||\"\",'\" ',f!=null&&f.length>0?\"href='\"+f+\"'\":\"\",\" target='\",j.makeNodeTarget(c),\"' style='\",m.join(\"\"),\"'\");h.apply(b.view.showTitle,[b.treeId,c],b.view.showTitle)&&d&&a.push(\"title='\",d.replace(/'/g,\"&#39;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\"),\"'\");a.push(\">\")},makeNodeFontCss:function(a,b){var c=h.apply(a.view.fontCss,[a.treeId,b],a.view.fontCss);\r\nreturn c&&typeof c!=\"function\"?c:{}},makeNodeIcoClass:function(a,b){var c=[\"ico\"];if(!b.isAjaxing){var d=e.nodeIsParent(a,b);c[0]=(b.iconSkin?b.iconSkin+\"_\":\"\")+c[0];d?c.push(b.open?g.folder.OPEN:g.folder.CLOSE):c.push(g.folder.DOCU)}return g.className.BUTTON+\" \"+c.join(\"_\")},makeNodeIcoStyle:function(a,b){var c=[];if(!b.isAjaxing){var d=e.nodeIsParent(a,b)&&b.iconOpen&&b.iconClose?b.open?b.iconOpen:b.iconClose:b[a.data.key.icon];d&&c.push(\"background:url(\",d,\") 0 0 no-repeat;\");(a.view.showIcon==\r\n!1||!h.apply(a.view.showIcon,[a.treeId,b],!0))&&c.push(\"width:0px;height:0px;\")}return c.join(\"\")},makeNodeLineClass:function(a,b){var c=[];a.view.showLine?b.level==0&&b.isFirstNode&&b.isLastNode?c.push(g.line.ROOT):b.level==0&&b.isFirstNode?c.push(g.line.ROOTS):b.isLastNode?c.push(g.line.BOTTOM):c.push(g.line.CENTER):c.push(g.line.NOLINE);e.nodeIsParent(a,b)?c.push(b.open?g.folder.OPEN:g.folder.CLOSE):c.push(g.folder.DOCU);return j.makeNodeLineClassEx(b)+c.join(\"_\")},makeNodeLineClassEx:function(a){return g.className.BUTTON+\r\n\" \"+g.className.LEVEL+a.level+\" \"+g.className.SWITCH+\" \"},makeNodeTarget:function(a){return a.target||\"_blank\"},makeNodeUrl:function(a,b){var c=a.data.key.url;return b[c]?b[c]:null},makeUlHtml:function(a,b,c,d){c.push(\"<ul id='\",b.tId,g.id.UL,\"' class='\",g.className.LEVEL,b.level,\" \",j.makeUlLineClass(a,b),\"' style='display:\",b.open?\"block\":\"none\",\"'>\");c.push(d);c.push(\"</ul>\")},makeUlLineClass:function(a,b){return a.view.showLine&&!b.isLastNode?g.line.LINE:\"\"},removeChildNodes:function(a,b){if(b){var c=\r\ne.nodeChildren(a,b);if(c){for(var d=0,f=c.length;d<f;d++)e.removeNodeCache(a,c[d]);e.removeSelectedNode(a);delete b[a.data.key.children];a.data.keep.parent?l(b,g.id.UL,a).empty():(e.nodeIsParent(a,b,!1),b.open=!1,c=l(b,g.id.SWITCH,a),d=l(b,g.id.ICON,a),j.replaceSwitchClass(b,c,g.folder.DOCU),j.replaceIcoClass(b,d,g.folder.DOCU),l(b,g.id.UL,a).remove())}}},scrollIntoView:function(a,b){if(b)if(typeof Element===\"undefined\"){var c=a.treeObj.get(0).getBoundingClientRect(),d=b.getBoundingClientRect();(d.top<\r\nc.top||d.bottom>c.bottom||d.right>c.right||d.left<c.left)&&b.scrollIntoView()}else{if(!Element.prototype.scrollIntoViewIfNeeded)Element.prototype.scrollIntoViewIfNeeded=function(a){function b(a,c){return{start:a,length:c,end:a+c}}function c(b,d){return!1===a||d.start<b.end&&b.start<d.end?Math.max(b.end-d.length,Math.min(d.start,b.start)):(b.start+b.end-d.length)/2}function d(a,b){return{x:a,y:b,translate:function(c,i){return d(a+c,b+i)}}}function e(a,b){for(;a;)b=b.translate(a.offsetLeft,a.offsetTop),\r\na=a.offsetParent;return b}for(var g=e(this,d(0,0)),j=d(this.offsetWidth,this.offsetHeight),h=this.parentNode,l;h instanceof HTMLElement;)l=e(h,d(h.clientLeft,h.clientTop)),h.scrollLeft=c(b(g.x-l.x,j.x),b(h.scrollLeft,h.clientWidth)),h.scrollTop=c(b(g.y-l.y,j.y),b(h.scrollTop,h.clientHeight)),g=g.translate(-h.scrollLeft,-h.scrollTop),h=h.parentNode};b.scrollIntoViewIfNeeded()}},setFirstNode:function(a,b){var c=e.nodeChildren(a,b);if(c.length>0)c[0].isFirstNode=!0},setLastNode:function(a,b){var c=e.nodeChildren(a,\r\nb);if(c.length>0)c[c.length-1].isLastNode=!0},removeNode:function(a,b){var c=e.getRoot(a),d=b.parentTId?b.getParentNode():c;b.isFirstNode=!1;b.isLastNode=!1;b.getPreNode=function(){return null};b.getNextNode=function(){return null};if(e.getNodeCache(a,b.tId)){l(b,a).remove();e.removeNodeCache(a,b);e.removeSelectedNode(a,b);for(var f=e.nodeChildren(a,d),i=0,h=f.length;i<h;i++)if(f[i].tId==b.tId){f.splice(i,1);break}j.setFirstNode(a,d);j.setLastNode(a,d);var k,i=f.length;if(!a.data.keep.parent&&i==\r\n0)e.nodeIsParent(a,d,!1),d.open=!1,delete d[a.data.key.children],i=l(d,g.id.UL,a),h=l(d,g.id.SWITCH,a),k=l(d,g.id.ICON,a),j.replaceSwitchClass(d,h,g.folder.DOCU),j.replaceIcoClass(d,k,g.folder.DOCU),i.css(\"display\",\"none\");else if(a.view.showLine&&i>0){var q=f[i-1],i=l(q,g.id.UL,a),h=l(q,g.id.SWITCH,a);k=l(q,g.id.ICON,a);d==c?f.length==1?j.replaceSwitchClass(q,h,g.line.ROOT):(c=l(f[0],g.id.SWITCH,a),j.replaceSwitchClass(f[0],c,g.line.ROOTS),j.replaceSwitchClass(q,h,g.line.BOTTOM)):j.replaceSwitchClass(q,\r\nh,g.line.BOTTOM);i.removeClass(g.line.LINE)}}},replaceIcoClass:function(a,b,c){if(b&&!a.isAjaxing&&(a=b.attr(\"class\"),a!=void 0)){a=a.split(\"_\");switch(c){case g.folder.OPEN:case g.folder.CLOSE:case g.folder.DOCU:a[a.length-1]=c}b.attr(\"class\",a.join(\"_\"))}},replaceSwitchClass:function(a,b,c){if(b){var d=b.attr(\"class\");if(d!=void 0){d=d.split(\"_\");switch(c){case g.line.ROOT:case g.line.ROOTS:case g.line.CENTER:case g.line.BOTTOM:case g.line.NOLINE:d[0]=j.makeNodeLineClassEx(a)+c;break;case g.folder.OPEN:case g.folder.CLOSE:case g.folder.DOCU:d[1]=\r\nc}b.attr(\"class\",d.join(\"_\"));c!==g.folder.DOCU?b.removeAttr(\"disabled\"):b.attr(\"disabled\",\"disabled\")}}},selectNode:function(a,b,c){c||j.cancelPreSelectedNode(a,null,b);l(b,g.id.A,a).addClass(g.node.CURSELECTED);e.addSelectedNode(a,b);a.treeObj.trigger(g.event.SELECTED,[a.treeId,b])},setNodeFontCss:function(a,b){var c=l(b,g.id.A,a),d=j.makeNodeFontCss(a,b);d&&c.css(d)},setNodeLineIcos:function(a,b){if(b){var c=l(b,g.id.SWITCH,a),d=l(b,g.id.UL,a),f=l(b,g.id.ICON,a),i=j.makeUlLineClass(a,b);i.length==\r\n0?d.removeClass(g.line.LINE):d.addClass(i);c.attr(\"class\",j.makeNodeLineClass(a,b));e.nodeIsParent(a,b)?c.removeAttr(\"disabled\"):c.attr(\"disabled\",\"disabled\");f.removeAttr(\"style\");f.attr(\"style\",j.makeNodeIcoStyle(a,b));f.attr(\"class\",j.makeNodeIcoClass(a,b))}},setNodeName:function(a,b){var c=e.nodeTitle(a,b),d=l(b,g.id.SPAN,a);d.empty();a.view.nameIsHTML?d.html(e.nodeName(a,b)):d.text(e.nodeName(a,b));h.apply(a.view.showTitle,[a.treeId,b],a.view.showTitle)&&l(b,g.id.A,a).attr(\"title\",!c?\"\":c)},\r\nsetNodeTarget:function(a,b){l(b,g.id.A,a).attr(\"target\",j.makeNodeTarget(b))},setNodeUrl:function(a,b){var c=l(b,g.id.A,a),d=j.makeNodeUrl(a,b);d==null||d.length==0?c.removeAttr(\"href\"):c.attr(\"href\",d)},switchNode:function(a,b){b.open||!h.canAsync(a,b)?j.expandCollapseNode(a,b,!b.open):a.async.enable?j.asyncNode(a,b)||j.expandCollapseNode(a,b,!b.open):b&&j.expandCollapseNode(a,b,!b.open)}};r.fn.zTree={consts:{className:{BUTTON:\"button\",LEVEL:\"level\",ICO_LOADING:\"ico_loading\",SWITCH:\"switch\",NAME:\"node_name\"},\r\nevent:{NODECREATED:\"ztree_nodeCreated\",CLICK:\"ztree_click\",EXPAND:\"ztree_expand\",COLLAPSE:\"ztree_collapse\",ASYNC_SUCCESS:\"ztree_async_success\",ASYNC_ERROR:\"ztree_async_error\",REMOVE:\"ztree_remove\",SELECTED:\"ztree_selected\",UNSELECTED:\"ztree_unselected\"},id:{A:\"_a\",ICON:\"_ico\",SPAN:\"_span\",SWITCH:\"_switch\",UL:\"_ul\"},line:{ROOT:\"root\",ROOTS:\"roots\",CENTER:\"center\",BOTTOM:\"bottom\",NOLINE:\"noline\",LINE:\"line\"},folder:{OPEN:\"open\",CLOSE:\"close\",DOCU:\"docu\"},node:{CURSELECTED:\"curSelectedNode\"}},_z:{tools:h,\r\nview:j,event:n,data:e},getZTreeObj:function(a){return(a=e.getZTreeTools(a))?a:null},destroy:function(a){if(a&&a.length>0)j.destroy(e.getSetting(a));else for(var b in t)j.destroy(t[b])},init:function(a,b,c){var d=h.clone(P);r.extend(!0,d,b);d.treeId=a.attr(\"id\");d.treeObj=a;d.treeObj.empty();t[d.treeId]=d;if(typeof document.body.style.maxHeight===\"undefined\")d.view.expandSpeed=\"\";e.initRoot(d);a=e.getRoot(d);c=c?h.clone(h.isArray(c)?c:[c]):[];d.data.simpleData.enable?e.nodeChildren(d,a,e.transformTozTreeFormat(d,\r\nc)):e.nodeChildren(d,a,c);e.initCache(d);n.unbindTree(d);n.bindTree(d);n.unbindEvent(d);n.bindEvent(d);var f={setting:d,addNodes:function(a,b,c,f){function g(){j.addNodes(d,a,b,n,f==!0)}a||(a=null);var l=e.nodeIsParent(d,a);if(a&&!l&&d.data.keep.leaf)return null;l=parseInt(b,10);isNaN(l)?(f=!!c,c=b,b=-1):b=l;if(!c)return null;var n=h.clone(h.isArray(c)?c:[c]);h.canAsync(d,a)?j.asyncNode(d,a,f,g):g();return n},cancelSelectedNode:function(a){j.cancelPreSelectedNode(d,a)},destroy:function(){j.destroy(d)},\r\nexpandAll:function(a){a=!!a;j.expandCollapseSonNode(d,null,a,!0);return a},expandNode:function(a,b,c,f,g){function n(){var b=l(a,d).get(0);b&&f!==!1&&j.scrollIntoView(d,b)}if(!a||!e.nodeIsParent(d,a))return null;b!==!0&&b!==!1&&(b=!a.open);if((g=!!g)&&b&&h.apply(d.callback.beforeExpand,[d.treeId,a],!0)==!1)return null;else if(g&&!b&&h.apply(d.callback.beforeCollapse,[d.treeId,a],!0)==!1)return null;b&&a.parentTId&&j.expandCollapseParentNode(d,a.getParentNode(),b,!1);if(b===a.open&&!c)return null;\r\ne.getRoot(d).expandTriggerFlag=g;!h.canAsync(d,a)&&c?j.expandCollapseSonNode(d,a,b,!0,n):(a.open=!b,j.switchNode(this.setting,a),n());return b},getNodes:function(){return e.getNodes(d)},getNodeByParam:function(a,b,c){return!a?null:e.getNodeByParam(d,c?e.nodeChildren(d,c):e.getNodes(d),a,b)},getNodeByTId:function(a){return e.getNodeCache(d,a)},getNodesByParam:function(a,b,c){return!a?null:e.getNodesByParam(d,c?e.nodeChildren(d,c):e.getNodes(d),a,b)},getNodesByParamFuzzy:function(a,b,c){return!a?null:\r\ne.getNodesByParamFuzzy(d,c?e.nodeChildren(d,c):e.getNodes(d),a,b)},getNodesByFilter:function(a,b,c,f){b=!!b;return!a||typeof a!=\"function\"?b?null:[]:e.getNodesByFilter(d,c?e.nodeChildren(d,c):e.getNodes(d),a,b,f)},getNodeIndex:function(a){if(!a)return null;for(var b=a.parentTId?a.getParentNode():e.getRoot(d),b=e.nodeChildren(d,b),c=0,f=b.length;c<f;c++)if(b[c]==a)return c;return-1},getSelectedNodes:function(){for(var a=[],b=e.getRoot(d).curSelectedList,c=0,f=b.length;c<f;c++)a.push(b[c]);return a},\r\nisSelectedNode:function(a){return e.isSelectedNode(d,a)},reAsyncChildNodesPromise:function(a,b,c){return new Promise(function(d,e){try{f.reAsyncChildNodes(a,b,c,function(){d(a)})}catch(g){e(g)}})},reAsyncChildNodes:function(a,b,c,f){if(this.setting.async.enable){var h=!a;h&&(a=e.getRoot(d));if(b==\"refresh\"){for(var b=e.nodeChildren(d,a),n=0,r=b?b.length:0;n<r;n++)e.removeNodeCache(d,b[n]);e.removeSelectedNode(d);e.nodeChildren(d,a,[]);h?this.setting.treeObj.empty():l(a,g.id.UL,d).empty()}j.asyncNode(this.setting,\r\nh?null:a,!!c,f)}},refresh:function(){this.setting.treeObj.empty();var a=e.getRoot(d),b=e.nodeChildren(d,a);e.initRoot(d);e.nodeChildren(d,a,b);e.initCache(d);j.createNodes(d,0,e.nodeChildren(d,a),null,-1)},removeChildNodes:function(a){if(!a)return null;var b=e.nodeChildren(d,a);j.removeChildNodes(d,a);return b?b:null},removeNode:function(a,b){a&&(b=!!b,b&&h.apply(d.callback.beforeRemove,[d.treeId,a],!0)==!1||(j.removeNode(d,a),b&&this.setting.treeObj.trigger(g.event.REMOVE,[d.treeId,a])))},selectNode:function(a,\r\nb,c){function e(){if(!c){var b=l(a,d).get(0);j.scrollIntoView(d,b)}}if(a&&h.uCanDo(d)){b=d.view.selectedMulti&&b;if(a.parentTId)j.expandCollapseParentNode(d,a.getParentNode(),!0,!1,e);else if(!c)try{l(a,d).focus().blur()}catch(f){}j.selectNode(d,a,b)}},transformTozTreeNodes:function(a){return e.transformTozTreeFormat(d,a)},transformToArray:function(a){return e.transformToArrayFormat(d,a)},updateNode:function(a){a&&l(a,d).get(0)&&h.uCanDo(d)&&(j.setNodeName(d,a),j.setNodeTarget(d,a),j.setNodeUrl(d,\r\na),j.setNodeLineIcos(d,a),j.setNodeFontCss(d,a))}};a.treeTools=f;e.setZTreeTools(d,f);(c=e.nodeChildren(d,a))&&c.length>0?j.createNodes(d,0,c,null,-1):d.async.enable&&d.async.url&&d.async.url!==\"\"&&j.asyncNode(d);return f}};var Q=r.fn.zTree,l=h.$,g=Q.consts})(jQuery);\r\n\r\n/*\r\n * JQuery zTree excheck v3.5.37\r\n * http://treejs.cn/\r\n *\r\n * Copyright (c) 2010 Hunter.z\r\n *\r\n * Licensed same as jquery - MIT License\r\n * http://www.opensource.org/licenses/mit-license.php\r\n *\r\n * email: hunter.z@263.net\r\n * Date: 2018-08-21\r\n */\r\n(function(n){var q,r,s,p={event:{CHECK:\"ztree_check\"},id:{CHECK:\"_check\"},checkbox:{STYLE:\"checkbox\",DEFAULT:\"chk\",DISABLED:\"disable\",FALSE:\"false\",TRUE:\"true\",FULL:\"full\",PART:\"part\",FOCUS:\"focus\"},radio:{STYLE:\"radio\",TYPE_ALL:\"all\",TYPE_LEVEL:\"level\"}},w={check:{enable:!1,autoCheckTrigger:!1,chkStyle:p.checkbox.STYLE,nocheckInherit:!1,chkDisabledInherit:!1,radioType:p.radio.TYPE_LEVEL,chkboxType:{Y:\"ps\",N:\"ps\"}},data:{key:{checked:\"checked\"}},callback:{beforeCheck:null,onCheck:null}};q=function(c,\r\na){if(a.chkDisabled===!0)return!1;var b=e.getSetting(c.data.treeId);if(i.apply(b.callback.beforeCheck,[b.treeId,a],!0)==!1)return!0;var d=e.nodeChecked(b,a);e.nodeChecked(b,a,!d);f.checkNodeRelation(b,a);d=m(a,h.id.CHECK,b);f.setChkClass(b,d,a);f.repairParentChkClassWithSelf(b,a);b.treeObj.trigger(h.event.CHECK,[c,b.treeId,a]);return!0};r=function(c,a){if(a.chkDisabled===!0)return!1;var b=e.getSetting(c.data.treeId),d=m(a,h.id.CHECK,b);a.check_Focus=!0;f.setChkClass(b,d,a);return!0};s=function(c,\r\na){if(a.chkDisabled===!0)return!1;var b=e.getSetting(c.data.treeId),d=m(a,h.id.CHECK,b);a.check_Focus=!1;f.setChkClass(b,d,a);return!0};n.extend(!0,n.fn.zTree.consts,p);n.extend(!0,n.fn.zTree._z,{tools:{},view:{checkNodeRelation:function(c,a){var b,d,j;d=h.radio;b=e.nodeChecked(c,a);if(c.check.chkStyle==d.STYLE){var g=e.getRadioCheckedList(c);if(b)if(c.check.radioType==d.TYPE_ALL){for(d=g.length-1;d>=0;d--){b=g[d];var k=e.nodeChecked(c,b);k&&b!=a&&(e.nodeChecked(c,b,!1),g.splice(d,1),f.setChkClass(c,\r\nm(b,h.id.CHECK,c),b),b.parentTId!=a.parentTId&&f.repairParentChkClassWithSelf(c,b))}g.push(a)}else{g=a.parentTId?a.getParentNode():e.getRoot(c);g=e.nodeChildren(c,g);for(d=0,j=g.length;d<j;d++)if(b=g[d],(k=e.nodeChecked(c,b))&&b!=a)e.nodeChecked(c,b,!1),f.setChkClass(c,m(b,h.id.CHECK,c),b)}else if(c.check.radioType==d.TYPE_ALL)for(d=0,j=g.length;d<j;d++)if(a==g[d]){g.splice(d,1);break}}else g=e.nodeChildren(c,a),b&&(!g||g.length==0||c.check.chkboxType.Y.indexOf(\"s\")>-1)&&f.setSonNodeCheckBox(c,a,\r\n!0),!b&&(!g||g.length==0||c.check.chkboxType.N.indexOf(\"s\")>-1)&&f.setSonNodeCheckBox(c,a,!1),b&&c.check.chkboxType.Y.indexOf(\"p\")>-1&&f.setParentNodeCheckBox(c,a,!0),!b&&c.check.chkboxType.N.indexOf(\"p\")>-1&&f.setParentNodeCheckBox(c,a,!1)},makeChkClass:function(c,a){var b=h.checkbox,d=h.radio,j=\"\",g=e.nodeChecked(c,a),j=a.chkDisabled===!0?b.DISABLED:a.halfCheck?b.PART:c.check.chkStyle==d.STYLE?a.check_Child_State<1?b.FULL:b.PART:g?a.check_Child_State===2||a.check_Child_State===-1?b.FULL:b.PART:\r\na.check_Child_State<1?b.FULL:b.PART,d=c.check.chkStyle+\"_\"+(g?b.TRUE:b.FALSE)+\"_\"+j,d=a.check_Focus&&a.chkDisabled!==!0?d+\"_\"+b.FOCUS:d;return h.className.BUTTON+\" \"+b.DEFAULT+\" \"+d},repairAllChk:function(c,a){if(c.check.enable&&c.check.chkStyle===h.checkbox.STYLE)for(var b=e.getRoot(c),b=e.nodeChildren(c,b),d=0,j=b.length;d<j;d++){var g=b[d];g.nocheck!==!0&&g.chkDisabled!==!0&&e.nodeChecked(c,g,a);f.setSonNodeCheckBox(c,g,a)}},repairChkClass:function(c,a){if(a&&(e.makeChkFlag(c,a),a.nocheck!==!0)){var b=\r\nm(a,h.id.CHECK,c);f.setChkClass(c,b,a)}},repairParentChkClass:function(c,a){if(a&&a.parentTId){var b=a.getParentNode();f.repairChkClass(c,b);f.repairParentChkClass(c,b)}},repairParentChkClassWithSelf:function(c,a){if(a){var b=e.nodeChildren(c,a);b&&b.length>0?f.repairParentChkClass(c,b[0]):f.repairParentChkClass(c,a)}},repairSonChkDisabled:function(c,a,b,d){if(a){if(a.chkDisabled!=b)a.chkDisabled=b;f.repairChkClass(c,a);if((a=e.nodeChildren(c,a))&&d)for(var j=0,g=a.length;j<g;j++)f.repairSonChkDisabled(c,\r\na[j],b,d)}},repairParentChkDisabled:function(c,a,b,d){if(a){if(a.chkDisabled!=b&&d)a.chkDisabled=b;f.repairChkClass(c,a);f.repairParentChkDisabled(c,a.getParentNode(),b,d)}},setChkClass:function(c,a,b){a&&(b.nocheck===!0?a.hide():a.show(),a.attr(\"class\",f.makeChkClass(c,b)))},setParentNodeCheckBox:function(c,a,b,d){var j=m(a,h.id.CHECK,c);d||(d=a);e.makeChkFlag(c,a);a.nocheck!==!0&&a.chkDisabled!==!0&&(e.nodeChecked(c,a,b),f.setChkClass(c,j,a),c.check.autoCheckTrigger&&a!=d&&c.treeObj.trigger(h.event.CHECK,\r\n[null,c.treeId,a]));if(a.parentTId){j=!0;if(!b)for(var g=e.nodeChildren(c,a.getParentNode()),k=0,o=g.length;k<o;k++){var l=g[k],i=e.nodeChecked(c,l);if(l.nocheck!==!0&&l.chkDisabled!==!0&&i||(l.nocheck===!0||l.chkDisabled===!0)&&l.check_Child_State>0){j=!1;break}}j&&f.setParentNodeCheckBox(c,a.getParentNode(),b,d)}},setSonNodeCheckBox:function(c,a,b,d){if(a){var j=m(a,h.id.CHECK,c);d||(d=a);var g=!1,k=e.nodeChildren(c,a);if(k)for(var o=0,l=k.length;o<l;o++){var i=k[o];f.setSonNodeCheckBox(c,i,b,d);\r\ni.chkDisabled===!0&&(g=!0)}if(a!=e.getRoot(c)&&a.chkDisabled!==!0){g&&a.nocheck!==!0&&e.makeChkFlag(c,a);if(a.nocheck!==!0&&a.chkDisabled!==!0){if(e.nodeChecked(c,a,b),!g)a.check_Child_State=k&&k.length>0?b?2:0:-1}else a.check_Child_State=-1;f.setChkClass(c,j,a);c.check.autoCheckTrigger&&a!=d&&a.nocheck!==!0&&a.chkDisabled!==!0&&c.treeObj.trigger(h.event.CHECK,[null,c.treeId,a])}}}},event:{},data:{getRadioCheckedList:function(c){for(var a=e.getRoot(c).radioCheckedList,b=0,d=a.length;b<d;b++)e.getNodeCache(c,\r\na[b].tId)||(a.splice(b,1),b--,d--);return a},getCheckStatus:function(c,a){if(!c.check.enable||a.nocheck||a.chkDisabled)return null;var b=e.nodeChecked(c,a);return{checked:b,half:a.halfCheck?a.halfCheck:c.check.chkStyle==h.radio.STYLE?a.check_Child_State===2:b?a.check_Child_State>-1&&a.check_Child_State<2:a.check_Child_State>0}},getTreeCheckedNodes:function(c,a,b,d){if(!a)return[];for(var j=b&&c.check.chkStyle==h.radio.STYLE&&c.check.radioType==h.radio.TYPE_ALL,d=!d?[]:d,g=0,f=a.length;g<f;g++){var i=\r\na[g],l=e.nodeChildren(c,i),m=e.nodeChecked(c,i);if(i.nocheck!==!0&&i.chkDisabled!==!0&&m==b&&(d.push(i),j))break;e.getTreeCheckedNodes(c,l,b,d);if(j&&d.length>0)break}return d},getTreeChangeCheckedNodes:function(c,a,b){if(!a)return[];for(var b=!b?[]:b,d=0,j=a.length;d<j;d++){var g=a[d],f=e.nodeChildren(c,g),h=e.nodeChecked(c,g);g.nocheck!==!0&&g.chkDisabled!==!0&&h!=g.checkedOld&&b.push(g);e.getTreeChangeCheckedNodes(c,f,b)}return b},makeChkFlag:function(c,a){if(a){var b=-1,d=e.nodeChildren(c,a);\r\nif(d)for(var j=0,g=d.length;j<g;j++){var f=d[j],i=e.nodeChecked(c,f),l=-1;if(c.check.chkStyle==h.radio.STYLE)if(l=f.nocheck===!0||f.chkDisabled===!0?f.check_Child_State:f.halfCheck===!0?2:i?2:f.check_Child_State>0?2:0,l==2){b=2;break}else l==0&&(b=0);else if(c.check.chkStyle==h.checkbox.STYLE)if(l=f.nocheck===!0||f.chkDisabled===!0?f.check_Child_State:f.halfCheck===!0?1:i?f.check_Child_State===-1||f.check_Child_State===2?2:1:f.check_Child_State>0?1:0,l===1){b=1;break}else if(l===2&&b>-1&&j>0&&l!==\r\nb){b=1;break}else if(b===2&&l>-1&&l<2){b=1;break}else l>-1&&(b=l)}a.check_Child_State=b}}}});var n=n.fn.zTree,i=n._z.tools,h=n.consts,f=n._z.view,e=n._z.data,m=i.$;e.nodeChecked=function(c,a,b){if(!a)return!1;c=c.data.key.checked;typeof b!==\"undefined\"&&(typeof b===\"string\"&&(b=i.eqs(b,\"true\")),a[c]=!!b);return a[c]};e.exSetting(w);e.addInitBind(function(c){c.treeObj.bind(h.event.CHECK,function(a,b,d,e){a.srcEvent=b;i.apply(c.callback.onCheck,[a,d,e])})});e.addInitUnBind(function(c){c.treeObj.unbind(h.event.CHECK)});\r\ne.addInitCache(function(){});e.addInitNode(function(c,a,b,d){if(b){a=e.nodeChecked(c,b);a=e.nodeChecked(c,b,a);b.checkedOld=a;if(typeof b.nocheck==\"string\")b.nocheck=i.eqs(b.nocheck,\"true\");b.nocheck=!!b.nocheck||c.check.nocheckInherit&&d&&!!d.nocheck;if(typeof b.chkDisabled==\"string\")b.chkDisabled=i.eqs(b.chkDisabled,\"true\");b.chkDisabled=!!b.chkDisabled||c.check.chkDisabledInherit&&d&&!!d.chkDisabled;if(typeof b.halfCheck==\"string\")b.halfCheck=i.eqs(b.halfCheck,\"true\");b.halfCheck=!!b.halfCheck;\r\nb.check_Child_State=-1;b.check_Focus=!1;b.getCheckStatus=function(){return e.getCheckStatus(c,b)};c.check.chkStyle==h.radio.STYLE&&c.check.radioType==h.radio.TYPE_ALL&&a&&e.getRoot(c).radioCheckedList.push(b)}});e.addInitProxy(function(c){var a=c.target,b=e.getSetting(c.data.treeId),d=\"\",f=null,g=\"\",k=null;if(i.eqs(c.type,\"mouseover\")){if(b.check.enable&&i.eqs(a.tagName,\"span\")&&a.getAttribute(\"treeNode\"+h.id.CHECK)!==null)d=i.getNodeMainDom(a).id,g=\"mouseoverCheck\"}else if(i.eqs(c.type,\"mouseout\")){if(b.check.enable&&\r\ni.eqs(a.tagName,\"span\")&&a.getAttribute(\"treeNode\"+h.id.CHECK)!==null)d=i.getNodeMainDom(a).id,g=\"mouseoutCheck\"}else if(i.eqs(c.type,\"click\")&&b.check.enable&&i.eqs(a.tagName,\"span\")&&a.getAttribute(\"treeNode\"+h.id.CHECK)!==null)d=i.getNodeMainDom(a).id,g=\"checkNode\";if(d.length>0)switch(f=e.getNodeCache(b,d),g){case \"checkNode\":k=q;break;case \"mouseoverCheck\":k=r;break;case \"mouseoutCheck\":k=s}return{stop:g===\"checkNode\",node:f,nodeEventType:g,nodeEventCallback:k,treeEventType:\"\",treeEventCallback:null}},\r\n!0);e.addInitRoot(function(c){e.getRoot(c).radioCheckedList=[]});e.addBeforeA(function(c,a,b){c.check.enable&&(e.makeChkFlag(c,a),b.push(\"<span ID='\",a.tId,h.id.CHECK,\"' class='\",f.makeChkClass(c,a),\"' treeNode\",h.id.CHECK,a.nocheck===!0?\" style='display:none;'\":\"\",\"></span>\"))});e.addZTreeTools(function(c,a){a.checkNode=function(a,b,g,k){var o=e.nodeChecked(c,a);if(a.chkDisabled!==!0&&(b!==!0&&b!==!1&&(b=!o),k=!!k,(o!==b||g)&&!(k&&i.apply(this.setting.callback.beforeCheck,[this.setting.treeId,a],\r\n!0)==!1)&&i.uCanDo(this.setting)&&this.setting.check.enable&&a.nocheck!==!0))e.nodeChecked(c,a,b),b=m(a,h.id.CHECK,this.setting),(g||this.setting.check.chkStyle===h.radio.STYLE)&&f.checkNodeRelation(this.setting,a),f.setChkClass(this.setting,b,a),f.repairParentChkClassWithSelf(this.setting,a),k&&this.setting.treeObj.trigger(h.event.CHECK,[null,this.setting.treeId,a])};a.checkAllNodes=function(a){f.repairAllChk(this.setting,!!a)};a.getCheckedNodes=function(a){var a=a!==!1,b=e.nodeChildren(c,e.getRoot(this.setting));\r\nreturn e.getTreeCheckedNodes(this.setting,b,a)};a.getChangeCheckedNodes=function(){var a=e.nodeChildren(c,e.getRoot(this.setting));return e.getTreeChangeCheckedNodes(this.setting,a)};a.setChkDisabled=function(a,b,c,e){b=!!b;c=!!c;f.repairSonChkDisabled(this.setting,a,b,!!e);f.repairParentChkDisabled(this.setting,a.getParentNode(),b,c)};var b=a.updateNode;a.updateNode=function(c,e){b&&b.apply(a,arguments);if(c&&this.setting.check.enable&&m(c,this.setting).get(0)&&i.uCanDo(this.setting)){var g=m(c,\r\nh.id.CHECK,this.setting);(e==!0||this.setting.check.chkStyle===h.radio.STYLE)&&f.checkNodeRelation(this.setting,c);f.setChkClass(this.setting,g,c);f.repairParentChkClassWithSelf(this.setting,c)}}});var t=f.createNodes;f.createNodes=function(c,a,b,d,e){t&&t.apply(f,arguments);b&&f.repairParentChkClassWithSelf(c,d)};var u=f.removeNode;f.removeNode=function(c,a){var b=a.getParentNode();u&&u.apply(f,arguments);a&&b&&(f.repairChkClass(c,b),f.repairParentChkClass(c,b))};var v=f.appendNodes;f.appendNodes=\r\nfunction(c,a,b,d,h,g,i){var m=\"\";v&&(m=v.apply(f,arguments));d&&e.makeChkFlag(c,d);return m}})(jQuery);\r\n\r\n/*\r\n * JQuery zTree exedit v3.5.37\r\n * http://treejs.cn/\r\n *\r\n * Copyright (c) 2010 Hunter.z\r\n *\r\n * Licensed same as jquery - MIT License\r\n * http://www.opensource.org/licenses/mit-license.php\r\n *\r\n * email: hunter.z@263.net\r\n * Date: 2018-08-21\r\n */\r\n(function(B){var I={event:{DRAG:\"ztree_drag\",DROP:\"ztree_drop\",RENAME:\"ztree_rename\",DRAGMOVE:\"ztree_dragmove\"},id:{EDIT:\"_edit\",INPUT:\"_input\",REMOVE:\"_remove\"},move:{TYPE_INNER:\"inner\",TYPE_PREV:\"prev\",TYPE_NEXT:\"next\"},node:{CURSELECTED_EDIT:\"curSelectedNode_Edit\",TMPTARGET_TREE:\"tmpTargetzTree\",TMPTARGET_NODE:\"tmpTargetNode\"}},v={onHoverOverNode:function(a,b){var c=i.getSetting(a.data.treeId),d=i.getRoot(c);if(d.curHoverNode!=b)v.onHoverOutNode(a);d.curHoverNode=b;e.addHoverDom(c,b)},onHoverOutNode:function(a){var a=\r\ni.getSetting(a.data.treeId),b=i.getRoot(a);if(b.curHoverNode&&!i.isSelectedNode(a,b.curHoverNode))e.removeTreeDom(a,b.curHoverNode),b.curHoverNode=null},onMousedownNode:function(a,b){function c(a){if(m.dragFlag==0&&Math.abs(N-a.clientX)<f.edit.drag.minMoveSize&&Math.abs(O-a.clientY)<f.edit.drag.minMoveSize)return!0;var b,c,g,j;L.css(\"cursor\",\"pointer\");if(m.dragFlag==0){if(k.apply(f.callback.beforeDrag,[f.treeId,n],!0)==!1)return l(a),!0;for(b=0,c=n.length;b<c;b++){if(b==0)m.dragNodeShowBefore=[];\r\ng=n[b];i.nodeIsParent(f,g)&&g.open?(e.expandCollapseNode(f,g,!g.open),m.dragNodeShowBefore[g.tId]=!0):m.dragNodeShowBefore[g.tId]=!1}m.dragFlag=1;y.showHoverDom=!1;k.showIfameMask(f,!0);j=!0;var p=-1;if(n.length>1){var o=n[0].parentTId?i.nodeChildren(f,n[0].getParentNode()):i.getNodes(f);g=[];for(b=0,c=o.length;b<c;b++)if(m.dragNodeShowBefore[o[b].tId]!==void 0&&(j&&p>-1&&p+1!==b&&(j=!1),g.push(o[b]),p=b),n.length===g.length){n=g;break}}j&&(H=n[0].getPreNode(),Q=n[n.length-1].getNextNode());C=q(\"<ul class='zTreeDragUL'></ul>\",\r\nf);for(b=0,c=n.length;b<c;b++)g=n[b],g.editNameFlag=!1,e.selectNode(f,g,b>0),e.removeTreeDom(f,g),b>f.edit.drag.maxShowNodeNum-1||(j=q(\"<li id='\"+g.tId+\"_tmp'></li>\",f),j.append(q(g,d.id.A,f).clone()),j.css(\"padding\",\"0\"),j.children(\"#\"+g.tId+d.id.A).removeClass(d.node.CURSELECTED),C.append(j),b==f.edit.drag.maxShowNodeNum-1&&(j=q(\"<li id='\"+g.tId+\"_moretmp'><a>  ...  </a></li>\",f),C.append(j)));C.attr(\"id\",n[0].tId+d.id.UL+\"_tmp\");C.addClass(f.treeObj.attr(\"class\"));C.appendTo(L);u=q(\"<span class='tmpzTreeMove_arrow'></span>\",\r\nf);u.attr(\"id\",\"zTreeMove_arrow_tmp\");u.appendTo(L);f.treeObj.trigger(d.event.DRAG,[a,f.treeId,n])}if(m.dragFlag==1){t&&u.attr(\"id\")==a.target.id&&w&&a.clientX+G.scrollLeft()+2>B(\"#\"+w+d.id.A,t).offset().left?(g=B(\"#\"+w+d.id.A,t),a.target=g.length>0?g.get(0):a.target):t&&(t.removeClass(d.node.TMPTARGET_TREE),w&&B(\"#\"+w+d.id.A,t).removeClass(d.node.TMPTARGET_NODE+\"_\"+d.move.TYPE_PREV).removeClass(d.node.TMPTARGET_NODE+\"_\"+I.move.TYPE_NEXT).removeClass(d.node.TMPTARGET_NODE+\"_\"+I.move.TYPE_INNER));\r\nw=t=null;J=!1;h=f;g=i.getSettings();for(var z in g)if(g[z].treeId&&g[z].edit.enable&&g[z].treeId!=f.treeId&&(a.target.id==g[z].treeId||B(a.target).parents(\"#\"+g[z].treeId).length>0))J=!0,h=g[z];z=G.scrollTop();j=G.scrollLeft();p=h.treeObj.offset();b=h.treeObj.get(0).scrollHeight;g=h.treeObj.get(0).scrollWidth;c=a.clientY+z-p.top;var E=h.treeObj.height()+p.top-a.clientY-z,r=a.clientX+j-p.left,s=h.treeObj.width()+p.left-a.clientX-j,p=c<f.edit.drag.borderMax&&c>f.edit.drag.borderMin,o=E<f.edit.drag.borderMax&&\r\nE>f.edit.drag.borderMin,F=r<f.edit.drag.borderMax&&r>f.edit.drag.borderMin,v=s<f.edit.drag.borderMax&&s>f.edit.drag.borderMin,E=c>f.edit.drag.borderMin&&E>f.edit.drag.borderMin&&r>f.edit.drag.borderMin&&s>f.edit.drag.borderMin,r=p&&h.treeObj.scrollTop()<=0,s=o&&h.treeObj.scrollTop()+h.treeObj.height()+10>=b,M=F&&h.treeObj.scrollLeft()<=0,P=v&&h.treeObj.scrollLeft()+h.treeObj.width()+10>=g;if(a.target&&k.isChildOrSelf(a.target,h.treeId)){for(var D=a.target;D&&D.tagName&&!k.eqs(D.tagName,\"li\")&&D.id!=\r\nh.treeId;)D=D.parentNode;var R=!0;for(b=0,c=n.length;b<c;b++)if(g=n[b],D.id===g.tId){R=!1;break}else if(q(g,f).find(\"#\"+D.id).length>0){R=!1;break}if(R&&a.target&&k.isChildOrSelf(a.target,D.id+d.id.A))t=B(D),w=D.id}g=n[0];if(E&&k.isChildOrSelf(a.target,h.treeId)){if(!t&&(a.target.id==h.treeId||r||s||M||P)&&(J||!J&&g.parentTId))t=h.treeObj;p?h.treeObj.scrollTop(h.treeObj.scrollTop()-10):o&&h.treeObj.scrollTop(h.treeObj.scrollTop()+10);F?h.treeObj.scrollLeft(h.treeObj.scrollLeft()-10):v&&h.treeObj.scrollLeft(h.treeObj.scrollLeft()+\r\n10);t&&t!=h.treeObj&&t.offset().left<h.treeObj.offset().left&&h.treeObj.scrollLeft(h.treeObj.scrollLeft()+t.offset().left-h.treeObj.offset().left)}C.css({top:a.clientY+z+3+\"px\",left:a.clientX+j+3+\"px\"});b=j=0;if(t&&t.attr(\"id\")!=h.treeId){var A=w==null?null:i.getNodeCache(h,w),p=(a.ctrlKey||a.metaKey)&&f.edit.drag.isMove&&f.edit.drag.isCopy||!f.edit.drag.isMove&&f.edit.drag.isCopy;c=!!(H&&w===H.tId);F=!!(Q&&w===Q.tId);o=g.parentTId&&g.parentTId==w;g=(p||!F)&&k.apply(h.edit.drag.prev,[h.treeId,n,A],\r\n!!h.edit.drag.prev);c=(p||!c)&&k.apply(h.edit.drag.next,[h.treeId,n,A],!!h.edit.drag.next);p=(p||!o)&&!(h.data.keep.leaf&&!i.nodeIsParent(f,A))&&k.apply(h.edit.drag.inner,[h.treeId,n,A],!!h.edit.drag.inner);o=function(){t=null;w=\"\";x=d.move.TYPE_INNER;u.css({display:\"none\"});if(window.zTreeMoveTimer)clearTimeout(window.zTreeMoveTimer),window.zTreeMoveTargetNodeTId=null};if(!g&&!c&&!p)o();else if(F=B(\"#\"+w+d.id.A,t),v=A.isLastNode?null:B(\"#\"+A.getNextNode().tId+d.id.A,t.next()),E=F.offset().top,r=\r\nF.offset().left,s=g?p?0.25:c?0.5:1:-1,M=c?p?0.75:g?0.5:0:-1,z=(a.clientY+z-E)/F.height(),(s==1||z<=s&&z>=-0.2)&&g?(j=1-u.width(),b=E-u.height()/2,x=d.move.TYPE_PREV):(M==0||z>=M&&z<=1.2)&&c?(j=1-u.width(),b=v==null||i.nodeIsParent(f,A)&&A.open?E+F.height()-u.height()/2:v.offset().top-u.height()/2,x=d.move.TYPE_NEXT):p?(j=5-u.width(),b=E,x=d.move.TYPE_INNER):o(),t){u.css({display:\"block\",top:b+\"px\",left:r+j+\"px\"});F.addClass(d.node.TMPTARGET_NODE+\"_\"+x);if(S!=w||T!=x)K=(new Date).getTime();if(A&&i.nodeIsParent(f,\r\nA)&&x==d.move.TYPE_INNER&&(z=!0,window.zTreeMoveTimer&&window.zTreeMoveTargetNodeTId!==A.tId?(clearTimeout(window.zTreeMoveTimer),window.zTreeMoveTargetNodeTId=null):window.zTreeMoveTimer&&window.zTreeMoveTargetNodeTId===A.tId&&(z=!1),z))window.zTreeMoveTimer=setTimeout(function(){x==d.move.TYPE_INNER&&A&&i.nodeIsParent(f,A)&&!A.open&&(new Date).getTime()-K>h.edit.drag.autoOpenTime&&k.apply(h.callback.beforeDragOpen,[h.treeId,A],!0)&&(e.switchNode(h,A),h.edit.drag.autoExpandTrigger&&h.treeObj.trigger(d.event.EXPAND,\r\n[h.treeId,A]))},h.edit.drag.autoOpenTime+50),window.zTreeMoveTargetNodeTId=A.tId}}else if(x=d.move.TYPE_INNER,t&&k.apply(h.edit.drag.inner,[h.treeId,n,null],!!h.edit.drag.inner)?t.addClass(d.node.TMPTARGET_TREE):t=null,u.css({display:\"none\"}),window.zTreeMoveTimer)clearTimeout(window.zTreeMoveTimer),window.zTreeMoveTargetNodeTId=null;S=w;T=x;f.treeObj.trigger(d.event.DRAGMOVE,[a,f.treeId,n])}return!1}function l(a){if(window.zTreeMoveTimer)clearTimeout(window.zTreeMoveTimer),window.zTreeMoveTargetNodeTId=\r\nnull;T=S=null;G.unbind(\"mousemove\",c);G.unbind(\"mouseup\",l);G.unbind(\"selectstart\",g);L.css(\"cursor\",\"\");t&&(t.removeClass(d.node.TMPTARGET_TREE),w&&B(\"#\"+w+d.id.A,t).removeClass(d.node.TMPTARGET_NODE+\"_\"+d.move.TYPE_PREV).removeClass(d.node.TMPTARGET_NODE+\"_\"+I.move.TYPE_NEXT).removeClass(d.node.TMPTARGET_NODE+\"_\"+I.move.TYPE_INNER));k.showIfameMask(f,!1);y.showHoverDom=!0;if(m.dragFlag!=0){m.dragFlag=0;var b,j,o;for(b=0,j=n.length;b<j;b++)o=n[b],i.nodeIsParent(f,o)&&m.dragNodeShowBefore[o.tId]&&\r\n!o.open&&(e.expandCollapseNode(f,o,!o.open),delete m.dragNodeShowBefore[o.tId]);C&&C.remove();u&&u.remove();var r=(a.ctrlKey||a.metaKey)&&f.edit.drag.isMove&&f.edit.drag.isCopy||!f.edit.drag.isMove&&f.edit.drag.isCopy;!r&&t&&w&&n[0].parentTId&&w==n[0].parentTId&&x==d.move.TYPE_INNER&&(t=null);if(t){var p=w==null?null:i.getNodeCache(h,w);if(k.apply(f.callback.beforeDrop,[h.treeId,n,p,x,r],!0)==!1)e.selectNodes(v,n);else{var s=r?k.clone(n):n;b=function(){if(J){if(!r)for(var b=0,c=n.length;b<c;b++)e.removeNode(f,\r\nn[b]);x==d.move.TYPE_INNER?e.addNodes(h,p,-1,s):e.addNodes(h,p.getParentNode(),x==d.move.TYPE_PREV?p.getIndex():p.getIndex()+1,s)}else if(r&&x==d.move.TYPE_INNER)e.addNodes(h,p,-1,s);else if(r)e.addNodes(h,p.getParentNode(),x==d.move.TYPE_PREV?p.getIndex():p.getIndex()+1,s);else if(x!=d.move.TYPE_NEXT)for(b=0,c=s.length;b<c;b++)e.moveNode(h,p,s[b],x,!1);else for(b=-1,c=s.length-1;b<c;c--)e.moveNode(h,p,s[c],x,!1);e.selectNodes(h,s);b=q(s[0],f).get(0);e.scrollIntoView(f,b);f.treeObj.trigger(d.event.DROP,\r\n[a,h.treeId,s,p,x,r])};x==d.move.TYPE_INNER&&k.canAsync(h,p)?e.asyncNode(h,p,!1,b):b()}}else e.selectNodes(v,n),f.treeObj.trigger(d.event.DROP,[a,f.treeId,n,null,null,null])}}function g(){return!1}var o,j,f=i.getSetting(a.data.treeId),m=i.getRoot(f),y=i.getRoots();if(a.button==2||!f.edit.enable||!f.edit.drag.isCopy&&!f.edit.drag.isMove)return!0;var r=a.target,s=i.getRoot(f).curSelectedList,n=[];if(i.isSelectedNode(f,b))for(o=0,j=s.length;o<j;o++){if(s[o].editNameFlag&&k.eqs(r.tagName,\"input\")&&r.getAttribute(\"treeNode\"+\r\nd.id.INPUT)!==null)return!0;n.push(s[o]);if(n[0].parentTId!==s[o].parentTId){n=[b];break}}else n=[b];e.editNodeBlur=!0;e.cancelCurEditNode(f);var G=B(f.treeObj.get(0).ownerDocument),L=B(f.treeObj.get(0).ownerDocument.body),C,u,t,J=!1,h=f,v=f,H,Q,S=null,T=null,w=null,x=d.move.TYPE_INNER,N=a.clientX,O=a.clientY,K=(new Date).getTime();k.uCanDo(f)&&G.bind(\"mousemove\",c);G.bind(\"mouseup\",l);G.bind(\"selectstart\",g);return!0}};B.extend(!0,B.fn.zTree.consts,I);B.extend(!0,B.fn.zTree._z,{tools:{getAbs:function(a){a=\r\na.getBoundingClientRect();return[a.left+(document.body.scrollLeft+document.documentElement.scrollLeft),a.top+(document.body.scrollTop+document.documentElement.scrollTop)]},inputFocus:function(a){a.get(0)&&(a.focus(),k.setCursorPosition(a.get(0),a.val().length))},inputSelect:function(a){a.get(0)&&(a.focus(),a.select())},setCursorPosition:function(a,b){if(a.setSelectionRange)a.focus(),a.setSelectionRange(b,b);else if(a.createTextRange){var c=a.createTextRange();c.collapse(!0);c.moveEnd(\"character\",\r\nb);c.moveStart(\"character\",b);c.select()}},showIfameMask:function(a,b){for(var c=i.getRoot(a);c.dragMaskList.length>0;)c.dragMaskList[0].remove(),c.dragMaskList.shift();if(b)for(var d=q(\"iframe\",a),g=0,e=d.length;g<e;g++){var j=d.get(g),f=k.getAbs(j),j=q(\"<div id='zTreeMask_\"+g+\"' class='zTreeMask' style='top:\"+f[1]+\"px; left:\"+f[0]+\"px; width:\"+j.offsetWidth+\"px; height:\"+j.offsetHeight+\"px;'></div>\",a);j.appendTo(q(\"body\",a));c.dragMaskList.push(j)}}},view:{addEditBtn:function(a,b){if(!(b.editNameFlag||\r\nq(b,d.id.EDIT,a).length>0)&&k.apply(a.edit.showRenameBtn,[a.treeId,b],a.edit.showRenameBtn)){var c=q(b,d.id.A,a),l=\"<span class='\"+d.className.BUTTON+\" edit' id='\"+b.tId+d.id.EDIT+\"' title='\"+k.apply(a.edit.renameTitle,[a.treeId,b],a.edit.renameTitle)+\"' treeNode\"+d.id.EDIT+\" style='display:none;'></span>\";c.append(l);q(b,d.id.EDIT,a).bind(\"click\",function(){if(!k.uCanDo(a)||k.apply(a.callback.beforeEditName,[a.treeId,b],!0)==!1)return!1;e.editNode(a,b);return!1}).show()}},addRemoveBtn:function(a,\r\nb){if(!(b.editNameFlag||q(b,d.id.REMOVE,a).length>0)&&k.apply(a.edit.showRemoveBtn,[a.treeId,b],a.edit.showRemoveBtn)){var c=q(b,d.id.A,a),l=\"<span class='\"+d.className.BUTTON+\" remove' id='\"+b.tId+d.id.REMOVE+\"' title='\"+k.apply(a.edit.removeTitle,[a.treeId,b],a.edit.removeTitle)+\"' treeNode\"+d.id.REMOVE+\" style='display:none;'></span>\";c.append(l);q(b,d.id.REMOVE,a).bind(\"click\",function(){if(!k.uCanDo(a)||k.apply(a.callback.beforeRemove,[a.treeId,b],!0)==!1)return!1;e.removeNode(a,b);a.treeObj.trigger(d.event.REMOVE,\r\n[a.treeId,b]);return!1}).bind(\"mousedown\",function(){return!0}).show()}},addHoverDom:function(a,b){if(i.getRoots().showHoverDom)b.isHover=!0,a.edit.enable&&(e.addEditBtn(a,b),e.addRemoveBtn(a,b)),k.apply(a.view.addHoverDom,[a.treeId,b])},cancelCurEditNode:function(a,b,c){var l=i.getRoot(a),g=l.curEditNode;if(g){var o=l.curEditInput,b=b?b:c?i.nodeName(a,g):o.val();if(k.apply(a.callback.beforeRename,[a.treeId,g,b,c],!0)===!1)return!1;i.nodeName(a,g,b);q(g,d.id.A,a).removeClass(d.node.CURSELECTED_EDIT);\r\no.unbind();e.setNodeName(a,g);g.editNameFlag=!1;l.curEditNode=null;l.curEditInput=null;e.selectNode(a,g,!1);a.treeObj.trigger(d.event.RENAME,[a.treeId,g,c])}return l.noSelection=!0},editNode:function(a,b){var c=i.getRoot(a);e.editNodeBlur=!1;if(i.isSelectedNode(a,b)&&c.curEditNode==b&&b.editNameFlag)setTimeout(function(){k.inputFocus(c.curEditInput)},0);else{b.editNameFlag=!0;e.removeTreeDom(a,b);e.cancelCurEditNode(a);e.selectNode(a,b,!1);q(b,d.id.SPAN,a).html(\"<input type=text class='rename' id='\"+\r\nb.tId+d.id.INPUT+\"' treeNode\"+d.id.INPUT+\" >\");var l=q(b,d.id.INPUT,a);l.attr(\"value\",i.nodeName(a,b));a.edit.editNameSelectAll?k.inputSelect(l):k.inputFocus(l);l.bind(\"blur\",function(){e.editNodeBlur||e.cancelCurEditNode(a)}).bind(\"keydown\",function(b){b.keyCode==\"13\"?(e.editNodeBlur=!0,e.cancelCurEditNode(a)):b.keyCode==\"27\"&&e.cancelCurEditNode(a,null,!0)}).bind(\"click\",function(){return!1}).bind(\"dblclick\",function(){return!1});q(b,d.id.A,a).addClass(d.node.CURSELECTED_EDIT);c.curEditInput=l;\r\nc.noSelection=!1;c.curEditNode=b}},moveNode:function(a,b,c,l,g,k){var j=i.getRoot(a);if(b!=c&&(!a.data.keep.leaf||!b||i.nodeIsParent(a,b)||l!=d.move.TYPE_INNER)){var f=c.parentTId?c.getParentNode():j,m=b===null||b==j;m&&b===null&&(b=j);if(m)l=d.move.TYPE_INNER;j=b.parentTId?b.getParentNode():j;if(l!=d.move.TYPE_PREV&&l!=d.move.TYPE_NEXT)l=d.move.TYPE_INNER;if(l==d.move.TYPE_INNER)if(m)c.parentTId=null;else{if(!i.nodeIsParent(a,b))i.nodeIsParent(a,b,!0),b.open=!!b.open,e.setNodeLineIcos(a,b);c.parentTId=\r\nb.tId}var y;m?y=m=a.treeObj:(!k&&l==d.move.TYPE_INNER?e.expandCollapseNode(a,b,!0,!1):k||e.expandCollapseNode(a,b.getParentNode(),!0,!1),m=q(b,a),y=q(b,d.id.UL,a),m.get(0)&&!y.get(0)&&(y=[],e.makeUlHtml(a,b,y,\"\"),m.append(y.join(\"\"))),y=q(b,d.id.UL,a));var r=q(c,a);r.get(0)?m.get(0)||r.remove():r=e.appendNodes(a,c.level,[c],null,-1,!1,!0).join(\"\");y.get(0)&&l==d.move.TYPE_INNER?y.append(r):m.get(0)&&l==d.move.TYPE_PREV?m.before(r):m.get(0)&&l==d.move.TYPE_NEXT&&m.after(r);var s;y=-1;var r=0,n=null,\r\nm=null,B=c.level,v=i.nodeChildren(a,f),C=i.nodeChildren(a,j),u=i.nodeChildren(a,b);if(c.isFirstNode){if(y=0,v.length>1)n=v[1],n.isFirstNode=!0}else if(c.isLastNode)y=v.length-1,n=v[y-1],n.isLastNode=!0;else for(j=0,s=v.length;j<s;j++)if(v[j].tId==c.tId){y=j;break}y>=0&&v.splice(y,1);if(l!=d.move.TYPE_INNER)for(j=0,s=C.length;j<s;j++)C[j].tId==b.tId&&(r=j);if(l==d.move.TYPE_INNER){u||(u=i.nodeChildren(a,b,[]));if(u.length>0)m=u[u.length-1],m.isLastNode=!1;u.splice(u.length,0,c);c.isLastNode=!0;c.isFirstNode=\r\nu.length==1}else b.isFirstNode&&l==d.move.TYPE_PREV?(C.splice(r,0,c),m=b,m.isFirstNode=!1,c.parentTId=b.parentTId,c.isFirstNode=!0,c.isLastNode=!1):b.isLastNode&&l==d.move.TYPE_NEXT?(C.splice(r+1,0,c),m=b,m.isLastNode=!1,c.parentTId=b.parentTId,c.isFirstNode=!1,c.isLastNode=!0):(l==d.move.TYPE_PREV?C.splice(r,0,c):C.splice(r+1,0,c),c.parentTId=b.parentTId,c.isFirstNode=!1,c.isLastNode=!1);i.fixPIdKeyValue(a,c);i.setSonNodeLevel(a,c.getParentNode(),c);e.setNodeLineIcos(a,c);e.repairNodeLevelClass(a,\r\nc,B);!a.data.keep.parent&&v.length<1?(i.nodeIsParent(a,f,!1),f.open=!1,b=q(f,d.id.UL,a),l=q(f,d.id.SWITCH,a),j=q(f,d.id.ICON,a),e.replaceSwitchClass(f,l,d.folder.DOCU),e.replaceIcoClass(f,j,d.folder.DOCU),b.css(\"display\",\"none\")):n&&e.setNodeLineIcos(a,n);m&&e.setNodeLineIcos(a,m);a.check&&a.check.enable&&e.repairChkClass&&(e.repairChkClass(a,f),e.repairParentChkClassWithSelf(a,f),f!=c.parent&&e.repairParentChkClassWithSelf(a,c));k||e.expandCollapseParentNode(a,c.getParentNode(),!0,g)}},removeEditBtn:function(a,\r\nb){q(b,d.id.EDIT,a).unbind().remove()},removeRemoveBtn:function(a,b){q(b,d.id.REMOVE,a).unbind().remove()},removeTreeDom:function(a,b){b.isHover=!1;e.removeEditBtn(a,b);e.removeRemoveBtn(a,b);k.apply(a.view.removeHoverDom,[a.treeId,b])},repairNodeLevelClass:function(a,b,c){if(c!==b.level){var e=q(b,a),g=q(b,d.id.A,a),a=q(b,d.id.UL,a),c=d.className.LEVEL+c,b=d.className.LEVEL+b.level;e.removeClass(c);e.addClass(b);g.removeClass(c);g.addClass(b);a.removeClass(c);a.addClass(b)}},selectNodes:function(a,\r\nb){for(var c=0,d=b.length;c<d;c++)e.selectNode(a,b[c],c>0)}},event:{},data:{setSonNodeLevel:function(a,b,c){if(c){var d=i.nodeChildren(a,c);c.level=b?b.level+1:0;if(d)for(var b=0,g=d.length;b<g;b++)d[b]&&i.setSonNodeLevel(a,c,d[b])}}}});var H=B.fn.zTree,k=H._z.tools,d=H.consts,e=H._z.view,i=H._z.data,q=k.$;i.exSetting({edit:{enable:!1,editNameSelectAll:!1,showRemoveBtn:!0,showRenameBtn:!0,removeTitle:\"remove\",renameTitle:\"rename\",drag:{autoExpandTrigger:!1,isCopy:!0,isMove:!0,prev:!0,next:!0,inner:!0,\r\nminMoveSize:5,borderMax:10,borderMin:-5,maxShowNodeNum:5,autoOpenTime:500}},view:{addHoverDom:null,removeHoverDom:null},callback:{beforeDrag:null,beforeDragOpen:null,beforeDrop:null,beforeEditName:null,beforeRename:null,onDrag:null,onDragMove:null,onDrop:null,onRename:null}});i.addInitBind(function(a){var b=a.treeObj,c=d.event;b.bind(c.RENAME,function(b,c,d,e){k.apply(a.callback.onRename,[b,c,d,e])});b.bind(c.DRAG,function(b,c,d,e){k.apply(a.callback.onDrag,[c,d,e])});b.bind(c.DRAGMOVE,function(b,\r\nc,d,e){k.apply(a.callback.onDragMove,[c,d,e])});b.bind(c.DROP,function(b,c,d,e,f,i,q){k.apply(a.callback.onDrop,[c,d,e,f,i,q])})});i.addInitUnBind(function(a){var a=a.treeObj,b=d.event;a.unbind(b.RENAME);a.unbind(b.DRAG);a.unbind(b.DRAGMOVE);a.unbind(b.DROP)});i.addInitCache(function(){});i.addInitNode(function(a,b,c){if(c)c.isHover=!1,c.editNameFlag=!1});i.addInitProxy(function(a){var b=a.target,c=i.getSetting(a.data.treeId),e=a.relatedTarget,g=\"\",o=null,j=\"\",f=null,m=null;if(k.eqs(a.type,\"mouseover\")){if(m=\r\nk.getMDom(c,b,[{tagName:\"a\",attrName:\"treeNode\"+d.id.A}]))g=k.getNodeMainDom(m).id,j=\"hoverOverNode\"}else if(k.eqs(a.type,\"mouseout\"))m=k.getMDom(c,e,[{tagName:\"a\",attrName:\"treeNode\"+d.id.A}]),m||(g=\"remove\",j=\"hoverOutNode\");else if(k.eqs(a.type,\"mousedown\")&&(m=k.getMDom(c,b,[{tagName:\"a\",attrName:\"treeNode\"+d.id.A}])))g=k.getNodeMainDom(m).id,j=\"mousedownNode\";if(g.length>0)switch(o=i.getNodeCache(c,g),j){case \"mousedownNode\":f=v.onMousedownNode;break;case \"hoverOverNode\":f=v.onHoverOverNode;\r\nbreak;case \"hoverOutNode\":f=v.onHoverOutNode}return{stop:!1,node:o,nodeEventType:j,nodeEventCallback:f,treeEventType:\"\",treeEventCallback:null}});i.addInitRoot(function(a){var a=i.getRoot(a),b=i.getRoots();a.curEditNode=null;a.curEditInput=null;a.curHoverNode=null;a.dragFlag=0;a.dragNodeShowBefore=[];a.dragMaskList=[];b.showHoverDom=!0});i.addZTreeTools(function(a,b){b.cancelEditName=function(a){i.getRoot(this.setting).curEditNode&&e.cancelCurEditNode(this.setting,a?a:null,!0)};b.copyNode=function(b,\r\nl,g,o){if(!l)return null;var j=i.nodeIsParent(a,b);if(b&&!j&&this.setting.data.keep.leaf&&g===d.move.TYPE_INNER)return null;var f=this,m=k.clone(l);if(!b)b=null,g=d.move.TYPE_INNER;g==d.move.TYPE_INNER?(l=function(){e.addNodes(f.setting,b,-1,[m],o)},k.canAsync(this.setting,b)?e.asyncNode(this.setting,b,o,l):l()):(e.addNodes(this.setting,b.parentNode,-1,[m],o),e.moveNode(this.setting,b,m,g,!1,o));return m};b.editName=function(a){a&&a.tId&&a===i.getNodeCache(this.setting,a.tId)&&(a.parentTId&&e.expandCollapseParentNode(this.setting,\r\na.getParentNode(),!0),e.editNode(this.setting,a))};b.moveNode=function(b,l,g,o){function j(){e.moveNode(m.setting,b,l,g,!1,o)}if(!l)return l;var f=i.nodeIsParent(a,b);if(b&&!f&&this.setting.data.keep.leaf&&g===d.move.TYPE_INNER)return null;else if(b&&(l.parentTId==b.tId&&g==d.move.TYPE_INNER||q(l,this.setting).find(\"#\"+b.tId).length>0))return null;else b||(b=null);var m=this;k.canAsync(this.setting,b)&&g===d.move.TYPE_INNER?e.asyncNode(this.setting,b,o,j):j();return l};b.setEditable=function(a){this.setting.edit.enable=\r\na;return this.refresh()}});var N=e.cancelPreSelectedNode;e.cancelPreSelectedNode=function(a,b){for(var c=i.getRoot(a).curSelectedList,d=0,g=c.length;d<g;d++)if(!b||b===c[d])if(e.removeTreeDom(a,c[d]),b)break;N&&N.apply(e,arguments)};var O=e.createNodes;e.createNodes=function(a,b,c,d,g){O&&O.apply(e,arguments);c&&e.repairParentChkClassWithSelf&&e.repairParentChkClassWithSelf(a,d)};var V=e.makeNodeUrl;e.makeNodeUrl=function(a,b){return a.edit.enable?null:V.apply(e,arguments)};var K=e.removeNode;e.removeNode=\r\nfunction(a,b){var c=i.getRoot(a);if(c.curEditNode===b)c.curEditNode=null;K&&K.apply(e,arguments)};var P=e.selectNode;e.selectNode=function(a,b,c){var d=i.getRoot(a);if(i.isSelectedNode(a,b)&&d.curEditNode==b&&b.editNameFlag)return!1;P&&P.apply(e,arguments);e.addHoverDom(a,b);return!0};var U=k.uCanDo;k.uCanDo=function(a,b){var c=i.getRoot(a);if(b&&(k.eqs(b.type,\"mouseover\")||k.eqs(b.type,\"mouseout\")||k.eqs(b.type,\"mousedown\")||k.eqs(b.type,\"mouseup\")))return!0;if(c.curEditNode)e.editNodeBlur=!1,c.curEditInput.focus();\r\nreturn!c.curEditNode&&(U?U.apply(e,arguments):!0)}})(jQuery);\r\n/*\r\n * JQuery zTree exHideNodes v3.5.37\r\n * http://treejs.cn/\r\n *\r\n * Copyright (c) 2010 Hunter.z\r\n *\r\n * Licensed same as jquery - MIT License\r\n * http://www.opensource.org/licenses/mit-license.php\r\n *\r\n * email: hunter.z@263.net\r\n * Date: 2018-08-21\r\n */\r\n(function(j){j.extend(!0,j.fn.zTree._z,{view:{clearOldFirstNode:function(c,a){for(var b=a.getNextNode();b;){if(b.isFirstNode){b.isFirstNode=!1;e.setNodeLineIcos(c,b);break}if(b.isLastNode)break;b=b.getNextNode()}},clearOldLastNode:function(c,a,b){for(a=a.getPreNode();a;){if(a.isLastNode){a.isLastNode=!1;b&&e.setNodeLineIcos(c,a);break}if(a.isFirstNode)break;a=a.getPreNode()}},makeDOMNodeMainBefore:function(c,a,b){a=d.isHidden(a,b);c.push(\"<li \",a?\"style='display:none;' \":\"\",\"id='\",b.tId,\"' class='\",\r\nl.className.LEVEL,b.level,\"' tabindex='0' hidefocus='true' treenode>\")},showNode:function(c,a){d.isHidden(c,a,!1);d.initShowForExCheck(c,a);k(a,c).show()},showNodes:function(c,a,b){if(a&&a.length!=0){var f={},g,i;for(g=0,i=a.length;g<i;g++){var h=a[g];if(!f[h.parentTId]){var u=h.getParentNode();f[h.parentTId]=u===null?d.getRoot(c):h.getParentNode()}e.showNode(c,h,b)}for(var j in f)a=d.nodeChildren(c,f[j]),e.setFirstNodeForShow(c,a),e.setLastNodeForShow(c,a)}},hideNode:function(c,a){d.isHidden(c,a,\r\n!0);a.isFirstNode=!1;a.isLastNode=!1;d.initHideForExCheck(c,a);e.cancelPreSelectedNode(c,a);k(a,c).hide()},hideNodes:function(c,a,b){if(a&&a.length!=0){var f={},g,i;for(g=0,i=a.length;g<i;g++){var h=a[g];if((h.isFirstNode||h.isLastNode)&&!f[h.parentTId]){var j=h.getParentNode();f[h.parentTId]=j===null?d.getRoot(c):h.getParentNode()}e.hideNode(c,h,b)}for(var k in f)a=d.nodeChildren(c,f[k]),e.setFirstNodeForHide(c,a),e.setLastNodeForHide(c,a)}},setFirstNode:function(c,a){var b=d.nodeChildren(c,a),f=\r\nd.isHidden(c,b[0],!1);b.length>0&&!f?b[0].isFirstNode=!0:b.length>0&&e.setFirstNodeForHide(c,b)},setLastNode:function(c,a){var b=d.nodeChildren(c,a),f=d.isHidden(c,b[0]);b.length>0&&!f?b[b.length-1].isLastNode=!0:b.length>0&&e.setLastNodeForHide(c,b)},setFirstNodeForHide:function(c,a){var b,f,g;for(f=0,g=a.length;f<g;f++){b=a[f];if(b.isFirstNode)break;if(!d.isHidden(c,b)&&!b.isFirstNode){b.isFirstNode=!0;e.setNodeLineIcos(c,b);break}else b=null}return b},setFirstNodeForShow:function(c,a){var b,f,\r\ng,i,h;for(f=0,g=a.length;f<g;f++){b=a[f];var j=d.isHidden(c,b);if(!i&&!j&&b.isFirstNode){i=b;break}else if(!i&&!j&&!b.isFirstNode)b.isFirstNode=!0,i=b,e.setNodeLineIcos(c,b);else if(i&&b.isFirstNode){b.isFirstNode=!1;h=b;e.setNodeLineIcos(c,b);break}}return{\"new\":i,old:h}},setLastNodeForHide:function(c,a){var b,f;for(f=a.length-1;f>=0;f--){b=a[f];if(b.isLastNode)break;if(!d.isHidden(c,b)&&!b.isLastNode){b.isLastNode=!0;e.setNodeLineIcos(c,b);break}else b=null}return b},setLastNodeForShow:function(c,\r\na){var b,f,g,i;for(f=a.length-1;f>=0;f--){b=a[f];var h=d.isHidden(c,b);if(!g&&!h&&b.isLastNode){g=b;break}else if(!g&&!h&&!b.isLastNode)b.isLastNode=!0,g=b,e.setNodeLineIcos(c,b);else if(g&&b.isLastNode){b.isLastNode=!1;i=b;e.setNodeLineIcos(c,b);break}}return{\"new\":g,old:i}}},data:{initHideForExCheck:function(c,a){if(d.isHidden(c,a)&&c.check&&c.check.enable){if(typeof a._nocheck==\"undefined\")a._nocheck=!!a.nocheck,a.nocheck=!0;a.check_Child_State=-1;e.repairParentChkClassWithSelf&&e.repairParentChkClassWithSelf(c,\r\na)}},initShowForExCheck:function(c,a){if(!d.isHidden(c,a)&&c.check&&c.check.enable){if(typeof a._nocheck!=\"undefined\")a.nocheck=a._nocheck,delete a._nocheck;if(e.setChkClass){var b=k(a,l.id.CHECK,c);e.setChkClass(c,b,a)}e.repairParentChkClassWithSelf&&e.repairParentChkClassWithSelf(c,a)}}}});var j=j.fn.zTree,m=j._z.tools,l=j.consts,e=j._z.view,d=j._z.data,k=m.$;d.isHidden=function(c,a,b){if(!a)return!1;c=c.data.key.isHidden;typeof b!==\"undefined\"&&(typeof b===\"string\"&&(b=m.eqs(checked,\"true\")),a[c]=\r\n!!b);return a[c]};d.exSetting({data:{key:{isHidden:\"isHidden\"}}});d.addInitNode(function(c,a,b){a=d.isHidden(c,b);d.isHidden(c,b,a);d.initHideForExCheck(c,b)});d.addBeforeA(function(){});d.addZTreeTools(function(c,a){a.showNodes=function(a,b){e.showNodes(c,a,b)};a.showNode=function(a,b){a&&e.showNodes(c,[a],b)};a.hideNodes=function(a,b){e.hideNodes(c,a,b)};a.hideNode=function(a,b){a&&e.hideNodes(c,[a],b)};var b=a.checkNode;if(b)a.checkNode=function(f,e,i,h){(!f||!d.isHidden(c,f))&&b.apply(a,arguments)}});\r\nvar n=d.initNode;d.initNode=function(c,a,b,f,g,i,h){var j=(f?f:d.getRoot(c))[c.data.key.children];d.tmpHideFirstNode=e.setFirstNodeForHide(c,j);d.tmpHideLastNode=e.setLastNodeForHide(c,j);h&&(e.setNodeLineIcos(c,d.tmpHideFirstNode),e.setNodeLineIcos(c,d.tmpHideLastNode));g=d.tmpHideFirstNode===b;i=d.tmpHideLastNode===b;n&&n.apply(d,arguments);h&&i&&e.clearOldLastNode(c,b,h)};var o=d.makeChkFlag;if(o)d.makeChkFlag=function(c,a){(!a||!d.isHidden(c,a))&&o.apply(d,arguments)};var p=d.getTreeCheckedNodes;\r\nif(p)d.getTreeCheckedNodes=function(c,a,b,f){if(a&&a.length>0){var e=a[0].getParentNode();if(e&&d.isHidden(c,e))return[]}return p.apply(d,arguments)};var q=d.getTreeChangeCheckedNodes;if(q)d.getTreeChangeCheckedNodes=function(c,a,b){if(a&&a.length>0){var e=a[0].getParentNode();if(e&&d.isHidden(c,e))return[]}return q.apply(d,arguments)};var r=e.expandCollapseSonNode;if(r)e.expandCollapseSonNode=function(c,a,b,f,g){(!a||!d.isHidden(c,a))&&r.apply(e,arguments)};var s=e.setSonNodeCheckBox;if(s)e.setSonNodeCheckBox=\r\nfunction(c,a,b,f){(!a||!d.isHidden(c,a))&&s.apply(e,arguments)};var t=e.repairParentChkClassWithSelf;if(t)e.repairParentChkClassWithSelf=function(c,a){(!a||!d.isHidden(c,a))&&t.apply(e,arguments)}})(jQuery);\r\n\r\n\r\n\r\n\r\n  TreeSelect.prototype.render = function (options) {\r\n    var elem = options.elem,\r\n      // 请求地址\r\n      data = options.data,\r\n      // 请求方式\r\n      type = options.type === undefined ? 'GET' : options.type,\r\n      // 节点点击回调\r\n      click = options.click,\r\n      // 渲染成功后的回调函数\r\n      success = options.success,\r\n      // 占位符（提示信息）\r\n      placeholder = options.placeholder === undefined ? '请选择' : options.placeholder,\r\n      // 是否开启搜索\r\n      search = options.search === undefined ? false : options.search,\r\n      process = options.process;\r\n      // 样式配置项\r\n      style = options.style,\r\n      // 唯一id\r\n      tmp = new Date().getTime(),\r\n      DATA = {},\r\n      selected = 'layui-form-selected',\r\n      TREE_OBJ = undefined,\r\n      TREE_INPUT_ID = 'treeSelect-input-' + tmp,\r\n      TREE_INPUT_CLASS = 'layui-treeselect',\r\n      TREE_SELECT_ID = 'layui-treeSelect-' + tmp,\r\n      TREE_SELECT_CLASS = 'layui-treeSelect',\r\n      TREE_SELECT_TITLE_ID = 'layui-select-title-' + tmp,\r\n      TREE_SELECT_TITLE_CLASS = 'layui-select-title',\r\n      TREE_SELECT_BODY_ID = 'layui-treeSelect-body-' + tmp,\r\n      TREE_SELECT_BODY_CLASS = 'layui-treeSelect-body',\r\n      TREE_SELECT_SEARCHED_CLASS = 'layui-treeSelect-search-ed';\r\n\r\n\r\n    var a = {\r\n      init: function () {\r\n        $.ajax({\r\n          url: data,\r\n          type: type,\r\n          dataType: 'json',\r\n          success: function (d) {\r\n          \tif(process){\r\n          \t\td = process(d);\r\n          \t}\r\n            DATA = d;\r\n            a.hideElem().input().toggleSelect().loadCss().preventEvent();\r\n            $.fn.zTree.init($('#' + TREE_SELECT_BODY_ID), a.setting(), d);\r\n            TREE_OBJ = $.fn.zTree.getZTreeObj(TREE_SELECT_BODY_ID);\r\n            if (search) {\r\n              a.searchParam();\r\n            }\r\n            a.configStyle();\r\n            if (success) {\r\n              var obj = {\r\n                treeId: TREE_SELECT_ID,\r\n                data: d\r\n              };\r\n              success(obj);\r\n            }\r\n          }\r\n        });\r\n        return a;\r\n      },\r\n      // 检查input是否有默认值\r\n      checkDefaultValue: function () {\r\n\r\n      },\r\n      setting: function () {\r\n        var setting = {\r\n          callback: {\r\n            onClick: a.onClick,\r\n            onExpand: a.onExpand,\r\n            onCollapse: a.onCollapse,\r\n            beforeExpand: a.ztreeCallBack.beforeExpand\r\n          }\r\n        };\r\n        return setting;\r\n      },\r\n      ztreeCallBack: {\r\n        beforeExpand: function () {\r\n          a.configStyle();\r\n        },\r\n      },\r\n      onCollapse: function () {\r\n        a.focusInput();\r\n      },\r\n      onExpand: function () {\r\n        a.configStyle();\r\n        a.focusInput();\r\n      },\r\n      focusInput: function () {\r\n        $('#' + TREE_INPUT_ID).focus();\r\n      },\r\n      onClick: function (event, treeId, treeNode) {\r\n        var name = treeNode.name,\r\n          id = treeNode.id,\r\n          $input = $('#' + TREE_SELECT_TITLE_ID + ' input');\r\n        $input.val(name);\r\n        $(elem).attr('value', id).val(id);\r\n        $('#' + TREE_SELECT_ID).removeClass(selected);\r\n\r\n        if (click) {\r\n          var obj = {\r\n            data: DATA,\r\n            current: treeNode,\r\n            treeId: TREE_SELECT_ID\r\n          };\r\n          click(obj);\r\n        }\r\n        return a;\r\n      },\r\n      hideElem: function () {\r\n        $(elem).hide();\r\n        return a;\r\n      },\r\n      input: function () {\r\n        var readonly = '';\r\n        if (!search) {\r\n          readonly = 'readonly';\r\n        }\r\n        var selectHtml = '<div class=\"' + TREE_SELECT_CLASS + ' layui-unselect layui-form-select\" id=\"' + TREE_SELECT_ID + '\">' +\r\n          '<div class=\"' + TREE_SELECT_TITLE_CLASS + '\" id=\"' + TREE_SELECT_TITLE_ID + '\">' +\r\n          ' <input type=\"text\" id=\"' + TREE_INPUT_ID + '\" placeholder=\"' + placeholder + '\" value=\"\" ' + readonly + ' class=\"layui-input layui-unselect\">' +\r\n          '<i class=\"layui-edge\"></i>' +\r\n          '</div>' +\r\n          '<div class=\"layui-anim layui-anim-upbit\" style=\"\">' +\r\n          '<div class=\"' + TREE_SELECT_BODY_CLASS + ' ztree\" id=\"' + TREE_SELECT_BODY_ID + '\"></div>' +\r\n          '</div>' +\r\n          '</div>';\r\n        $(elem).parent().append(selectHtml);\r\n        return a;\r\n      },\r\n      /**\r\n       * 展开/折叠下拉框\r\n       */\r\n      toggleSelect: function () {\r\n        var item = '#' + TREE_SELECT_TITLE_ID;\r\n        a.event('click', item, function (e) {\r\n          var $select = $('#' + TREE_SELECT_ID);\r\n          if ($select.hasClass(selected)) {\r\n            $select.removeClass(selected);\r\n            $('#' + TREE_INPUT_ID).blur();\r\n          } else {\r\n            // 隐藏其他picker\r\n            $('.layui-form-select').removeClass(selected);\r\n            // 显示当前picker\r\n            $select.addClass(selected);\r\n          }\r\n          e.stopPropagation();\r\n        });\r\n        $(document).click(function () {\r\n          var $select = $('#' + TREE_SELECT_ID);\r\n          if ($select.hasClass(selected)) {\r\n            $select.removeClass(selected);\r\n            $('#' + TREE_INPUT_ID).blur();\r\n          }\r\n        });\r\n        return a;\r\n      },\r\n      // 模糊查询\r\n      searchParam: function () {\r\n        if (!search) {\r\n          return;\r\n        }\r\n\r\n        var item = '#' + TREE_INPUT_ID;\r\n        a.fuzzySearch(item, null, true);\r\n      },\r\n      fuzzySearch: function (searchField, isHighLight, isExpand) {\r\n        var zTreeObj = TREE_OBJ;//get the ztree object by ztree id\r\n        if (!zTreeObj) {\r\n          alert(\"fail to get ztree object\");\r\n        }\r\n        var nameKey = zTreeObj.setting.data.key.name; //get the key of the node name\r\n        isHighLight = isHighLight === false ? false : true;//default true, only use false to disable highlight\r\n        isExpand = isExpand ? true : false; // not to expand in default\r\n        zTreeObj.setting.view.nameIsHTML = isHighLight; //allow use html in node name for highlight use\r\n\r\n        var metaChar = '[\\\\[\\\\]\\\\\\\\\\^\\\\$\\\\.\\\\|\\\\?\\\\*\\\\+\\\\(\\\\)]'; //js meta characters\r\n        var rexMeta = new RegExp(metaChar, 'gi');//regular expression to match meta characters\r\n\r\n        // keywords filter function \r\n        function ztreeFilter(zTreeObj, _keywords, callBackFunc) {\r\n          if (!_keywords) {\r\n            _keywords = ''; //default blank for _keywords \r\n          }\r\n\r\n          // function to find the matching node\r\n          function filterFunc(node) {\r\n            if (node && node.oldname && node.oldname.length > 0) {\r\n              node[nameKey] = node.oldname; //recover oldname of the node if exist\r\n            }\r\n            zTreeObj.updateNode(node); //update node to for modifications take effect\r\n            if (_keywords.length == 0) {\r\n              //return true to show all nodes if the keyword is blank\r\n              zTreeObj.showNode(node);\r\n              zTreeObj.expandNode(node, isExpand);\r\n              return true;\r\n            }\r\n            //transform node name and keywords to lowercase\r\n            if (node[nameKey] && node[nameKey].toLowerCase().indexOf(_keywords.toLowerCase()) != -1) {\r\n              zTreeObj.showNode(node);//show node with matching keywords\r\n              return true; //return true and show this node\r\n            }\r\n\r\n            zTreeObj.hideNode(node); // hide node that not matched\r\n            return false; //return false for node not matched\r\n          }\r\n\r\n          var nodesShow = zTreeObj.getNodesByFilter(filterFunc); //get all nodes that would be shown\r\n          processShowNodes(nodesShow, _keywords);//nodes should be reprocessed to show correctly\r\n        }\r\n\r\n        /**\r\n         * reprocess of nodes before showing\r\n         */\r\n        function processShowNodes(nodesShow, _keywords) {\r\n          if (nodesShow && nodesShow.length > 0) {\r\n            //process the ancient nodes if _keywords is not blank\r\n            if (_keywords.length > 0) {\r\n              $.each(nodesShow, function (n, obj) {\r\n                var pathOfOne = obj.getPath();//get all the ancient nodes including current node\r\n                if (pathOfOne && pathOfOne.length > 0) {\r\n                  //i < pathOfOne.length-1 process every node in path except self\r\n                  for (var i = 0; i < pathOfOne.length - 1; i++) {\r\n                    zTreeObj.showNode(pathOfOne[i]); //show node \r\n                    zTreeObj.expandNode(pathOfOne[i], true); //expand node\r\n                  }\r\n                }\r\n              });\r\n            } else { //show all nodes when _keywords is blank and expand the root nodes\r\n              var rootNodes = zTreeObj.getNodesByParam('level', '0');//get all root nodes\r\n              $.each(rootNodes, function (n, obj) {\r\n                zTreeObj.expandNode(obj, true); //expand all root nodes\r\n              });\r\n            }\r\n          }\r\n        }\r\n\r\n        //listen to change in input element\r\n        $(searchField).bind('input propertychange', function () {\r\n          var _keywords = $(this).val();\r\n          searchNodeLazy(_keywords); //call lazy load\r\n        });\r\n\r\n        var timeoutId = null;\r\n        // excute lazy load once after input change, the last pending task will be cancled  \r\n        function searchNodeLazy(_keywords) {\r\n          if (timeoutId) {\r\n            //clear pending task\r\n            clearTimeout(timeoutId);\r\n          }\r\n          timeoutId = setTimeout(function () {\r\n            ztreeFilter(zTreeObj, _keywords); //lazy load ztreeFilter function \r\n            $(searchField).focus();//focus input field again after filtering\r\n          }, 200);\r\n        }\r\n      },\r\n      checkNodes: function (nodes) {\r\n        for (var i = 0; i < nodes.length; i++) {\r\n          var o = nodes[i],\r\n            pid = o.parentTId,\r\n            tid = o.tId;\r\n          if (pid !== null) {\r\n            // 获取父节点\r\n            $('#' + pid).addClass(TREE_SELECT_SEARCHED_CLASS);\r\n            var pNode = TREE_OBJ.getNodesByParam(\"tId\", pid, null);\r\n            TREE_OBJ.expandNode(pNode[0], true, false, true);\r\n          }\r\n          $('#' + tid).addClass(TREE_SELECT_SEARCHED_CLASS);\r\n        }\r\n      },\r\n      // 阻止Layui的一些默认事件\r\n      preventEvent: function () {\r\n        var item = '#' + TREE_SELECT_ID + ' .layui-anim';\r\n        a.event('click', item, function (e) {\r\n          e.stopPropagation();\r\n        });\r\n        return a;\r\n      },\r\n      loadCss: function () {\r\n        var ztree = '.ztree *{padding:0;margin:0;font-size:12px;font-family:Verdana,Arial,Helvetica,AppleGothic,sans-serif}.ztree{margin:0;padding:5px;color:#333}.ztree li{padding:0;margin:0;list-style:none;line-height:14px;text-align:left;white-space:nowrap;outline:0}.ztree li ul{margin:0;padding:0 0 0 18px}.ztree li ul.line{background:url(./img/line_conn.gif) 0 0 repeat-y;}.ztree li a{padding:1px 3px 0 0;margin:0;cursor:pointer;height:17px;color:#333;background-color:transparent;text-decoration:none;vertical-align:top;display:inline-block}.ztree li a:hover{text-decoration:underline}.ztree li a.curSelectedNode{padding-top:0px;background-color:#FFE6B0;color:black;height:16px;border:1px #FFB951 solid;opacity:0.8;}.ztree li a.curSelectedNode_Edit{padding-top:0px;background-color:#FFE6B0;color:black;height:16px;border:1px #FFB951 solid;opacity:0.8;}.ztree li a.tmpTargetNode_inner{padding-top:0px;background-color:#316AC5;color:white;height:16px;border:1px #316AC5 solid;opacity:0.8;filter:alpha(opacity=80)}.ztree li a.tmpTargetNode_prev{}.ztree li a.tmpTargetNode_next{}.ztree li a input.rename{height:14px;width:80px;padding:0;margin:0;font-size:12px;border:1px #7EC4CC solid;*border:0px}.ztree li span{line-height:16px;margin-right:2px}.ztree li span.button{line-height:0;margin:0;width:16px;height:16px;display:inline-block;vertical-align:middle;border:0 none;cursor:pointer;outline:none;background-color:transparent;background-repeat:no-repeat;background-attachment:scroll;background-image:url(\"./img/zTreeStandard.png\");*background-image:url(\"./img/zTreeStandard.gif\")}.ztree li span.button.chk{width:13px;height:13px;margin:0 3px 0 0;cursor:auto}.ztree li span.button.chk.checkbox_false_full{background-position:0 0}.ztree li span.button.chk.checkbox_false_full_focus{background-position:0 -14px}.ztree li span.button.chk.checkbox_false_part{background-position:0 -28px}.ztree li span.button.chk.checkbox_false_part_focus{background-position:0 -42px}.ztree li span.button.chk.checkbox_false_disable{background-position:0 -56px}.ztree li span.button.chk.checkbox_true_full{background-position:-14px 0}.ztree li span.button.chk.checkbox_true_full_focus{background-position:-14px -14px}.ztree li span.button.chk.checkbox_true_part{background-position:-14px -28px}.ztree li span.button.chk.checkbox_true_part_focus{background-position:-14px -42px}.ztree li span.button.chk.checkbox_true_disable{background-position:-14px -56px}.ztree li span.button.chk.radio_false_full{background-position:-28px 0}.ztree li span.button.chk.radio_false_full_focus{background-position:-28px -14px}.ztree li span.button.chk.radio_false_part{background-position:-28px -28px}.ztree li span.button.chk.radio_false_part_focus{background-position:-28px -42px}.ztree li span.button.chk.radio_false_disable{background-position:-28px -56px}.ztree li span.button.chk.radio_true_full{background-position:-42px 0}.ztree li span.button.chk.radio_true_full_focus{background-position:-42px -14px}.ztree li span.button.chk.radio_true_part{background-position:-42px -28px}.ztree li span.button.chk.radio_true_part_focus{background-position:-42px -42px}.ztree li span.button.chk.radio_true_disable{background-position:-42px -56px}.ztree li span.button.switch{width:18px;height:18px}.ztree li span.button.root_open{background-position:-92px -54px}.ztree li span.button.root_close{background-position:-74px -54px}.ztree li span.button.roots_open{background-position:-92px 0}.ztree li span.button.roots_close{background-position:-74px 0}.ztree li span.button.center_open{background-position:-92px -18px}.ztree li span.button.center_close{background-position:-74px -18px}.ztree li span.button.bottom_open{background-position:-92px -36px}.ztree li span.button.bottom_close{background-position:-74px -36px}.ztree li span.button.noline_open{background-position:-92px -72px}.ztree li span.button.noline_close{background-position:-74px -72px}.ztree li span.button.root_docu{background:none;}.ztree li span.button.roots_docu{background-position:-56px 0}.ztree li span.button.center_docu{background-position:-56px -18px}.ztree li span.button.bottom_docu{background-position:-56px -36px}.ztree li span.button.noline_docu{background:none;}.ztree li span.button.ico_open{display:none!important;margin-right:2px;background-position:-110px -16px;vertical-align:top;*vertical-align:middle}.ztree li span.button.ico_close{display:none!important;margin-right:2px;background-position:-110px 0;vertical-align:top;*vertical-align:middle}.ztree li span.button.ico_docu{margin-right:2px;background-position:-110px -32px;vertical-align:top;*vertical-align:middle}.ztree li span.button.edit{margin-right:2px;background-position:-110px -48px;vertical-align:top;*vertical-align:middle}.ztree li span.button.remove{margin-right:2px;background-position:-110px -64px;vertical-align:top;*vertical-align:middle}.ztree li span.button.ico_loading{margin-right:2px;background:url(./img/loading.gif) no-repeat scroll 0 0 transparent;vertical-align:top;*vertical-align:middle}ul.tmpTargetzTree{background-color:#FFE6B0;opacity:0.8;filter:alpha(opacity=80)}span.tmpzTreeMove_arrow{width:16px;height:16px;display:inline-block;padding:0;margin:2px 0 0 1px;border:0 none;position:absolute;background-color:transparent;background-repeat:no-repeat;background-attachment:scroll;background-position:-110px -80px;background-image:url(\"./img/zTreeStandard.png\");*background-image:url(\"./img/zTreeStandard.gif\")}ul.ztree.zTreeDragUL{margin:0;padding:0;position:absolute;width:auto;height:auto;overflow:hidden;background-color:#cfcfcf;border:1px #00B83F dotted;opacity:0.8;filter:alpha(opacity=80)}.zTreeMask{z-index:10000;background-color:#cfcfcf;opacity:0.0;filter:alpha(opacity=0);position:absolute}',\r\n          ztree_ex = '.layui-treeSelect .ztree li span.button{font-family:layui-icon!important;font-size:16px;font-style:normal;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background:none;line-height:inherit;}.layui-treeSelect .ztree li span.button.ico_open{display:none;}.layui-treeSelect .ztree li span.button.ico_close{display:none;}.layui-treeSelect .ztree li span.button.ico_docu:before{content:\"\\\\e621\";}.layui-treeSelect .ztree li span.button.bottom_close:before,.layui-treeSelect .ztree li span.button.center_close:before,.layui-treeSelect .ztree li span.button.roots_close:before,.layui-treeSelect .ztree li span.button.root_close:before{content:\"\\\\e623\";}.layui-treeSelect .ztree li span.button.bottom_open:before,.layui-treeSelect .ztree li span.button.roots_open:before,.layui-treeSelect .ztree li span.button.center_open:before,.layui-treeSelect .ztree li span.button.root_open:before{content:\"\\\\e625\";}.layui-treeSelect .ztree li a:hover{text-decoration:none;}.layui-treeSelect .ztree *{font-size:14px;}.layui-treeSelect .ztree li{line-height:inherit;padding:2px 0;}.layui-treeSelect .ztree li span.button.switch{position:relative;top:-1px;}.layui-treeSelect .ztree li a,.ztree li span{line-height:18px;height:inherit;}.layui-treeSelect .ztree li a.curSelectedNode{color:#5FB878;background:none;border:none;height:inherit;padding-top:1px;}.layui-treeSelect .layui-anim::-webkit-scrollbar{width:6px;height:6px;background-color:#F5F5F5;}.layui-treeSelect .layui-anim::-webkit-scrollbar-track{box-shadow:inset 0 0 6px rgba(107,98,98,0.3);border-radius:10px;background-color:#F5F5F5;}.layui-treeSelect .layui-anim::-webkit-scrollbar-thumb{border-radius:10px;box-shadow:inset 0 0 6px rgba(107,98,98,0.3);background-color:#555;}.layui-treeSelect.layui-form-select .layui-anim{display:none;position:absolute;left:0;top:42px;padding:5px 0;z-index:9999;min-width:100%;border:1px solid #d2d2d2;max-height:185px;overflow-y:auto;background-color:#fff;border-radius:2px;box-shadow:0 2px 4px rgba(0,0,0,.12);box-sizing:border-box;}.layui-treeSelect.layui-form-selected .layui-anim{display:block;}.layui-treeSelect .ztree li ul.line{background:none;position:relative;}.layui-treeSelect .ztree li ul.line:before{content:\"\";height:100%;border-left:1px dotted #ece;position:absolute;left:8px;}.layui-treeSelect .ztree li .center_docu:before,.ztree li .bottom_docu::before{content:\"\";height:100%;border-left:1px dotted #ece;position:absolute;left:8px;}.layui-treeSelect .ztree li .center_docu::after,.ztree li .bottom_docu::after{content:\"\";position:absolute;left:8px;top:8px;width:8px;border-top:1px dotted #ece;}.layui-treeSelect .ztree li span.button.ico_open{display:inline-block;position:relative;top:1px;}.layui-treeSelect .ztree li span.button.ico_close{display:inline-block;position:relative;top:1px;}.layui-treeSelect .ztree li span.button.ico_open:before{content:\"\\\\e643\";}.layui-treeSelect .ztree li span.button.ico_close:before{content:\"\\\\e63f\";}';\r\n        $head = $('head'),\r\n          ztreeStyle = $head.find('style[ztree]');\r\n\r\n        if (ztreeStyle.length === 0) {\r\n          $head.append($('<style ztree>').append(ztree).append(ztree_ex))\r\n        }\r\n        return a;\r\n      },\r\n      configStyle: function () {\r\n        if (style == undefined || style.line == undefined || !style.line.enable) {\r\n          $('#' + TREE_SELECT_ID).find('li .center_docu,li .bottom_docu').hide();\r\n          //.layui-treeSelect .ztree li .center_docu:before, .ztree li .bottom_docu::before\r\n        }\r\n\r\n        if (style == undefined || style.folder == undefined || !style.folder.enable) {\r\n          $('#' + TREE_SELECT_ID).find('li span.button.ico_open').hide();\r\n          $('#' + TREE_SELECT_ID).find('li span.button.ico_close').hide();\r\n        }\r\n      },\r\n      event: function (evt, el, fn) {\r\n        $('body').on(evt, el, fn);\r\n      }\r\n    };\r\n    a.init();\r\n    return new TreeSelect();\r\n  };\r\n\r\n  /**\r\n   * 重新加载trerSelect\r\n   * @param filter\r\n   */\r\n  TreeSelect.prototype.refresh = function (filter) {\r\n      var treeObj = obj.treeObj(filter);\r\n      treeObj.reAsyncChildNodes(null, \"refresh\");\r\n  };\r\n\r\n  /**\r\n   * 选中节点，因为tree是异步加载，所以必须在success回调中调用checkNode函数，否则无法获取生成的DOM元素\r\n   * @param filter lay-filter属性\r\n   * @param id 选中的id\r\n   */\r\n  TreeSelect.prototype.checkNode = function(filter, id){\r\n    var o = obj.filter(filter),\r\n        treeInput = o.find('.layui-select-title input'),\r\n        treeObj = obj.treeObj(filter),\r\n        node = treeObj.getNodeByParam(\"id\", id, null),\r\n        name = node.name;\r\n    treeInput.val(name);\r\n    o.find('a[treenode_a]').removeClass('curSelectedNode');\r\n    obj.get(filter).val(id).attr('value', id);\r\n    treeObj.selectNode(node);\r\n  };\r\n\r\n  /**\r\n   * 撤销选中的节点\r\n   * @param filter lay-filter属性\r\n   * @param fn 回调函数\r\n   */\r\n  TreeSelect.prototype.revokeNode = function(filter, fn){\r\n    var o = obj.filter(filter);\r\n    o.find('a[treenode_a]').removeClass('curSelectedNode');\r\n    o.find('.layui-select-title input.layui-input').val('');\r\n    obj.get(filter).attr('value', '').val('');\r\n    obj.treeObj(filter).expandAll(false);\r\n    if (fn){\r\n      fn({\r\n        treeId: o.attr('id')\r\n      });\r\n    }\r\n  }\r\n\r\n  /**\r\n   * 销毁组件\r\n   */\r\n  TreeSelect.prototype.destroy = function(filter) {\r\n    var o = obj.filter(filter);\r\n    o.remove();\r\n    obj.get(filter).show();\r\n  }\r\n\r\n  /**\r\n   * 获取zTree对象，可调用所有zTree函数\r\n   * @param filter\r\n   */\r\n  TreeSelect.prototype.zTree = function (filter) {\r\n    return obj.treeObj(filter);\r\n  };\r\n\r\n  var obj = {\r\n    get: function(filter){\r\n      if (!filter) {\r\n        layui.hint().error('filter 不能为空');\r\n      }\r\n      return $('*[lay-filter='+ filter +']');\r\n    },\r\n    filter: function(filter){\r\n      var tf = obj.get(filter),\r\n          o = tf.next();\r\n      return o;\r\n    },\r\n    treeObj: function (filter) {\r\n      var o = obj.filter(filter),\r\n          treeId = o.find('.layui-treeSelect-body').attr('id'),\r\n          tree = $.fn.zTree.getZTreeObj(treeId);\r\n      return tree;\r\n    }\r\n  };\r\n\r\n  //输出接口\r\n  var mod = new TreeSelect();\r\n  exports(_MOD, mod);\r\n});    "
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/layui/extends/formSelects-v4.css",
    "content": "/* formSelects多选css */\r\nselect[xm-select]{display: none !important;}\r\n.xm-select-parent * {margin: 0;padding: 0;font-family: \"Helvetica Neue\", Helvetica, \"PingFang SC\", 微软雅黑, Tahoma, Arial, sans-serif; box-sizing: initial;}\r\n.xm-select-parent {text-align: left;}\r\n.xm-select-parent select {display: none;}\r\n.xm-select-parent .xm-select-title {position: relative;min-height: 36px;}\r\n.xm-select-parent .xm-input {cursor: pointer;border-radius: 2px;border-width: 1px;border-style: solid;border-color: #E6E6E6;display: block;width: 100%;box-sizing: border-box;background-color: #FFF;height: 36px;line-height: 1.3;padding-left: 10px;outline: 0}\r\n.xm-select-parent .xm-select-sj {display: inline-block;width: 0;height: 0;border-style: dashed;border-color: transparent;overflow: hidden;position: absolute;right: 10px;top: 50%;margin-top: -3px;cursor: pointer;border-width: 6px;border-top-color: #C2C2C2;border-top-style: solid;transition: all .3s;-webkit-transition: all .3s}\r\n.xm-select-parent .xm-form-selected .xm-select-sj {margin-top: -9px;transform: rotate(180deg)}\r\n.xm-select-parent .xm-form-select dl {display: none;position: absolute;left: 0;top: 42px;padding: 5px 0;z-index: 999;min-width: 100%;border: 1px solid #d2d2d2;max-height: 300px;overflow-y: auto;background-color: #fff;border-radius: 2px;box-shadow: 0 2px 4px rgba(0, 0, 0, .12);box-sizing: border-box;animation-fill-mode: both;-webkit-animation-name: layui-upbit;animation-name: layui-upbit;-webkit-animation-duration: .3s;animation-duration: .3s;-webkit-animation-fill-mode: both;animation-fill-mode: both}\r\n@-webkit-keyframes layui-upbit {\r\n\tfrom {-webkit-transform: translate3d(0, 30px, 0);opacity: .3}\r\n\tto {-webkit-transform: translate3d(0, 0, 0);opacity: 1}\r\n}\r\n@keyframes layui-upbit {\r\n\tfrom {transform: translate3d(0, 30px, 0);opacity: .3}\r\n\tto {transform: translate3d(0, 0, 0);opacity: 1}\r\n}\r\n.xm-select-parent .xm-form-selected dl {display: block}\r\n.xm-select-parent .xm-form-select dl dd,.xm-select-parent .xm-form-select dl dt {padding: 0 10px;line-height: 36px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis}\r\n.xm-select-parent .xm-form-select dl dd {cursor: pointer;height: 36px;}\r\n.xm-select-parent .xm-form-select dl dd:hover {background-color: #f2f2f2}\r\n.xm-select-parent .xm-form-select dl dt {font-size: 12px;color: #999}\r\n.layui-select-disabled .xm-dis-disabled {border-color: #eee!important}\r\n.xm-select-parent .xm-form-select dl .xm-select-tips {padding-left: 10px!important;color: #999;font-size: 14px}\r\n.xm-unselect {-moz-user-select: none;-webkit-user-select: none;-ms-user-select: none}\r\n\r\n.xm-form-checkbox {position: relative;display: block;vertical-align: middle;cursor: pointer;font-size: 0;-webkit-transition: .1s linear;transition: .1s linear;box-sizing: border-box;height: auto!important;line-height: normal!important;border: none!important;margin-right: 0;padding-right: 0;background: 0 0;}\r\n.xm-form-checkbox > i {color: #FFF; font-size: 16px; width: 16px; height: 16px; position: absolute; top: 9px; border: 1px solid #5FB878; border-radius: 3px; z-index: 2;}\r\n.xm-form-checkbox:hover > i {border-color: #5FB878;}\r\n.xm-form-checkbox > span{display: block;position: relative;padding: 0 15px 0 30px;height: 100%;font-size: 14px;border-radius: 2px 0 0 2px;background-color: #d2d2d2;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;background: 0 0;color: #666;line-height: 36px;}\r\n\r\n.xm-select-parent dl{width: 100%;}\r\n.xm-select-parent dl dd{position: relative;}\r\n.xm-select-parent dl dd > i:not(.icon-sousuo){position: absolute; right: 10px; top: 0; color: #AAAAAA;}\r\n.xm-select-parent dl dd.xm-select-this div i {border: none; color: #009688; font-size: 18px;}\r\n.xm-select-parent dl dd.xm-select-this div i:after{content: '\\e613';}\r\n.xm-select-parent dl dd.xm-dis-disabled div i {border-color: #C2C2C2;}\r\n.xm-select-parent dl dd.xm-dis-disabled.xm-select-this div i {color: #C2C2C2;}\r\n.xm-select-radio div.xm-form-checkbox > i {border-radius: 20px;}\r\n.xm-select-parent dl.xm-select-radio dd.xm-select-this div i:after{content: '\\e62b';}\r\n\r\n.xm-dis-disabled,.xm-dis-disabled:hover {cursor: not-allowed!important}\r\n.xm-form-select dl dd.xm-dis-disabled {background-color: #fff!important}\r\n.xm-form-select dl dd.xm-dis-disabled span {color: #C2C2C2}\r\n.xm-form-select dl dd.xm-dis-disabled .xm-icon-yes {border-color: #C2C2C2}\r\n.xm-select-parent {position: relative;-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none}\r\n.xm-select-parent .xm-select {line-height: normal;height: auto;padding: 4px 10px 1px 10px;overflow: hidden;min-height: 36px;left: 0;z-index: 99;position: absolute;background: 0 0;padding-right: 20px}\r\n.xm-select-parent .xm-select:hover {border-color: #C0C4CC}\r\n.xm-select-parent .xm-select .xm-select-label {display: inline-block;margin: 0;vertical-align: middle}\r\n.xm-select-parent .xm-select-title div.xm-select-label>span {position: relative;padding: 2px 5px;background-color: #009688;border-radius: 2px;color: #FFF;display: inline-block;line-height: 18px;height: 18px;margin: 2px 5px 2px 0;cursor: initial;user-select: none;font-size: 14px;padding-right: 25px;-webkit-user-select: none;}\r\n.xm-select-parent .xm-select-title div.xm-select-label>span i {position: absolute; margin-left: 8px; font-size: 12px; cursor: pointer; line-height: 20px;}\r\n.xm-select-parent .xm-select .xm-select-input {border: none;height: 28px;background-color: transparent;padding: 0;vertical-align: middle;display: inline-block;width: 50px}\r\n.xm-select-parent .xm-select--suffix input {border: none}\r\n.xm-form-selected .xm-select,.xm-form-selected .xm-select:hover {border-color: #009688!important}\r\n.xm-select--suffix+div {position: absolute;top: 0;left: 0;bottom: 0;right: 0}\r\n.xm-select-dis .xm-select--suffix+div {z-index: 100;cursor: no-drop!important;opacity: .2;background-color: #FFF;}\r\n.xm-select-disabled,.xm-select-disabled:hover {color: #d2d2d2!important;cursor: not-allowed!important;background-color: #fff}\r\n.xm-select-none {display: none;margin: 5px 0;text-align: center;}\r\n.xm-select-none:hover {background-color: #FFF!important}\r\n.xm-select-empty {display: block}\r\n.xm-span-hide {display: none!important;}\r\n.layui-form-pane .xm-select,.layui-form-pane .xm-select:hover {border: none!important;top: 0px}\r\n.layui-form-pane .xm-select-title {border: 1px solid #e6e6e6!important}\r\n.xm-select-hide {display: none !important;}\r\ndiv[xm-hg] .xm-select-label{white-space: nowrap; overflow: hidden; position: absolute; right: 30px; left: 0; padding-left: 10px;}\r\n\r\n/* 颜色相关 */\r\ndiv[xm-select-skin] .xm-select-title div.xm-select-label>span {border: 1px solid #009688}\r\ndiv[xm-select-skin] .xm-select-title div.xm-select-label>span i:hover {opacity: .8;filter: alpha(opacity=80);cursor: pointer}\r\ndiv[xm-select-skin=default] .xm-select-title div.xm-select-label>span {background-color: #F0F2F5;color: #909399;border: 1px solid #F0F2F5}\r\ndiv[xm-select-skin=default] .xm-select-title div.xm-select-label>span i {color: #C0C4CC}\r\ndiv[xm-select-skin=default] .xm-select-title div.xm-select-label>span i:before {content: '\\e60b'; font-size: 16px; margin-left: -3px;}\r\ndiv[xm-select-skin=default] dl dd:not(.xm-dis-disabled) i {border-color: #5FB878}\r\ndiv[xm-select-skin=default] dl dd.xm-select-this:not(.xm-dis-disabled) i {color: #5FB878}\r\ndiv[xm-select-skin=default].xm-form-selected .xm-select,div[xm-select-skin=default].xm-form-selected .xm-select:hover {border-color: #C0C4CC!important}\r\ndiv[xm-select-skin=primary] .xm-select-title div.xm-select-label>span {background-color: #009688;color: #FFF;border: 1px solid #009688}\r\ndiv[xm-select-skin=primary] .xm-select-title div.xm-select-label>span i {background-color: #009688; color: #FFF}\r\ndiv[xm-select-skin=primary] dl dd:not(.xm-dis-disabled) i {border-color: #009688}\r\ndiv[xm-select-skin=primary] dl dd.xm-select-this:not(.xm-dis-disabled) i {color: #009688}\r\ndiv[xm-select-skin=primary].xm-form-selected .xm-select,div[xm-select-skin=primary].xm-form-selected .xm-select:hover {border-color: #009688!important}\r\ndiv[xm-select-skin=normal] .xm-select-title div.xm-select-label>span {background-color: #1E9FFF;color: #FFF;border: 1px solid #1E9FFF}\r\ndiv[xm-select-skin=normal] .xm-select-title div.xm-select-label>span i {background-color: #1E9FFF;color: #FFF}\r\ndiv[xm-select-skin=normal] dl dd:not(.xm-dis-disabled) i {border-color: #1E9FFF}\r\ndiv[xm-select-skin=normal] dl dd.xm-select-this:not(.xm-dis-disabled) i {color: #1E9FFF}\r\ndiv[xm-select-skin=normal].xm-form-selected .xm-select,div[xm-select-skin=normal].xm-form-selected .xm-select:hover {border-color: #1E9FFF!important}\r\ndiv[xm-select-skin=warm] .xm-select-title div.xm-select-label>span {background-color: #FFB800;color: #FFF;border: 1px solid #FFB800}\r\ndiv[xm-select-skin=warm] .xm-select-title div.xm-select-label>span i {background-color: #FFB800;color: #FFF}\r\ndiv[xm-select-skin=warm] dl dd:not(.xm-dis-disabled) i {border-color: #FFB800}\r\ndiv[xm-select-skin=warm] dl dd.xm-select-this:not(.xm-dis-disabled) i {color: #FFB800}\r\ndiv[xm-select-skin=warm].xm-form-selected .xm-select,div[xm-select-skin=warm].xm-form-selected .xm-select:hover {border-color: #FFB800!important}\r\ndiv[xm-select-skin=danger] .xm-select-title div.xm-select-label>span {background-color: #FF5722;color: #FFF;border: 1px solid #FF5722}\r\ndiv[xm-select-skin=danger] .xm-select-title div.xm-select-label>span i {background-color: #FF5722;color: #FFF}\r\ndiv[xm-select-skin=danger] dl dd:not(.xm-dis-disabled) i {border-color: #FF5722}\r\ndiv[xm-select-skin=danger] dl dd.xm-select-this:not(.xm-dis-disabled) i {color: #FF5722}\r\ndiv[xm-select-skin=danger].xm-form-selected .xm-select,div[xm-select-skin=danger].xm-form-selected .xm-select:hover {border-color: #FF5722!important}\r\n\r\n\r\n/* 多选联动  */\r\n.xm-select-parent .layui-form-danger+.xm-select-title .xm-select {border-color: #FF5722 !important;}\r\n.xm-select-linkage li {padding: 10px 0px;cursor: pointer;}\r\n.xm-select-linkage li span {padding-left: 20px;padding-right: 30px;display: inline-block;height: 20px;overflow: hidden;text-overflow: ellipsis;}\r\n.xm-select-linkage li.xm-select-this span {border-left: 5px solid #009688;color: #009688;padding-left: 15px;}\r\n.xm-select-linkage-group {position: absolute;left: 0;top: 0;right: 0;bottom: 0;overflow-x: hidden;overflow-y: auto;}\r\n.xm-select-linkage-group li:hover {border-left: 1px solid #009688;}\r\n.xm-select-linkage-group li:hover span {padding-left: 19px;}\r\n.xm-select-linkage-group li.xm-select-this:hover span {padding-left: 15px;border-left-width: 4px;}\r\n.xm-select-linkage-group:nth-child(4n+1){background-color: #EFEFEF; left: 0;}\r\n.xm-select-linkage-group:nth-child(4n+1) li.xm-select-active{background-color: #F5F5F5;}\r\n.xm-select-linkage-group:nth-child(4n+2){background-color: #F5F5F5; left: 100px;}\r\n.xm-select-linkage-group:nth-child(4n+3) li.xm-select-active{background-color: #FAFAFA;}\r\n.xm-select-linkage-group:nth-child(4n+3){background-color: #FAFAFA; left: 200px;}\r\n.xm-select-linkage-group:nth-child(4n+3) li.xm-select-active{background-color: #FFFFFF;}\r\n.xm-select-linkage-group:nth-child(4n+4){background-color: #FFFFFF; left: 300px;}\r\n.xm-select-linkage-group:nth-child(4n+4) li.xm-select-active{background-color: #EFEFEF;}\r\n.xm-select-linkage li{list-style: none;}\r\n.xm-select-linkage-hide {display: none;}\r\n.xm-select-linkage-show {display: block;}\r\n\r\ndiv[xm-select-skin='default'] .xm-select-linkage li.xm-select-this span {border-left-color: #5FB878;color: #5FB878;}\r\ndiv[xm-select-skin='default'] .xm-select-linkage-group li:hover {border-left-color: #5FB878;}\r\ndiv[xm-select-skin='primary'] .xm-select-linkage li.xm-select-this span {border-left-color: #1E9FFF;color: #1E9FFF;}\r\ndiv[xm-select-skin='primary'] .xm-select-linkage-group li:hover {border-left-color: #1E9FFF;}\r\ndiv[xm-select-skin='normal'] .xm-select-linkage li.xm-select-this span {border-left-color: #1E9FFF;color: #1E9FFF;}\r\ndiv[xm-select-skin='normal'] .xm-select-linkage-group li:hover {border-left-color: #1E9FFF;}\r\ndiv[xm-select-skin='warm'] .xm-select-linkage li.xm-select-this span {border-left-color: #FFB800;color: #FFB800;}\r\ndiv[xm-select-skin='warm'] .xm-select-linkage-group li:hover {border-left-color: #FFB800;}\r\ndiv[xm-select-skin='danger'] .xm-select-linkage li.xm-select-this span {border-left-color: #FF5722;color: #FF5722;}\r\ndiv[xm-select-skin='danger'] .xm-select-linkage-group li:hover {border-left-color: #FF5722;}\r\n\r\n\r\n/* 快捷操作 */\r\n.xm-select-tips[style]:hover{background-color: #FFF!important;}\r\n.xm-select-parent dd > .xm-cz{position: absolute; top: 0px; right: 10px;}\r\n.xm-select-parent dd > .xm-cz-group{margin-right: 30px; border-right: 2px solid #ddd; height: 16px; margin-top: 10px; line-height: 16px; overflow: hidden;}\r\n.xm-select-parent dd > .xm-cz-group .xm-cz{display: inline-block; margin-right: 30px;}\r\n.xm-select-parent dd > .xm-cz-group .xm-cz i{margin-right: 10px;}\r\n.xm-select-parent dd > .xm-cz-group[show='name'] .xm-cz i{display: none;}\r\n.xm-select-parent dd > .xm-cz-group[show='icon'] .xm-cz span{display: none;}\r\n.xm-select-parent dd .xm-cz:hover{color: #009688;}\r\ndiv[xm-select-skin='default'] dd .xm-cz:hover{color: #C0C4CC;}\r\ndiv[xm-select-skin='primary'] dd .xm-cz:hover{color: #009688;}\r\ndiv[xm-select-skin='normal'] dd .xm-cz:hover{color: #1E9FFF;}\r\ndiv[xm-select-skin='warm'] dd .xm-cz:hover{color: #FFB800;}\r\ndiv[xm-select-skin='danger'] dd .xm-cz:hover{color: #FF5722;}\r\n\r\n\r\n/* 下拉里面的搜索 */\r\n.xm-select-tips .xm-input{border: none; border-bottom: 1px solid #E6E6E6; padding-left: 27px;}\r\n.xm-select-tips .icon-sousuo{position: absolute;}\r\n.xm-select-tips.xm-dl-input{display: none;}\r\ndiv[xm-select-search-type=\"1\"] .xm-select-tips.xm-dl-input{display: block;}\r\ndiv[xm-select-search-type=\"1\"] .xm-select .xm-select-input{display: none !important;}\r\n\r\n/* 阿里巴巴矢量图标库 */\r\n@font-face {font-family: \"xm-iconfont\";\r\n  src: url('//at.alicdn.com/t/font_792691_qxv28s6g1l9.eot?t=1534240067831'); /* IE9*/\r\n  src: url('//at.alicdn.com/t/font_792691_qxv28s6g1l9.eot?t=1534240067831#iefix') format('embedded-opentype'), /* IE6-IE8 */\r\n  url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAsYAAsAAAAAEQwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFY8ukovY21hcAAAAYAAAACrAAACPBtV6wxnbHlmAAACLAAABnEAAAmMovtEvWhlYWQAAAigAAAAMQAAADYSctBCaGhlYQAACNQAAAAgAAAAJAgBA69obXR4AAAI9AAAABsAAAAwMCX//WxvY2EAAAkQAAAAGgAAABoN8gwubWF4cAAACSwAAAAeAAAAIAEiAM9uYW1lAAAJTAAAAUUAAAJtPlT+fXBvc3QAAAqUAAAAhAAAALJ1LunfeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2BkYWacwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGByeMbwwZ27438AQw9zMcAQozAiSAwDk4AxmeJzlks0JwzAMhZ8bN/1xD4GU0h2Se26BbJMJOkkn6KmTPbJF8mT5UGg3qMRn0EPIRs8A9gAq0YsIhDcCLF5SQ9YrnLMe8VB9RSMlMjCxYcueIyfOy7CuAFHU7lP9iqApt5L3ksBJbzlgZ9PVkXDUvbWa6x8T/i0u+XyWKtmmHW0NDI55yeRok2DjaKdg65jX7Bzzm71jXnN08vzJkQvg7Ng/WAYH9Qb3wzM/AHicjVVvbFzFEd/Zfbv7/vn9uXf33vl8Pt/dO99BHOzEZ9/DKTImRS0KjUoLDUFCjtpCMGkT1D9qldQmhkiUSv2G1BBB1VYqilGREOIDViWEGzttqkpI/cAXqyL5gFRALVIF+VCJe9fZd+fEpR/o6d3s7G9mZ2dmZ3aJIKR3h0ZYmVgkIjGZJV8mDxECtenOTDOu1UU+hJoD+TCqzcNMk2V8O5OCbDVRPgZhEt4JCNTZ/4HA3+DfuWIxl8pcFFErG3K7oD7fvev8UaMUmEu259lrRjBsfs6cLhYbRfzSbSjGRVAkfQYihUXsyPkHTVyyZDNmXzSHg3Tl+aPKxpJFqbWGdtLl8w8iYDxuDTQIx7yc1YCdIx7Jk3HSwbwQwGBcyMKZVtG0ZCuJxjFJBb+foMSfhJaPOSr4FYgwSwqIx2MHJALtAdBi/7xcSMJL+fxmmBS2guD61tZm96X02mgcj0J1NAaIR9UMmhXIV24FuLUC71+r1AEmK1AYrQHUK/Tly/m8MrOZz2+FSf7jzc3NK9XR9F2lVq+gmRp0r+HK9B+VJmR263Rgd7ALwR/FOFfx/FeJS0YxQh9drakgMJhaBVizkwgqWxLD6eQ0Qo8f7p44fJziSH9x+PjLZUO+/jZ9+K35X37ljn/Rv+yW4Ziuf2nl4PfS5/LrP47OHTsFJULYjf369UZAEBmSqEOSJmG4Me6LeznA0BFkcDoJlGynVzmH2vY21DhPr25v9DjvbfTp2TXG1s5mlK0q4S7lT++6obbRox/s6CHF2LMEsHvoFfSFQIKnKQMZJVFCD6WH0p0PVvvcRx8uph8eUks0jOFNtskOkpDsJ18k9+NqVRg3qqMCSSerjyRuYUi1/vFH7YIqikGVcD+ehFl/pqPSPKZ6DG6mHisljFhBFvU/PoRkSNd/JHO6Ja5JOXcfwIGJbm/igBq/hn8Kfb57YbYUxyX4cwkLKH1u4gD9GVSL6USxCjjCO2p8VdcvH9XRYIQWqUblu3pR/v2BvXMAc3tTmJiDAQ895B9NL0C9BFdKqqRKczDX/Whg7O1irVbcqZ8/sbfYBOZwihC+6wSDzszUf+dF7rRO1O+fKaDO+nXOr6+vf8L5J44Qe4UvnlyRntwrxMoKzpFdeRJBNb9dGyiur1+nE59R+uwi9M1G395jb9KP0bcK2YM9nJB5cojcS75OFskxclzdc+pW699z8iYbtf14BGKf77ruZNyXKC0e50OEBI+V/Aug5Dex/9WjJfipuqnS00gfybjXbNe1f762tXmRPp3Bdl/l6g5JXyqXR0bK8J3PR+jvwYs8/GBnTM+kr8FX4ZknwC16XtG9iH9QfNn1vDHPe2GAj3ieV3XdF2+IPdeteh62Ra+HfQrsKWKSBtlHSOBgM7KkKQBLWnZoq1mVwotCLRGhOtSkMzMuqq2ml3SqUehdnZtynbtPLB88/Dy9dDrYVzoy/MTT6Svnlpd/AHueon5wpnGsEae/PZm+d3Jp6SSUTy7R3xw4f9/B5RN3O+5t3VNncjm6Cnt+uLx8DpedGj4yvD84HceNxTcG6ku4VPmZ9n6nNdj95BHyB3IJKxBPsKm6rpn4QopmqzlFm1MwqdxO5rPGnIc7aSfCGg1Vqyo6nUlQhnh7WiFhXzgGhVC4qjPRki9xdGCc4zXeSWb9BG1ktlqz2Q5Y7S2sIJfivkpVKCCDpyCWdbQzECj76qMVqvyJ/LxyI2rTv1bTC25lSM9xAUJ4Lc+U0wXTsKXDmaA8tHX+hvDt4Wa9IHLcMUBz9VwpL4xi2aGasAPPKNUbbmD/2jAtk0uXY4eJx8zRgj9iAnVNt5X+BL5vlHTOaiOmG7g6+7ZBNUOaefNXuJF3u25RjVvBLeW8E4wV7ZJBpbAXXGnqrwgupWVTAKqZjq5HbW44fMguNJhgwmw8oOk8GCqE8F3GhLB0uS/UDVt4lgjtqGxK/rpwuaDAqKHZNuWmJjVKuWUxbpg2B9DtoRdN3TKF9B0hw4p41C5i3CI9w4civP3aQLlmLMK3wpJpaI7BvmlhPtH3nPWCKQAdE2hK9zyuUeAm921qCA2kvqY8N1yDMq4beJlG+4XQqHDCQnqPlJIyyN579S4tIGcRv/82BbFfK9SgnVHkZzMeaSQjqR5/fP5XF2Chh+sW0g0gn27snqXv3/bsszsfJbCAIiTdjRTVCBL6jV0K5D8H/8xVAAAAeJxjYGRgYADi16c/vIvnt/nKwM3CAALXZxxzhtH///23YVFhbgZyORiYQKIAm34OJQAAAHicY2BkYGBu+N/AEMOi/P/f//8sKgxAERTAAwCmuAa3eJxjYWBgYAFhRiiNFf//z6L8/x+IDQAkCQRQAAAAAAAAjAEAATgBfgGaAiACbgMMA2AEhATGAAB4nGNgZGBg4GE4DMQgwATEXEDIwPAfzGcAAB2tAfIAAHicZY9NTsMwEIVf+gekEqqoYIfkBWIBKP0Rq25YVGr3XXTfpk6bKokjx63UA3AejsAJOALcgDvwSCebNpbH37x5Y08A3OAHHo7fLfeRPVwyO3INF7gXrlN/EG6QX4SbaONVuEX9TdjHM6bCbXRheYPXuGL2hHdhDx18CNdwjU/hOvUv4Qb5W7iJO/wKt9Dx6sI+5l5XuI1HL/bHVi+cXqnlQcWhySKTOb+CmV7vkoWt0uqca1vEJlODoF9JU51pW91T7NdD5yIVWZOqCas6SYzKrdnq0AUb5/JRrxeJHoQm5Vhj/rbGAo5xBYUlDowxQhhkiMro6DtVZvSvsUPCXntWPc3ndFsU1P9zhQEC9M9cU7qy0nk6T4E9XxtSdXQrbsuelDSRXs1JErJCXta2VELqATZlV44RelzRiT8oZ0j/AAlabsgAAAB4nG2L3QqCQBCFZ9RWU7sOfAeh8IFi3N10EHYUG1p8+gSjqz44F+cPEjgo4T81Jphihic0mGOBZyyxwhovUCxKIe4ylthRuDqV+I22UcLQ6+QH4ubWdZZkU3m4o/0tUqtSvT33TPLits12fzc+zhRcvoquo0o281OLhcMw7Q+AD8sULE0=') format('woff'),\r\n  url('//at.alicdn.com/t/font_792691_qxv28s6g1l9.ttf?t=1534240067831') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/\r\n  url('//at.alicdn.com/t/font_792691_qxv28s6g1l9.svg?t=1534240067831#iconfont') format('svg'); /* iOS 4.1- */\r\n}\r\n.xm-iconfont {font-family:\"xm-iconfont\" !important; font-size:16px; font-style:normal; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;}\r\n.icon-quanxuan:before { content: \"\\e62c\"; }\r\n.icon-caidan:before { content: \"\\e610\"; }\r\n.icon-fanxuan:before { content: \"\\e837\"; }\r\n.icon-pifu:before { content: \"\\e668\"; }\r\n.icon-qingkong:before { content: \"\\e63e\"; }\r\n.icon-sousuo:before { content: \"\\e600\"; }\r\n.icon-danx:before { content: \"\\e62b\"; }\r\n.icon-duox:before { content: \"\\e613\"; }\r\n.icon-close:before { content: \"\\e601\"; }\r\n.icon-expand:before { content: \"\\e641\"; }\r\n\r\n"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/layui/extends/formSelects-v4.js",
    "content": "'use strict';\n\nvar _typeof = typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; };\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\n/**\r\n * name: formSelects\r\n * 基于Layui Select多选\r\n * version: 4.0.0.0910\r\n * http://sun.faysunshine.com/layui/formSelects-v4/dist/formSelects-v4.js\r\n */\n(function (layui, window, factory) {\n\tif ((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object') {\n\t\t// 支持 CommonJS\n\t\tmodule.exports = factory();\n\t} else if (typeof define === 'function' && define.amd) {\n\t\t// 支持 AMD\n\t\tdefine(factory);\n\t} else if (window.layui && layui.define) {\n\t\t//layui加载\n\t\tlayui.define(['jquery'], function (exports) {\n\t\t\texports('formSelects', factory());\n\t\t});\n\t} else {\n\t\twindow.formSelects = factory();\n\t}\n})(typeof layui == 'undefined' ? null : layui, window, function () {\n\tvar v = '4.0.0.0910',\n\t    NAME = 'xm-select',\n\t    PNAME = 'xm-select-parent',\n\t    INPUT = 'xm-select-input',\n\t    TDIV = 'xm-select--suffix',\n\t    THIS = 'xm-select-this',\n\t    LABEL = 'xm-select-label',\n\t    SEARCH = 'xm-select-search',\n\t    SEARCH_TYPE = 'xm-select-search-type',\n\t    SHOW_COUNT = 'xm-select-show-count',\n\t    CREATE = 'xm-select-create',\n\t    CREATE_LONG = 'xm-select-create-long',\n\t    MAX = 'xm-select-max',\n\t    SKIN = 'xm-select-skin',\n\t    DIRECTION = \"xm-select-direction\",\n\t    HEIGHT = 'xm-select-height',\n\t    DISABLED = 'xm-dis-disabled',\n\t    DIS = 'xm-select-dis',\n\t    TEMP = 'xm-select-temp',\n\t    RADIO = 'xm-select-radio',\n\t    LINKAGE = 'xm-select-linkage',\n\t    DL = 'xm-select-dl',\n\t    DD_HIDE = 'xm-select-hide',\n\t    HIDE_INPUT = 'xm-hide-input',\n\t    SANJIAO = 'xm-select-sj',\n\t    ICON_CLOSE = 'xm-icon-close',\n\t    FORM_TITLE = 'xm-select-title',\n\t    FORM_SELECT = 'xm-form-select',\n\t    FORM_SELECTED = 'xm-form-selected',\n\t    FORM_NONE = 'xm-select-none',\n\t    FORM_EMPTY = 'xm-select-empty',\n\t    FORM_INPUT = 'xm-input',\n\t    FORM_DL_INPUT = 'xm-dl-input',\n\t    FORM_SELECT_TIPS = 'xm-select-tips',\n\t    CHECKBOX_YES = 'xm-iconfont',\n\t    FORM_TEAM_PID = 'XM_PID_VALUE',\n\t    CZ = 'xm-cz',\n\t    CZ_GROUP = 'xm-cz-group',\n\t    TIPS = '请选择',\n\t    data = {},\n\t    events = {\n\t\ton: {},\n\t\tendOn: {},\n\t\tfilter: {},\n\t\tmaxTips: {},\n\t\topened: {},\n\t\tclosed: {}\n\t},\n\t    ajax = {\n\t\ttype: 'get',\n\t\theader: {},\n\t\tfirst: true,\n\t\tdata: {},\n\t\tsearchUrl: '',\n\t\tsearchName: 'keyword',\n\t\tsearchVal: null,\n\t\tkeyName: 'name',\n\t\tkeyVal: 'value',\n\t\tkeySel: 'selected',\n\t\tkeyDis: 'disabled',\n\t\tkeyChildren: 'children',\n\t\tdataType: '',\n\t\tdelay: 500,\n\t\tbeforeSuccess: null,\n\t\tsuccess: null,\n\t\terror: null,\n\t\tbeforeSearch: null,\n\t\tresponse: {\n\t\t\tstatusCode: 0,\n\t\t\tstatusName: 'code',\n\t\t\tmsgName: 'msg',\n\t\t\tdataName: 'data'\n\t\t},\n\t\ttree: {\n\t\t\tnextClick: function nextClick(id, item, callback) {\n\t\t\t\tcallback([]);\n\t\t\t},\n\t\t\tfolderChoose: true,\n\t\t\tlazy: true\n\t\t}\n\t},\n\t    quickBtns = [{ icon: 'xm-iconfont icon-quanxuan', name: '全选', click: function click(id, cm) {\n\t\t\tcm.selectAll(id, true, true);\n\t\t} }, { icon: 'xm-iconfont icon-qingkong', name: '清空', click: function click(id, cm) {\n\t\t\tcm.removeAll(id, true, true);\n\t\t} }, { icon: 'xm-iconfont icon-fanxuan', name: '反选', click: function click(id, cm) {\n\t\t\tcm.reverse(id, true, true);\n\t\t} }, { icon: 'xm-iconfont icon-pifu', name: '换肤', click: function click(id, cm) {\n\t\t\tcm.skin(id);\n\t\t} }],\n\t    $ = window.$ || window.layui && window.layui.jquery,\n\t    $win = $(window),\n\t    ajaxs = {},\n\t    fsConfig = {},\n\t    fsConfigs = {},\n\t    FormSelects = function FormSelects(options) {\n\t\tvar _this = this;\n\n\t\tthis.config = {\n\t\t\tname: null, //xm-select=\"xxx\"\n\t\t\tmax: null,\n\t\t\tmaxTips: function maxTips(id, vals, val, max) {\n\t\t\t\tvar ipt = $('[xid=\"' + _this.config.name + '\"]').prev().find('.' + NAME);\n\t\t\t\tif (ipt.parents('.layui-form-item[pane]').length) {\n\t\t\t\t\tipt = ipt.parents('.layui-form-item[pane]');\n\t\t\t\t}\n\t\t\t\tipt.attr('style', 'border-color: red !important');\n\t\t\t\tsetTimeout(function () {\n\t\t\t\t\tipt.removeAttr('style');\n\t\t\t\t}, 300);\n\t\t\t},\n\t\t\tinit: null, //初始化的选择值,\n\t\t\ton: null, //select值发生变化\n\t\t\topened: null,\n\t\t\tclosed: null,\n\t\t\tfilter: function filter(id, inputVal, val, isDisabled) {\n\t\t\t\treturn val.name.indexOf(inputVal) == -1;\n\t\t\t},\n\t\t\tclearid: -1,\n\t\t\tdirection: 'auto',\n\t\t\theight: null,\n\t\t\tisEmpty: false,\n\t\t\tbtns: [quickBtns[0], quickBtns[1], quickBtns[2]],\n\t\t\tsearchType: 0,\n\t\t\tcreate: function create(id, name) {\n\t\t\t\treturn Date.now();\n\t\t\t},\n\t\t\ttemplate: function template(id, item) {\n\t\t\t\treturn item.name;\n\t\t\t},\n\t\t\tshowCount: 0,\n\t\t\tisCreate: false,\n\t\t\tplaceholder: TIPS,\n\t\t\tclearInput: false\n\t\t};\n\t\tthis.select = null;\n\t\tthis.values = [];\n\t\t$.extend(this.config, options, {\n\t\t\tsearchUrl: options.isSearch ? options.searchUrl : null,\n\t\t\tplaceholder: options.optionsFirst ? options.optionsFirst.value ? TIPS : options.optionsFirst.innerHTML || TIPS : TIPS,\n\t\t\tbtns: options.radio ? [quickBtns[1]] : [quickBtns[0], quickBtns[1], quickBtns[2]]\n\t\t}, fsConfigs[options.name] || fsConfig);\n\t\tif (isNaN(this.config.showCount) || this.config.showCount <= 0) {\n\t\t\tthis.config.showCount = 19921012;\n\t\t}\n\t};\n\n\t//一些简单的处理方法\n\tvar Common = function Common() {\n\t\tthis.appender();\n\t\tthis.on();\n\t\tthis.onreset();\n\t};\n\n\tCommon.prototype.appender = function () {\n\t\t//针对IE做的一些拓展\n\t\t//拓展Array map方法\n\t\tif (!Array.prototype.map) {\n\t\t\tArray.prototype.map = function (i, h) {\n\t\t\t\tvar b,\n\t\t\t\t    a,\n\t\t\t\t    c,\n\t\t\t\t    e = Object(this),\n\t\t\t\t    f = e.length >>> 0;if (h) {\n\t\t\t\t\tb = h;\n\t\t\t\t}a = new Array(f);c = 0;while (c < f) {\n\t\t\t\t\tvar d, g;if (c in e) {\n\t\t\t\t\t\td = e[c];g = i.call(b, d, c, e);a[c] = g;\n\t\t\t\t\t}c++;\n\t\t\t\t}return a;\n\t\t\t};\n\t\t};\n\n\t\t//拓展Array foreach方法\n\t\tif (!Array.prototype.forEach) {\n\t\t\tArray.prototype.forEach = function forEach(g, b) {\n\t\t\t\tvar d, c;if (this == null) {\n\t\t\t\t\tthrow new TypeError(\"this is null or not defined\");\n\t\t\t\t}var f = Object(this);var a = f.length >>> 0;if (typeof g !== \"function\") {\n\t\t\t\t\tthrow new TypeError(g + \" is not a function\");\n\t\t\t\t}if (arguments.length > 1) {\n\t\t\t\t\td = b;\n\t\t\t\t}c = 0;while (c < a) {\n\t\t\t\t\tvar e;if (c in f) {\n\t\t\t\t\t\te = f[c];g.call(d, e, c, f);\n\t\t\t\t\t}c++;\n\t\t\t\t}\n\t\t\t};\n\t\t};\n\n\t\t//拓展Array filter方法\n\t\tif (!Array.prototype.filter) {\n\t\t\tArray.prototype.filter = function (b) {\n\t\t\t\tif (this === void 0 || this === null) {\n\t\t\t\t\tthrow new TypeError();\n\t\t\t\t}var f = Object(this);var a = f.length >>> 0;if (typeof b !== \"function\") {\n\t\t\t\t\tthrow new TypeError();\n\t\t\t\t}var e = [];var d = arguments[1];for (var c = 0; c < a; c++) {\n\t\t\t\t\tif (c in f) {\n\t\t\t\t\t\tvar g = f[c];if (b.call(d, g, c, f)) {\n\t\t\t\t\t\t\te.push(g);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}return e;\n\t\t\t};\n\t\t};\n\t};\n\n\tCommon.prototype.init = function (target) {\n\t\tvar _this2 = this;\n\n\t\t//初始化页面上已有的select\n\t\t$(target ? target : 'select[' + NAME + ']').each(function (index, select) {\n\t\t\tvar othis = $(select),\n\t\t\t    id = othis.attr(NAME),\n\t\t\t    hasLayuiRender = othis.next('.layui-form-select'),\n\t\t\t    hasRender = othis.next('.' + PNAME),\n\t\t\t    options = {\n\t\t\t\tname: id,\n\t\t\t\tdisabled: select.disabled,\n\t\t\t\tmax: othis.attr(MAX) - 0,\n\t\t\t\tisSearch: othis.attr(SEARCH) != undefined,\n\t\t\t\tsearchUrl: othis.attr(SEARCH),\n\t\t\t\tisCreate: othis.attr(CREATE) != undefined,\n\t\t\t\tradio: othis.attr(RADIO) != undefined,\n\t\t\t\tskin: othis.attr(SKIN),\n\t\t\t\tdirection: othis.attr(DIRECTION),\n\t\t\t\toptionsFirst: select.options[0],\n\t\t\t\theight: othis.attr(HEIGHT),\n\t\t\t\tformname: othis.attr('name') || othis.attr('_name'),\n\t\t\t\tlayverify: othis.attr('lay-verify') || othis.attr('_lay-verify'),\n\t\t\t\tlayverType: othis.attr('lay-verType'),\n\t\t\t\tsearchType: othis.attr(SEARCH_TYPE) == 'dl' ? 1 : 0,\n\t\t\t\tshowCount: othis.attr(SHOW_COUNT) - 0\n\t\t\t},\n\t\t\t    value = othis.find('option[selected]').toArray().map(function (option) {\n\t\t\t\t//获取已选中的数据\n\t\t\t\treturn {\n\t\t\t\t\tname: option.innerHTML,\n\t\t\t\t\tvalue: option.value\n\t\t\t\t};\n\t\t\t}),\n\t\t\t    fs = new FormSelects(options);\n\n\t\t\tfs.values = value;\n\n\t\t\tif (fs.config.init) {\n\t\t\t\tfs.values = fs.config.init.map(function (item) {\n\t\t\t\t\tif ((typeof item === 'undefined' ? 'undefined' : _typeof(item)) == 'object') {\n\t\t\t\t\t\treturn item;\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tname: othis.find('option[value=\"' + item + '\"]').text(),\n\t\t\t\t\t\tvalue: item\n\t\t\t\t\t};\n\t\t\t\t}).filter(function (item) {\n\t\t\t\t\treturn item.name;\n\t\t\t\t});\n\t\t\t\tfs.config.init = fs.values.concat([]);\n\t\t\t} else {\n\t\t\t\tfs.config.init = value.concat([]);\n\t\t\t}\n\n\t\t\t!fs.values && (fs.values = []);\n\n\t\t\tdata[id] = fs;\n\n\t\t\t//先取消layui对select的渲染\n\t\t\thasLayuiRender[0] && hasLayuiRender.remove();\n\t\t\thasRender[0] && hasRender.remove();\n\n\t\t\t//构造渲染div\n\t\t\tvar dinfo = _this2.renderSelect(id, fs.config.placeholder, select);\n\t\t\tvar heightStyle = !fs.config.height || fs.config.height == 'auto' ? '' : 'xm-hg style=\"height: 34px;\"';\n\t\t\tvar inputHtml = ['<div class=\"' + LABEL + '\">', '<input type=\"text\" fsw class=\"' + FORM_INPUT + ' ' + INPUT + '\" ' + (fs.config.isSearch ? '' : 'style=\"display: none;\"') + ' autocomplete=\"off\" debounce=\"0\" />', '</div>'];\n\t\t\tvar reElem = $('<div class=\"' + FORM_SELECT + '\" ' + SKIN + '=\"' + fs.config.skin + '\">\\n\\t\\t\\t\\t\\t<input class=\"' + HIDE_INPUT + '\" value=\"\" name=\"' + fs.config.formname + '\" lay-verify=\"' + fs.config.layverify + '\" lay-verType=\"' + fs.config.layverType + '\" type=\"text\" style=\"position: absolute;bottom: 0; z-index: -1;width: 100%; height: 100%; border: none; opacity: 0;\"/>\\n\\t\\t\\t\\t\\t<div class=\"' + FORM_TITLE + ' ' + (fs.config.disabled ? DIS : '') + '\">\\n\\t\\t\\t\\t\\t\\t<div class=\"' + FORM_INPUT + ' ' + NAME + '\" ' + heightStyle + '>\\n\\t\\t\\t\\t\\t\\t\\t' + inputHtml.join('') + '\\n\\t\\t\\t\\t\\t\\t\\t<i class=\"' + SANJIAO + '\"></i>\\n\\t\\t\\t\\t\\t\\t</div>\\n\\t\\t\\t\\t\\t\\t<div class=\"' + TDIV + '\">\\n\\t\\t\\t\\t\\t\\t\\t<input type=\"text\" autocomplete=\"off\" placeholder=\"' + fs.config.placeholder + '\" readonly=\"readonly\" unselectable=\"on\" class=\"' + FORM_INPUT + '\">\\n\\t\\t\\t\\t\\t\\t</div>\\n\\t\\t\\t\\t\\t\\t<div></div>\\n\\t\\t\\t\\t\\t</div>\\n\\t\\t\\t\\t\\t<dl xid=\"' + id + '\" class=\"' + DL + ' ' + (fs.config.radio ? RADIO : '') + '\">' + dinfo + '</dl>\\n\\t\\t\\t\\t</div>');\n\n\t\t\tvar $parent = $('<div class=\"' + PNAME + '\" FS_ID=\"' + id + '\"></div>');\n\t\t\t$parent.append(reElem);\n\t\t\tothis.after($parent);\n\t\t\tothis.attr('lay-ignore', '');\n\t\t\tothis.removeAttr('name') && othis.attr('_name', fs.config.formname);\n\t\t\tothis.removeAttr('lay-verify') && othis.attr('_lay-verify', fs.config.layverify);\n\n\t\t\t//如果可搜索, 加上事件\n\t\t\tif (fs.config.isSearch) {\n\t\t\t\tajaxs[id] = $.extend({}, ajax, { searchUrl: fs.config.searchUrl }, ajaxs[id]);\n\t\t\t\t$(document).on('input', 'div.' + PNAME + '[FS_ID=\"' + id + '\"] .' + INPUT, function (e) {\n\t\t\t\t\t_this2.search(id, e, fs.config.searchUrl);\n\t\t\t\t});\n\t\t\t\tif (fs.config.searchUrl) {\n\t\t\t\t\t//触发第一次请求事件\n\t\t\t\t\t_this2.triggerSearch(reElem, true);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t//隐藏第二个dl\n\t\t\t\treElem.find('dl dd.' + FORM_DL_INPUT).css('display', 'none');\n\t\t\t}\n\t\t});\n\t};\n\n\tCommon.prototype.search = function (id, e, searchUrl, call) {\n\t\tvar _this3 = this;\n\n\t\tvar input = void 0;\n\t\tif (call) {\n\t\t\tinput = call;\n\t\t} else {\n\t\t\tinput = e.target;\n\t\t\tvar keyCode = e.keyCode;\n\t\t\tif (keyCode === 9 || keyCode === 13 || keyCode === 37 || keyCode === 38 || keyCode === 39 || keyCode === 40) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\tvar inputValue = $.trim(input.value);\n\t\t//过滤一下tips\n\t\tthis.changePlaceHolder($(input));\n\n\t\tvar ajaxConfig = ajaxs[id] ? ajaxs[id] : ajax;\n\t\tsearchUrl = ajaxConfig.searchUrl || searchUrl;\n\t\tvar fs = data[id],\n\t\t    isCreate = fs.config.isCreate,\n\t\t    reElem = $('dl[xid=\"' + id + '\"]').parents('.' + FORM_SELECT);\n\t\t//如果开启了远程搜索\n\t\tif (searchUrl) {\n\t\t\tif (ajaxConfig.searchVal) {\n\t\t\t\tinputValue = ajaxConfig.searchVal;\n\t\t\t\tajaxConfig.searchVal = '';\n\t\t\t}\n\t\t\tif (!ajaxConfig.beforeSearch || ajaxConfig.beforeSearch && ajaxConfig.beforeSearch instanceof Function && ajaxConfig.beforeSearch(id, searchUrl, inputValue)) {\n\t\t\t\tvar delay = ajaxConfig.delay;\n\t\t\t\tif (ajaxConfig.first) {\n\t\t\t\t\tajaxConfig.first = false;\n\t\t\t\t\tdelay = 10;\n\t\t\t\t}\n\t\t\t\tclearTimeout(fs.clearid);\n\t\t\t\tfs.clearid = setTimeout(function () {\n\t\t\t\t\treElem.find('dl > *:not(.' + FORM_SELECT_TIPS + ')').remove();\n\t\t\t\t\treElem.find('dd.' + FORM_NONE).addClass(FORM_EMPTY).text('请求中');\n\t\t\t\t\t_this3.ajax(id, searchUrl, inputValue, false, null, true);\n\t\t\t\t}, delay);\n\t\t\t}\n\t\t} else {\n\t\t\treElem.find('dl .' + DD_HIDE).removeClass(DD_HIDE);\n\t\t\t//遍历选项, 选择可以显示的值\n\t\t\treElem.find('dl dd:not(.' + FORM_SELECT_TIPS + ')').each(function (idx, item) {\n\t\t\t\tvar _item = $(item);\n\t\t\t\tvar searchFun = events.filter[id] || data[id].config.filter;\n\t\t\t\tif (searchFun && searchFun(id, inputValue, _this3.getItem(id, _item), _item.hasClass(DISABLED)) == true) {\n\t\t\t\t\t_item.addClass(DD_HIDE);\n\t\t\t\t}\n\t\t\t});\n\t\t\t//控制分组名称\n\t\t\treElem.find('dl dt').each(function (index, item) {\n\t\t\t\tif (!$(item).nextUntil('dt', ':not(.' + DD_HIDE + ')').length) {\n\t\t\t\t\t$(item).addClass(DD_HIDE);\n\t\t\t\t}\n\t\t\t});\n\t\t\t//动态创建\n\t\t\tthis.create(id, isCreate, inputValue);\n\t\t\tvar shows = reElem.find('dl dd:not(.' + FORM_SELECT_TIPS + '):not(.' + DD_HIDE + ')');\n\t\t\tif (!shows.length) {\n\t\t\t\treElem.find('dd.' + FORM_NONE).addClass(FORM_EMPTY).text('无匹配项');\n\t\t\t} else {\n\t\t\t\treElem.find('dd.' + FORM_NONE).removeClass(FORM_EMPTY);\n\t\t\t}\n\t\t}\n\t};\n\n\tCommon.prototype.isArray = function (obj) {\n\t\treturn Object.prototype.toString.call(obj) == \"[object Array]\";\n\t};\n\n\tCommon.prototype.triggerSearch = function (div, isCall) {\n\t\tvar _this4 = this;\n\n\t\t(div ? [div] : $('.' + FORM_SELECT).toArray()).forEach(function (reElem, index) {\n\t\t\treElem = $(reElem);\n\t\t\tvar id = reElem.find('dl').attr('xid');\n\t\t\tif (id && data[id] && data[id].config.isEmpty || isCall) {\n\t\t\t\t_this4.search(id, null, null, data[id].config.searchType == 0 ? reElem.find('.' + LABEL + ' .' + INPUT) : reElem.find('dl .' + FORM_DL_INPUT + ' .' + INPUT));\n\t\t\t}\n\t\t});\n\t};\n\n\tCommon.prototype.clearInput = function (id) {\n\t\tvar div = $('.' + PNAME + '[fs_id=\"' + id + '\"]');\n\t\tvar input = data[id].config.searchType == 0 ? div.find('.' + LABEL + ' .' + INPUT) : div.find('dl .' + FORM_DL_INPUT + ' .' + INPUT);\n\t\tinput.val('');\n\t};\n\n\tCommon.prototype.ajax = function (id, searchUrl, inputValue, isLinkage, linkageWidth, isSearch, successCallback, isReplace) {\n\t\tvar _this5 = this;\n\n\t\tvar reElem = $('.' + PNAME + ' dl[xid=\"' + id + '\"]').parents('.' + FORM_SELECT);\n\t\tif (!reElem[0] || !searchUrl) {\n\t\t\treturn;\n\t\t}\n\t\tvar ajaxConfig = ajaxs[id] ? ajaxs[id] : ajax;\n\t\tvar ajaxData = $.extend(true, {}, ajaxConfig.data);\n\t\tajaxData[ajaxConfig.searchName] = inputValue;\n\t\t//是否需要对ajax添加随机时间\n\t\t//ajaxData['_'] = Date.now();\n\t\t$.ajax({\n\t\t\ttype: ajaxConfig.type,\n\t\t\theaders: ajaxConfig.header,\n\t\t\turl: searchUrl,\n\t\t\tdata: ajaxConfig.dataType == 'json' ? JSON.stringify(ajaxData) : ajaxData,\n\t\t\tsuccess: function success(res) {\n\t\t\t\tif (typeof res == 'string') {\n\t\t\t\t\tres = JSON.parse(res);\n\t\t\t\t}\n\t\t\t\tajaxConfig.beforeSuccess && ajaxConfig.beforeSuccess instanceof Function && (res = ajaxConfig.beforeSuccess(id, searchUrl, inputValue, res));\n\t\t\t\tif (_this5.isArray(res)) {\n\t\t\t\t\tvar newRes = {};\n\t\t\t\t\tnewRes[ajaxConfig.response.statusName] = ajaxConfig.response.statusCode;\n\t\t\t\t\tnewRes[ajaxConfig.response.msgName] = \"\";\n\t\t\t\t\tnewRes[ajaxConfig.response.dataName] = res;\n\t\t\t\t\tres = newRes;\n\t\t\t\t}\n\t\t\t\tif (res[ajaxConfig.response.statusName] != ajaxConfig.response.statusCode) {\n\t\t\t\t\treElem.find('dd.' + FORM_NONE).addClass(FORM_EMPTY).text(res[ajaxConfig.response.msgName]);\n\t\t\t\t} else {\n\t\t\t\t\treElem.find('dd.' + FORM_NONE).removeClass(FORM_EMPTY);\n\t\t\t\t\t_this5.renderData(id, res[ajaxConfig.response.dataName], isLinkage, linkageWidth, isSearch, isReplace);\n\t\t\t\t\tdata[id].config.isEmpty = res[ajaxConfig.response.dataName].length == 0;\n\t\t\t\t}\n\t\t\t\tsuccessCallback && successCallback(id);\n\t\t\t\tajaxConfig.success && ajaxConfig.success instanceof Function && ajaxConfig.success(id, searchUrl, inputValue, res);\n\t\t\t},\n\t\t\terror: function error(err) {\n\t\t\t\treElem.find('dd[lay-value]:not(.' + FORM_SELECT_TIPS + ')').remove();\n\t\t\t\treElem.find('dd.' + FORM_NONE).addClass(FORM_EMPTY).text('服务异常');\n\t\t\t\tajaxConfig.error && ajaxConfig.error instanceof Function && ajaxConfig.error(id, searchUrl, inputValue, err);\n\t\t\t}\n\t\t});\n\t};\n\n\tCommon.prototype.renderData = function (id, dataArr, linkage, linkageWidth, isSearch, isReplace) {\n\t\tvar _this6 = this;\n\n\t\tif (linkage) {\n\t\t\t//渲染多级联动\n\t\t\tthis.renderLinkage(id, dataArr, linkageWidth);\n\t\t\treturn;\n\t\t}\n\t\tif (isReplace) {\n\t\t\tthis.renderReplace(id, dataArr);\n\t\t\treturn;\n\t\t}\n\n\t\tvar reElem = $('.' + PNAME + ' dl[xid=\"' + id + '\"]').parents('.' + FORM_SELECT);\n\t\tvar ajaxConfig = ajaxs[id] ? ajaxs[id] : ajax;\n\t\tvar pcInput = reElem.find('.' + TDIV + ' input');\n\n\t\tdataArr = this.exchangeData(id, dataArr);\n\t\tvar values = [];\n\t\treElem.find('dl').html(this.renderSelect(id, pcInput.attr('placeholder') || pcInput.attr('back'), dataArr.map(function (item) {\n\t\t\tvar itemVal = $.extend({}, item, {\n\t\t\t\tinnerHTML: item[ajaxConfig.keyName],\n\t\t\t\tvalue: item[ajaxConfig.keyVal],\n\t\t\t\tsel: item[ajaxConfig.keySel],\n\t\t\t\tdisabled: item[ajaxConfig.keyDis],\n\t\t\t\ttype: item.type,\n\t\t\t\tname: item[ajaxConfig.keyName]\n\t\t\t});\n\t\t\tif (itemVal.sel) {\n\t\t\t\tvalues.push(itemVal);\n\t\t\t}\n\t\t\treturn itemVal;\n\t\t})));\n\n\t\tvar label = reElem.find('.' + LABEL);\n\t\tvar dl = reElem.find('dl[xid]');\n\t\tif (isSearch) {\n\t\t\t//如果是远程搜索, 这里需要判重\n\t\t\tvar oldVal = data[id].values;\n\t\t\toldVal.forEach(function (item, index) {\n\t\t\t\tdl.find('dd[lay-value=\"' + item.value + '\"]').addClass(THIS);\n\t\t\t});\n\t\t\tvalues.forEach(function (item, index) {\n\t\t\t\tif (_this6.indexOf(oldVal, item) == -1) {\n\t\t\t\t\t_this6.addLabel(id, label, item);\n\t\t\t\t\tdl.find('dd[lay-value=\"' + item.value + '\"]').addClass(THIS);\n\t\t\t\t\toldVal.push(item);\n\t\t\t\t}\n\t\t\t});\n\t\t} else {\n\t\t\tvalues.forEach(function (item, index) {\n\t\t\t\t_this6.addLabel(id, label, item);\n\t\t\t\tdl.find('dd[lay-value=\"' + item.value + '\"]').addClass(THIS);\n\t\t\t});\n\t\t\tdata[id].values = values;\n\t\t}\n\t\tthis.commonHandler(id, label);\n\t};\n\n\tCommon.prototype.renderLinkage = function (id, dataArr, linkageWidth) {\n\t\tvar result = [],\n\t\t    index = 0,\n\t\t    temp = { \"0\": dataArr },\n\t\t    ajaxConfig = ajaxs[id] ? ajaxs[id] : ajax;\n\t\tdb[id] = {};\n\n\t\tvar _loop = function _loop() {\n\t\t\tvar group = result[index++] = [],\n\t\t\t    _temp = temp;\n\t\t\ttemp = {};\n\t\t\t$.each(_temp, function (pid, arr) {\n\t\t\t\t$.each(arr, function (idx, item) {\n\t\t\t\t\tvar val = {\n\t\t\t\t\t\tpid: pid,\n\t\t\t\t\t\tname: item[ajaxConfig.keyName],\n\t\t\t\t\t\tvalue: item[ajaxConfig.keyVal]\n\t\t\t\t\t};\n\t\t\t\t\tdb[id][val.value] = $.extend(item, val);\n\t\t\t\t\tgroup.push(val);\n\t\t\t\t\tvar children = item[ajaxConfig.keyChildren];\n\t\t\t\t\tif (children && children.length) {\n\t\t\t\t\t\ttemp[val.value] = children;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t});\n\t\t};\n\n\t\tdo {\n\t\t\t_loop();\n\t\t} while (Object.getOwnPropertyNames(temp).length);\n\n\t\tvar reElem = $('.' + PNAME + ' dl[xid=\"' + id + '\"]').parents('.' + FORM_SELECT);\n\t\tvar html = ['<div class=\"xm-select-linkage\">'];\n\n\t\t$.each(result, function (idx, arr) {\n\t\t\tvar groupDiv = ['<div style=\"left: ' + (linkageWidth - 0) * idx + 'px;\" class=\"xm-select-linkage-group xm-select-linkage-group' + (idx + 1) + ' ' + (idx != 0 ? 'xm-select-linkage-hide' : '') + '\">'];\n\t\t\t$.each(arr, function (idx2, item) {\n\t\t\t\tvar span = '<li title=\"' + item.name + '\" pid=\"' + item.pid + '\" xm-value=\"' + item.value + '\"><span>' + item.name + '</span></li>';\n\t\t\t\tgroupDiv.push(span);\n\t\t\t});\n\t\t\tgroupDiv.push('</div>');\n\t\t\thtml = html.concat(groupDiv);\n\t\t});\n\t\thtml.push('<div style=\"clear: both; height: 288px;\"></div>');\n\t\thtml.push('</div>');\n\t\treElem.find('dl').html(html.join(''));\n\t\treElem.find('.' + INPUT).css('display', 'none'); //联动暂时不支持搜索\n\t};\n\n\tCommon.prototype.renderReplace = function (id, dataArr) {\n\t\tvar _this7 = this;\n\n\t\tvar dl = $('.' + PNAME + ' dl[xid=\"' + id + '\"]');\n\t\tvar ajaxConfig = ajaxs[id] ? ajaxs[id] : ajax;\n\n\t\tdataArr = this.exchangeData(id, dataArr);\n\t\tdb[id] = dataArr;\n\n\t\tvar html = dataArr.map(function (item) {\n\t\t\tvar itemVal = $.extend({}, item, {\n\t\t\t\tinnerHTML: item[ajaxConfig.keyName],\n\t\t\t\tvalue: item[ajaxConfig.keyVal],\n\t\t\t\tsel: item[ajaxConfig.keySel],\n\t\t\t\tdisabled: item[ajaxConfig.keyDis],\n\t\t\t\ttype: item.type,\n\t\t\t\tname: item[ajaxConfig.keyName]\n\t\t\t});\n\t\t\treturn _this7.createDD(id, itemVal);\n\t\t}).join('');\n\n\t\tdl.find('dd:not(.' + FORM_SELECT_TIPS + '),dt:not([style])').remove();\n\t\tdl.find('dt[style]').after($(html));\n\t};\n\n\tCommon.prototype.exchangeData = function (id, arr) {\n\t\t//这里处理树形结构\n\t\tvar ajaxConfig = ajaxs[id] ? ajaxs[id] : ajax;\n\t\tvar childrenName = ajaxConfig['keyChildren'];\n\t\tvar disabledName = ajaxConfig['keyDis'];\n\t\tdb[id] = {};\n\t\tvar result = this.getChildrenList(arr, childrenName, disabledName, [], false);\n\t\treturn result;\n\t};\n\n\tCommon.prototype.getChildrenList = function (arr, childrenName, disabledName, pid, disabled) {\n\t\tvar result = [],\n\t\t    offset = 0;\n\t\tfor (var a = 0; a < arr.length; a++) {\n\t\t\tvar item = arr[a];\n\t\t\tif (item.type && item.type == 'optgroup') {\n\t\t\t\tresult.push(item);\n\t\t\t\tcontinue;\n\t\t\t} else {\n\t\t\t\toffset++;\n\t\t\t}\n\t\t\tvar parentIds = pid.concat([]);\n\t\t\tparentIds.push(offset - 1 + '_E');\n\t\t\titem[FORM_TEAM_PID] = JSON.stringify(parentIds);\n\t\t\titem[disabledName] = item[disabledName] || disabled;\n\t\t\tresult.push(item);\n\t\t\tvar child = item[childrenName];\n\t\t\tif (child && common.isArray(child) && child.length) {\n\t\t\t\titem['XM_TREE_FOLDER'] = true;\n\t\t\t\tvar pidArr = parentIds.concat([]);\n\t\t\t\tvar childResult = this.getChildrenList(child, childrenName, disabledName, pidArr, item[disabledName]);\n\t\t\t\tresult = result.concat(childResult);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t};\n\n\tCommon.prototype.create = function (id, isCreate, inputValue) {\n\t\tif (isCreate && inputValue) {\n\t\t\tvar fs = data[id],\n\t\t\t    dl = $('[xid=\"' + id + '\"]'),\n\t\t\t    tips = dl.find('dd.' + FORM_SELECT_TIPS + '.' + FORM_DL_INPUT),\n\t\t\t    tdd = null,\n\t\t\t    temp = dl.find('dd.' + TEMP);\n\t\t\tdl.find('dd:not(.' + FORM_SELECT_TIPS + '):not(.' + TEMP + ')').each(function (index, item) {\n\t\t\t\tif (inputValue == $(item).find('span').attr('name')) {\n\t\t\t\t\ttdd = item;\n\t\t\t\t}\n\t\t\t});\n\t\t\tif (!tdd) {\n\t\t\t\t//如果不存在, 则创建\n\t\t\t\tvar val = fs.config.create(id, inputValue);\n\t\t\t\tif (temp[0]) {\n\t\t\t\t\ttemp.attr('lay-value', val);\n\t\t\t\t\ttemp.find('span').text(inputValue);\n\t\t\t\t\ttemp.find('span').attr(\"name\", inputValue);\n\t\t\t\t\ttemp.removeClass(DD_HIDE);\n\t\t\t\t} else {\n\t\t\t\t\ttips.after($(this.createDD(id, {\n\t\t\t\t\t\tname: inputValue,\n\t\t\t\t\t\tinnerHTML: inputValue,\n\t\t\t\t\t\tvalue: val\n\t\t\t\t\t}, TEMP + ' ' + CREATE_LONG)));\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t$('[xid=' + id + '] dd.' + TEMP).remove();\n\t\t}\n\t};\n\n\tCommon.prototype.createDD = function (id, item, clz) {\n\t\tvar ajaxConfig = ajaxs[id] ? ajaxs[id] : ajax;\n\t\tvar name = $.trim(item.innerHTML);\n\t\tdb[id][item.value] = $(item).is('option') ? item = function () {\n\t\t\tvar resultItem = {};\n\t\t\tresultItem[ajaxConfig.keyName] = name;\n\t\t\tresultItem[ajaxConfig.keyVal] = item.value;\n\t\t\tresultItem[ajaxConfig.keyDis] = item.disabled;\n\t\t\treturn resultItem;\n\t\t}() : item;\n\t\tvar template = data[id].config.template(id, item);\n\t\tvar pid = item[FORM_TEAM_PID];\n\t\tpid ? pid = JSON.parse(pid) : pid = [-1];\n\t\tvar attr = pid[0] == -1 ? '' : 'tree-id=\"' + pid.join('-') + '\" tree-folder=\"' + !!item['XM_TREE_FOLDER'] + '\"';\n\t\treturn '<dd lay-value=\"' + item.value + '\" class=\"' + (item.disabled ? DISABLED : '') + ' ' + (clz ? clz : '') + '\" ' + attr + '>\\n\\t\\t\\t\\t\\t<div class=\"xm-unselect xm-form-checkbox ' + (item.disabled ? DISABLED : '') + '\"  style=\"margin-left: ' + (pid.length - 1) * 30 + 'px\">\\n\\t\\t\\t\\t\\t\\t<i class=\"' + CHECKBOX_YES + '\"></i>\\n\\t\\t\\t\\t\\t\\t<span name=\"' + name + '\">' + template + '</span>\\n\\t\\t\\t\\t\\t</div>\\n\\t\\t\\t\\t</dd>';\n\t};\n\n\tCommon.prototype.createQuickBtn = function (obj, right) {\n\t\treturn '<div class=\"' + CZ + '\" method=\"' + obj.name + '\" title=\"' + obj.name + '\" ' + (right ? 'style=\"margin-right: ' + right + '\"' : '') + '><i class=\"' + obj.icon + '\"></i><span>' + obj.name + '</span></div>';\n\t};\n\n\tCommon.prototype.renderBtns = function (id, show, right) {\n\t\tvar _this8 = this;\n\n\t\tvar quickBtn = [];\n\t\tvar dl = $('dl[xid=\"' + id + '\"]');\n\t\tquickBtn.push('<div class=\"' + CZ_GROUP + '\" show=\"' + show + '\" style=\"max-width: ' + (dl.prev().width() - 54) + 'px;\">');\n\t\t$.each(data[id].config.btns, function (index, item) {\n\t\t\tquickBtn.push(_this8.createQuickBtn(item, right));\n\t\t});\n\t\tquickBtn.push('</div>');\n\t\tquickBtn.push(this.createQuickBtn({ icon: 'xm-iconfont icon-caidan', name: '' }));\n\t\treturn quickBtn.join('');\n\t};\n\n\tCommon.prototype.renderSelect = function (id, tips, select) {\n\t\tvar _this9 = this;\n\n\t\tdb[id] = {};\n\t\tvar arr = [];\n\t\tif (data[id].config.btns.length) {\n\t\t\tsetTimeout(function () {\n\t\t\t\tvar dl = $('dl[xid=\"' + id + '\"]');\n\t\t\t\tdl.parents('.' + FORM_SELECT).attr(SEARCH_TYPE, data[id].config.searchType);\n\t\t\t\tdl.find('.' + CZ_GROUP).css('max-width', dl.prev().width() - 54 + 'px');\n\t\t\t}, 10);\n\t\t\tarr.push(['<dd lay-value=\"\" class=\"' + FORM_SELECT_TIPS + '\" style=\"background-color: #FFF!important;\">', this.renderBtns(id, null, '30px'), '</dd>', '<dd lay-value=\"\" class=\"' + FORM_SELECT_TIPS + ' ' + FORM_DL_INPUT + '\" style=\"background-color: #FFF!important;\">', '<i class=\"xm-iconfont icon-sousuo\"></i>', '<input type=\"text\" class=\"' + FORM_INPUT + ' ' + INPUT + '\" placeholder=\"\\u8BF7\\u641C\\u7D22\"/>', '</dd>'].join(''));\n\t\t} else {\n\t\t\tarr.push('<dd lay-value=\"\" class=\"' + FORM_SELECT_TIPS + '\">' + tips + '</dd>');\n\t\t}\n\t\tif (this.isArray(select)) {\n\t\t\t$(select).each(function (index, item) {\n\t\t\t\tif (item) {\n\t\t\t\t\tif (item.type && item.type === 'optgroup') {\n\t\t\t\t\t\tarr.push('<dt>' + item.name + '</dt>');\n\t\t\t\t\t} else {\n\t\t\t\t\t\tarr.push(_this9.createDD(id, item));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t} else {\n\t\t\t$(select).find('*').each(function (index, item) {\n\t\t\t\tif (item.tagName.toLowerCase() == 'option' && index == 0 && !item.value) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (item.tagName.toLowerCase() === 'optgroup') {\n\t\t\t\t\tarr.push('<dt>' + item.label + '</dt>');\n\t\t\t\t} else {\n\t\t\t\t\tarr.push(_this9.createDD(id, item));\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\tarr.push('<dt style=\"display:none;\"> </dt>');\n\t\tarr.push('<dd class=\"' + FORM_SELECT_TIPS + ' ' + FORM_NONE + ' ' + (arr.length === 2 ? FORM_EMPTY : '') + '\">\\u6CA1\\u6709\\u9009\\u9879</dd>');\n\t\treturn arr.join('');\n\t};\n\n\tCommon.prototype.on = function () {\n\t\tvar _this10 = this;\n\n\t\t//事件绑定\n\t\tthis.one();\n\n\t\t$(document).on('click', function (e) {\n\t\t\tif (!$(e.target).parents('.' + FORM_TITLE)[0]) {\n\t\t\t\t//清空input中的值\n\t\t\t\t$('.' + PNAME + ' dl .' + DD_HIDE).removeClass(DD_HIDE);\n\t\t\t\t$('.' + PNAME + ' dl dd.' + FORM_EMPTY).removeClass(FORM_EMPTY);\n\t\t\t\t$('.' + PNAME + ' dl dd.' + TEMP).remove();\n\t\t\t\t$.each(data, function (key, fs) {\n\t\t\t\t\t_this10.clearInput(key);\n\t\t\t\t\tif (!fs.values.length) {\n\t\t\t\t\t\t_this10.changePlaceHolder($('div[FS_ID=\"' + key + '\"] .' + LABEL));\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\t$('.' + PNAME + ' .' + FORM_SELECTED).each(function (index, item) {\n\t\t\t\t_this10.changeShow($(item).find('.' + FORM_TITLE), false);\n\t\t\t});\n\t\t});\n\t};\n\n\tCommon.prototype.calcLabelLeft = function (label, w, call) {\n\t\tvar pos = this.getPosition(label[0]);\n\t\tpos.y = pos.x + label[0].clientWidth;\n\t\tvar left = label[0].offsetLeft;\n\t\tif (!label.find('span').length) {\n\t\t\tleft = 0;\n\t\t} else if (call) {\n\t\t\t//校正归位\n\t\t\tvar span = label.find('span:last');\n\t\t\tspan.css('display') == 'none' ? span = span.prev()[0] : span = span[0];\n\t\t\tvar spos = this.getPosition(span);\n\t\t\tspos.y = spos.x + span.clientWidth;\n\n\t\t\tif (spos.y > pos.y) {\n\t\t\t\tleft = left - (spos.y - pos.y) - 5;\n\t\t\t} else {\n\t\t\t\tleft = 0;\n\t\t\t}\n\t\t} else {\n\t\t\tif (w < 0) {\n\t\t\t\tvar _span = label.find(':last');\n\t\t\t\t_span.css('display') == 'none' ? _span = _span.prev()[0] : _span = _span[0];\n\t\t\t\tvar _spos = this.getPosition(_span);\n\t\t\t\t_spos.y = _spos.x + _span.clientWidth;\n\t\t\t\tif (_spos.y > pos.y) {\n\t\t\t\t\tleft -= 10;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (left < 0) {\n\t\t\t\t\tleft += 10;\n\t\t\t\t}\n\t\t\t\tif (left > 0) {\n\t\t\t\t\tleft = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tlabel.css('left', left + 'px');\n\t};\n\n\tCommon.prototype.one = function (target) {\n\t\tvar _this11 = this;\n\n\t\t//一次性事件绑定\n\t\t$(target ? target : document).off('click', '.' + FORM_TITLE).on('click', '.' + FORM_TITLE, function (e) {\n\t\t\tvar othis = $(e.target),\n\t\t\t    title = othis.is(FORM_TITLE) ? othis : othis.parents('.' + FORM_TITLE),\n\t\t\t    dl = title.next(),\n\t\t\t    id = dl.attr('xid');\n\n\t\t\t//清空非本select的input val\n\t\t\t$('dl[xid]').not(dl).each(function (index, item) {\n\t\t\t\t_this11.clearInput($(item).attr('xid'));\n\t\t\t});\n\t\t\t$('dl[xid]').not(dl).find('dd.' + DD_HIDE).removeClass(DD_HIDE);\n\n\t\t\t//如果是disabled select\n\t\t\tif (title.hasClass(DIS)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t//如果点击的是右边的三角或者只读的input\n\t\t\tif (othis.is('.' + SANJIAO) || othis.is('.' + INPUT + '[readonly]')) {\n\t\t\t\t_this11.changeShow(title, !title.parents('.' + FORM_SELECT).hasClass(FORM_SELECTED));\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t//如果点击的是input的右边, focus一下\n\t\t\tif (title.find('.' + INPUT + ':not(readonly)')[0]) {\n\t\t\t\tvar input = title.find('.' + INPUT),\n\t\t\t\t    epos = { x: e.pageX, y: e.pageY },\n\t\t\t\t    pos = _this11.getPosition(title[0]),\n\t\t\t\t    width = title.width();\n\t\t\t\twhile (epos.x > pos.x) {\n\t\t\t\t\tif ($(document.elementFromPoint(epos.x, epos.y)).is(input)) {\n\t\t\t\t\t\tinput.focus();\n\t\t\t\t\t\t_this11.changeShow(title, true);\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\tepos.x -= 50;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t//如果点击的是可搜索的input\n\t\t\tif (othis.is('.' + INPUT)) {\n\t\t\t\t_this11.changeShow(title, true);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t//如果点击的是x按钮\n\t\t\tif (othis.is('i[fsw=\"' + NAME + '\"]')) {\n\t\t\t\tvar val = _this11.getItem(id, othis),\n\t\t\t\t    dd = dl.find('dd[lay-value=\\'' + val.value + '\\']');\n\t\t\t\tif (dd.hasClass(DISABLED)) {\n\t\t\t\t\t//如果是disabled状态, 不可选, 不可删\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\t_this11.handlerLabel(id, dd, false, val);\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t_this11.changeShow(title, !title.parents('.' + FORM_SELECT).hasClass(FORM_SELECTED));\n\t\t\treturn false;\n\t\t});\n\t\t$(target ? target : document).off('click', 'dl.' + DL).on('click', 'dl.' + DL, function (e) {\n\t\t\tvar othis = $(e.target);\n\t\t\tif (othis.is('.' + LINKAGE) || othis.parents('.' + LINKAGE)[0]) {\n\t\t\t\t//linkage的处理\n\t\t\t\tothis = othis.is('li') ? othis : othis.parents('li[xm-value]');\n\t\t\t\tvar _group = othis.parents('.xm-select-linkage-group'),\n\t\t\t\t    _id = othis.parents('dl').attr('xid');\n\t\t\t\tif (!_id) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\t//激活li\n\t\t\t\t_group.find('.xm-select-active').removeClass('xm-select-active');\n\t\t\t\tothis.addClass('xm-select-active');\n\t\t\t\t//激活下一个group, 激活前显示对应数据\n\t\t\t\t_group.nextAll('.xm-select-linkage-group').addClass('xm-select-linkage-hide');\n\t\t\t\tvar nextGroup = _group.next('.xm-select-linkage-group');\n\t\t\t\tnextGroup.find('li').addClass('xm-select-linkage-hide');\n\t\t\t\tnextGroup.find('li[pid=\"' + othis.attr('xm-value') + '\"]').removeClass('xm-select-linkage-hide');\n\t\t\t\t//如果没有下一个group, 或没有对应的值\n\t\t\t\tif (!nextGroup[0] || nextGroup.find('li:not(.xm-select-linkage-hide)').length == 0) {\n\t\t\t\t\tvar vals = [],\n\t\t\t\t\t    index = 0,\n\t\t\t\t\t    isAdd = !othis.hasClass('xm-select-this');\n\t\t\t\t\tif (data[_id].config.radio) {\n\t\t\t\t\t\tothis.parents('.xm-select-linkage').find('.xm-select-this').removeClass('xm-select-this');\n\t\t\t\t\t}\n\t\t\t\t\tdo {\n\t\t\t\t\t\tvals[index++] = {\n\t\t\t\t\t\t\tname: othis.find('span').text(),\n\t\t\t\t\t\t\tvalue: othis.attr('xm-value')\n\t\t\t\t\t\t};\n\t\t\t\t\t\tothis = othis.parents('.xm-select-linkage-group').prev().find('li[xm-value=\"' + othis.attr('pid') + '\"]');\n\t\t\t\t\t} while (othis.length);\n\t\t\t\t\tvals.reverse();\n\t\t\t\t\tvar val = {\n\t\t\t\t\t\tname: vals.map(function (item) {\n\t\t\t\t\t\t\treturn item.name;\n\t\t\t\t\t\t}).join('/'),\n\t\t\t\t\t\tvalue: vals.map(function (item) {\n\t\t\t\t\t\t\treturn item.value;\n\t\t\t\t\t\t}).join('/')\n\t\t\t\t\t};\n\t\t\t\t\t_this11.handlerLabel(_id, null, isAdd, val);\n\t\t\t\t} else {\n\t\t\t\t\tnextGroup.removeClass('xm-select-linkage-hide');\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tif (othis.is('dl')) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tif (othis.is('dt')) {\n\t\t\t\tothis.nextUntil('dt').each(function (index, item) {\n\t\t\t\t\titem = $(item);\n\t\t\t\t\tif (item.hasClass(DISABLED) || item.hasClass(THIS)) {} else {\n\t\t\t\t\t\titem.find('i:not(.icon-expand)').click();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tvar dd = othis.is('dd') ? othis : othis.parents('dd');\n\t\t\tvar id = dd.parent('dl').attr('xid');\n\n\t\t\tif (dd.hasClass(DISABLED)) {\n\t\t\t\t//被禁用选项的处理\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t//菜单功效\n\t\t\tif (othis.is('i.icon-caidan')) {\n\t\t\t\tvar opens = [],\n\t\t\t\t    closes = [];\n\t\t\t\tothis.parents('dl').find('dd[tree-folder=\"true\"]').each(function (index, item) {\n\t\t\t\t\t$(item).attr('xm-tree-hidn') == undefined ? opens.push(item) : closes.push(item);\n\t\t\t\t});\n\t\t\t\tvar arr = closes.length ? closes : opens;\n\t\t\t\tarr.forEach(function (item) {\n\t\t\t\t\treturn item.click();\n\t\t\t\t});\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t//树状结构的选择\n\t\t\tvar treeId = dd.attr('tree-id');\n\t\t\tif (treeId) {\n\t\t\t\t//忽略右边的图标\n\t\t\t\tif (othis.is('i:not(.icon-expand)')) {\n\t\t\t\t\t_this11.handlerLabel(id, dd, !dd.hasClass(THIS));\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tvar ajaxConfig = ajaxs[id] || ajax;\n\t\t\t\tvar treeConfig = ajaxConfig.tree;\n\t\t\t\tvar childrens = dd.nextAll('dd[tree-id^=\"' + treeId + '\"]');\n\t\t\t\tif (childrens && childrens.length) {\n\t\t\t\t\tvar len = childrens[0].clientHeight;\n\t\t\t\t\tlen ? (_this11.addTreeHeight(dd, len), len = 0) : (len = dd.attr('xm-tree-hidn') || 36, dd.removeAttr('xm-tree-hidn'), dd.find('>i').remove(), childrens = childrens.filter(function (index, item) {\n\t\t\t\t\t\treturn $(item).attr('tree-id').split('-').length - 1 == treeId.split('-').length;\n\t\t\t\t\t}));\n\t\t\t\t\tchildrens.animate({\n\t\t\t\t\t\theight: len\n\t\t\t\t\t}, 150);\n\t\t\t\t\treturn false;\n\t\t\t\t} else {\n\t\t\t\t\tif (treeConfig.nextClick && treeConfig.nextClick instanceof Function) {\n\t\t\t\t\t\ttreeConfig.nextClick(id, _this11.getItem(id, dd), function (res) {\n\t\t\t\t\t\t\tif (!res || !res.length) {\n\t\t\t\t\t\t\t\t_this11.handlerLabel(id, dd, !dd.hasClass(THIS));\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdd.attr('tree-folder', 'true');\n\t\t\t\t\t\t\t\tvar ddChilds = [];\n\t\t\t\t\t\t\t\tres.forEach(function (item, idx) {\n\t\t\t\t\t\t\t\t\titem.innerHTML = item[ajaxConfig.keyName];\n\t\t\t\t\t\t\t\t\titem[FORM_TEAM_PID] = JSON.stringify(treeId.split('-').concat([idx]));\n\t\t\t\t\t\t\t\t\tddChilds.push(_this11.createDD(id, item));\n\t\t\t\t\t\t\t\t\tdb[id][item[ajaxConfig.keyVal]] = item;\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tdd.after(ddChilds.join(''));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (dd.hasClass(FORM_SELECT_TIPS)) {\n\t\t\t\t//tips的处理\n\t\t\t\tvar btn = othis.is('.' + CZ) ? othis : othis.parents('.' + CZ);\n\t\t\t\tif (!btn[0]) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tvar method = btn.attr('method');\n\t\t\t\tvar obj = data[id].config.btns.filter(function (bean) {\n\t\t\t\t\treturn bean.name == method;\n\t\t\t\t})[0];\n\t\t\t\tobj && obj.click && obj.click instanceof Function && obj.click(id, _this11);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t_this11.handlerLabel(id, dd, !dd.hasClass(THIS));\n\t\t\treturn false;\n\t\t});\n\t};\n\n\tCommon.prototype.addTreeHeight = function (dd, len) {\n\t\tvar _this12 = this;\n\n\t\tvar treeId = dd.attr('tree-id');\n\t\tvar childrens = dd.nextAll('dd[tree-id^=\"' + treeId + '\"]');\n\t\tif (childrens.length) {\n\t\t\tdd.append('<i class=\"xm-iconfont icon-expand\"></i>');\n\t\t\tdd.attr('xm-tree-hidn', len);\n\t\t\tchildrens.each(function (index, item) {\n\t\t\t\tvar that = $(item);\n\t\t\t\t_this12.addTreeHeight(that, len);\n\t\t\t});\n\t\t}\n\t};\n\n\tvar db = {};\n\tCommon.prototype.getItem = function (id, value) {\n\t\tif (value instanceof $) {\n\t\t\tif (value.is('i[fsw=\"' + NAME + '\"]')) {\n\t\t\t\tvar span = value.parent();\n\t\t\t\treturn db[id][value] || {\n\t\t\t\t\tname: span.find('font').text(),\n\t\t\t\t\tvalue: span.attr('value')\n\t\t\t\t};\n\t\t\t}\n\t\t\tvar val = value.attr('lay-value');\n\t\t\treturn !db[id][val] ? db[id][val] = {\n\t\t\t\tname: value.find('span[name]').attr('name'),\n\t\t\t\tvalue: val\n\t\t\t} : db[id][val];\n\t\t} else if (typeof value == 'string' && value.indexOf('/') != -1) {\n\t\t\treturn db[id][value] || {\n\t\t\t\tname: this.valToName(id, value),\n\t\t\t\tvalue: value\n\t\t\t};\n\t\t}\n\t\treturn db[id][value];\n\t};\n\n\tCommon.prototype.linkageAdd = function (id, val) {\n\t\tvar dl = $('dl[xid=\"' + id + '\"]');\n\t\tdl.find('.xm-select-active').removeClass('xm-select-active');\n\t\tvar vs = val.value.split('/');\n\t\tvar pid = void 0,\n\t\t    li = void 0,\n\t\t    index = 0;\n\t\tvar lis = [];\n\t\tdo {\n\t\t\tpid = vs[index];\n\t\t\tli = dl.find('.xm-select-linkage-group' + (index + 1) + ' li[xm-value=\"' + pid + '\"]');\n\t\t\tli[0] && lis.push(li);\n\t\t\tindex++;\n\t\t} while (li.length && pid != undefined);\n\t\tif (lis.length == vs.length) {\n\t\t\t$.each(lis, function (idx, item) {\n\t\t\t\titem.addClass('xm-select-this');\n\t\t\t});\n\t\t}\n\t};\n\n\tCommon.prototype.linkageDel = function (id, val) {\n\t\tvar dl = $('dl[xid=\"' + id + '\"]');\n\t\tvar vs = val.value.split('/');\n\t\tvar pid = void 0,\n\t\t    li = void 0,\n\t\t    index = vs.length - 1;\n\t\tdo {\n\t\t\tpid = vs[index];\n\t\t\tli = dl.find('.xm-select-linkage-group' + (index + 1) + ' li[xm-value=\"' + pid + '\"]');\n\t\t\tif (!li.parent().next().find('li[pid=' + pid + '].xm-select-this').length) {\n\t\t\t\tli.removeClass('xm-select-this');\n\t\t\t}\n\t\t\tindex--;\n\t\t} while (li.length && pid != undefined);\n\t};\n\n\tCommon.prototype.valToName = function (id, val) {\n\t\tvar dl = $('dl[xid=\"' + id + '\"]');\n\t\tvar vs = (val + \"\").split('/');\n\t\tif (!vs.length) {\n\t\t\treturn null;\n\t\t}\n\t\tvar names = [];\n\t\t$.each(vs, function (idx, item) {\n\t\t\tvar name = dl.find('.xm-select-linkage-group' + (idx + 1) + ' li[xm-value=\"' + item + '\"] span').text();\n\t\t\tnames.push(name);\n\t\t});\n\t\treturn names.length == vs.length ? names.join('/') : null;\n\t};\n\n\tCommon.prototype.commonHandler = function (key, label) {\n\t\tif (!label || !label[0]) {\n\t\t\treturn;\n\t\t}\n\t\tthis.checkHideSpan(key, label);\n\t\t//计算input的提示语\n\t\tthis.changePlaceHolder(label);\n\t\t//计算高度\n\t\tthis.retop(label.parents('.' + FORM_SELECT));\n\t\tthis.calcLabelLeft(label, 0, true);\n\t\t//表单默认值\n\t\tthis.setHidnVal(key, label);\n\t\t//title值\n\t\tlabel.parents('.' + FORM_TITLE + ' .' + NAME).attr('title', data[key].values.map(function (val) {\n\t\t\treturn val.name;\n\t\t}).join(','));\n\t};\n\n\tCommon.prototype.initVal = function (id) {\n\t\tvar _this13 = this;\n\n\t\tvar target = {};\n\t\tif (id) {\n\t\t\ttarget[id] = data[id];\n\t\t} else {\n\t\t\ttarget = data;\n\t\t}\n\t\t$.each(target, function (key, val) {\n\t\t\tvar values = val.values,\n\t\t\t    div = $('dl[xid=\"' + key + '\"]').parent(),\n\t\t\t    label = div.find('.' + LABEL),\n\t\t\t    dl = div.find('dl');\n\t\t\tdl.find('dd.' + THIS).removeClass(THIS);\n\n\t\t\tvar _vals = values.concat([]);\n\t\t\t_vals.concat([]).forEach(function (item, index) {\n\t\t\t\t_this13.addLabel(key, label, item);\n\t\t\t\tdl.find('dd[lay-value=\"' + item.value + '\"]').addClass(THIS);\n\t\t\t});\n\t\t\tif (val.config.radio) {\n\t\t\t\t_vals.length && values.push(_vals[_vals.length - 1]);\n\t\t\t}\n\t\t\t_this13.commonHandler(key, label);\n\t\t});\n\t};\n\n\tCommon.prototype.setHidnVal = function (key, label) {\n\t\tif (!label || !label[0]) {\n\t\t\treturn;\n\t\t}\n\t\tlabel.parents('.' + PNAME).find('.' + HIDE_INPUT).val(data[key].values.map(function (val) {\n\t\t\treturn val.value;\n\t\t}).join(','));\n\t};\n\n\tCommon.prototype.handlerLabel = function (id, dd, isAdd, oval, notOn) {\n\t\tvar div = $('[xid=\"' + id + '\"]').prev().find('.' + LABEL),\n\t\t    val = dd && this.getItem(id, dd),\n\t\t    vals = data[id].values,\n\t\t    on = data[id].config.on || events.on[id],\n\t\t    endOn = data[id].config.endOn || events.endOn[id];\n\t\tif (oval) {\n\t\t\tval = oval;\n\t\t}\n\t\tvar fs = data[id];\n\t\tif (isAdd && fs.config.max && fs.values.length >= fs.config.max) {\n\t\t\tvar maxTipsFun = events.maxTips[id] || data[id].config.maxTips;\n\t\t\tmaxTipsFun && maxTipsFun(id, vals.concat([]), val, fs.config.max);\n\t\t\treturn;\n\t\t}\n\t\tif (!notOn) {\n\t\t\tif (on && on instanceof Function && on(id, vals.concat([]), val, isAdd, dd && dd.hasClass(DISABLED)) == false) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tvar dl = $('dl[xid=\"' + id + '\"]');\n\t\tisAdd ? (dd && dd[0] ? (dd.addClass(THIS), dd.removeClass(TEMP)) : dl.find('.xm-select-linkage')[0] && this.linkageAdd(id, val), this.addLabel(id, div, val), vals.push(val)) : (dd && dd[0] ? dd.removeClass(THIS) : dl.find('.xm-select-linkage')[0] && this.linkageDel(id, val), this.delLabel(id, div, val), this.remove(vals, val));\n\t\tif (!div[0]) return;\n\t\t//单选选完后直接关闭选择域\n\t\tif (fs.config.radio) {\n\t\t\tthis.changeShow(div, false);\n\t\t}\n\t\t//移除表单验证的红色边框\n\t\tdiv.parents('.' + FORM_TITLE).prev().removeClass('layui-form-danger');\n\n\t\t//清空搜索值\n\t\tfs.config.clearInput && this.clearInput(id);\n\n\t\tthis.commonHandler(id, div);\n\n\t\t!notOn && endOn && endOn instanceof Function && endOn(id, vals.concat([]), val, isAdd, dd && dd.hasClass(DISABLED));\n\t};\n\n\tCommon.prototype.addLabel = function (id, div, val) {\n\t\tif (!val) return;\n\t\tvar tips = 'fsw=\"' + NAME + '\"';\n\t\tvar _ref = [$('<span ' + tips + ' value=\"' + val.value + '\"><font ' + tips + '>' + val.name + '</font></span>'), $('<i ' + tips + ' class=\"xm-iconfont icon-close\"></i>')],\n\t\t    $label = _ref[0],\n\t\t    $close = _ref[1];\n\n\t\t$label.append($close);\n\t\t//如果是radio模式\n\t\tvar fs = data[id];\n\t\tif (fs.config.radio) {\n\t\t\tfs.values.length = 0;\n\t\t\t$('dl[xid=\"' + id + '\"]').find('dd.' + THIS + ':not([lay-value=\"' + val.value + '\"])').removeClass(THIS);\n\t\t\tdiv.find('span').remove();\n\t\t}\n\t\t//如果是固定高度\n\t\tdiv.find('input').css('width', '50px');\n\t\tdiv.find('input').before($label);\n\t};\n\n\tCommon.prototype.delLabel = function (id, div, val) {\n\t\tif (!val) return;\n\t\tdiv.find('span[value=\"' + val.value + '\"]:first').remove();\n\t};\n\n\tCommon.prototype.checkHideSpan = function (id, div) {\n\t\tvar parentHeight = div.parents('.' + NAME)[0].offsetHeight + 5;\n\t\tdiv.find('span.xm-span-hide').removeClass('xm-span-hide');\n\t\tdiv.find('span[style]').remove();\n\n\t\tvar count = data[id].config.showCount;\n\t\tdiv.find('span').each(function (index, item) {\n\t\t\tif (index >= count) {\n\t\t\t\t$(item).addClass('xm-span-hide');\n\t\t\t}\n\t\t});\n\n\t\tvar prefix = div.find('span:eq(' + count + ')');\n\t\tprefix[0] && prefix.before($('<span style=\"padding-right: 6px;\" fsw=\"' + NAME + '\"> + ' + (div.find('span').length - count) + '</span>'));\n\t};\n\n\tCommon.prototype.retop = function (div) {\n\t\t//计算dl显示的位置\n\t\tvar dl = div.find('dl'),\n\t\t    top = div.offset().top + div.outerHeight() + 5 - $win.scrollTop(),\n\t\t    dlHeight = dl.outerHeight();\n\t\tvar up = div.hasClass('layui-form-selectup') || dl.css('top').indexOf('-') != -1 || top + dlHeight > $win.height() && top >= dlHeight;\n\t\tdiv = div.find('.' + NAME);\n\n\t\tvar fs = data[dl.attr('xid')];\n\t\tvar base = dl.parents('.layui-form-pane')[0] && dl.prev()[0].clientHeight > 38 ? 14 : 10;\n\t\tif (fs && fs.config.direction == 'up' || up) {\n\t\t\tup = true;\n\t\t\tif (fs && fs.config.direction == 'down') {\n\t\t\t\tup = false;\n\t\t\t}\n\t\t}\n\t\tvar reHeight = div[0].offsetTop + div.height() + base;\n\t\tif (up) {\n\t\t\tdl.css({\n\t\t\t\ttop: 'auto',\n\t\t\t\tbottom: reHeight + 3 + 'px'\n\t\t\t});\n\t\t} else {\n\t\t\tdl.css({\n\t\t\t\ttop: reHeight + 'px',\n\t\t\t\tbottom: 'auto'\n\t\t\t});\n\t\t}\n\t};\n\n\tCommon.prototype.changeShow = function (children, isShow) {\n\t\t//显示于隐藏\n\t\t$('.layui-form-selected').removeClass('layui-form-selected');\n\t\tvar top = children.parents('.' + FORM_SELECT),\n\t\t    realShow = top.hasClass(FORM_SELECTED),\n\t\t    id = top.find('dl').attr('xid');\n\t\t$('.' + PNAME + ' .' + FORM_SELECT).not(top).removeClass(FORM_SELECTED);\n\t\tif (isShow) {\n\t\t\tthis.retop(top);\n\t\t\ttop.addClass(FORM_SELECTED);\n\t\t\ttop.find('.' + INPUT).focus();\n\t\t\tif (!top.find('dl dd[lay-value]:not(.' + FORM_SELECT_TIPS + ')').length) {\n\t\t\t\ttop.find('dl .' + FORM_NONE).addClass(FORM_EMPTY);\n\t\t\t}\n\t\t} else {\n\t\t\ttop.removeClass(FORM_SELECTED);\n\t\t\tthis.clearInput(id);\n\t\t\ttop.find('dl .' + FORM_EMPTY).removeClass(FORM_EMPTY);\n\t\t\ttop.find('dl dd.' + DD_HIDE).removeClass(DD_HIDE);\n\t\t\ttop.find('dl dd.' + TEMP).remove();\n\t\t\t//计算ajax数据是否为空, 然后重新请求数据\n\t\t\tif (id && data[id] && data[id].config.isEmpty) {\n\t\t\t\tthis.triggerSearch(top);\n\t\t\t}\n\t\t\tthis.changePlaceHolder(top.find('.' + LABEL));\n\t\t}\n\t\tif (isShow != realShow) {\n\t\t\tvar openFun = data[id].config.opened || events.opened[id];\n\t\t\tisShow && openFun && openFun instanceof Function && openFun(id);\n\t\t\tvar closeFun = data[id].config.closed || events.closed[id];\n\t\t\t!isShow && closeFun && closeFun instanceof Function && closeFun(id);\n\t\t}\n\t};\n\n\tCommon.prototype.changePlaceHolder = function (div) {\n\t\t//显示于隐藏提示语\n\t\t//调整pane模式下的高度\n\t\tvar title = div.parents('.' + FORM_TITLE);\n\t\ttitle[0] || (title = div.parents('dl').prev());\n\t\tif (!title[0]) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar id = div.parents('.' + PNAME).find('dl[xid]').attr('xid');\n\t\tif (data[id] && data[id].config.height) {//既然固定高度了, 那就看着办吧\n\n\t\t} else {\n\t\t\tvar height = title.find('.' + NAME)[0].clientHeight;\n\t\t\ttitle.css('height', (height > 36 ? height + 4 : height) + 'px');\n\t\t\t//如果是layui pane模式, 处理label的高度\n\t\t\tvar label = title.parents('.' + PNAME).parent().prev();\n\t\t\tif (label.is('.layui-form-label') && title.parents('.layui-form-pane')[0]) {\n\t\t\t\theight = height > 36 ? height + 4 : height;\n\t\t\t\ttitle.css('height', height + 'px');\n\t\t\t\tlabel.css({\n\t\t\t\t\theight: height + 2 + 'px',\n\t\t\t\t\tlineHeight: height - 18 + 'px'\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tvar input = title.find('.' + TDIV + ' input'),\n\t\t    isShow = !div.find('span:last')[0] && !title.find('.' + INPUT).val();\n\t\tif (isShow) {\n\t\t\tvar ph = input.attr('back');\n\t\t\tinput.removeAttr('back');\n\t\t\tinput.attr('placeholder', ph);\n\t\t} else {\n\t\t\tvar _ph = input.attr('placeholder');\n\t\t\tinput.removeAttr('placeholder');\n\t\t\tinput.attr('back', _ph);\n\t\t}\n\t};\n\n\tCommon.prototype.indexOf = function (arr, val) {\n\t\tfor (var i = 0; i < arr.length; i++) {\n\t\t\tif (arr[i].value == val || arr[i].value == (val ? val.value : val) || arr[i] == val || JSON.stringify(arr[i]) == JSON.stringify(val)) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t\treturn -1;\n\t};\n\n\tCommon.prototype.remove = function (arr, val) {\n\t\tvar idx = this.indexOf(arr, val ? val.value : val);\n\t\tif (idx > -1) {\n\t\t\tarr.splice(idx, 1);\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t};\n\n\tCommon.prototype.selectAll = function (id, isOn, skipDis) {\n\t\tvar _this14 = this;\n\n\t\tvar dl = $('[xid=\"' + id + '\"]');\n\t\tif (!dl[0]) {\n\t\t\treturn;\n\t\t}\n\t\tif (dl.find('.xm-select-linkage')[0]) {\n\t\t\treturn;\n\t\t}\n\t\tdl.find('dd[lay-value]:not(.' + FORM_SELECT_TIPS + '):not(.' + THIS + ')' + (skipDis ? ':not(.' + DISABLED + ')' : '')).each(function (index, item) {\n\t\t\titem = $(item);\n\t\t\tvar val = _this14.getItem(id, item);\n\t\t\t_this14.handlerLabel(id, dl.find('dd[lay-value=\"' + val.value + '\"]'), true, val, !isOn);\n\t\t});\n\t};\n\n\tCommon.prototype.removeAll = function (id, isOn, skipDis) {\n\t\tvar _this15 = this;\n\n\t\tvar dl = $('[xid=\"' + id + '\"]');\n\t\tif (!dl[0]) {\n\t\t\treturn;\n\t\t}\n\t\tif (dl.find('.xm-select-linkage')[0]) {\n\t\t\t//针对多级联动的处理\n\t\t\tdata[id].values.concat([]).forEach(function (item, idx) {\n\t\t\t\tvar vs = item.value.split('/');\n\t\t\t\tvar pid = void 0,\n\t\t\t\t    li = void 0,\n\t\t\t\t    index = 0;\n\t\t\t\tdo {\n\t\t\t\t\tpid = vs[index++];\n\t\t\t\t\tli = dl.find('.xm-select-linkage-group' + index + ':not(.xm-select-linkage-hide) li[xm-value=\"' + pid + '\"]');\n\t\t\t\t\tli.click();\n\t\t\t\t} while (li.length && pid != undefined);\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tdata[id].values.concat([]).forEach(function (item, index) {\n\t\t\tif (skipDis && dl.find('dd[lay-value=\"' + item.value + '\"]').hasClass(DISABLED)) {} else {\n\t\t\t\t_this15.handlerLabel(id, dl.find('dd[lay-value=\"' + item.value + '\"]'), false, item, !isOn);\n\t\t\t}\n\t\t});\n\t};\n\n\tCommon.prototype.reverse = function (id, isOn, skipDis) {\n\t\tvar _this16 = this;\n\n\t\tvar dl = $('[xid=\"' + id + '\"]');\n\t\tif (!dl[0]) {\n\t\t\treturn;\n\t\t}\n\t\tif (dl.find('.xm-select-linkage')[0]) {\n\t\t\treturn;\n\t\t}\n\t\tdl.find('dd[lay-value]:not(.' + FORM_SELECT_TIPS + ')' + (skipDis ? ':not(.' + DISABLED + ')' : '')).each(function (index, item) {\n\t\t\titem = $(item);\n\t\t\tvar val = _this16.getItem(id, item);\n\t\t\t_this16.handlerLabel(id, dl.find('dd[lay-value=\"' + val.value + '\"]'), !item.hasClass(THIS), val, !isOn);\n\t\t});\n\t};\n\n\tCommon.prototype.skin = function (id) {\n\t\tvar skins = ['default', 'primary', 'normal', 'warm', 'danger'];\n\t\tvar skin = skins[Math.floor(Math.random() * skins.length)];\n\t\t$('dl[xid=\"' + id + '\"]').parents('.' + PNAME).find('.' + FORM_SELECT).attr('xm-select-skin', skin);\n\t\tthis.check(id) && this.commonHandler(id, $('dl[xid=\"' + id + '\"]').parents('.' + PNAME).find('.' + LABEL));\n\t};\n\n\tCommon.prototype.getPosition = function (e) {\n\t\tvar x = 0,\n\t\t    y = 0;\n\t\twhile (e != null) {\n\t\t\tx += e.offsetLeft;\n\t\t\ty += e.offsetTop;\n\t\t\te = e.offsetParent;\n\t\t}\n\t\treturn { x: x, y: y };\n\t};\n\n\tCommon.prototype.onreset = function () {\n\t\t//监听reset按钮, 然后重置多选\n\t\t$(document).on('click', '[type=reset]', function (e) {\n\t\t\t$(e.target).parents('form').find('.' + PNAME + ' dl[xid]').each(function (index, item) {\n\t\t\t\tvar id = item.getAttribute('xid'),\n\t\t\t\t    dl = $(item),\n\t\t\t\t    dd = void 0,\n\t\t\t\t    temp = {};\n\t\t\t\tcommon.removeAll(id);\n\t\t\t\tdata[id].config.init.forEach(function (val, idx) {\n\t\t\t\t\tif (val && (!temp[val] || data[id].config.repeat) && (dd = dl.find('dd[lay-value=\"' + val.value + '\"]'))[0]) {\n\t\t\t\t\t\tcommon.handlerLabel(id, dd, true);\n\t\t\t\t\t\ttemp[val] = 1;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\t};\n\n\tCommon.prototype.bindEvent = function (name, id, fun) {\n\t\tif (id && id instanceof Function) {\n\t\t\tfun = id;\n\t\t\tid = null;\n\t\t}\n\t\tif (fun && fun instanceof Function) {\n\t\t\tif (!id) {\n\t\t\t\t$.each(data, function (id, val) {\n\t\t\t\t\tdata[id] ? data[id].config[name] = fun : events[name][id] = fun;\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tdata[id] ? (data[id].config[name] = fun, delete events[name][id]) : events[name][id] = fun;\n\t\t\t}\n\t\t}\n\t};\n\n\tCommon.prototype.check = function (id, notAutoRender) {\n\t\tif ($('dl[xid=\"' + id + '\"]').length) {\n\t\t\treturn true;\n\t\t} else if ($('select[xm-select=\"' + id + '\"]').length) {\n\t\t\tif (!notAutoRender) {\n\t\t\t\tthis.render(id, $('select[xm-select=\"' + id + '\"]'));\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} else {\n\t\t\tdelete data[id];\n\t\t\treturn false;\n\t\t}\n\t};\n\n\tCommon.prototype.render = function (id, select) {\n\t\tcommon.init(select);\n\t\tcommon.one($('dl[xid=\"' + id + '\"]').parents('.' + PNAME));\n\t\tcommon.initVal(id);\n\t};\n\n\tCommon.prototype.log = function (obj) {\n\t\tconsole.log(obj);\n\t};\n\n\tvar Select4 = function Select4() {\n\t\tthis.v = v;\n\t\tthis.render();\n\t};\n\tvar common = new Common();\n\n\tSelect4.prototype.value = function (id, type, isAppend) {\n\t\tif (typeof id != 'string') {\n\t\t\treturn [];\n\t\t}\n\t\tvar fs = data[id];\n\t\tif (!common.check(id)) {\n\t\t\treturn [];\n\t\t}\n\t\tif (typeof type == 'string' || type == undefined) {\n\t\t\tvar arr = fs.values.concat([]) || [];\n\t\t\tif (type == 'val') {\n\t\t\t\treturn arr.map(function (val) {\n\t\t\t\t\treturn val.value;\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (type == 'valStr') {\n\t\t\t\treturn arr.map(function (val) {\n\t\t\t\t\treturn val.value;\n\t\t\t\t}).join(',');\n\t\t\t}\n\t\t\tif (type == 'name') {\n\t\t\t\treturn arr.map(function (val) {\n\t\t\t\t\treturn val.name;\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (type == 'nameStr') {\n\t\t\t\treturn arr.map(function (val) {\n\t\t\t\t\treturn val.name;\n\t\t\t\t}).join(',');\n\t\t\t}\n\t\t\treturn arr;\n\t\t}\n\t\tif (common.isArray(type)) {\n\t\t\tvar dl = $('[xid=\"' + id + '\"]'),\n\t\t\t    temp = {},\n\t\t\t    dd = void 0,\n\t\t\t    isAdd = true;\n\t\t\tif (isAppend == false) {\n\t\t\t\t//删除传入的数组\n\t\t\t\tisAdd = false;\n\t\t\t} else if (isAppend == true) {\n\t\t\t\t//追加模式\n\t\t\t\tisAdd = true;\n\t\t\t} else {\n\t\t\t\t//删除原有的数据\n\t\t\t\tcommon.removeAll(id);\n\t\t\t}\n\t\t\tif (isAdd) {\n\t\t\t\tfs.values.forEach(function (val, index) {\n\t\t\t\t\ttemp[val.value] = 1;\n\t\t\t\t});\n\t\t\t}\n\t\t\ttype.forEach(function (val, index) {\n\t\t\t\tif (val && (!temp[val] || fs.config.repeat)) {\n\t\t\t\t\tif ((dd = dl.find('dd[lay-value=\"' + val + '\"]'))[0]) {\n\t\t\t\t\t\tcommon.handlerLabel(id, dd, isAdd, null, true);\n\t\t\t\t\t\ttemp[val] = 1;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvar name = common.valToName(id, val);\n\t\t\t\t\t\tif (name) {\n\t\t\t\t\t\t\tcommon.handlerLabel(id, dd, isAdd, common.getItem(id, val), true);\n\t\t\t\t\t\t\ttemp[val] = 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n\n\tSelect4.prototype.on = function (id, fun, isEnd) {\n\t\tcommon.bindEvent(isEnd ? 'endOn' : 'on', id, fun);\n\t\treturn this;\n\t};\n\n\tSelect4.prototype.filter = function (id, fun) {\n\t\tcommon.bindEvent('filter', id, fun);\n\t\treturn this;\n\t};\n\n\tSelect4.prototype.maxTips = function (id, fun) {\n\t\tcommon.bindEvent('maxTips', id, fun);\n\t\treturn this;\n\t};\n\n\tSelect4.prototype.opened = function (id, fun) {\n\t\tcommon.bindEvent('opened', id, fun);\n\t\treturn this;\n\t};\n\n\tSelect4.prototype.closed = function (id, fun) {\n\t\tcommon.bindEvent('closed', id, fun);\n\t\treturn this;\n\t};\n\n\tSelect4.prototype.config = function (id, config, isJson) {\n\t\tif (id && (typeof id === 'undefined' ? 'undefined' : _typeof(id)) == 'object') {\n\t\t\tisJson = config == true;\n\t\t\tconfig = id;\n\t\t\tid = null;\n\t\t}\n\t\tif (config && (typeof config === 'undefined' ? 'undefined' : _typeof(config)) == 'object') {\n\t\t\tif (isJson) {\n\t\t\t\tconfig.header || (config.header = {});\n\t\t\t\tconfig.header['Content-Type'] = 'application/json; charset=UTF-8';\n\t\t\t\tconfig.dataType = 'json';\n\t\t\t}\n\t\t\tid ? (ajaxs[id] = $.extend(true, {}, ajaxs[id] || ajax, config), !common.check(id) && this.render(id), data[id] && config.direction && (data[id].config.direction = config.direction), data[id] && config.clearInput && (data[id].config.clearInput = true), config.searchUrl && data[id] && common.triggerSearch($('.' + PNAME + ' dl[xid=\"' + id + '\"]').parents('.' + FORM_SELECT), true)) : ($.extend(true, ajax, config), $.each(ajaxs, function (key, item) {\n\t\t\t\t$.extend(true, item, config);\n\t\t\t}));\n\t\t}\n\t\treturn this;\n\t};\n\n\tSelect4.prototype.render = function (id, options) {\n\t\tvar _ref2;\n\n\t\tif (id && (typeof id === 'undefined' ? 'undefined' : _typeof(id)) == 'object') {\n\t\t\toptions = id;\n\t\t\tid = null;\n\t\t}\n\t\tvar config = options ? (_ref2 = {\n\t\t\tinit: options.init,\n\t\t\tdisabled: options.disabled,\n\t\t\tmax: options.max,\n\t\t\tisSearch: options.isSearch,\n\t\t\tsearchUrl: options.searchUrl,\n\t\t\tisCreate: options.isCreate,\n\t\t\tradio: options.radio,\n\t\t\tskin: options.skin,\n\t\t\tdirection: options.direction,\n\t\t\theight: options.height,\n\t\t\tformname: options.formname,\n\t\t\tlayverify: options.layverify,\n\t\t\tlayverType: options.layverType,\n\t\t\tshowCount: options.showCount,\n\t\t\tplaceholder: options.placeholder,\n\t\t\tcreate: options.create,\n\t\t\tfilter: options.filter,\n\t\t\tmaxTips: options.maxTips,\n\t\t\ton: options.on\n\t\t}, _defineProperty(_ref2, 'on', options.on), _defineProperty(_ref2, 'opened', options.opened), _defineProperty(_ref2, 'closed', options.closed), _defineProperty(_ref2, 'template', options.template), _defineProperty(_ref2, 'clearInput', options.clearInput), _ref2) : {};\n\n\t\toptions && options.searchType != undefined && (config.searchType = options.searchType == 'dl' ? 1 : 0);\n\n\t\tif (id) {\n\t\t\tfsConfigs[id] = {};\n\t\t\t$.extend(fsConfigs[id], data[id] ? data[id].config : {}, config);\n\t\t} else {\n\t\t\t$.extend(fsConfig, config);\n\t\t}\n\n\t\t($('select[' + NAME + '=\"' + id + '\"]')[0] ? $('select[' + NAME + '=\"' + id + '\"]') : $('select[' + NAME + ']')).each(function (index, select) {\n\t\t\tvar sid = select.getAttribute(NAME);\n\t\t\tcommon.render(sid, select);\n\t\t\tsetTimeout(function () {\n\t\t\t\treturn common.setHidnVal(sid, $('select[xm-select=\"' + sid + '\"] + div.' + PNAME + ' .' + LABEL));\n\t\t\t}, 10);\n\t\t});\n\t\treturn this;\n\t};\n\n\tSelect4.prototype.disabled = function (id) {\n\t\tvar target = {};\n\t\tid ? common.check(id) && (target[id] = data[id]) : target = data;\n\n\t\t$.each(target, function (key, val) {\n\t\t\t$('dl[xid=\"' + key + '\"]').prev().addClass(DIS);\n\t\t});\n\t\treturn this;\n\t};\n\n\tSelect4.prototype.undisabled = function (id) {\n\t\tvar target = {};\n\t\tid ? common.check(id) && (target[id] = data[id]) : target = data;\n\n\t\t$.each(target, function (key, val) {\n\t\t\t$('dl[xid=\"' + key + '\"]').prev().removeClass(DIS);\n\t\t});\n\t\treturn this;\n\t};\n\n\tSelect4.prototype.data = function (id, type, config) {\n\t\tif (!id || !type || !config) {\n\t\t\tcommon.log('id: ' + id + ' param error !!!');\n\t\t\treturn this;\n\t\t}\n\t\tif (!common.check(id)) {\n\t\t\tcommon.log('id: ' + id + ' not render !!!');\n\t\t\treturn this;\n\t\t}\n\t\tthis.value(id, []);\n\t\tthis.config(id, config);\n\t\tif (type == 'local') {\n\t\t\tcommon.renderData(id, config.arr, config.linkage == true, config.linkageWidth ? config.linkageWidth : '100');\n\t\t} else if (type == 'server') {\n\t\t\tcommon.ajax(id, config.url, config.keyword, config.linkage == true, config.linkageWidth ? config.linkageWidth : '100');\n\t\t}\n\t\treturn this;\n\t};\n\n\tSelect4.prototype.btns = function (id, btns, config) {\n\t\tif (id && common.isArray(id)) {\n\t\t\tbtns = id;\n\t\t\tid = null;\n\t\t}\n\t\tif (!btns || !common.isArray(btns)) {\n\t\t\treturn this;\n\t\t};\n\t\tvar target = {};\n\t\tid ? common.check(id) && (target[id] = data[id]) : target = data;\n\n\t\tbtns = btns.map(function (obj) {\n\t\t\tif (typeof obj == 'string') {\n\t\t\t\tif (obj == 'select') {\n\t\t\t\t\treturn quickBtns[0];\n\t\t\t\t}\n\t\t\t\tif (obj == 'remove') {\n\t\t\t\t\treturn quickBtns[1];\n\t\t\t\t}\n\t\t\t\tif (obj == 'reverse') {\n\t\t\t\t\treturn quickBtns[2];\n\t\t\t\t}\n\t\t\t\tif (obj == 'skin') {\n\t\t\t\t\treturn quickBtns[3];\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn obj;\n\t\t});\n\n\t\t$.each(target, function (key, val) {\n\t\t\tval.config.btns = btns;\n\t\t\tvar dd = $('dl[xid=\"' + key + '\"]').find('.' + FORM_SELECT_TIPS + ':first');\n\t\t\tif (btns.length) {\n\t\t\t\tvar show = config && config.show && (config.show == 'name' || config.show == 'icon') ? config.show : '';\n\t\t\t\tvar html = common.renderBtns(key, show, config && config.space ? config.space : '30px');\n\t\t\t\tdd.html(html);\n\t\t\t} else {\n\t\t\t\tvar pcInput = dd.parents('.' + FORM_SELECT).find('.' + TDIV + ' input');\n\t\t\t\tvar _html = pcInput.attr('placeholder') || pcInput.attr('back');\n\t\t\t\tdd.html(_html);\n\t\t\t\tdd.removeAttr('style');\n\t\t\t}\n\t\t});\n\n\t\treturn this;\n\t};\n\n\tSelect4.prototype.search = function (id, val) {\n\t\tif (id && common.check(id)) {\n\t\t\tajaxs[id] = $.extend(true, {}, ajaxs[id] || ajax, {\n\t\t\t\tfirst: true,\n\t\t\t\tsearchVal: val\n\t\t\t});\n\t\t\tcommon.triggerSearch($('dl[xid=\"' + id + '\"]').parents('.' + FORM_SELECT), true);\n\t\t}\n\t\treturn this;\n\t};\n\n\tSelect4.prototype.replace = function (id, type, config) {\n\t\tvar _this17 = this;\n\n\t\tif (!id || !type || !config) {\n\t\t\tcommon.log('id: ' + id + ' param error !!!');\n\t\t\treturn this;\n\t\t}\n\t\tif (!common.check(id, true)) {\n\t\t\tcommon.log('id: ' + id + ' not render !!!');\n\t\t\treturn this;\n\t\t}\n\t\tvar oldVals = this.value(id, 'val');\n\t\tthis.value(id, []);\n\t\tthis.config(id, config);\n\t\tif (type == 'local') {\n\t\t\tcommon.renderData(id, config.arr, config.linkage == true, config.linkageWidth ? config.linkageWidth : '100', false, true);\n\t\t\tthis.value(id, oldVals, true);\n\t\t} else if (type == 'server') {\n\t\t\tcommon.ajax(id, config.url, config.keyword, config.linkage == true, config.linkageWidth ? config.linkageWidth : '100', false, function (id) {\n\t\t\t\t_this17.value(id, oldVals, true);\n\t\t\t}, true);\n\t\t}\n\t};\n\n\treturn new Select4();\n});"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/layui/extends/treeGrid.js",
    "content": "/**\n @Name：treeGrid树状表格\n @Author：beijiyi\n @version: 0.1\n 码云地址：https://gitee.com/beijiyi/tree_table_treegrid_based_on_layui\n 在线demo：http://beijiyi.com/\n */\nlayui.define(['laytpl', 'laypage', 'layer', 'form'], function(exports){\n    \"use strict\";\n    var $ = layui.$\n        ,laytpl = layui.laytpl\n        ,laypage = layui.laypage\n        ,layer = layui.layer\n        ,form = layui.form\n        ,hint = layui.hint()\n        ,device = layui.device()\n        //外部接口\n        ,table = {\n            config: {//全局配置项,表格级别\n                indexName: 'lay_table_index' //下标索引名\n                ,cols:{//节点级别的附加字段\n                    isCheckName: 'lay_is_checked' //选中状态（true，false）\n                    ,isRadio:'lay_is_radio'//单选状态（true，false）\n                    ,isOpen:'lay_is_open'//是否展开节点\n                    ,isShow:'lay_is_show'//是否显示节点\n                    ,level:'lay_level'//节点的层级关系（不需要设置）\n                    ,children:'children'//存放下级的变量\n\n                    ,cheDisabled:'lay_che_disabled'//禁止多选（true，false）\n                    ,radDisabled:'lay_rad_disabled'//禁止单选（true，false）\n\n                    ,iconOpen:'lay_icon_open'//打开的图标\n                    ,iconClose:'lay_icon_close'//关闭的图标\n                    ,icon:'lay_icon'//叶子节点图标\n                }\n                ,initWidth:{//默认列宽度定义\n                    checkbox: 48\n                    ,space: 15\n                    ,numbers: 40\n                    ,radio:48\n                }\n            }\n            /**\n             * 缓存数据\n             *\n             * 结构图示\n             * cache{}                  缓存（对象）\n             *      key['data']{}       全部数据缓存（对象）\n             *          key[list][]     列表数据对象（数组）\n             *          key[map]{}      列表数据Map对象（Map）\n             *          key[treeList][] 树状结构的对象（数组）\n             *      key['cla']{}        全部已初始化过的Calss对象类（注意渲染是异步执行）\n             *          key['claIds'][] 全部已经吊用过初始化方法的表格类\n             *          key[claObjs]{key[tableId]}  全部已经初始化好的cla对象\n             *\n             */\n            ,cache: {\n                tableId:{\n                    data:{\n                        list:[]//列表数据\n                        ,map:{}//列表数据以idField或唯一值作为key的Map数据\n                        ,treeList:[]//树状数据\n                        ,upIds:[]//父节点集合  在一次外部请求前严格按照第一次的顺序\n                    }\n                }\n                ,cla:{\n                    claIds:{\n                        tableId:true\n                    }\n                    ,claObjs:{\n                        tableId:{}\n                    }\n                }\n                ,selectcode:{//数据字典缓存\n                    demokey:[\n                        {\n                            key:{value:''}\n                        }\n                    ]\n                }\n            }\n            ,index: layui.table ? (layui.table.index + 10000) : 0\n            /**\n             * 设置全局项\n             * @param options\n             * @return {table}\n             */\n            ,set: function(options){\n                var that = this;\n                that.config = $.extend({}, that.config, options);\n                return that;\n            }\n            /**\n             * 事件监听\n             * @param events\n             * @param callback\n             * @return {*}\n             */\n            ,on: function(events, callback){\n                return layui.onevent.call(this, MOD_NAME, events, callback);\n            }\n            ,getClass:function (tableId) {\n                return table.cache.cla.claObjs[tableId];;\n            }\n            ,pushClass:function (tableId,that) {\n                table.cache.cla.claObjs[tableId]=that;\n            }\n            ,isCalss:function (tableId) {\n                var ids=this.cache.cla.claIds||{};\n                return  ids.hasOwnProperty(tableId)||false;\n            }\n            ,isClassYes:function (tableId) {\n                var ids=this.cache.cla.claIds||{};\n                return ids[tableId]||false;\n            }\n            ,pushClassIds:function (tableId,is) {\n                this.cache.cla.claIds[tableId]=is;\n            }\n            ,setObj:function (tableId,key,o) {\n                if(!this.obj[tableId])this.obj[tableId]={};\n                this.obj[tableId][key]=o;\n            }\n            ,getObj:function (tableId, key) {\n                return this.obj[tableId]?this.obj[tableId][key]:null;\n            }\n            /**\n             * 获取列表数据\n             */\n            ,getDataList:function (tableId) {\n                if(table.cache[tableId]){\n                    return table.cache[tableId].data.list;\n                }\n                return [];\n            }\n            /**\n             * 设置列表数据\n             */\n            ,setDataList:function (tableId, list) {\n                if(!table.cache[tableId])table.cache[tableId]={};\n                if(!table.cache[tableId].data)table.cache[tableId].data={};\n                if(!table.cache[tableId].data.list)table.cache[tableId].data.list=[];\n                table.cache[tableId].data.list=list;\n            }\n            /**\n             * 获取列表数据\n             */\n            ,getDataMap:function (tableId) {\n                if(table.cache[tableId]){\n                    return table.cache[tableId].data.map;\n                }\n                return {};\n            }\n            /**\n             * 设置列表数据\n             */\n            ,setDataMap:function (tableId, map) {\n                if(!table.cache[tableId])table.cache[tableId]={};\n                if(!table.cache[tableId].data)table.cache[tableId].data={};\n                if(!table.cache[tableId].data.map)table.cache[tableId].data.map={};\n                table.cache[tableId].data.map=map;\n            }\n            /**\n             * 获取树状数据\n             */\n            ,getDataTreeList:function (tableId) {\n                if(table.cache[tableId]){\n                    return table.cache[tableId].data.treeList;\n                }\n                return [];\n            }\n            /**\n             * 设置树状数据\n             */\n            ,setDataTreeList:function (tableId, treeList) {\n                if(!table.cache[tableId])table.cache[tableId]={};\n                if(!table.cache[tableId].data)table.cache[tableId].data={};\n                if(!table.cache[tableId].data.treeList)table.cache[tableId].data.treeList={};\n                table.cache[tableId].data.treeList=treeList;\n            }\n            /**\n             * 获取根节点数据\n             */\n            ,getDataRootList:function (tableId) {\n                if(table.cache[tableId]){\n                    return table.cache[tableId].data.upIds||[];\n                }\n                return [];\n            }\n            /**\n             * 设置根节点数据\n             */\n            ,setDataRootList:function (tableId, rootList) {\n                if(!table.cache[tableId])table.cache[tableId]={};\n                if(!table.cache[tableId].data)table.cache[tableId].data={};\n                if(!table.cache[tableId].data.upIds)table.cache[tableId].data.upIds=[];\n                table.cache[tableId].data.upIds=rootList;\n            }\n            /**\n             * 初始化\n             * @param filter\n             * @param settings\n             * @return {table}\n             */\n            ,init:function (filter, settings) {\n                settings = settings || {};\n                var that = this\n                    ,elemTable = filter ? $('table[lay-filter=\"'+ filter +'\"]') : $(ELEM + '[lay-data]')\n                    ,errorTips = 'Table element property lay-data configuration item has a syntax error: ';\n                //遍历数据表格\n                elemTable.each(function(){\n                    var othis = $(this), tableData = othis.attr('lay-data');\n                    try{\n                        tableData = new Function('return '+ tableData)();\n                    } catch(e){\n                        hint.error(errorTips + tableData)\n                    }\n                    var cols = [], options = $.extend({\n                        elem: this\n                        ,cols: []\n                        ,data: []\n                        ,skin: othis.attr('lay-skin') //风格\n                        ,size: othis.attr('lay-size') //尺寸\n                        ,even: typeof othis.attr('lay-even') === 'string' //偶数行背景\n                    }, table.config, settings, tableData);\n\n                    filter && othis.hide();\n\n                    //获取表头数据\n                    othis.find('thead>tr').each(function(i){\n                        options.cols[i] = [];\n                        $(this).children().each(function(ii){\n                            var th = $(this), itemData = th.attr('lay-data');\n\n                            try{\n                                itemData = new Function('return '+ itemData)();\n                            } catch(e){\n                                return hint.error(errorTips + itemData)\n                            }\n\n                            var row = $.extend({\n                                title: th.text()\n                                ,colspan: th.attr('colspan') || 0 //列单元格\n                                ,rowspan: th.attr('rowspan') || 0 //行单元格\n                            }, itemData);\n\n                            if(row.colspan < 2) cols.push(row);\n                            options.cols[i].push(row);\n                        });\n                    });\n\n                    //获取表体数据\n                    othis.find('tbody>tr').each(function(i1){\n                        var tr = $(this), row = {};\n                        //如果定义了字段名\n                        tr.children('td').each(function(i2, item2){\n                            var td = $(this)\n                                ,field = td.data('field');\n                            if(field){\n                                return row[field] = td.html();\n                            }\n                        });\n                        //如果未定义字段名\n                        layui.each(cols, function(i3, item3){\n                            var td = tr.children('td').eq(i3);\n                            row[item3.field] = td.html();\n                        });\n                        options.data[i1] = row;\n                    });\n                    table.render(options);\n                });\n\n                return that;\n            }\n            /**\n             * 渲染入口方法（核心入口）\n             */\n            ,render:function (options) {\n                table.pushClassIds(options.id);\n                var inst = new Class(options);\n                return thisTable.call(inst);\n            }\n            /**\n             * 对应的表格加载完成后执行(方法已弃用，请使用parseData代替)\n             * @param tableId\n             * @param fn\n             */\n            ,ready:function (tableId,fn) {\n                var is=false;\n                var myDate=new Date();\n                function isReady() {\n                    if(tableId){\n                        var that=table.getClass(tableId);\n                        if(that&&that.hasOwnProperty('layBody')){\n                            fn(that);\n                            is=true;\n                        }else{\n                            var myDate2=new Date();\n                            var i=myDate2.getTime()-myDate.getTime();\n                            if(i<=(1000*10)&&!is){//大于10秒退出\n                                setTimeout(isReady,50);\n                            }\n                        }\n                    }\n                }\n                if(tableId&&fn){\n                    setTimeout(isReady,50);\n                }\n            }\n            /**\n             * 获取表格选中记录\n             * @param tableId\n             * @return {{data: Array, isAll: boolean}}\n             */\n            ,checkStatus:function (tableId) {\n                var nums = 0\n                    ,invalidNum = 0\n                    ,arr = []\n                    ,data = table.getDataList(tableId) || [];\n                //计算全选个数\n                layui.each(data, function(i, item){\n                    if(item.constructor === Array){\n                        invalidNum++; //无效数据，或已删除的\n                        return;\n                    }\n                    if(item[table.config.cols.isCheckName]){\n                        nums++;\n                        arr.push(table.clearCacheKey(item));\n                    }\n                });\n                return {\n                    data: arr //选中的数据\n                    ,isAll: data.length ? (nums === (data.length - invalidNum)) : false //是否全选\n                };\n            }\n            /**\n             * 设置表格复选状态\n             * @param tableId\n             * @param value     此值存在时为设置操作\n             * @returns {*}\n             */\n            ,setCheckStatus:function(tableId, fildName, ids){\n                var retObj=null;\n                var that=table.getClass(tableId)\n                    ,invalidNum = 0\n                    ,arr = []\n                    ,data = table.getDataList(tableId) || []\n                    ,childs = that.layBody.find('input[name=\"'+TABLE_CHECKBOX_ID+'\"]')//复选框\n                ;\n                if(fildName&&ids){//设置选中\n                    var idsarr=ids.split(',');\n                    idsarr.forEach(function (o) {\n                        var temo=null;\n                        data.forEach(function (e) {\n                            var b1=e[fildName]+\"\";\n                            var b2=o+\"\";\n                            if(b1==b2){\n                                temo=e;\n                                return;\n                            };\n                        });\n                        if(temo){\n                            var v=temo[table.config.indexName];\n                            that.layBody.find('input[name=\"'+TABLE_CHECKBOX_ID+'\"][value=\"'+v+'\"]').prop(\"checked\",true);\n                            that.setCheckData(v, true);\n                        }\n                    });\n                    that.syncCheckAll();\n                    that.renderForm('checkbox');\n                }\n                return retObj;\n            }\n            /**\n             * 表格单选状态\n             * @param tableId\n             * @param value     此值存在时为设置操作\n             * @returns {*}\n             */\n            ,radioStatus:function (tableId) {\n                var that=table.getClass(tableId);\n                var retObj=null;\n                var nums = 0\n                    ,invalidNum = 0\n                    ,arr = []\n                    ,data = table.getDataList(tableId) || [];\n                var v=that.layBody.find(\"input[name='\"+TABLE_RADIO_ID+\"']:checked\").val();\n                v=parseInt(v);\n                data.forEach(function (e) {\n                    if(e[table.config.indexName]==v){\n                        retObj=e;\n                    };\n                });\n                return table.clearCacheKey(retObj);\n            }\n            /**\n             * 设置表格单选状态\n             * @param tableId\n             * @param value     此值存在时为设置操作\n             * @returns {*}\n             */\n            ,setRadioStatus:function (tableId,fildName,value) {\n                var that=table.getClass(tableId);\n                var retObj=null;\n                var nums = 0\n                    ,invalidNum = 0\n                    ,arr = []\n                    ,data = table.getDataList(tableId) || [];\n\n                if(fildName&&value){//设置选中\n                    data.forEach(function (e) {\n                        var b1=e[fildName]+\"\";\n                        var b2=value+\"\";\n                        if(b1==b2){\n                            retObj=e;\n                            return;\n                        };\n                    });\n\n                    if(retObj){\n                        var v=retObj[table.config.indexName];\n                        that.layBody.find(\"input:radio[name='\"+TABLE_RADIO_ID+\"'][value='\"+v+\"']\").prop(\"checked\",true);\n                        form.render('radio');\n                    }\n                }\n                return retObj;\n            }\n            /**\n             * 清除临时Key\n             * @param data\n             * @return {*}\n             */\n            ,clearCacheKey:function (data) {\n                data = $.extend({}, data);\n                delete data[table.config.cols.isCheckName];\n                delete data[table.config.indexName];\n                return data;\n            }\n            /**\n             * 刷新数据\n             * @param id\n             * @param options\n             * @return {*}\n             */\n            ,query:function (tableId, options) {\n                var that= table.getClass(tableId);\n                that.renderTdCss();\n                if(that.config.data && that.config.data.constructor === Array) delete that.config.data;\n                that.config = $.extend({}, that.config, options);\n                that.pullData(that.page, that.loading());\n            }\n            /**\n             * 此方法为整体重新渲染（重量级刷新方法）\n             * @param id\n             * @param options\n             */\n            ,reload:function (tableId, options) {\n                var config = thisTable.config[tableId];\n                options = options || {};\n                if(!config) return hint.error('The ID option was not found in the table instance');\n                if(options.data && options.data.constructor === Array) delete config.data;\n                return table.render($.extend(true, {}, config, options));\n            }\n            /**\n             * 添加一行或多行数据\n             * @param tableId   表格id\n             * @param index     在第几个位置插入（从0开始）\n             * @param data      数据\n             * @returns {*}\n             */\n            ,addRow:function (tableId, index, data) {\n                var that=table.getClass(tableId)\n                    ,options=that.config\n                    ,uo = []//父级节点\n                    ,treeList=table.getDataTreeList(tableId)\n                    ,list = table.getDataList(tableId) || [];\n                that.resetData(data);\n                //插入到父节点后面\n                list.splice(index,0,data);//更新缓存\n                table.kit.restNumbers(list);//重置下标\n                table.setDataMap(tableId,that.resetDataMap(list));//处理map\n                if(options.isTree){//处理层级\n                    //1、处理父级  2、处理treeObj 3、层级\n                    var uo=that.treeFindUpData(data);\n                    if(uo) {\n                        var clist=uo.children;\n                        uo.children.push(data);\n                        data[table.config.cols.level]=uo[table.config.cols.level]+1;\n                    }else{\n                        data[table.config.cols.level]=1;\n                        treeList.push(data);\n                    }\n                }\n                //生成html\n                var tds=that.renderTr(data,data[table.config.indexName]);\n                var trs='<tr data-index=\"'+ data[table.config.indexName] +'\"'+that.renderTrUpids(data)+'>'+ tds.join('') + '</tr>';\n                if(index==0){//在第一个位置插入\n                    var  tbody=that.layBody.find('table tbody');\n                    $(tbody).prepend(trs);\n                    that.layBody.find(\".layui-none\").remove();\n                }else{\n                    var o=that.layBody.find('[data-index='+(index-1)+']');//父节点dom树\n                    $(o).after(trs);\n                }\n                that.renderForm();\n                if(options.isPage)that.renderPage(that.config.page.count+1);//分页渲染\n                that.restNumbers();\n                that.events();\n                if(options.isTree) {//展开节点\n                    that.treeNodeOpen(uo, true);\n                    that.renderTreeConvertShowName(uo);\n                }\n            }\n            /**\n             * 删除一行或多行数据\n             * （如果是树状则删除自己和子节点）\n             * @param tableId\n             * @param data（1、数组；2、对象）\n             */\n            ,delRow:function (tableId, data) {\n                //1、页面清除 2、缓存清除\n                var that=table.getClass(tableId)\n                    ,options=that.config\n                    ,list=table.getDataList(tableId);\n                var sonList=[];//需要删除的数据\n                var delIds={};//需要删除的数据map\n                var delDatas=[];\n                var upDelDatas=[];//全部待删除节点的父节点（处理折叠）\n                if(!that||!data)return;\n                if(table.kit.isArray(data)){//是数组，删除多个\n                    delDatas=data;\n                }else{\n                    delDatas[0]=data;\n                }\n                delDatas.forEach(function(temo) {//记录全部父节点\n                    var uo=that.treeFindUpData(temo);\n                    if(uo){\n                        upDelDatas.push(uo);\n                    }\n                });\n                sonList=options.isTree?table.treeFindSonList(that.config.id,delDatas):delDatas;\n                sonList.forEach(function (temo) {//页面元素处理\n                    var index=temo[table.config.indexName];\n                    delIds[index]=index;//设置代删除的id集合\n                    var tr = that.layBody.find('tr[data-index=\"'+ index +'\"]');\n                    tr.remove();\n                });\n                that.restNumbers();//数据处理\n                var newList=[];//重构一个新的数组\n                for (var i=0,len=list.length;i<len;i++) {\n                    var isP=true;\n                    var temo1=null;\n                    sonList.forEach(function (temo) {\n                        if (temo[table.config.indexName] === list[i][table.config.indexName]) {\n                            isP = false;\n                        }\n                    });\n                    if(isP){\n                        newList.push(list[i]);\n                    }\n                }\n                table.kit.restNumbers(newList);//下标重新编号\n                table.setDataList(tableId,newList);//处理list\n                table.setDataMap(tableId,that.resetDataMap(newList));//处理map\n                table.setDataTreeList(tableId,that.resetDataTreeList(newList,table.getDataRootList(tableId)));//处理树结构\n                upDelDatas.forEach(function(temo) {//处理父级节点\n                    that.renderTreeConvertShowName(temo);\n                });\n                if(options.isPage)that.renderPage(that.config.page.count-Object.keys(delIds).length);//分页渲染\n                that.events();//重新注册事件\n            }\n            /**\n             * 更新指定的记录\n             * @param obj\n             * @param index\n             */\n            ,updateRow:function (tableId,obj) {\n                var that=table.getClass(tableId);\n                if(!that||!obj)return;\n                var id=obj[that.config.idField];\n                //更新缓存数据\n                var maps=table.getDataMap(tableId);\n                var thisobj=maps[id];\n                if(thisobj){\n                    $.extend(thisobj, obj);\n                }else{\n                    return;\n                }\n                //更新页面\n                var oi=thisobj[table.config.indexName];\n                var  tds=that.renderTr(thisobj,oi);\n                var tr=that.layBody.find('tr[data-index='+oi+']');\n                $(tr).html(tds);\n            }\n            ,treeNodeOpen:function (tableId,o, isOpen) {\n                var that=table.getClass(tableId);\n                if(!that||!o)return;\n                that.treeNodeOpen(o,isOpen);\n            }\n            /**\n             * 折叠或展开全部(默认为展开全部节点)\n             * @param tableId\n             * @param isOpen    展开还是折叠（默认值为true 展开）\n             */\n            ,treeOpenAll:function (tableId,isOpen) {\n                var that=table.getClass(tableId);\n                if(!that)return;\n                if(isOpen===undefined){isOpen=true;}\n                var list=table.getDataList(tableId);\n                if(!list)return;\n                list.forEach(function (temo) {\n                    that.treeNodeOpen(temo,isOpen);\n                });\n            }\n            /**\n             * 获取全部需要子节点对象集合\n             * @param data（数组或对象）\n             */\n            ,treeFindSonList:function (tableId,data) {\n                var that=table.getClass(tableId);\n                if(!that||!data)return [];\n                var delDatas=[];\n                var sonList=[];//需要删除的数据\n                var delIds={};//需要删除的数据map\n                if(table.kit.isArray(data)){//是数组，删除多个\n                    delDatas=data;\n                }else{\n                    delDatas[0]=data;\n                }\n                delDatas.forEach(function (temo) {\n                    if(temo.children.length>0){\n                        var temSonList=that.treeFindSonData(temo);\n                        temSonList.forEach(function (temii) {\n                            if(!delIds[temii[table.config.indexName]]){\n                                sonList.push(temii);\n                                delIds[temii[table.config.indexName]]=temii[table.config.indexName];\n                            }\n                        });\n                    }\n                    sonList.push(temo);\n                    delIds[temo[table.config.indexName]]=temo[table.config.indexName];\n                });\n                return sonList;\n            }\n            ,treeFindUpDatas:function (tableId, o) {\n                var that=table.getClass(tableId);\n                if(!that||!o)return [];\n                return that.treeFindUpDatas(o);\n            }\n            ,treeFindUpData:function (tableId, o) {\n                var that=table.getClass(tableId);\n                if(!that||!o)return [];\n                return that.treeFindUpData(o);\n            }\n            /**\n             * 获取全部需要子节点id集合\n             * @param data（数组或对象）\n             */\n            ,treeFindSonIds:function (tableId,data) {\n                var delIds=[];\n                var sonList=table.treeFindSonList(tableId,data);\n                sonList.forEach(function (temo) {\n                    delIds.push([table.config.indexName]);\n                });\n                return delIds;\n            }\n            /**\n             * 获取全部的id字段集合\n             * @param tableId\n             * @param data\n             * @returns {Array}\n             */\n            ,treeFindSonIdFields:function (tableId,data) {\n                var idField=[];\n                var that=table.getClass(tableId);\n                var sonList=table.treeFindSonList(tableId,data);\n                sonList.forEach(function (temo) {\n                    idField.push(temo[that.config.idField]);\n                });\n                return idField;\n            }\n            ,treeIconRender:function (tableId, o) {\n                var that=table.getClass(tableId);\n                if(!that||!o)return [];\n                return that.treeIconRender(o,false);\n            }\n            /**\n             * 工具方法对象\n             */\n            ,kit:{\n                isArray:function (o) {\n                    return Object.prototype.toString.call(o) === '[object Array]';\n                }\n                ,isNumber:function (val){\n                    var regPos = /^\\d+(\\.\\d+)?$/; //非负浮点数\n                    var regNeg = /^(-(([0-9]+\\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\\.[0-9]+)|([0-9]*[1-9][0-9]*)))$/; //负浮点数\n                    if(regPos.test(val) || regNeg.test(val)){\n                        return true;\n                    }else{\n                        return false;\n                    }\n\n                }\n                ,restNumbers:function (list) {\n                    if(!list)return;\n                    var i=0;\n                    list.forEach(function (o) {\n                        o[table.config.indexName]=i;\n                        i++;\n                    });\n                }\n            }\n        }\n        //操作当前实例\n        ,thisTable = function(){\n            var that = this\n                ,options = that.config\n                ,id = options.id;\n            id && (thisTable.config[id] = options);\n            return {\n                reload: function(options){\n                    that.reload.call(that, options);\n                }\n                ,config: options\n            }\n        }\n        //字符常量\n        ,MOD_NAME = 'treeGrid', ELEM = '.layui-table', THIS = 'layui-this', SHOW = 'layui-show', HIDE = 'layui-hide', DISABLED = 'layui-disabled', NONE = 'layui-none'\n        ,ELEM_VIEW = 'layui-table-view', ELEM_HEADER = '.layui-table-header', ELEM_BODY = '.layui-table-body', ELEM_MAIN = '.layui-table-main', ELEM_FIXED = '.layui-table-fixed', ELEM_FIXL = '.layui-table-fixed-l', ELEM_FIXR = '.layui-table-fixed-r', ELEM_TOOL = '.layui-table-tool', ELEM_PAGE = '.layui-table-page', ELEM_SORT = '.layui-table-sort', ELEM_EDIT = 'layui-table-edit', ELEM_HOVER = 'layui-table-hover'\n        ,TABLE_RADIO_ID='table_radio_',TABLE_CHECKBOX_ID='layTableCheckbox'\n        ,ELEM_FILTER='.layui-table-filter'\n        ,TREE_ID='treeId',TREE_UPID='treeUpId',TREE_SHOW_NAME='treeShowName',TREE_KEY_MAP='tree_key_map'\n        //thead区域模板\n        ,TPL_HEADER = function(options){\n            var rowCols = '{{#if(item2.colspan){}} colspan=\"{{item2.colspan}}\"{{#} if(item2.rowspan){}} rowspan=\"{{item2.rowspan}}\"{{#}}}';\n            options = options || {};\n            return ['<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" class=\"layui-table\" '\n                ,'{{# if(d.data.skin){ }}lay-skin=\"{{d.data.skin}}\"{{# } }} {{# if(d.data.size){ }}lay-size=\"{{d.data.size}}\"{{# } }} {{# if(d.data.even){ }}lay-even{{# } }}>'\n                ,'<thead>'\n                ,'{{# layui.each(d.data.cols, function(i1, item1){ }}'\n                ,'<tr>'\n                ,'{{# layui.each(item1, function(i2, item2){ }}'\n                ,'{{# if(item2.fixed && item2.fixed !== \"right\"){ left = true; } }}'\n                ,'{{# if(item2.fixed === \"right\"){ right = true; } }}'\n                ,function(){\n                    if(options.fixed && options.fixed !== 'right'){\n                        return '{{# if(item2.fixed && item2.fixed !== \"right\"){ }}';\n                    }\n                    if(options.fixed === 'right'){\n                        return '{{# if(item2.fixed === \"right\"){ }}';\n                    }\n                    return '';\n                }()\n                ,'<th data-field=\"{{ item2.field||i2 }}\" {{# if(item2.minWidth){ }}data-minwidth=\"{{item2.minWidth}}\"{{# } }} '+ rowCols +' {{# if(item2.unresize){ }}data-unresize=\"true\"{{# } }}>'\n                ,'<div class=\"layui-table-cell laytable-cell-'\n                ,'{{# if(item2.colspan > 1){ }}'\n                ,'group'\n                ,'{{# } else { }}'\n                ,'{{d.index}}-{{item2.field || i2}}'\n                ,'{{# if(item2.type !== \"normal\"){ }}'\n                ,' laytable-cell-{{ item2.type }}'\n                ,'{{# } }}'\n                ,'{{# } }}'\n                ,'\" {{#if(item2.align){}}align=\"{{item2.align}}\"{{#}}}>'\n                ,'{{# if(item2.type === \"checkbox\"){ }}' //复选框\n                ,'<input type=\"checkbox\" name=\"layTableCheckbox\" lay-skin=\"primary\" lay-filter=\"layTableAllChoose\">'\n                ,'{{# } else { }}'\n                ,'<span>{{item2.title||\"\"}}</span>'\n                ,'{{# if(!(item2.colspan > 1) && item2.sort){ }}'\n                ,'<span class=\"layui-table-sort layui-inline\"><i class=\"layui-edge layui-table-sort-asc\"></i><i class=\"layui-edge layui-table-sort-desc\"></i></span>'\n                ,'{{# } }}'\n                ,'{{# } }}'\n                ,'</div>'\n                ,'</th>'\n                ,(options.fixed ? '{{# }; }}' : '')\n                ,'{{# }); }}'\n                ,'</tr>'\n                ,'{{# }); }}'\n                ,'</thead>'\n                ,'</table>'].join('');\n        }\n        /**\n         * 行内过滤区域\n         */\n        ,TPL_FILTER = function(options){\n        }\n        //tbody区域模板\n        ,TPL_BODY = ['<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" class=\"layui-table\" '\n            ,'{{# if(d.data.skin){ }}lay-skin=\"{{d.data.skin}}\"{{# } }} {{# if(d.data.size){ }}lay-size=\"{{d.data.size}}\"{{# } }} {{# if(d.data.even){ }}lay-even{{# } }}>'\n            ,'<tbody></tbody>'\n            ,'</table>'].join('')\n        //主模板\n        ,TPL_MAIN = ['<div class=\"layui-form layui-border-box {{d.VIEW_CLASS}}\" lay-filter=\"LAY-table-{{d.index}}\" style=\"{{# if(d.data.width){ }}width:{{d.data.width}}px;{{# } }} {{# if(d.data.height){ }}height:{{d.data.height}}px;{{# } }}\">'\n\n            ,'{{# if(d.data.toolbar){ }}'\n            ,'<div class=\"layui-table-tool\"></div>'\n            ,'{{# } }}'\n\n            ,'<div class=\"layui-table-box\">'\n            ,'{{# var left, right; }}'\n            ,'<div class=\"layui-table-header\">'\n            ,TPL_HEADER()\n            ,'</div>'\n            ,'<div class=\"layui-table-filter\">'\n            ,TPL_FILTER()\n            ,'</div>'\n            ,'<div class=\"layui-table-body layui-table-main\">'\n            ,TPL_BODY\n            ,'</div>'\n\n            ,'{{# if(left){ }}'\n            ,'<div class=\"layui-table-fixed layui-table-fixed-l\">'\n            ,'<div class=\"layui-table-header\">'\n            ,TPL_HEADER({fixed: true})\n            ,'</div>'\n            ,'<div class=\"layui-table-body\">'\n            ,TPL_BODY\n            ,'</div>'\n            ,'</div>'\n            ,'{{# }; }}'\n\n            ,'{{# if(right){ }}'\n            ,'<div class=\"layui-table-fixed layui-table-fixed-r\">'\n            ,'<div class=\"layui-table-header\">'\n            ,TPL_HEADER({fixed: 'right'})\n            ,'<div class=\"layui-table-mend\"></div>'\n            ,'</div>'\n            ,'<div class=\"layui-table-body\">'\n            ,TPL_BODY\n            ,'</div>'\n            ,'</div>'\n            ,'{{# }; }}'\n            ,'</div>'\n\n            ,'{{# if(d.data.isPage){ }}'\n            ,'<div class=\"layui-table-page\">'\n            ,'<div id=\"layui-table-page{{d.index}}\"></div>'\n            ,'</div>'\n            ,'{{# } }}'\n\n            /*,'<style>'\n            ,'{{# layui.each(d.data.cols, function(i1, item1){'\n            ,'layui.each(item1, function(i2, item2){ }}'\n            ,'.laytable-cell-{{d.index}}-{{item2.field||i2}}{ '\n            ,'{{# if(item2.width){ }}'\n            ,'width: {{item2.width}}px;'\n            ,'{{# } }}'\n            ,' }'\n            ,'{{# });'\n            ,'}); }}'\n            ,'</style>'*/\n            ,'</div>'].join('')\n        ,_WIN = $(window)\n        ,_DOC = $(document)\n\n        //构造器\n        ,Class = function(options){\n            var that = this;\n            that.index = ++table.index;\n            that.config = $.extend({}, that.config, table.config, options);\n            that.configFirst = $.extend({}, that.config, table.config, options);\n            that.render();\n            table.pushClass(options.id,that);\n        };\n    /**\n     * 表格行为属性（默认配置）\n     */\n    Class.prototype.config = {\n        limit: 10 //每页显示的数量\n        ,loading: true //请求数据时，是否显示loading\n        ,cellMinWidth: 60 //所有单元格默认最小宽度\n        ,heightRemove:[]//非固定高度情况下，不参与计算的高度\n        ,text: {\n            none: '无数据'\n        }\n        ,isFilter:false//是否开启行内过滤\n        ,method:'post'//默认以post方式请求后台\n        ,radDisabledNum:0//禁止单选的记录数\n        ,cheDisabledNum:0//禁止多选的记录数\n        //树相关图表\n        ,branch: ['&#xe622;', '&#xe624;'] //父节点\n        ,leaf: '&#xe621;' //叶节点\n        ,iconOpen:true//默认开启树节点图标\n        ,isOpenDefault:true//默认展开还是折叠节点\n        ,parseData:null//加载数据后的回调方法\n        ,onClickRow:null//行单击事件\n        ,onDblClickRow:null//行双击事件\n        ,onBeforeCheck:null//复选前事件\n        ,onCheck:null//复选事件  (obj 对象,checked 选中状态,isAll 是否全选)\n        ,onRadio:null//单选事件  （）\n        ,isTree:true//默认为树表格\n        ,isPage:false//不分页\n        ,height:'100%'//默认高度100%\n    };\n    Class.prototype.configFirst={};//页面定义时原始参数\n    //表格渲染\n    Class.prototype.render = function(){\n        var that = this\n            ,options = that.config;\n        options.elem = $(options.elem);\n        options.where = options.where || {};\n        options.id = options.id || options.elem.attr('id');\n        that.test();\n        //请求参数的自定义格式\n        options.request = $.extend({\n            pageName: 'page'\n            ,limitName: 'limit'\n        }, options.request)\n        //响应数据的自定义格式\n        options.response = $.extend({\n            statusName: 'code'\n            ,statusCode: 0\n            ,msgName: 'msg'\n            ,dataName: 'data'\n            ,countName: 'count'\n        }, options.response);\n        //如果 page 传入 laypage 对象\n        if(typeof options.page === 'object'){\n            options.limit = options.page.limit || options.limit;\n            options.limits = options.page.limits || options.limits;\n            that.page = options.page.curr = options.page.curr || 1;\n            delete options.page.elem;\n            delete options.page.jump;\n        }\n        if(!options.elem[0]) return that;\n        that.columnWidthInit();//列宽度计算\n        //开始插入替代元素\n        var othis = options.elem\n            ,hasRender = othis.next('.' + ELEM_VIEW)\n            //主容器\n            ,reElem = that.elem = $(laytpl(TPL_MAIN).render({\n                VIEW_CLASS: ELEM_VIEW\n                ,data: options\n                ,index: that.index //索引\n            }));\n        options.index = that.index;\n        //生成替代元素\n        hasRender[0] && hasRender.remove(); //如果已经渲染，则Rerender\n        othis.after(reElem);\n        that.renderTdCss();\n        //各级容器\n        that.layHeader = reElem.find(ELEM_HEADER);  //表头\n        that.layMain = reElem.find(ELEM_MAIN);//内容区域\n        that.layBody = reElem.find(ELEM_BODY);//内容区域\n        that.layFixed = reElem.find(ELEM_FIXED);//浮动区域\n        that.layFixLeft = reElem.find(ELEM_FIXL);//左浮动\n        that.layFixRight = reElem.find(ELEM_FIXR);//有浮动\n        that.layTool = reElem.find(ELEM_TOOL);//工具栏区域\n        that.layPage = reElem.find(ELEM_PAGE);//分页区域\n        that.layFilter=reElem.find(ELEM_FILTER);//行内过滤条件区域\n        that.layTool.html(\n            laytpl($(options.toolbar).html()||'').render(options)\n        );\n        if(options.height){\n            that.tableHeight();//表格高度计算\n            that.resizeHeight();//高度控制\n            that.renderCss();\n        }\n        //如果多级表头，则填补表头高度\n        if(options.cols.length > 1){\n            var th = that.layFixed.find(ELEM_HEADER).find('th');\n            th.height(that.layHeader.height() - 1 - parseFloat(th.css('padding-top')) - parseFloat(th.css('padding-bottom')));\n        }\n        //渲染过滤区域\n        if(options.isFilter){\n            that.layFilter.html(\n                that.renderFilter()\n            );\n        }\n        //请求数据\n        that.pullData(that.page,that.loading());\n        that.test();\n    };\n    //根据列类型，定制化参数\n    Class.prototype.initOpts = function(item){\n        var that = this,\n            options = that.config;\n        //让 type 参数兼容旧版本\n        if(item.checkbox) item.type = \"checkbox\";\n        if(item.space) item.type = \"space\";\n        if(!item.type) item.type = \"normal\";\n\n        if(item.type !== \"normal\"){\n            item.unresize = true;\n            item.width = item.width || table.config.initWidth[item.type];\n        }\n\n        if(options.isFilter){//开启行内过滤\n            if(item.isFilter!=false){\n                item.isFilter=true;\n            }\n        }\n    };\n\n    /**\n     * 表格获取父容器高度\n     * @param tableId\n     */\n    Class.prototype.getParentDivHeight = function(tableId){\n        var th=$(\"#\"+tableId).parent().height();\n        return th;\n    };\n\n    /**\n     * 获取列定义\n     * @param tableId\n     */\n    Class.prototype.getCols = function(field){\n        var that = this;\n        var o={};\n        var cols=that.config.cols[0];\n        var isInt=false;\n        var reg = /^[0-9]+.?[0-9]*$/;\n        if (reg.test(field)) {//数字\n            isInt=true;\n        }\n        for(var ii in cols){\n            if(isInt){\n                if(ii==parseInt(field)) return cols[ii];\n            }else{\n                if(field==cols[ii].field)return cols[ii];\n            }\n        }\n        return o;\n    };\n\n    //表格重载\n    Class.prototype.reload = function(options){\n        var that = this;\n        if(that.config.data && that.config.data.constructor === Array) delete that.config.data;\n        that.config = $.extend({}, that.config, options);\n        that.configFirst = $.extend({}, that.config, options);\n        //获取行内过滤的值\n        that.render();\n    };\n    //页码\n    Class.prototype.page = 1;\n    /**\n     * 重置下标（插入删除等操作）\n     * 1、data-index中的下标\n     * 2、tr中data的值\n     * 3、下标字段的值\n     * @param list\n     */\n    Class.prototype.restNumbers=function(){\n        var that = this\n            ,options = that.config;\n        var  trs=that.layBody.find('table tbody tr');\n        var i=0;\n        trs.each(function (o) {\n            $(this).attr(\"data-index\",i);\n            $(this).find(\".laytable-cell-numbers p\").text(i+1);\n            $(this).data('index',i);\n            i++;\n        });\n    }\n\n    /**\n     * 初始化节点\n     * 在每一次数据加载时只执行一次\n     * query、reload时会执行\n     * @param o\n     */\n    Class.prototype.resetData=function(n) {\n        var that = this\n            ,options = that.config;\n        if(options.isTree){\n            if(!n.hasOwnProperty(table.config.cols.isOpen)){//如果不存在该属性则默认为true\n                n[table.config.cols.isOpen]=options.isOpenDefault;\n            }\n            if(!n.hasOwnProperty(table.config.cols.isShow)){//如果不存在该属性则默认为true\n                n[table.config.cols.isShow]=options.isOpenDefault?true:false;\n            }\n        }\n        //禁止设置\n        if(!n.hasOwnProperty(table.config.cols.cheDisabled)){//不存在则默认为false\n            n[table.config.cols.cheDisabled]=false;\n        }\n        //记录禁止多选、单选的记录数\n        if(n[table.config.cols.cheDisabled])options.cheDisabledNum++;\n        if(n[table.config.cols.radDisabled])options.radDisabledNum++;\n        n.children=[];\n    }\n    /**\n     * 构建map数据\n     * @param list\n     * @return {{}}\n     */\n    Class.prototype.resetDataMap=function(list) {\n        var that = this\n            ,options = that.config;\n        var field_Id=options.idField;\n        var map={};\n        if(list){\n            list.forEach(function (o) {\n                map[o[field_Id]]=o;\n            });\n        }\n        return map;\n    }\n    Class.prototype.resetDataresetRoot=true;//是否重新确定根节点\n    /**\n     * 确定根节点id(重新登录根节点)\n     */\n    Class.prototype.resetDataRoot=function (list) {\n        var that = this\n            ,options = that.config;\n        var field_Id=options[TREE_ID];\n        var field_upId=options[TREE_UPID];\n        var map=table.getDataMap(that.config.id);//列表map，fieldId为key  //设置map数据集合\n        var rootList=table.cache[options.id].data.upIds||[];//根节点list集合\n        var rootMap={};//根节点map集合\n        table.cache[options.id].data.upIds=[];\n        rootList=table.cache[options.id].data.upIds;\n        for(var i=0;i<list.length;i++){\n            var temo=list[i];\n            if(!map[temo[field_upId]]){//没有找到父节点\n                if(!rootMap[temo[field_upId]]){//还不存在\n                    var temis=true;\n                    rootList.forEach(function (temoo) {\n                        if(temoo===temo[field_upId])temis=false;\n                    });\n                    if(temis)rootList.push(temo[field_upId]);\n                }\n                rootMap[temo[field_upId]]=temo[field_upId];\n            }\n        }\n        return rootList;\n    }\n    /**\n     * 处理树结构\n     * 1、原始列表数据\n     * 2、根节点集合\n     */\n    Class.prototype.resetDataTreeList=function (list, rootList) {\n        var that = this\n            ,options = that.config;\n        var field_Id=options[TREE_ID];\n        var field_upId=options[TREE_UPID];\n        var treeList=[];\n        //处理树结构\n        var fa = function(upId) {\n            var _array = [];\n            for (var i = 0; i < list.length; i++) {\n                var n = list[i];\n                if (n[field_upId] === upId) {\n                    n.children = fa(n[field_Id]);\n                    _array.push(n);\n                }\n            }\n            return _array;\n        }\n        rootList.forEach(function (temo) {\n            var temTreeObj=fa(temo);//递归\n            if(temTreeObj){\n                temTreeObj.forEach(function (o) {\n                    treeList.push(o);\n                });\n            }\n        });\n        return treeList;\n    }\n\n    /**\n     * 处理数据列表结构\n     * 1、树结构\n     */\n    Class.prototype.resetDataTableList=function (treeList) {\n        var that = this\n            ,options = that.config;\n        var field_Id=options[TREE_ID];\n        var field_upId=options[TREE_UPID];\n        var tableList=[];\n        //处理表格结构\n        var fa2=function (l,level) {\n            for (var i = 0; i < l.length; i++) {\n                var n = l[i];\n                n[table.config.cols.level]=level;//设置当前层级\n                tableList.push(n);\n                if (n.children&&n.children.length>0) {\n                    fa2(n.children,1+level);\n                }\n            }\n            return;\n        }\n        fa2(treeList,1);\n\n        //设置isOpen 和is_show状态\n        tableList.forEach(function (o) {\n            var uo=that.treeFindUpData(o);\n            if(!uo||(uo[table.config.cols.isOpen]&&uo[table.config.cols.isShow])){//没有父亲：显示；父亲打开状态（显示状态：显示；隐藏状态：隐藏）\n                o[table.config.cols.isShow]=true;\n            }else{\n                o[table.config.cols.isShow]=false;\n            }\n        });\n        return tableList;\n    }\n\n    /**\n     * 重置当前表格的数据（1、普通列表；2树状表格）\n     * 将列表数据转成树形结构和符合table展示的列表\n     * @param list          列表数据\n     * @param field_Id      树形结构主键字段\n     * @param field_upId    树形结构上级字段\n     * @returns {Array}     [0]表格列表  [1]树形结构\n     */\n    Class.prototype.resetDatas=function(list) {\n        //console.time(\"resetDatas\");\n        var that = this\n            ,options = that.config;\n        var field_Id=options[TREE_ID];\n        var field_upId=options[TREE_UPID];\n        var datas=[];\n        var treeList=[];\n        var tableList=list;\n        var map=that.resetDataMap(list);//列表map，fieldId为key  //设置map数据集合\n        datas.push(tableList);//table结构\n        datas.push(treeList)//tree树结构\n        datas.push(map)//data数据 map结构\n        //设置到内存中去\n        table.setDataList(that.config.id,tableList);\n        table.setDataTreeList(that.config.id,treeList);\n        table.setDataMap(that.config.id,map);\n        if(list==null||list.length<=0)return datas;\n        //设置默认参数\n        for (var i = 0; i < list.length; i++) {\n            that.resetData(list[i]);\n        }\n        if(options.isTree){//树状\n            tableList=[];\n            table.setDataList(that.config.id,tableList);\n            var rootList=table.cache[options.id].data.upIds||[];//根节点list集合\n            if(rootList.length<=0||that.resetDataresetRoot){//确定根节点\n                table.cache[options.id].data.upIds=[];\n                rootList=that.resetDataRoot(list);\n                that.resetDataresetRoot=false;\n            }\n            treeList=that.resetDataTreeList(list,rootList);\n            table.setDataTreeList(that.config.id,treeList);//设置树结构到缓存\n            tableList=that.resetDataTableList(treeList);\n            table.setDataList(that.config.id,tableList);//设置数据列表结构到缓存\n        }\n        //console.timeEnd(\"resetDatas\");\n        return datas;\n    }\n    /**\n     * 根据id从表格数据中获取对象\n     * @param data\n     * @param field_Id\n     * @param field_upId\n     * @returns {Array}\n     */\n    Class.prototype.treeFindDataById=function(u_Id) {\n        var that = this\n            ,options = that.config;\n        var e=null;\n        var list=table.getDataList(that.key);\n        var key=options[TREE_ID];\n        list.forEach(function (o) {\n            if(o[key]==u_Id){\n                e=o;\n                return;\n            }\n        });\n        return e;\n    }\n    /**\n     * 获取父节点\n     * @param u_Id\n     */\n    Class.prototype.treeFindUpData=function(o){\n        var uOjb=null;\n        var that = this\n            ,options = that.config;\n        //处理父级\n        var key=options[TREE_UPID];//父节点key名称\n        var mapData=table.getDataMap(that.config.id);//获取map形式对象集合\n        uOjb=mapData[o[key]];\n        return uOjb;\n    }\n    /**\n     * 获取全部父节点集合，返回list(不包含自己)\n     * @param u_Id\n     */\n    Class.prototype.treeFindUpDatas=function(o){\n        var uOjb=null;\n        var that = this\n            ,options = that.config;\n        var list=[];\n        var temf=function (temo) {\n            var uo=that.treeFindUpData(temo);\n            if(uo){\n                list.push(uo);\n                temf(uo);\n            }\n        };\n        temf(o);\n        return list;\n    }\n    /**\n     * 根据父id获取全部的叶子节点(递归)\n     * @param o 数据对象\n     * @return {string}\n     */\n    Class.prototype.treeFindSonData=function (data) {\n        var objs=[];\n        function f(o) {\n            if(o.children.length>0){\n                o.children.forEach(function (i) {\n                    objs.push(i);\n                    if(i.children.length>0){\n                        f(i);\n                    }\n                });\n            }\n        }f(data);\n        return objs;\n    };\n    /**\n     * 叶子节点显示转换\n     * @param o             数据\n     * @param fieldName     树显示列名\n     * @returns {string}\n     */\n    Class.prototype.treeConvertShowName=function (o) {\n        var that = this\n            ,options = that.config;\n        var isTreeNode=(o.children&&o.children.length>0);\n        var temhtml='<div style=\"float: left;height: 28px;line-height: 28px;padding-left: '+\n            function () {\n                if(isTreeNode){\n                    return '5px'\n                }else{\n                    return '21px'\n                }\n            }()\n            +'\">'\n            +function () {//位移量\n                var nbspHtml=\"<i>\"//一次位移\n                for(var i=1;i<o[table.config.cols.level];i++) {\n                    nbspHtml = nbspHtml + \"&nbsp;&nbsp;&nbsp;&nbsp;\";\n                }\n                nbspHtml=nbspHtml+\"</i>\";\n                return nbspHtml;\n            }()\n            +function () {//图标或占位符\n                var temTreeHtml='';\n                var temTreeIsOpen=o[table.config.cols.isOpen]?\"&#xe625;\":\"&#xe623;\";\n                if(isTreeNode){//父节点\n                    temTreeHtml='<i class=\"layui-icon layui-tree-head\">'+temTreeIsOpen+'</i>'\n                        +that.treeIconRender(o,true);\n                }else{//叶子节点\n                    temTreeHtml+=that.treeIconRender(o,true);\n                }\n                return temTreeHtml;\n            }()\n            +'</div>';\n        return temhtml;\n    };\n    /**\n     * 节点的展开或折叠\n     * @param o         节点数据（树状表格）\n     * @param isOpen    展开（true）或折叠（false）\n     *\n     * 每个节点有两种状态，\n     * 1、打开状态（isOpen）   打开状态只需在点击瞬间控制，其他时候不需要变动\n     * 2、显示状态（显示或隐藏） 显示状态根据父级节点控制，父级节点是显示并且打开状态的则显示，否则不显示，但不影响其打开状态\n     */\n    Class.prototype.treeNodeOpen=function (o,isOpen) {\n        var that = this\n            ,tr = that.layBody.find('tr[data-index=\"'+ o[table.config.indexName] +'\"]');\n        if(!o){\n            return\n        }\n        o[table.config.cols.isOpen]=isOpen;\n        //处理树结构\n        var fa = function(e) {\n            if(e.children&&e.children.length>0){\n                var temList=e.children;\n                for (var i = 0; i < temList.length; i++) {\n                    var n = temList[i];\n                    if(o[table.config.cols.isOpen]){//打开状态的，关闭\n                        if(e[table.config.cols.isOpen]&&e[table.config.cols.isShow]){//该节点显示\n                            var temo=that.layBody.find('tr[data-index=\"'+ n[table.config.indexName] +'\"]');\n                            temo.css('display', '');\n                            n[table.config.cols.isShow]=true;\n                        }\n                    }else{\n                        var temo=that.layBody.find('tr[data-index=\"'+ n[table.config.indexName] +'\"]');\n                        temo.css('display', 'none');\n                        n[table.config.cols.isShow]=false;\n                    }\n                    fa(n);\n                }\n            }\n        }\n        fa(o);\n        //处理图标\n        that.treeIconRender(o,false);\n        var dbClickI=tr.find('.layui-tree-head');\n        if(o[table.config.cols.isOpen]){//打开状态\n            dbClickI.html('&#xe625;');\n        }else{\n            dbClickI.html('&#xe623;');\n        }\n    };\n\n    /**\n     * icon渲染\n     * @param o\n     * @param isHtml  true（返回html）  false（立即渲染）\n     */\n    Class.prototype.treeIconRender=function (o,isHtml) {\n        var that = this\n            ,options = that.config\n            ,iconOpen=options.iconOpen\n            ,isTreeNode=(o.children&&o.children.length>0);\n        var temTreeHtml='';\n        if(iconOpen){\n            var temf=function () {//自定义图标\n                var temhtml='<i class=\"layui-tree-'+o[options.idField]+'\" style=\"display:inline-block;width: 16px;height: 16px;background:url(';\n                if(isTreeNode){//父节点\n                    if(o[table.config.cols.isOpen]){\n                        temhtml+=o[table.config.cols.iconOpen];\n                    }else{\n                        temhtml+=o[table.config.cols.iconClose];\n                    }\n                }else{\n                    temhtml+=o[table.config.cols.icon];\n                }\n                temhtml+=') 0 0 no-repeat;\"></i>';\n                return temhtml;\n            }\n            if(isTreeNode){//父节点\n                if((o[table.config.cols.iconOpen]||o[table.config.cols.iconClose])){\n                    temTreeHtml=temf();\n                }else{\n                    temTreeHtml='<i class=\"layui-icon layui-tree-'+o[options.idField]+' layui-tree-'+ (o[table.config.cols.isOpen] ? \"branch\" : \"leaf\") +'\" '+iconOpen+'>'+(o[table.config.cols.isOpen]?that.config.branch[1]:that.config.branch[0])+'</i>';\n                }\n            }else{//叶子节点\n                if(o[table.config.cols.icon]){\n                    temTreeHtml=temf();\n                }else{\n                    temTreeHtml+='<i class=\"layui-icon layui-tree-'+o[options.idField]+' layui-tree-leaf\"  '+iconOpen+'>'+that.config.leaf+'</i>';\n                }\n            }\n            if(isHtml){\n                return temTreeHtml;\n            }else{\n                var temdiv=that.layBody.find('tr[data-index=\"'+ o[table.config.indexName] +'\"]').find('td[data-field='+options[TREE_SHOW_NAME]+']').find('.layui-table-cell');\n                $(temdiv).find('div .layui-tree-'+o[options.idField]).remove();//节点附加div\n                $(temdiv).find('div').append(temTreeHtml);\n            }\n        }else{\n            return temTreeHtml;\n        }\n    }\n    //获得数据\n    Class.prototype.pullData = function(curr, loadIndex){\n        var that = this\n            ,options = that.config\n            ,request = options.request\n            ,response = options.response\n            ,sort = function(){\n                if(typeof options.initSort === 'object'){\n                    that.sort(options.initSort.field, options.initSort.type);\n                }\n            };\n        that.startTime = new Date().getTime(); //渲染开始时间\n        if(options.url){ //Ajax请求\n            var params = {};\n            params[request.pageName] = curr;\n            params[request.limitName] = options.limit;\n            that.filterRulesSet(params);//行内过滤条件\n           // that.sortSet(params);//排序条件\n            $.ajax({\n                type: options.method || 'get'\n                ,url: options.url\n                ,data: $.extend(params, options.where)\n                ,dataType: 'json'\n                ,success: function(res){\n                    if(!res[response.dataName]){//返回是未定义或null时转成[]\n                        res[response.dataName]=[]\n                        res[response.statusName]=0;\n                        res[response.countName]=0;\n                        res[response.msgName]='返回的数据状态异常';\n                    };\n                    that.resetDataresetRoot=true;\n                    //如果有数据解析的回调，则获得其返回的数据\n                    if(typeof options.parseData === 'function'){\n                        res = options.parseData(res) || res;\n                    }\n                    that.resetDatas(res[response.dataName]);\n                    res[response.dataName]=table.getDataList(options.id);\n                    if(res[response.statusName] != response.statusCode){\n                        that.renderForm();\n                        that.layMain.html('<div class=\"'+ NONE +'\">'+ (res[response.msgName] || '返回的数据状态异常') +'</div>');\n                    } else {\n                        that.renderData(res, curr, res[response.countName]);\n                        options.time = (new Date().getTime() - that.startTime) + ' ms'; //耗时（接口请求+视图渲染）\n                    }\n                    loadIndex && layer.close(loadIndex);\n                    that.events();\n                    typeof options.done === 'function' && options.done(res, curr, res[response.countName]);\n                }\n                ,error: function(e, m){\n                    that.layMain.html('<div class=\"'+ NONE +'\">数据接口请求异常</div>');\n                    that.renderForm();\n                    loadIndex && layer.close(loadIndex);\n                }\n            });\n        } else if(options.data && options.data.constructor === Array){ //已知数据\n            var res = {},startLimit = curr*options.limit - options.limit\n            res[response.dataName] = options.data.concat().splice(startLimit, options.limit);\n            res[response.countName] = options.data.length;\n            that.renderData(res, curr, options.data.length);\n            that.events();\n            typeof options.done === 'function' && options.done(res, curr, res[response.countName]);\n        }\n    };\n    /**\n     * 设置过滤条件\n     */\n    Class.prototype.filterRulesSet=function (p) {\n        var that = this;\n        p[\"filterRules\"]=JSON.stringify(that.filterRules());\n    }\n    /**\n     * 获取过滤条件\n     * filterRules:\n     * [\n     * {\"field\":\"XXXX\",\"op\":\"equals\",\"value\":[\"1\"],\"datatype\":\"array\"}\n     * ,{\"field\":\"XXXX\",\"op\":\"contains\",\"value\":\"3\",\"datatype\":\"string\"}\n     * ]\n     */\n    Class.prototype.filterRules=function () {\n        var that = this;\n        var filterRules=[];\n        //行内过滤条件\n        var list=that.layFilter.find(\"[name^='filter_']\");\n        layui.each(list,function (i, o) {\n            if($(o).val()){\n                var tem={\n                    \"field\":o.name\n                    ,\"op\":\"like\"\n                    ,\"value\":$(o).val()\n                    ,\"datatype\":\"string\"\n                }\n                filterRules.push(tem);\n            }\n        });\n        // console.log(filterRules,filterRules.toString(),JSON.stringify(filterRules));\n        return filterRules;\n    }\n\n    //遍历表头\n    Class.prototype.eachCols = function(callback){\n        var cols = $.extend(true, [], this.config.cols)\n            ,arrs = [], index = 0;\n\n        //重新整理表头结构\n        layui.each(cols, function(i1, item1){\n            layui.each(item1, function(i2, item2){\n                //如果是组合列，则捕获对应的子列\n                if(item2.colspan > 1){\n                    var childIndex = 0;\n                    index++\n                    item2.CHILD_COLS = [];\n                    layui.each(cols[i1 + 1], function(i22, item22){\n                        if(item22.PARENT_COL || childIndex == item2.colspan) return;\n                        item22.PARENT_COL = index;\n                        item2.CHILD_COLS.push(item22);\n                        childIndex = childIndex + (item22.colspan > 1 ? item22.colspan : 1);\n                    });\n                }\n                if(item2.PARENT_COL) return; //如果是子列，则不进行追加，因为已经存储在父列中\n                arrs.push(item2)\n            });\n        });\n\n        //重新遍历列，如果有子列，则进入递归\n        var eachArrs = function(obj){\n            layui.each(obj || arrs, function(i, item){\n                if(item.CHILD_COLS) return eachArrs(item.CHILD_COLS);\n                callback(i, item);\n            });\n        };\n\n        eachArrs();\n    };\n    /**\n     * 渲染节点显示\n     * @param callback\n     */\n    Class.prototype.renderTreeConvertShowName = function(o){\n        var that = this\n            ,options = that.config\n            ,m=options.elem\n            ,hasRender = m.next('.' + ELEM_VIEW);\n        var temhtml=that.treeConvertShowName(o);\n        var temdiv=that.layBody.find('tr[data-index=\"'+ o[table.config.indexName] +'\"]').find('td[data-field='+options[TREE_SHOW_NAME]+']').find('.layui-table-cell');\n        $(temdiv).find('div').remove();\n        $(temdiv).prepend(temhtml);\n    }\n    /**\n     * 渲染表格单元格样式(宽度样式设置)\n     * @param callback\n     */\n    Class.prototype.renderTdCss = function(){\n        var that = this\n            ,options = that.config\n            ,m=options.elem\n            ,hasRender = m.next('.' + ELEM_VIEW);\n        var id= that.index+\"_\"+MOD_NAME+'_td_style';\n        hasRender.find(\"#\"+id).remove();\n        var styel='<style id=\"'+id+'\">'\n            +function () {\n                var ret=\"\";\n                layui.each(that.config.cols,function (i1, item1) {\n                    layui.each(item1, function(i2, item2){\n                        ret+='.laytable-cell-'+that.index+'-'+(item2.field||i2)+'{' +\n                            'width:'+(item2.width?item2.width+\"px\":\"0px\")\n                            +'}';\n                    });\n                });\n                return ret;\n            }()+'</style>';\n        hasRender.append(styel);\n    }\n\n    /**\n     * 插件内css\n     */\n    Class.prototype.renderCss = function(){\n        var that = this\n            ,options = that.config\n            ,m=options.elem\n            ,hasRender = m.next('.' + ELEM_VIEW);\n        var id=that.index+\"_\"+MOD_NAME+'_style';\n        hasRender.find(\"#\"+id).remove();\n        var styel='<style id=\"'+id+'\">'\n            +function () {\n                var ret=\".layui-tree-head{cursor: pointer;}\";//树图标点击样式\n                ret+=\".layui-table-view {margin:0;}\";\n                return ret;\n            }()+'</style>';\n        hasRender.append(styel);\n    }\n\n    /**\n     * 生成单元格\n     * @param obj       行数据\n     * @param numbers   下标\n     * @param cols      列定义数据\n     * @param i3        第几列\n     */\n    Class.prototype.renderTrUpids=function (obj) {\n        var that = this\n            ,options = that.config;\n        var tree_upid_key=options[TREE_UPID];\n        var upids=' upids=\"'+obj[\"upIds\"]+'\" ';\n        var u_id=' u_id=\"'+obj[tree_upid_key]+'\" '\n        var ret=options.isTree?u_id:'';\n        return ret;\n    }\n    /**\n     * 生成单元格\n     * @param obj       行数据\n     * @param numbers   下标\n     * @param cols      列定义数据\n     * @param i3        第几列\n     */\n    Class.prototype.renderTd=function (obj,cols,numbers,i3) {\n        var that = this\n            ,options = that.config;\n        var v=obj[cols.field]==null?'':String(obj[cols.field]);\n\n        var field = cols.field || i3, content = v\n            ,cell = that.getColElem(that.layHeader, field);\n\n        var treeImgHtml='';\n        if(options.isTree){\n            if(options.treeShowName==cols.field){\n                treeImgHtml=that.treeConvertShowName(obj);\n            }\n        }\n        //td内容\n        var td = ['<td data-field=\"'+ field +'\" '+ function(){\n            var attr = [];\n            if(cols.edit) attr.push('data-edit=\"'+ cols.edit +'\"'); //是否允许单元格编辑\n            if(cols.align) attr.push('align=\"'+ cols.align +'\"'); //对齐方式\n            if(cols.templet) attr.push('data-content=\"'+ content +'\"'); //自定义模板\n            if(cols.toolbar) attr.push('data-off=\"true\"'); //自定义模板\n            if(cols.event) attr.push('lay-event=\"'+ cols.event +'\"'); //自定义事件\n            if(cols.style) attr.push('style=\"'+ cols.style +'\"'); //自定义样式\n            if(cols.minWidth) attr.push('data-minwidth=\"'+ cols.minWidth +'\"'); //单元格最小宽度\n            return attr.join(' ');\n        }() +'>'\n            ,'<div class=\"layui-table-cell laytable-cell-'+ function(){ //返回对应的CSS类标识\n                var str = (options.index + '-' + field);\n                return cols.type === 'normal' ? str\n                    : (str + ' laytable-cell-' + cols.type);\n            }() +'\">'+treeImgHtml+'<p style=\"width: auto;height: 100%;\">'+ function(){\n                var tplData = $.extend(true, {LAY_INDEX: numbers}, obj);\n                //渲染复选框列视图\n                if(cols.type === 'checkbox'){\n                    return tplData[table.config.cols.cheDisabled]?''\n                        :'<input type=\"checkbox\" name=\"'+TABLE_CHECKBOX_ID+'\" value=\"'+tplData[table.config.indexName]+'\" lay-skin=\"primary\" '+ function(){\n                        var isCheckName = table.config.cols.isCheckName;\n                        //如果是全选\n                        if(cols[isCheckName]){\n                            obj[isCheckName] = cols[isCheckName];\n                            return cols[isCheckName] ? 'checked' : '';\n                        }\n                        return tplData[isCheckName] ? 'checked' : '';\n                    }() +'>';\n                } else if(cols.type === 'numbers'){ //渲染序号\n                    return numbers;\n                }else if(cols.type === 'drop'){//下拉框\n                    var rowsField=dl.ui.table.drop.findFieldObj(options.cols[0],field);\n                    if(rowsField&&rowsField['drop']){\n                        var o=dl.cache.code.get(rowsField.drop);\n                        return dl.ui.table.drop.findDropLable(rowsField.drop,content);\n                    }\n                }else if(cols.type === 'radio'){//单选\n                    return tplData[table.config.cols.radDisabled]?''\n                        :'<input type=\"radio\" name=\"'+TABLE_RADIO_ID+'\" '+function () {\n                        var isRadio = table.config.cols.isRadio;\n                        if(cols[isRadio]){\n                            obj[isRadio] = cols[isRadio];\n                            return cols[isRadio] ? 'checked' : '';\n                        }\n                        return tplData[isRadio] ? 'checked' : '';\n                    }()+' value=\"'+tplData[table.config.indexName]+'\" title=\" \">';\n                }\n\n                //解析工具列模板\n                if(cols.toolbar){\n                    return laytpl($(cols.toolbar).html()||'').render(tplData);\n                }\n\n                return cols.templet ? function(){\n                    return typeof cols.templet === 'function'\n                        ? cols.templet(tplData)\n                        : laytpl($(cols.templet).html() || String(content)).render(tplData)\n                }() : content;\n            }()\n            ,'</p></div></td>'].join('');\n        return td;\n    }\n    /**\n     * 生成tr中的一行\n     * @param obj            行数据\n     * @param numbers          行号\n     */\n    Class.prototype.renderTr=function (obj,numbers) {\n        var that = this\n            ,options = that.config;\n        var tds= [];\n        that.eachCols(function(i3, cols){//cols列定义\n            var field = cols.field || i3, content = obj[field];\n            if(cols.colspan > 1) return;\n            var td = that.renderTd(obj,cols,numbers,i3);//td内容\n            tds.push(td);\n            // if(item3.fixed && item3.fixed !== 'right') tds_fixed.push(td);\n            // if(item3.fixed === 'right') tds_fixed_r.push(td);\n        });\n        return tds;\n    };\n    /**\n     * 表格数据部分渲染入口\n     * @param res\n     * @param curr\n     * @param count\n     * @param sort\n     */\n    Class.prototype.renderData = function(res, curr, count, sort){\n        var that = this\n            ,options = that.config\n            ,data = res[options.response.dataName] || []\n            ,trs = []\n            ,trs_fixed = []\n            ,trs_fixed_r = []\n            //渲染视图\n            ,render = function(){ //后续性能提升的重点\n                if(!sort && that.sortKey){\n                    return that.sort(that.sortKey.field, that.sortKey.sort, true);\n                }\n\n                layui.each(data, function(i1, obj){\n                    var uo=that.treeFindUpData(obj);\n                    var display=\"\";\n                    if(!obj[table.config.cols.isShow]&&options.isTree){\n                        display=\"display: none;\";\n                    }\n                    var tds = [], tds_fixed = [], tds_fixed_r = []\n                        ,numbers = i1 + options.limit*(curr - 1) + 1; //序号\n                    if(obj.length === 0) return;\n                    if(!sort){\n                        obj[table.config.indexName] = i1;\n                    }\n                    tds=that.renderTr(obj,numbers);\n                    trs.push('<tr style=\"'+display+'\" data-index=\"'+ i1 +'\" '+that.renderTrUpids(obj)+'>'+ tds.join('') + '</tr>');\n                    trs_fixed.push('<tr data-index=\"'+ i1 +'\">'+ tds_fixed.join('') + '</tr>');\n                    trs_fixed_r.push('<tr data-index=\"'+ i1 +'\">'+ tds_fixed_r.join('') + '</tr>');\n                });\n                //if(data.length === 0) return;\n                that.layBody.scrollTop(0);\n                that.layMain.find('.'+ NONE).remove();\n                that.layMain.find('tbody').html(trs.join(''));\n                that.layFixLeft.find('tbody').html(trs_fixed.join(''));\n                that.layFixRight.find('tbody').html(trs_fixed_r.join(''));\n                that.renderForm();\n                that.haveInit ? that.scrollPatch() : setTimeout(function(){\n                    that.scrollPatch();\n                }, 50);\n                that.haveInit = true;\n                layer.close(that.tipsIndex);\n            };\n        that.key = options.id || options.index;\n        // table.cache[that.key] = data; //记录数据\n        table.setDataList(that.key,data);\n        //显示隐藏分页栏\n        that.layPage[data.length === 0 && curr == 1 ? 'addClass' : 'removeClass'](HIDE);\n        //排序\n        if(sort){\n            return render();\n        }\n        if(data.length === 0){\n            that.renderForm();\n            that.layFixed.remove();\n            that.layMain.find('tbody').html('');\n            that.layMain.find('.'+ NONE).remove();\n            return that.layMain.append('<div class=\"'+ NONE +'\">'+(res[options.response.msgName]?res[options.response.msgName]:options.text.none)+'</div>');\n        }\n        render();\n        that.renderPage(count);//分页渲染\n        //calss加载完成\n        table.pushClassIds(options.id,true);\n    };\n    /**\n     * 渲染分页\n     */\n    Class.prototype.renderPage=function (count) {\n        var that = this\n            ,options = that.config;\n        //同步分页状态\n        if(options.isPage){\n            options.page = $.extend({\n                elem: 'layui-table-page' + options.index\n                ,count: count\n                ,limit: options.limit\n                ,limits: options.limits || [10,15,20,30,40,50,60,70,80,90]\n                ,groups: 3\n                ,layout: ['prev', 'page', 'next', 'skip', 'count', 'limit']\n                ,prev: '<i class=\"layui-icon\">&#xe603;</i>'\n                ,next: '<i class=\"layui-icon\">&#xe602;</i>'\n                ,jump: function(obj, first){\n                    if(!first){\n                        //分页本身并非需要做以下更新，下面参数的同步，主要是因为其它处理统一用到了它们\n                        //而并非用的是 options.page 中的参数（以确保分页未开启的情况仍能正常使用）\n                        that.page = obj.curr; //更新页码\n                        options.limit = obj.limit; //更新每页条数\n                        that.pullData(obj.curr, that.loading());\n                    }\n                }\n            }, options.page);\n            options.page.count = count; //更新总条数\n            laypage.render(options.page);\n        }\n    };\n    /**\n     * 过滤区域的渲染\n     */\n    Class.prototype.renderFilter = function(){\n        var that = this\n            ,options = that.config\n            ,VIEW_CLASS=ELEM_VIEW\n            ,index=that.index; //索引\n        var v = [];\n        v.push('<form method=\"post\"  id=\"'+options.id+'_filter_form\">');\n        v.push('<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" class=\"layui-table\"><thead><tr>');\n        layui.each(options.cols,function (i, o) {\n            layui.each(o, function(i2, item2){\n                var field=item2.field||i2;\n                var minW=item2.minWidth?\"data-minwidth='\"+item2.minWidth+\"'\":\"\";\n                var rowCols=item2.colspan?'colspan=\"'+item2.colspan+'\"':'';\n                var rowspan=item2.rowspan?'rowspan=\"'+item2.rowspan+'\"':'';\n                var unresize=item2.unresize?'data-unresize=\"true\"':'';\n                v.push('<th data-field=\"'+field+'\"'+minW+rowCols+rowspan +unresize+'>');\n                v.push('<div class=\"layui-table-cell laytable-cell-'+function () {\n                    var tem=\"\";\n                    if (item2.colspan > 1) {\n                        tem='group';\n                    }else{\n                        tem=index+\"-\"+field;\n                        if(item2.type !== \"normal\"){\n                            tem+=\" laytable-cell-\"+item2.type;\n                        }\n                    }\n                    return tem;\n                }()+'\">');\n                if(!item2.isFilter||!item2.field){//不开启行内过滤或没有列名\n                    v.push('');\n                }else{\n                    v.push('<input class=\"layui-input '+ ELEM_EDIT +'\" id=\"filter_'+item2.field+'\" name=\"filter_'+item2.field+'\">');\n                }\n                v.push('</div></th>');\n\n            });\n        });\n        v.push('</tr></thead></table>');\n        v.push('</form>');\n        return v.join('');\n    };\n    //找到对应的列元素\n    Class.prototype.getColElem = function(parent, field){\n        var that = this\n            ,options = that.config;\n        return parent.eq(0).find('.laytable-cell-'+ (options.index + '-' + field) + ':eq(0)');\n    };\n    //渲染表单\n    Class.prototype.renderForm = function(type){\n        form.render(type, 'LAY-table-'+ this.index);\n    }\n    /**\n     * 设置排序参数\n     * @param p\n     */\n    Class.prototype.sortSet=function (p) {\n        var that = this;\n        var sort=[];\n        var cols=that.config.cols[0];\n        cols.forEach(function (t) {\n            if(t.sortType){\n                var tem={\n                   \"field\":t.field\n                    ,\"sort\":t.sortType\n                }\n                sort.push(tem);\n            }\n        });\n        p.sort=JSON.stringify(sort);\n    }\n    /**\n     * 设置排序字段\n     * @param th\n     * @param type\n     * @param pull\n     * @param formEvent\n     */\n    Class.prototype.sort = function(th, type, pull, formEvent){\n        var that = this\n            ,field\n            ,res = {}\n            ,options = that.config\n            ,filter = options.elem.attr('lay-filter')\n            ,data = table.getDataList(that.key), thisData;\n        //字段匹配\n        if(typeof th === 'string'){\n            that.layHeader.find('th').each(function(i, item){\n                var othis = $(this)\n                    ,_field = othis.data('field');\n                if(_field === th){\n                    th = othis;\n                    field = _field;\n                    return false;\n                }\n            });\n        }\n        try {\n            var field = field || th.data('field');\n            //如果欲执行的排序已在状态中，则不执行渲染\n            if(that.sortKey && !pull){\n                if(field === that.sortKey.field && type === that.sortKey.sort){\n                    return;\n                }\n            }\n            var elemSort = that.layHeader.find('th .laytable-cell-'+ options.index +'-'+ field).find(ELEM_SORT);\n            //that.layHeader.find('th').find(ELEM_SORT).removeAttr('lay-sort'); //清除其它标题排序状态\n            elemSort.attr('lay-sort', type || null);\n            that.layFixed.find('th')\n        } catch(e){\n            return hint.error('Table modules: Did not match to field');\n        }\n       /* //记录排序索引和类型\n        that.sortKey = {\n            field: field\n            ,sort: type\n        };\n        if(type === 'asc'){ //升序\n        } else if(type === 'desc'){ //降序\n            thisData = layui.sort(data, field, true);\n        } else { //清除排序\n            thisData = layui.sort(data, table.config.indexName);\n        }\n        */\n        var cols=that.getCols(field);\n        if(cols){\n            cols.sortType=type\n        }\n    };\n    //请求loading\n    Class.prototype.loading = function(){\n        var that = this\n            ,options = that.config;\n        if(options.loading && options.url){\n            return layer.msg('数据请求中', {\n                icon: 16\n                ,offset: [\n                    that.elem.offset().top + that.elem.height()/2 - 35 - _WIN.scrollTop() + 'px'\n                    ,that.elem.offset().left + that.elem.width()/2 - 90 - _WIN.scrollLeft() + 'px'\n                ]\n                ,time: -1\n                ,anim: -1\n                ,fixed: false\n            });\n        }\n    };\n    //同步选中值状态\n    Class.prototype.setCheckData = function(index, checked){\n        var that = this\n            ,options = that.config\n            ,thisData = table.getDataList(that.key);\n        if(!thisData[index]) return;\n        if(thisData[index].constructor === Array) return;\n        thisData[index][table.config.cols.isCheckName] = checked;\n    };\n    //同步全选按钮状态\n    Class.prototype.syncCheckAll = function(){\n            var that = this\n                ,options = that.config;\n            var list=table.getDataList(that.config.id);\n            if(!list)return;\n            var temis=true;//全选\n            var checkNum=0;//选中的个数\n            list.forEach(function (t) {\n                if(!t[table.config.cols.cheDisabled]){\n                    if(t[table.config.cols.isCheckName]){\n                        var  checkAllElem = that.layBody.find('tr[data-index='+t[table.config.indexName]+']').find('input[name=\"'+TABLE_CHECKBOX_ID+'\"]');\n                        checkAllElem.prop('checked', true);\n                        checkNum++;\n                    }else{\n                        temis=false;\n                        var  checkAllElem = that.layBody.find('tr[data-index='+t[table.config.indexName]+']').find('input[name=\"'+TABLE_CHECKBOX_ID+'\"]');\n                        checkAllElem.prop('checked', false);\n                    }\n                }\n            });\n            if(temis){//设置全选\n                var  checkAllElem = that.layHeader.find('input[name=\"'+TABLE_CHECKBOX_ID+'\"]');\n                checkAllElem.prop('checked', true);\n            }\n            if(checkNum<(list.length-options.cheDisabledNum)){\n                var  checkAllElem = that.layHeader.find('input[name=\"'+TABLE_CHECKBOX_ID+'\"]');\n                checkAllElem.prop('checked', false);\n            }\n        // console.time(\"pullData\");\n        // console.timeEnd(\"pullData\");\n        that.renderForm('checkbox');\n    };\n    //获取cssRule\n    Class.prototype.getCssRule = function(field, callback){\n        var that = this\n            ,style = that.elem.find('style')[0]\n            ,sheet = style.sheet || style.styleSheet || {}\n            ,rules = sheet.cssRules || sheet.rules;\n        layui.each(rules, function(i, item){\n            if(item.selectorText === ('.laytable-cell-'+ that.index +'-'+ field)){\n                return callback(item), true;\n            }\n        });\n    };\n\n    Class.prototype.test = function(){\n    }\n    /**\n     * 窗体变化自适应\n     */\n    Class.prototype.resize = function(){\n        var that = this;\n        //根据父窗体高度设置table的高度\n        // 1、table自身顶级容器高度（layui-table-view）\n        // 2、内容区域高度（layui-table-main）\n        that.columnWidthInit();//列宽度计算\n        that.tableHeight();//表格高度计算\n        that.resizeHeight();//高度控制\n        that.resizeWidth();//宽度控制\n    };\n\n    //动态分配列宽高\n    Class.prototype.setArea = function(){\n        var that = this;\n        that.columnWidthInit();//列宽度计算\n        that.tableHeight();//表格高度计算\n    };\n    /**\n     * 列宽度计算\n     */\n    Class.prototype.columnWidthInit = function(){\n        var that = this,\n            options = that.config\n            ,colNums = 0 //列个数\n            ,autoColNums = 0 //自动列宽的列个数\n            ,autoWidth = 0 //自动列分配的宽度\n            ,countWidth = 0 //所有列总宽度和\n            ,cntrWidth = options.width ||function(){ //获取容器宽度\n                //如果父元素宽度为0（一般为隐藏元素），则继续查找上层元素，直到找到真实宽度为止\n                var getWidth = function(parent){\n                    var width, isNone;\n                    parent = parent || options.elem.parent()\n                    width = parent.width();\n                    try {\n                        isNone = parent.css('display') === 'none';\n                    } catch(e){}\n                    if(parent[0] && (!width || isNone)) return getWidth(parent.parent());\n                    return width;\n                };\n                return getWidth();\n            }()-17;\n        //统计列个数\n        that.eachCols(function(){\n            colNums++;\n        });\n        //减去边框差\n        cntrWidth = cntrWidth - function(){\n            return (options.skin === 'line' || options.skin === 'nob') ? 2 : colNums + 1;\n        }();\n\n        //遍历所有列\n        layui.each(options.cols, function(i1, item1){\n            layui.each(item1, function(i2, item2){\n                var width;\n                if(!item2){\n                    item1.splice(i2, 1);\n                    return;\n                }\n                that.initOpts(item2);\n                width = item2.width || 0;\n                if(item2.colspan > 1) return;\n                if(/\\d+%$/.test(width)){\n                    item2.width = width = Math.floor((parseFloat(width) / 100) * cntrWidth);\n                } else if(item2._is_width_dev||!width){ //列宽未填写\n                    item2._is_width_dev=true;//采用默认宽度的列\n                    item2.width = width = 0;\n                    autoColNums++;\n                }\n                countWidth = countWidth + width;\n            });\n        });\n        that.autoColNums = autoColNums; //记录自动列数\n        //如果未填充满，则将剩余宽度平分。否则，给未设定宽度的列赋值一个默认宽\n        (cntrWidth > countWidth && autoColNums) && (\n            autoWidth = (cntrWidth - countWidth) / autoColNums\n        );\n        layui.each(options.cols, function(i1, item1){\n            layui.each(item1, function(i2, item2){\n                var minWidth = item2.minWidth || options.cellMinWidth;\n                if(item2.colspan > 1) return;\n                if(item2.width === 0){\n                    item2.width = Math.floor(autoWidth >= minWidth ? autoWidth : minWidth); //不能低于设定的最小宽度\n                }\n            });\n        });\n    };\n    /**\n     * 重新渲染宽度\n     */\n    Class.prototype.resizeWidth = function(){\n        var that = this;\n        that.renderTdCss();\n    };\n    /**\n     * 表格高度计算\n     */\n    Class.prototype.tableHeight = function(){\n        var that = this,\n            options = that.config,\n            optionsFirst = that.configFirst;\n        //确定高度\n        if(!table.kit.isNumber(optionsFirst.height)){//如果非固定高度，则计算高度\n            var htremove=0;//减去的高度\n            if(options.heightRemove&&table.kit.isArray(options.heightRemove)){\n                var htatt=options.heightRemove;\n                htatt.forEach(function (t) {\n                    var temh=table.kit.isNumber(t)?t:$(t).outerHeight(true);\n                    if(table.kit.isNumber(temh)){\n                        htremove+=temh;\n                    }\n                });\n            }\n            //高度铺满：full-差距值\n            var th=_WIN.height()-htremove-1;//that.getParentDivHeight(options.id);\n            that.fullHeightGap=0;\n            if(options.height){\n                if(/^full-\\d+$/.test(options.height)){\n                    that.fullHeightGap = options.height.split('-')[1];\n                }\n            }\n            options.height = th - that.fullHeightGap;\n        }\n    };\n    /**\n     * 重新渲染高度\n     */\n    Class.prototype.resizeHeight = function(){\n        var that = this\n            ,options = that.config\n            ,height = options.height, bodyHeight;\n        if(height < 135) height = 135;\n        that.elem.css('height', height);\n        //tbody区域高度\n        // bodyHeight = parseFloat(height) - parseFloat(that.layHeader.height()) - 1;//原本代码\n        var theader=options.isFilter?76:38;//没有行内过滤区域\n        bodyHeight = parseFloat(height) - theader - 1;//###注意：现在写死表头固定高度为38px，即不支持多表头方式（在tab方式下无法获取正确的高度，待处理）\n        if(options.toolbar){\n            bodyHeight = bodyHeight - that.layTool.outerHeight();\n        }\n        if(options.isPage){\n            bodyHeight = bodyHeight - that.layPage.outerHeight() - 1;\n        }\n        that.layMain.css('height', bodyHeight);\n    };\n    //获取滚动条宽度\n    Class.prototype.getScrollWidth = function(elem){\n        var width = 0;\n        if(elem){\n            width = elem.offsetWidth - elem.clientWidth;\n        } else {\n            elem = document.createElement('div');\n            elem.style.width = '100px';\n            elem.style.height = '100px';\n            elem.style.overflowY = 'scroll';\n\n            document.body.appendChild(elem);\n            width = elem.offsetWidth - elem.clientWidth;\n            document.body.removeChild(elem);\n        }\n        return width;\n    };\n    //滚动条补丁\n    Class.prototype.scrollPatch = function(){\n        var that = this\n            ,layMainTable = that.layMain.children('table')\n            ,scollWidth = that.layMain.width() - that.layMain.prop('clientWidth') //纵向滚动条宽度\n            ,scollHeight = that.layMain.height() - that.layMain.prop('clientHeight') //横向滚动条高度\n            ,getScrollWidth = that.getScrollWidth(that.layMain[0]) //获取主容器滚动条宽度，如果有的话\n            ,outWidth = layMainTable.outerWidth() - that.layMain.width(); //表格内容器的超出宽度\n\n        //如果存在自动列宽，则要保证绝对填充满，并且不能出现横向滚动条\n        if(that.autoColNums && outWidth < 5 && !that.scrollPatchWStatus){\n            var th = that.layHeader.eq(0).find('thead th:last-child')\n                ,field = th.data('field');\n            that.getCssRule(field, function(item){\n                var width = item.style.width || th.outerWidth();\n                item.style.width = (parseFloat(width) - getScrollWidth - outWidth) + 'px';\n                //二次校验，如果仍然出现横向滚动条\n                if(that.layMain.height() - that.layMain.prop('clientHeight') > 0){\n                    item.style.width = parseFloat(item.style.width) - 1 + 'px';\n                }\n                that.scrollPatchWStatus = true;\n            });\n        }\n        if(scollWidth && scollHeight){\n            if(that.elem.find('.layui-table-patch').length<=0){\n                var patchElem = $('<th class=\"layui-table-patch\"><div class=\"layui-table-cell\"></div></th>'); //补丁元素\n                patchElem.find('div').css({\n                    width: scollWidth\n                });\n                that.layHeader.eq(0).find('thead tr').append(patchElem);\n                //that.layFilter.find('table thead tr').append(patchElem);\n            }\n        } else {\n            that.layFilter.eq(0).find('.layui-table-patch').remove();\n            that.layHeader.eq(0).find('.layui-table-patch').remove();\n        }\n        //固定列区域高度\n        var mainHeight = that.layMain.height()\n            ,fixHeight = mainHeight - scollHeight;\n        that.layFixed.find(ELEM_BODY).css('height', layMainTable.height() > fixHeight ? fixHeight : 'auto');\n        //表格宽度小于容器宽度时，隐藏固定列\n        that.layFixRight[outWidth > 0 ? 'removeClass' : 'addClass'](HIDE);\n        //操作栏\n        that.layFixRight.css('right', scollWidth - 1);\n    };\n    //事件处理\n    Class.prototype.events = function(){\n        var that = this\n            ,options = that.config\n            ,_BODY = $('body')\n            ,dict = {}\n            ,th = that.layHeader.find('th')\n            ,bodytr=that.layBody.find('tr')\n            ,resizing;\n        //行点击事件\n        bodytr.unbind('click').on('click',function (e) {\n            var index=$(this).attr(\"data-index\");\n            var list=table.getDataList(that.config.id);\n            var o=list[index];\n            typeof options.onClickRow === 'function' && options.onClickRow(index,o);\n        });\n        //行双击事件\n        bodytr.unbind('dblclick').on('dblclick',function (e) {\n            var index=$(this).attr(\"data-index\");\n            var list=table.getDataList(that.config.id);\n            var o=list[index];\n            typeof options.onDblClickRow === 'function' && options.onDblClickRow(index,o);\n        });\n        //拖拽调整宽度\n        th.unbind('mousemove').on('mousemove', function(e){\n            var othis = $(this)\n                ,oLeft = othis.offset().left\n                ,pLeft = e.clientX - oLeft;\n            if(othis.attr('colspan') > 1 || othis.data('unresize') || dict.resizeStart){\n                return;\n            }\n            dict.allowResize = othis.width() - pLeft <= 10; //是否处于拖拽允许区域\n            _BODY.css('cursor', (dict.allowResize ? 'col-resize' : ''));\n        })\n        th.unbind('mouseleave').on('mouseleave', function(){\n            var othis = $(this);\n            if(dict.resizeStart) return;\n            _BODY.css('cursor', '');\n        })\n        th.unbind('mousedown').on('mousedown', function(e){\n            var othis = $(this);\n            if(dict.allowResize){\n                var field = othis.data('field');\n                e.preventDefault();\n                dict.resizeStart = true; //开始拖拽\n                dict.offset = [e.clientX, e.clientY]; //记录初始坐标\n\n                that.getCssRule(field, function(item){\n                    var width = item.style.width || othis.outerWidth();\n                    dict.rule = item;\n                    dict.ruleWidth = parseFloat(width);\n                    dict.minWidth = othis.data('minwidth') || options.cellMinWidth;\n                });\n            }\n        });\n        //拖拽中\n        _DOC.unbind('mousemove').on('mousemove', function(e){\n            if(dict.resizeStart){\n                e.preventDefault();\n                if(dict.rule){\n                    var setWidth = dict.ruleWidth + e.clientX - dict.offset[0];\n                    if(setWidth < dict.minWidth) setWidth = dict.minWidth;\n                    dict.rule.style.width = setWidth + 'px';\n                    layer.close(that.tipsIndex);\n                }\n                resizing = 1\n            }\n        })\n        _DOC.unbind('mouseup').on('mouseup', function(e){\n            if(dict.resizeStart){\n                dict = {};\n                _BODY.css('cursor', '');\n                that.scrollPatch();\n            }\n            if(resizing === 2){\n                resizing = null;\n            }\n        });\n        //排序\n        th.unbind('click').on('click', function(){//点击标题列\n            var othis = $(this)\n                ,elemSort = othis.find(ELEM_SORT)\n                ,nowType = elemSort.attr('lay-sort')\n                ,type;\n\n            if(!elemSort[0] || resizing === 1) return resizing = 2;\n\n            if(nowType === 'asc'){\n                type = 'desc';\n            } else if(nowType === 'desc'){\n                type = null;\n            } else {\n                type = 'asc';\n            }\n            that.sort(othis, type, null, true);\n            table.query(that.key);//从新查询\n        })\n        th.find(ELEM_SORT+' .layui-edge ').unbind('click').on('click', function(e){//点击小三角形\n            var othis = $(this)\n                ,index = othis.index()\n                ,field = othis.parents('th').eq(0).data('field')\n            layui.stope(e);\n            if(index === 0){\n                that.sort(field, 'asc', null, true);\n            } else {\n                that.sort(field, 'desc', null, true);\n            }\n            table.query(that.key);//从新查询\n        });\n        if(!that.eventsinitIsRun){\n            that.eventsinit();\n            that.eventsinitIsRun=true;\n        }\n        //同步滚动条\n        that.layMain.unbind('scroll').on('scroll', function(){\n            var othis = $(this)\n                ,scrollLeft = othis.scrollLeft()\n                ,scrollTop = othis.scrollTop();\n\n            that.layHeader.scrollLeft(scrollLeft);\n            that.layFilter.scrollLeft(scrollLeft);\n            that.layFixed.find(ELEM_BODY).scrollTop(scrollTop);\n\n            layer.close(that.tipsIndex);\n        });\n        _WIN.unbind('resize').on('resize', function(){ //自适应\n            that.resize();\n        });\n    };\n    //事件处理单元格编辑(只执行一次)\n    Class.prototype.eventsinitIsRun=false;\n    //只执行一次的事件\n    Class.prototype.eventsinit = function(){\n        var that = this\n            ,options = that.config\n            ,ELEM_CELL = '.layui-table-cell'\n            ,filter = options.elem.attr('lay-filter');\n        //行内过滤\n        that.layFilter.on('keyup',\"[name^='filter_']\",function () {\n            that.page=1;\n            that.pullData(that.page, that.loading());\n        });\n        //行事件\n        that.layBody.on('mouseenter','tr',function(){\n            var othis = $(this)\n                ,index = othis.index();\n            that.layBody.find('tr:eq('+ index +')').addClass(ELEM_HOVER)\n        })\n        that.layBody.on('mouseleave','tr', function(){\n            var othis = $(this)\n                ,index = othis.index();\n            that.layBody.find('tr:eq('+ index +')').removeClass(ELEM_HOVER)\n        });\n        //单元格事件\n        that.layBody.on('click','td div.layui-table-cell p',function(){\n            var othis = $(this).parent().parent()\n                ,field = othis.data('field')\n                ,editType = othis.data('edit')\n                ,index = othis.parents('tr').eq(0).data('index')\n                ,data = table.getDataList(that.key)[index]\n                ,elemCell = othis.children(ELEM_CELL);\n            var  options = that.config;\n            layer.close(that.tipsIndex);\n            if(othis.data('off')) return;\n\n            //显示编辑表单\n            if(editType){\n                if(editType === 'select') { //选择框\n                    var dropName=othis.data('drop');\n                    var rowsField=dl.ui.table.drop.findFieldObj(options.cols[0],field);\n                    var o=dl.cache.code.get(rowsField.drop);\n                    var html='';\n                    var scv=o.syscodevaluecache;\n                    for(var i in scv){\n                        var isSelected=\"\";\n                        if(scv[i].scv_value==data[field]){\n                            isSelected=\"selected='selected'\";\n                        }\n                        //选中\n                        html+='<option '+isSelected+'  value=\"'+scv[i].scv_value+'\">'+scv[i].scv_show_name+'</option>'\n                    }\n                    var select = $('<select class=\"'+ ELEM_EDIT +'\" lay-ignore>' +\n                        html+\n                        '</select>');\n                    othis.find('.'+ELEM_EDIT)[0] || othis.append(select);\n                } else { //输入框\n                    var input = $('<input class=\"layui-input '+ ELEM_EDIT +'\">');\n                    input[0].value = $.trim($(this).text());//  othis.data('content') || elemCell.text();\n                    othis.find('.'+ELEM_EDIT)[0] || othis.append(input);\n                    input.focus();\n                }\n                return;\n            }\n\n            //如果出现省略，则可查看更多\n            var c=that.getCols(field);\n\n            if(!table.config.initWidth[c[\"type\"]]){\n                if(elemCell.find('.layui-form-switch,.layui-form-checkbox')[0]) return; //限制不出现更多（暂时）\n                if(Math.round(elemCell.prop('scrollWidth')) > Math.round(elemCell.outerWidth())){\n                    that.tipsIndex = layer.tips([\n                        '<div class=\"layui-table-tips-main\" style=\"margin-top: -'+ (elemCell.height() + 16) +'px;'+ function(){\n                            if(options.size === 'sm'){\n                                return 'padding: 4px 15px; font-size: 12px;';\n                            }\n                            if(options.size === 'lg'){\n                                return 'padding: 14px 15px;';\n                            }\n                            return '';\n                        }() +'\">'\n                        ,elemCell.html()\n                        ,'</div>'\n                        ,'<i class=\"layui-icon layui-table-tips-c\">&#x1006;</i>'\n                    ].join(''), elemCell[0], {\n                        tips: [3, '']\n                        ,time: -1\n                        ,anim: -1\n                        ,maxWidth: (device.ios || device.android) ? 300 : 600\n                        ,isOutAnim: false\n                        ,skin: 'layui-table-tips'\n                        ,success: function(layero, index){\n                            layero.find('.layui-table-tips-c').on('click', function(){\n                                layer.close(index);\n                            });\n                        }\n                    });\n                }\n            }\n        });\n        that.layBody.on('change','.'+ELEM_EDIT, function(){\n            var othis = $(this)\n                ,value = this.value\n                ,field = othis.parent().data('field')\n                ,index = othis.parents('tr').eq(0).data('index')\n                ,data = table.getDataList(that.key)[index];\n            data[field] = value; //更新缓存中的值\n            layui.event.call(this, MOD_NAME, 'edit('+ filter +')', {\n                value: value\n                ,data: data\n                ,field: field\n            });\n        });\n        that.layBody.on('blur','.'+ELEM_EDIT, function(){//单元格失去焦点\n            var templet\n                ,othis = $(this)\n                ,field = othis.parent().data('field')\n                ,index = othis.parents('tr').eq(0).data('index')\n                ,editType = othis.parent().data('edit')\n                ,data = table.getDataList(that.key)[index];\n            var  options = that.config;\n            that.eachCols(function(i, item){\n                if(item.field == field && item.templet){\n                    templet = item.templet;\n                }\n            });\n            var value=\"\";\n            if(editType === 'select') { //选择框\n                var rowsField=dl.ui.table.drop.findFieldObj(options.cols[0],field);\n                if(rowsField&&rowsField['drop']){\n                    var o=dl.cache.code.get(rowsField.drop);\n                    value=dl.ui.table.drop.findDropLable(rowsField.drop,this.value);\n                }\n                othis.parent().find(ELEM_CELL+' p').html(\n                    templet ? laytpl($(templet).html() || value).render(data) : value\n                );\n            } else {//输入框\n                othis.parent().find(ELEM_CELL+' p').html(\n                    templet ? laytpl($(templet).html() || this.value).render(data) : this.value\n                );\n            }\n            othis.parent().data('content', this.value);\n            othis.remove();\n        });\n        //树形节点点击事件（隐藏展开下级节点）\n        that.elem.on('click','i.layui-tree-head', function(){\n            var othis = $(this)\n                ,index = othis.parents('tr').eq(0).data('index')\n                ,options=that.config\n                ,datas=table.getDataList(that.key);//数据\n            var o=datas[index];\n            that.treeNodeOpen(o,!o[table.config.cols.isOpen]);\n            that.resize();\n        });\n        //复选框选择\n        that.elem.on('click','input[name=\"'+TABLE_CHECKBOX_ID+'\"]+', function(){\n            var checkbox = $(this).prev()\n                ,childs = that.layBody.find('input[name=\"'+TABLE_CHECKBOX_ID+'\"]')\n                ,index = checkbox.parents('tr').eq(0).data('index')\n                ,checked = checkbox[0].checked\n                ,obj=table.getDataList(that.config.id)[index]\n                ,isAll = checkbox.attr('lay-filter') === 'layTableAllChoose';\n            //全选\n            if(isAll){\n                var list=table.getDataList(that.key);\n                list.forEach(function (temo) {\n                    if(!temo[table.config.cols.cheDisabled]){//可以选择的才设置\n                        that.setCheckData(temo[table.config.indexName], checked);\n                    }\n                });\n            } else {\n                that.setCheckData(index, checked);\n                if(options.isTree){\n                    //处理下级\n                    var sonList=that.treeFindSonData(obj);\n                    sonList.forEach(function (temo) {\n                        if(!temo[table.config.cols.cheDisabled]){//可以选择的才设置\n                            that.setCheckData(temo[table.config.indexName], checked);\n                        }\n                    });\n\n                    //处理上级\n                    var temf=function (o) {\n                        if(o==null)return;\n                        if(o&&o.children.length>0){\n                            var temis=true;\n                            o.children.forEach(function (temo) {\n                                if(temo[table.config.cols.isCheckName]){\n                                    temis=false;\n                                }\n                            });\n                            if(checked||temis){\n                                that.setCheckData(o[table.config.indexName], checked);\n                            }\n                            var temuo=that.treeFindUpData(o);\n                            if(temuo){\n                                temf(temuo);\n                            }\n                        }\n                    }\n                    var uo=that.treeFindUpData(obj);\n                    temf(uo);\n                }\n            }\n            that.syncCheckAll();\n            layui.event.call(this, MOD_NAME, 'checkbox('+ filter +')', {\n                checked: checked\n                ,data: table.getDataList(that.key) ? (obj || {}) : {}\n                ,type: isAll ? 'all' : 'one'\n            });\n            typeof options.onCheck === 'function' && options.onCheck(obj,checked,isAll);\n        });\n\n        //单选框选择\n        that.elem.on('click','input[name=\"'+TABLE_RADIO_ID+'\"]+', function(){\n            var checkbox = $(this).prev()\n                ,index = checkbox.parents('tr').eq(0).data('index')\n                ,obj=table.getDataList(that.config.id)[index];\n            typeof options.onRadio === 'function' && options.onRadio(obj);\n        });\n\n        //工具条操作事件\n        that.layBody.on('click', '*[lay-event]',function(){\n            var othis = $(this)\n                ,index = othis.parents('tr').eq(0).data('index')\n                ,tr = that.layBody.find('tr[data-index=\"'+ index +'\"]')\n                ,ELEM_CLICK = 'layui-table-click'\n                ,list = table.getDataList(that.key)\n                ,data = table.getDataList(that.key)[index];\n            layui.event.call(this, MOD_NAME, 'tool('+ filter +')', {\n                data: data//table.clearCacheKey(data)\n                ,event: othis.attr('lay-event')\n                ,tr: tr\n                ,del: function(){\n                    table.delRow(options.id,data);\n                }\n                ,update: function(fields){\n                    fields = fields || {};\n                    layui.each(fields, function(key, value){\n                        if(key in data){\n                            var templet, td = tr.children('td[data-field=\"'+ key +'\"]');\n                            data[key] = value;\n                            that.eachCols(function(i, item2){\n                                if(item2.field == key && item2.templet){\n                                    templet = item2.templet;\n                                }\n                            });\n                            td.children(ELEM_CELL).html(\n                                templet ? laytpl($(templet).html() || value).render(data) : value\n                            );\n                            td.data('content', value);\n                        }\n                    });\n                }\n            });\n            tr.addClass(ELEM_CLICK).siblings('tr').removeClass(ELEM_CLICK);\n        });\n    }\n    //表格重载\n    thisTable.config = {};\n    //自动完成渲染\n    table.init();\n    // layui.link('treeGrid.css');\n    exports(MOD_NAME, table);\n});"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/layui/layui.all.js",
    "content": "/** layui-v2.4.5 MIT License By https://www.layui.com */\n ;!function(e){\"use strict\";var t=document,o={modules:{},status:{},timeout:10,event:{}},n=function(){this.v=\"2.4.5\"},r=function(){var e=t.currentScript?t.currentScript.src:function(){for(var e,o=t.scripts,n=o.length-1,r=n;r>0;r--)if(\"interactive\"===o[r].readyState){e=o[r].src;break}return e||o[n].src}();return e.substring(0,e.lastIndexOf(\"/\")+1)}(),i=function(t){e.console&&console.error&&console.error(\"Layui hint: \"+t)},a=\"undefined\"!=typeof opera&&\"[object Opera]\"===opera.toString(),u={layer:\"modules/layer\",laydate:\"modules/laydate\",laypage:\"modules/laypage\",laytpl:\"modules/laytpl\",layim:\"modules/layim\",layedit:\"modules/layedit\",form:\"modules/form\",upload:\"modules/upload\",tree:\"modules/tree\",table:\"modules/table\",element:\"modules/element\",rate:\"modules/rate\",colorpicker:\"modules/colorpicker\",slider:\"modules/slider\",carousel:\"modules/carousel\",flow:\"modules/flow\",util:\"modules/util\",code:\"modules/code\",jquery:\"modules/jquery\",mobile:\"modules/mobile\",\"layui.all\":\"../layui.all\"};n.prototype.cache=o,n.prototype.define=function(e,t){var n=this,r=\"function\"==typeof e,i=function(){var e=function(e,t){layui[e]=t,o.status[e]=!0};return\"function\"==typeof t&&t(function(n,r){e(n,r),o.callback[n]=function(){t(e)}}),this};return r&&(t=e,e=[]),layui[\"layui.all\"]||!layui[\"layui.all\"]&&layui[\"layui.mobile\"]?i.call(n):(n.use(e,i),n)},n.prototype.use=function(e,n,l){function s(e,t){var n=\"PLaySTATION 3\"===navigator.platform?/^complete$/:/^(complete|loaded)$/;(\"load\"===e.type||n.test((e.currentTarget||e.srcElement).readyState))&&(o.modules[f]=t,d.removeChild(v),function r(){return++m>1e3*o.timeout/4?i(f+\" is not a valid module\"):void(o.status[f]?c():setTimeout(r,4))}())}function c(){l.push(layui[f]),e.length>1?y.use(e.slice(1),n,l):\"function\"==typeof n&&n.apply(layui,l)}var y=this,p=o.dir=o.dir?o.dir:r,d=t.getElementsByTagName(\"head\")[0];e=\"string\"==typeof e?[e]:e,window.jQuery&&jQuery.fn.on&&(y.each(e,function(t,o){\"jquery\"===o&&e.splice(t,1)}),layui.jquery=layui.$=jQuery);var f=e[0],m=0;if(l=l||[],o.host=o.host||(p.match(/\\/\\/([\\s\\S]+?)\\//)||[\"//\"+location.host+\"/\"])[0],0===e.length||layui[\"layui.all\"]&&u[f]||!layui[\"layui.all\"]&&layui[\"layui.mobile\"]&&u[f])return c(),y;if(o.modules[f])!function g(){return++m>1e3*o.timeout/4?i(f+\" is not a valid module\"):void(\"string\"==typeof o.modules[f]&&o.status[f]?c():setTimeout(g,4))}();else{var v=t.createElement(\"script\"),h=(u[f]?p+\"lay/\":/^\\{\\/\\}/.test(y.modules[f])?\"\":o.base||\"\")+(y.modules[f]||f)+\".js\";h=h.replace(/^\\{\\/\\}/,\"\"),v.async=!0,v.charset=\"utf-8\",v.src=h+function(){var e=o.version===!0?o.v||(new Date).getTime():o.version||\"\";return e?\"?v=\"+e:\"\"}(),d.appendChild(v),!v.attachEvent||v.attachEvent.toString&&v.attachEvent.toString().indexOf(\"[native code\")<0||a?v.addEventListener(\"load\",function(e){s(e,h)},!1):v.attachEvent(\"onreadystatechange\",function(e){s(e,h)}),o.modules[f]=h}return y},n.prototype.getStyle=function(t,o){var n=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return n[n.getPropertyValue?\"getPropertyValue\":\"getAttribute\"](o)},n.prototype.link=function(e,n,r){var a=this,u=t.createElement(\"link\"),l=t.getElementsByTagName(\"head\")[0];\"string\"==typeof n&&(r=n);var s=(r||e).replace(/\\.|\\//g,\"\"),c=u.id=\"layuicss-\"+s,y=0;return u.rel=\"stylesheet\",u.href=e+(o.debug?\"?v=\"+(new Date).getTime():\"\"),u.media=\"all\",t.getElementById(c)||l.appendChild(u),\"function\"!=typeof n?a:(function p(){return++y>1e3*o.timeout/100?i(e+\" timeout\"):void(1989===parseInt(a.getStyle(t.getElementById(c),\"width\"))?function(){n()}():setTimeout(p,100))}(),a)},o.callback={},n.prototype.factory=function(e){if(layui[e])return\"function\"==typeof o.callback[e]?o.callback[e]:null},n.prototype.addcss=function(e,t,n){return layui.link(o.dir+\"css/\"+e,t,n)},n.prototype.img=function(e,t,o){var n=new Image;return n.src=e,n.complete?t(n):(n.onload=function(){n.onload=null,\"function\"==typeof t&&t(n)},void(n.onerror=function(e){n.onerror=null,\"function\"==typeof o&&o(e)}))},n.prototype.config=function(e){e=e||{};for(var t in e)o[t]=e[t];return this},n.prototype.modules=function(){var e={};for(var t in u)e[t]=u[t];return e}(),n.prototype.extend=function(e){var t=this;e=e||{};for(var o in e)t[o]||t.modules[o]?i(\"模块名 \"+o+\" 已被占用\"):t.modules[o]=e[o];return t},n.prototype.router=function(e){var t=this,e=e||location.hash,o={path:[],search:{},hash:(e.match(/[^#](#.*$)/)||[])[1]||\"\"};return/^#\\//.test(e)?(e=e.replace(/^#\\//,\"\"),o.href=\"/\"+e,e=e.replace(/([^#])(#.*$)/,\"$1\").split(\"/\")||[],t.each(e,function(e,t){/^\\w+=/.test(t)?function(){t=t.split(\"=\"),o.search[t[0]]=t[1]}():o.path.push(t)}),o):o},n.prototype.data=function(t,o,n){if(t=t||\"layui\",n=n||localStorage,e.JSON&&e.JSON.parse){if(null===o)return delete n[t];o=\"object\"==typeof o?o:{key:o};try{var r=JSON.parse(n[t])}catch(i){var r={}}return\"value\"in o&&(r[o.key]=o.value),o.remove&&delete r[o.key],n[t]=JSON.stringify(r),o.key?r[o.key]:r}},n.prototype.sessionData=function(e,t){return this.data(e,t,sessionStorage)},n.prototype.device=function(t){var o=navigator.userAgent.toLowerCase(),n=function(e){var t=new RegExp(e+\"/([^\\\\s\\\\_\\\\-]+)\");return e=(o.match(t)||[])[1],e||!1},r={os:function(){return/windows/.test(o)?\"windows\":/linux/.test(o)?\"linux\":/iphone|ipod|ipad|ios/.test(o)?\"ios\":/mac/.test(o)?\"mac\":void 0}(),ie:function(){return!!(e.ActiveXObject||\"ActiveXObject\"in e)&&((o.match(/msie\\s(\\d+)/)||[])[1]||\"11\")}(),weixin:n(\"micromessenger\")};return t&&!r[t]&&(r[t]=n(t)),r.android=/android/.test(o),r.ios=\"ios\"===r.os,r},n.prototype.hint=function(){return{error:i}},n.prototype.each=function(e,t){var o,n=this;if(\"function\"!=typeof t)return n;if(e=e||[],e.constructor===Object){for(o in e)if(t.call(e[o],o,e[o]))break}else for(o=0;o<e.length&&!t.call(e[o],o,e[o]);o++);return n},n.prototype.sort=function(e,t,o){var n=JSON.parse(JSON.stringify(e||[]));return t?(n.sort(function(e,o){var n=/^-?\\d+$/,r=e[t],i=o[t];return n.test(r)&&(r=parseFloat(r)),n.test(i)&&(i=parseFloat(i)),r&&!i?1:!r&&i?-1:r>i?1:r<i?-1:0}),o&&n.reverse(),n):n},n.prototype.stope=function(t){t=t||e.event;try{t.stopPropagation()}catch(o){t.cancelBubble=!0}},n.prototype.onevent=function(e,t,o){return\"string\"!=typeof e||\"function\"!=typeof o?this:n.event(e,t,null,o)},n.prototype.event=n.event=function(e,t,n,r){var i=this,a=null,u=t.match(/\\((.*)\\)$/)||[],l=(e+\".\"+t).replace(u[0],\"\"),s=u[1]||\"\",c=function(e,t){var o=t&&t.call(i,n);o===!1&&null===a&&(a=!1)};return r?(o.event[l]=o.event[l]||{},o.event[l][s]=[r],this):(layui.each(o.event[l],function(e,t){return\"{*}\"===s?void layui.each(t,c):(\"\"===e&&layui.each(t,c),void(s&&e===s&&layui.each(t,c)))}),a)},e.layui=new n}(window);layui.define(function(a){var i=layui.cache;layui.config({dir:i.dir.replace(/lay\\/dest\\/$/,\"\")}),a(\"layui.all\",layui.v)});layui.define(function(e){\"use strict\";var r={open:\"{{\",close:\"}}\"},c={exp:function(e){return new RegExp(e,\"g\")},query:function(e,c,t){var o=[\"#([\\\\s\\\\S])+?\",\"([^{#}])*?\"][e||0];return n((c||\"\")+r.open+o+r.close+(t||\"\"))},escape:function(e){return String(e||\"\").replace(/&(?!#?[a-zA-Z0-9]+;)/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(/'/g,\"&#39;\").replace(/\"/g,\"&quot;\")},error:function(e,r){var c=\"Laytpl Error：\";return\"object\"==typeof console&&console.error(c+e+\"\\n\"+(r||\"\")),c+e}},n=c.exp,t=function(e){this.tpl=e};t.pt=t.prototype,window.errors=0,t.pt.parse=function(e,t){var o=this,p=e,a=n(\"^\"+r.open+\"#\",\"\"),l=n(r.close+\"$\",\"\");e=e.replace(/\\s+|\\r|\\t|\\n/g,\" \").replace(n(r.open+\"#\"),r.open+\"# \").replace(n(r.close+\"}\"),\"} \"+r.close).replace(/\\\\/g,\"\\\\\\\\\").replace(n(r.open+\"!(.+?)!\"+r.close),function(e){return e=e.replace(n(\"^\"+r.open+\"!\"),\"\").replace(n(\"!\"+r.close),\"\").replace(n(r.open+\"|\"+r.close),function(e){return e.replace(/(.)/g,\"\\\\$1\")})}).replace(/(?=\"|')/g,\"\\\\\").replace(c.query(),function(e){return e=e.replace(a,\"\").replace(l,\"\"),'\";'+e.replace(/\\\\/g,\"\")+';view+=\"'}).replace(c.query(1),function(e){var c='\"+(';return e.replace(/\\s/g,\"\")===r.open+r.close?\"\":(e=e.replace(n(r.open+\"|\"+r.close),\"\"),/^=/.test(e)&&(e=e.replace(/^=/,\"\"),c='\"+_escape_('),c+e.replace(/\\\\/g,\"\")+')+\"')}),e='\"use strict\";var view = \"'+e+'\";return view;';try{return o.cache=e=new Function(\"d, _escape_\",e),e(t,c.escape)}catch(u){return delete o.cache,c.error(u,p)}},t.pt.render=function(e,r){var n,t=this;return e?(n=t.cache?t.cache(e,c.escape):t.parse(t.tpl,e),r?void r(n):n):c.error(\"no data\")};var o=function(e){return\"string\"!=typeof e?c.error(\"Template not found\"):new t(e)};o.config=function(e){e=e||{};for(var c in e)r[c]=e[c]},o.v=\"1.2.0\",e(\"laytpl\",o)});layui.define(function(e){\"use strict\";var a=document,t=\"getElementById\",n=\"getElementsByTagName\",i=\"laypage\",r=\"layui-disabled\",u=function(e){var a=this;a.config=e||{},a.config.index=++s.index,a.render(!0)};u.prototype.type=function(){var e=this.config;if(\"object\"==typeof e.elem)return void 0===e.elem.length?2:3},u.prototype.view=function(){var e=this,a=e.config,t=a.groups=\"groups\"in a?0|a.groups:5;a.layout=\"object\"==typeof a.layout?a.layout:[\"prev\",\"page\",\"next\"],a.count=0|a.count,a.curr=0|a.curr||1,a.limits=\"object\"==typeof a.limits?a.limits:[10,20,30,40,50],a.limit=0|a.limit||10,a.pages=Math.ceil(a.count/a.limit)||1,a.curr>a.pages&&(a.curr=a.pages),t<0?t=1:t>a.pages&&(t=a.pages),a.prev=\"prev\"in a?a.prev:\"&#x4E0A;&#x4E00;&#x9875;\",a.next=\"next\"in a?a.next:\"&#x4E0B;&#x4E00;&#x9875;\";var n=a.pages>t?Math.ceil((a.curr+(t>1?1:0))/(t>0?t:1)):1,i={prev:function(){return a.prev?'<a href=\"javascript:;\" class=\"layui-laypage-prev'+(1==a.curr?\" \"+r:\"\")+'\" data-page=\"'+(a.curr-1)+'\">'+a.prev+\"</a>\":\"\"}(),page:function(){var e=[];if(a.count<1)return\"\";n>1&&a.first!==!1&&0!==t&&e.push('<a href=\"javascript:;\" class=\"layui-laypage-first\" data-page=\"1\"  title=\"&#x9996;&#x9875;\">'+(a.first||1)+\"</a>\");var i=Math.floor((t-1)/2),r=n>1?a.curr-i:1,u=n>1?function(){var e=a.curr+(t-i-1);return e>a.pages?a.pages:e}():t;for(u-r<t-1&&(r=u-t+1),a.first!==!1&&r>2&&e.push('<span class=\"layui-laypage-spr\">&#x2026;</span>');r<=u;r++)r===a.curr?e.push('<span class=\"layui-laypage-curr\"><em class=\"layui-laypage-em\" '+(/^#/.test(a.theme)?'style=\"background-color:'+a.theme+';\"':\"\")+\"></em><em>\"+r+\"</em></span>\"):e.push('<a href=\"javascript:;\" data-page=\"'+r+'\">'+r+\"</a>\");return a.pages>t&&a.pages>u&&a.last!==!1&&(u+1<a.pages&&e.push('<span class=\"layui-laypage-spr\">&#x2026;</span>'),0!==t&&e.push('<a href=\"javascript:;\" class=\"layui-laypage-last\" title=\"&#x5C3E;&#x9875;\"  data-page=\"'+a.pages+'\">'+(a.last||a.pages)+\"</a>\")),e.join(\"\")}(),next:function(){return a.next?'<a href=\"javascript:;\" class=\"layui-laypage-next'+(a.curr==a.pages?\" \"+r:\"\")+'\" data-page=\"'+(a.curr+1)+'\">'+a.next+\"</a>\":\"\"}(),count:'<span class=\"layui-laypage-count\">共 '+a.count+\" 条</span>\",limit:function(){var e=['<span class=\"layui-laypage-limits\"><select lay-ignore>'];return layui.each(a.limits,function(t,n){e.push('<option value=\"'+n+'\"'+(n===a.limit?\"selected\":\"\")+\">\"+n+\" 条/页</option>\")}),e.join(\"\")+\"</select></span>\"}(),refresh:['<a href=\"javascript:;\" data-page=\"'+a.curr+'\" class=\"layui-laypage-refresh\">','<i class=\"layui-icon layui-icon-refresh\"></i>',\"</a>\"].join(\"\"),skip:function(){return['<span class=\"layui-laypage-skip\">&#x5230;&#x7B2C;','<input type=\"text\" min=\"1\" value=\"'+a.curr+'\" class=\"layui-input\">','&#x9875;<button type=\"button\" class=\"layui-laypage-btn\">&#x786e;&#x5b9a;</button>',\"</span>\"].join(\"\")}()};return['<div class=\"layui-box layui-laypage layui-laypage-'+(a.theme?/^#/.test(a.theme)?\"molv\":a.theme:\"default\")+'\" id=\"layui-laypage-'+a.index+'\">',function(){var e=[];return layui.each(a.layout,function(a,t){i[t]&&e.push(i[t])}),e.join(\"\")}(),\"</div>\"].join(\"\")},u.prototype.jump=function(e,a){if(e){var t=this,i=t.config,r=e.children,u=e[n](\"button\")[0],l=e[n](\"input\")[0],p=e[n](\"select\")[0],c=function(){var e=0|l.value.replace(/\\s|\\D/g,\"\");e&&(i.curr=e,t.render())};if(a)return c();for(var o=0,y=r.length;o<y;o++)\"a\"===r[o].nodeName.toLowerCase()&&s.on(r[o],\"click\",function(){var e=0|this.getAttribute(\"data-page\");e<1||e>i.pages||(i.curr=e,t.render())});p&&s.on(p,\"change\",function(){var e=this.value;i.curr*e>i.count&&(i.curr=Math.ceil(i.count/e)),i.limit=e,t.render()}),u&&s.on(u,\"click\",function(){c()})}},u.prototype.skip=function(e){if(e){var a=this,t=e[n](\"input\")[0];t&&s.on(t,\"keyup\",function(t){var n=this.value,i=t.keyCode;/^(37|38|39|40)$/.test(i)||(/\\D/.test(n)&&(this.value=n.replace(/\\D/,\"\")),13===i&&a.jump(e,!0))})}},u.prototype.render=function(e){var n=this,i=n.config,r=n.type(),u=n.view();2===r?i.elem&&(i.elem.innerHTML=u):3===r?i.elem.html(u):a[t](i.elem)&&(a[t](i.elem).innerHTML=u),i.jump&&i.jump(i,e);var s=a[t](\"layui-laypage-\"+i.index);n.jump(s),i.hash&&!e&&(location.hash=\"!\"+i.hash+\"=\"+i.curr),n.skip(s)};var s={render:function(e){var a=new u(e);return a.index},index:layui.laypage?layui.laypage.index+1e4:0,on:function(e,a,t){return e.attachEvent?e.attachEvent(\"on\"+a,function(a){a.target=a.srcElement,t.call(e,a)}):e.addEventListener(a,t,!1),this}};e(i,s)});!function(){\"use strict\";var e=window.layui&&layui.define,t={getPath:function(){var e=document.currentScript?document.currentScript.src:function(){for(var e,t=document.scripts,n=t.length-1,a=n;a>0;a--)if(\"interactive\"===t[a].readyState){e=t[a].src;break}return e||t[n].src}();return e.substring(0,e.lastIndexOf(\"/\")+1)}(),getStyle:function(e,t){var n=e.currentStyle?e.currentStyle:window.getComputedStyle(e,null);return n[n.getPropertyValue?\"getPropertyValue\":\"getAttribute\"](t)},link:function(e,a,i){if(n.path){var r=document.getElementsByTagName(\"head\")[0],o=document.createElement(\"link\");\"string\"==typeof a&&(i=a);var s=(i||e).replace(/\\.|\\//g,\"\"),l=\"layuicss-\"+s,d=0;o.rel=\"stylesheet\",o.href=n.path+e,o.id=l,document.getElementById(l)||r.appendChild(o),\"function\"==typeof a&&!function c(){return++d>80?window.console&&console.error(\"laydate.css: Invalid\"):void(1989===parseInt(t.getStyle(document.getElementById(l),\"width\"))?a():setTimeout(c,100))}()}}},n={v:\"5.0.9\",config:{},index:window.laydate&&window.laydate.v?1e5:0,path:t.getPath,set:function(e){var t=this;return t.config=w.extend({},t.config,e),t},ready:function(a){var i=\"laydate\",r=\"\",o=(e?\"modules/laydate/\":\"theme/\")+\"default/laydate.css?v=\"+n.v+r;return e?layui.addcss(o,a,i):t.link(o,a,i),this}},a=function(){var e=this;return{hint:function(t){e.hint.call(e,t)},config:e.config}},i=\"laydate\",r=\".layui-laydate\",o=\"layui-this\",s=\"laydate-disabled\",l=\"开始日期超出了结束日期<br>建议重新选择\",d=[100,2e5],c=\"layui-laydate-static\",m=\"layui-laydate-list\",u=\"laydate-selected\",h=\"layui-laydate-hint\",y=\"laydate-day-prev\",f=\"laydate-day-next\",p=\"layui-laydate-footer\",g=\".laydate-btns-confirm\",v=\"laydate-time-text\",D=\".laydate-btns-time\",T=function(e){var t=this;t.index=++n.index,t.config=w.extend({},t.config,n.config,e),n.ready(function(){t.init()})},w=function(e){return new C(e)},C=function(e){for(var t=0,n=\"object\"==typeof e?[e]:(this.selector=e,document.querySelectorAll(e||null));t<n.length;t++)this.push(n[t])};C.prototype=[],C.prototype.constructor=C,w.extend=function(){var e=1,t=arguments,n=function(e,t){e=e||(t.constructor===Array?[]:{});for(var a in t)e[a]=t[a]&&t[a].constructor===Object?n(e[a],t[a]):t[a];return e};for(t[0]=\"object\"==typeof t[0]?t[0]:{};e<t.length;e++)\"object\"==typeof t[e]&&n(t[0],t[e]);return t[0]},w.ie=function(){var e=navigator.userAgent.toLowerCase();return!!(window.ActiveXObject||\"ActiveXObject\"in window)&&((e.match(/msie\\s(\\d+)/)||[])[1]||\"11\")}(),w.stope=function(e){e=e||window.event,e.stopPropagation?e.stopPropagation():e.cancelBubble=!0},w.each=function(e,t){var n,a=this;if(\"function\"!=typeof t)return a;if(e=e||[],e.constructor===Object){for(n in e)if(t.call(e[n],n,e[n]))break}else for(n=0;n<e.length&&!t.call(e[n],n,e[n]);n++);return a},w.digit=function(e,t,n){var a=\"\";e=String(e),t=t||2;for(var i=e.length;i<t;i++)a+=\"0\";return e<Math.pow(10,t)?a+(0|e):e},w.elem=function(e,t){var n=document.createElement(e);return w.each(t||{},function(e,t){n.setAttribute(e,t)}),n},C.addStr=function(e,t){return e=e.replace(/\\s+/,\" \"),t=t.replace(/\\s+/,\" \").split(\" \"),w.each(t,function(t,n){new RegExp(\"\\\\b\"+n+\"\\\\b\").test(e)||(e=e+\" \"+n)}),e.replace(/^\\s|\\s$/,\"\")},C.removeStr=function(e,t){return e=e.replace(/\\s+/,\" \"),t=t.replace(/\\s+/,\" \").split(\" \"),w.each(t,function(t,n){var a=new RegExp(\"\\\\b\"+n+\"\\\\b\");a.test(e)&&(e=e.replace(a,\"\"))}),e.replace(/\\s+/,\" \").replace(/^\\s|\\s$/,\"\")},C.prototype.find=function(e){var t=this,n=0,a=[],i=\"object\"==typeof e;return this.each(function(r,o){for(var s=i?[e]:o.querySelectorAll(e||null);n<s.length;n++)a.push(s[n]);t.shift()}),i||(t.selector=(t.selector?t.selector+\" \":\"\")+e),w.each(a,function(e,n){t.push(n)}),t},C.prototype.each=function(e){return w.each.call(this,this,e)},C.prototype.addClass=function(e,t){return this.each(function(n,a){a.className=C[t?\"removeStr\":\"addStr\"](a.className,e)})},C.prototype.removeClass=function(e){return this.addClass(e,!0)},C.prototype.hasClass=function(e){var t=!1;return this.each(function(n,a){new RegExp(\"\\\\b\"+e+\"\\\\b\").test(a.className)&&(t=!0)}),t},C.prototype.attr=function(e,t){var n=this;return void 0===t?function(){if(n.length>0)return n[0].getAttribute(e)}():n.each(function(n,a){a.setAttribute(e,t)})},C.prototype.removeAttr=function(e){return this.each(function(t,n){n.removeAttribute(e)})},C.prototype.html=function(e){return this.each(function(t,n){n.innerHTML=e})},C.prototype.val=function(e){return this.each(function(t,n){n.value=e})},C.prototype.append=function(e){return this.each(function(t,n){\"object\"==typeof e?n.appendChild(e):n.innerHTML=n.innerHTML+e})},C.prototype.remove=function(e){return this.each(function(t,n){e?n.removeChild(e):n.parentNode.removeChild(n)})},C.prototype.on=function(e,t){return this.each(function(n,a){a.attachEvent?a.attachEvent(\"on\"+e,function(e){e.target=e.srcElement,t.call(a,e)}):a.addEventListener(e,t,!1)})},C.prototype.off=function(e,t){return this.each(function(n,a){a.detachEvent?a.detachEvent(\"on\"+e,t):a.removeEventListener(e,t,!1)})},T.isLeapYear=function(e){return e%4===0&&e%100!==0||e%400===0},T.prototype.config={type:\"date\",range:!1,format:\"yyyy-MM-dd\",value:null,isInitValue:!0,min:\"1900-1-1\",max:\"2099-12-31\",trigger:\"focus\",show:!1,showBottom:!0,btns:[\"clear\",\"now\",\"confirm\"],lang:\"cn\",theme:\"default\",position:null,calendar:!1,mark:{},zIndex:null,done:null,change:null},T.prototype.lang=function(){var e=this,t=e.config,n={cn:{weeks:[\"日\",\"一\",\"二\",\"三\",\"四\",\"五\",\"六\"],time:[\"时\",\"分\",\"秒\"],timeTips:\"选择时间\",startTime:\"开始时间\",endTime:\"结束时间\",dateTips:\"返回日期\",month:[\"一\",\"二\",\"三\",\"四\",\"五\",\"六\",\"七\",\"八\",\"九\",\"十\",\"十一\",\"十二\"],tools:{confirm:\"确定\",clear:\"清空\",now:\"现在\"}},en:{weeks:[\"Su\",\"Mo\",\"Tu\",\"We\",\"Th\",\"Fr\",\"Sa\"],time:[\"Hours\",\"Minutes\",\"Seconds\"],timeTips:\"Select Time\",startTime:\"Start Time\",endTime:\"End Time\",dateTips:\"Select Date\",month:[\"Jan\",\"Feb\",\"Mar\",\"Apr\",\"May\",\"Jun\",\"Jul\",\"Aug\",\"Sep\",\"Oct\",\"Nov\",\"Dec\"],tools:{confirm:\"Confirm\",clear:\"Clear\",now:\"Now\"}}};return n[t.lang]||n.cn},T.prototype.init=function(){var e=this,t=e.config,n=\"yyyy|y|MM|M|dd|d|HH|H|mm|m|ss|s\",a=\"static\"===t.position,i={year:\"yyyy\",month:\"yyyy-MM\",date:\"yyyy-MM-dd\",time:\"HH:mm:ss\",datetime:\"yyyy-MM-dd HH:mm:ss\"};t.elem=w(t.elem),t.eventElem=w(t.eventElem),t.elem[0]&&(t.range===!0&&(t.range=\"-\"),t.format===i.date&&(t.format=i[t.type]),e.format=t.format.match(new RegExp(n+\"|.\",\"g\"))||[],e.EXP_IF=\"\",e.EXP_SPLIT=\"\",w.each(e.format,function(t,a){var i=new RegExp(n).test(a)?\"\\\\d{\"+function(){return new RegExp(n).test(e.format[0===t?t+1:t-1]||\"\")?/^yyyy|y$/.test(a)?4:a.length:/^yyyy$/.test(a)?\"1,4\":/^y$/.test(a)?\"1,308\":\"1,2\"}()+\"}\":\"\\\\\"+a;e.EXP_IF=e.EXP_IF+i,e.EXP_SPLIT=e.EXP_SPLIT+\"(\"+i+\")\"}),e.EXP_IF=new RegExp(\"^\"+(t.range?e.EXP_IF+\"\\\\s\\\\\"+t.range+\"\\\\s\"+e.EXP_IF:e.EXP_IF)+\"$\"),e.EXP_SPLIT=new RegExp(\"^\"+e.EXP_SPLIT+\"$\",\"\"),e.isInput(t.elem[0])||\"focus\"===t.trigger&&(t.trigger=\"click\"),t.elem.attr(\"lay-key\")||(t.elem.attr(\"lay-key\",e.index),t.eventElem.attr(\"lay-key\",e.index)),t.mark=w.extend({},t.calendar&&\"cn\"===t.lang?{\"0-1-1\":\"元旦\",\"0-2-14\":\"情人\",\"0-3-8\":\"妇女\",\"0-3-12\":\"植树\",\"0-4-1\":\"愚人\",\"0-5-1\":\"劳动\",\"0-5-4\":\"青年\",\"0-6-1\":\"儿童\",\"0-9-10\":\"教师\",\"0-9-18\":\"国耻\",\"0-10-1\":\"国庆\",\"0-12-25\":\"圣诞\"}:{},t.mark),w.each([\"min\",\"max\"],function(e,n){var a=[],i=[];if(\"number\"==typeof t[n]){var r=t[n],o=(new Date).getTime(),s=864e5,l=new Date(r?r<s?o+r*s:r:o);a=[l.getFullYear(),l.getMonth()+1,l.getDate()],r<s||(i=[l.getHours(),l.getMinutes(),l.getSeconds()])}else a=(t[n].match(/\\d+-\\d+-\\d+/)||[\"\"])[0].split(\"-\"),i=(t[n].match(/\\d+:\\d+:\\d+/)||[\"\"])[0].split(\":\");t[n]={year:0|a[0]||(new Date).getFullYear(),month:a[1]?(0|a[1])-1:(new Date).getMonth(),date:0|a[2]||(new Date).getDate(),hours:0|i[0],minutes:0|i[1],seconds:0|i[2]}}),e.elemID=\"layui-laydate\"+t.elem.attr(\"lay-key\"),(t.show||a)&&e.render(),a||e.events(),t.value&&t.isInitValue&&(t.value.constructor===Date?e.setValue(e.parse(0,e.systemDate(t.value))):e.setValue(t.value)))},T.prototype.render=function(){var e=this,t=e.config,n=e.lang(),a=\"static\"===t.position,i=e.elem=w.elem(\"div\",{id:e.elemID,\"class\":[\"layui-laydate\",t.range?\" layui-laydate-range\":\"\",a?\" \"+c:\"\",t.theme&&\"default\"!==t.theme&&!/^#/.test(t.theme)?\" laydate-theme-\"+t.theme:\"\"].join(\"\")}),r=e.elemMain=[],o=e.elemHeader=[],s=e.elemCont=[],l=e.table=[],d=e.footer=w.elem(\"div\",{\"class\":p});if(t.zIndex&&(i.style.zIndex=t.zIndex),w.each(new Array(2),function(e){if(!t.range&&e>0)return!0;var a=w.elem(\"div\",{\"class\":\"layui-laydate-header\"}),i=[function(){var e=w.elem(\"i\",{\"class\":\"layui-icon laydate-icon laydate-prev-y\"});return e.innerHTML=\"&#xe65a;\",e}(),function(){var e=w.elem(\"i\",{\"class\":\"layui-icon laydate-icon laydate-prev-m\"});return e.innerHTML=\"&#xe603;\",e}(),function(){var e=w.elem(\"div\",{\"class\":\"laydate-set-ym\"}),t=w.elem(\"span\"),n=w.elem(\"span\");return e.appendChild(t),e.appendChild(n),e}(),function(){var e=w.elem(\"i\",{\"class\":\"layui-icon laydate-icon laydate-next-m\"});return e.innerHTML=\"&#xe602;\",e}(),function(){var e=w.elem(\"i\",{\"class\":\"layui-icon laydate-icon laydate-next-y\"});return e.innerHTML=\"&#xe65b;\",e}()],d=w.elem(\"div\",{\"class\":\"layui-laydate-content\"}),c=w.elem(\"table\"),m=w.elem(\"thead\"),u=w.elem(\"tr\");w.each(i,function(e,t){a.appendChild(t)}),m.appendChild(u),w.each(new Array(6),function(e){var t=c.insertRow(0);w.each(new Array(7),function(a){if(0===e){var i=w.elem(\"th\");i.innerHTML=n.weeks[a],u.appendChild(i)}t.insertCell(a)})}),c.insertBefore(m,c.children[0]),d.appendChild(c),r[e]=w.elem(\"div\",{\"class\":\"layui-laydate-main laydate-main-list-\"+e}),r[e].appendChild(a),r[e].appendChild(d),o.push(i),s.push(d),l.push(c)}),w(d).html(function(){var e=[],i=[];return\"datetime\"===t.type&&e.push('<span lay-type=\"datetime\" class=\"laydate-btns-time\">'+n.timeTips+\"</span>\"),w.each(t.btns,function(e,r){var o=n.tools[r]||\"btn\";t.range&&\"now\"===r||(a&&\"clear\"===r&&(o=\"cn\"===t.lang?\"重置\":\"Reset\"),i.push('<span lay-type=\"'+r+'\" class=\"laydate-btns-'+r+'\">'+o+\"</span>\"))}),e.push('<div class=\"laydate-footer-btns\">'+i.join(\"\")+\"</div>\"),e.join(\"\")}()),w.each(r,function(e,t){i.appendChild(t)}),t.showBottom&&i.appendChild(d),/^#/.test(t.theme)){var m=w.elem(\"style\"),u=[\"#{{id}} .layui-laydate-header{background-color:{{theme}};}\",\"#{{id}} .layui-this{background-color:{{theme}} !important;}\"].join(\"\").replace(/{{id}}/g,e.elemID).replace(/{{theme}}/g,t.theme);\"styleSheet\"in m?(m.setAttribute(\"type\",\"text/css\"),m.styleSheet.cssText=u):m.innerHTML=u,w(i).addClass(\"laydate-theme-molv\"),i.appendChild(m)}e.remove(T.thisElemDate),a?t.elem.append(i):(document.body.appendChild(i),e.position()),e.checkDate().calendar(),e.changeEvent(),T.thisElemDate=e.elemID,\"function\"==typeof t.ready&&t.ready(w.extend({},t.dateTime,{month:t.dateTime.month+1}))},T.prototype.remove=function(e){var t=this,n=(t.config,w(\"#\"+(e||t.elemID)));return n.hasClass(c)||t.checkDate(function(){n.remove()}),t},T.prototype.position=function(){var e=this,t=e.config,n=e.bindElem||t.elem[0],a=n.getBoundingClientRect(),i=e.elem.offsetWidth,r=e.elem.offsetHeight,o=function(e){return e=e?\"scrollLeft\":\"scrollTop\",document.body[e]|document.documentElement[e]},s=function(e){return document.documentElement[e?\"clientWidth\":\"clientHeight\"]},l=5,d=a.left,c=a.bottom;d+i+l>s(\"width\")&&(d=s(\"width\")-i-l),c+r+l>s()&&(c=a.top>r?a.top-r:s()-r,c-=2*l),t.position&&(e.elem.style.position=t.position),e.elem.style.left=d+(\"fixed\"===t.position?0:o(1))+\"px\",e.elem.style.top=c+(\"fixed\"===t.position?0:o())+\"px\"},T.prototype.hint=function(e){var t=this,n=(t.config,w.elem(\"div\",{\"class\":h}));t.elem&&(n.innerHTML=e||\"\",w(t.elem).find(\".\"+h).remove(),t.elem.appendChild(n),clearTimeout(t.hinTimer),t.hinTimer=setTimeout(function(){w(t.elem).find(\".\"+h).remove()},3e3))},T.prototype.getAsYM=function(e,t,n){return n?t--:t++,t<0&&(t=11,e--),t>11&&(t=0,e++),[e,t]},T.prototype.systemDate=function(e){var t=e||new Date;return{year:t.getFullYear(),month:t.getMonth(),date:t.getDate(),hours:e?e.getHours():0,minutes:e?e.getMinutes():0,seconds:e?e.getSeconds():0}},T.prototype.checkDate=function(e){var t,a,i=this,r=(new Date,i.config),o=r.dateTime=r.dateTime||i.systemDate(),s=i.bindElem||r.elem[0],l=(i.isInput(s)?\"val\":\"html\",i.isInput(s)?s.value:\"static\"===r.position?\"\":s.innerHTML),c=function(e){e.year>d[1]&&(e.year=d[1],a=!0),e.month>11&&(e.month=11,a=!0),e.hours>23&&(e.hours=0,a=!0),e.minutes>59&&(e.minutes=0,e.hours++,a=!0),e.seconds>59&&(e.seconds=0,e.minutes++,a=!0),t=n.getEndDate(e.month+1,e.year),e.date>t&&(e.date=t,a=!0)},m=function(e,t,n){var o=[\"startTime\",\"endTime\"];t=(t.match(i.EXP_SPLIT)||[]).slice(1),n=n||0,r.range&&(i[o[n]]=i[o[n]]||{}),w.each(i.format,function(s,l){var c=parseFloat(t[s]);t[s].length<l.length&&(a=!0),/yyyy|y/.test(l)?(c<d[0]&&(c=d[0],a=!0),e.year=c):/MM|M/.test(l)?(c<1&&(c=1,a=!0),e.month=c-1):/dd|d/.test(l)?(c<1&&(c=1,a=!0),e.date=c):/HH|H/.test(l)?(c<1&&(c=0,a=!0),e.hours=c,r.range&&(i[o[n]].hours=c)):/mm|m/.test(l)?(c<1&&(c=0,a=!0),e.minutes=c,r.range&&(i[o[n]].minutes=c)):/ss|s/.test(l)&&(c<1&&(c=0,a=!0),e.seconds=c,r.range&&(i[o[n]].seconds=c))}),c(e)};return\"limit\"===e?(c(o),i):(l=l||r.value,\"string\"==typeof l&&(l=l.replace(/\\s+/g,\" \").replace(/^\\s|\\s$/g,\"\")),i.startState&&!i.endState&&(delete i.startState,i.endState=!0),\"string\"==typeof l&&l?i.EXP_IF.test(l)?r.range?(l=l.split(\" \"+r.range+\" \"),i.startDate=i.startDate||i.systemDate(),i.endDate=i.endDate||i.systemDate(),r.dateTime=w.extend({},i.startDate),w.each([i.startDate,i.endDate],function(e,t){m(t,l[e],e)})):m(o,l):(i.hint(\"日期格式不合法<br>必须遵循下述格式：<br>\"+(r.range?r.format+\" \"+r.range+\" \"+r.format:r.format)+\"<br>已为你重置\"),a=!0):l&&l.constructor===Date?r.dateTime=i.systemDate(l):(r.dateTime=i.systemDate(),delete i.startState,delete i.endState,delete i.startDate,delete i.endDate,delete i.startTime,delete i.endTime),c(o),a&&l&&i.setValue(r.range?i.endDate?i.parse():\"\":i.parse()),e&&e(),i)},T.prototype.mark=function(e,t){var n,a=this,i=a.config;return w.each(i.mark,function(e,a){var i=e.split(\"-\");i[0]!=t[0]&&0!=i[0]||i[1]!=t[1]&&0!=i[1]||i[2]!=t[2]||(n=a||t[2])}),n&&e.html('<span class=\"laydate-day-mark\">'+n+\"</span>\"),a},T.prototype.limit=function(e,t,n,a){var i,r=this,o=r.config,l={},d=o[n>41?\"endDate\":\"dateTime\"],c=w.extend({},d,t||{});return w.each({now:c,min:o.min,max:o.max},function(e,t){l[e]=r.newDate(w.extend({year:t.year,month:t.month,date:t.date},function(){var e={};return w.each(a,function(n,a){e[a]=t[a]}),e}())).getTime()}),i=l.now<l.min||l.now>l.max,e&&e[i?\"addClass\":\"removeClass\"](s),i},T.prototype.calendar=function(e){var t,a,i,r=this,s=r.config,l=e||s.dateTime,c=new Date,m=r.lang(),u=\"date\"!==s.type&&\"datetime\"!==s.type,h=e?1:0,y=w(r.table[h]).find(\"td\"),f=w(r.elemHeader[h][2]).find(\"span\");if(l.year<d[0]&&(l.year=d[0],r.hint(\"最低只能支持到公元\"+d[0]+\"年\")),l.year>d[1]&&(l.year=d[1],r.hint(\"最高只能支持到公元\"+d[1]+\"年\")),r.firstDate||(r.firstDate=w.extend({},l)),c.setFullYear(l.year,l.month,1),t=c.getDay(),a=n.getEndDate(l.month||12,l.year),i=n.getEndDate(l.month+1,l.year),w.each(y,function(e,n){var d=[l.year,l.month],c=0;n=w(n),n.removeAttr(\"class\"),e<t?(c=a-t+e,n.addClass(\"laydate-day-prev\"),d=r.getAsYM(l.year,l.month,\"sub\")):e>=t&&e<i+t?(c=e-t,s.range||c+1===l.date&&n.addClass(o)):(c=e-i-t,n.addClass(\"laydate-day-next\"),d=r.getAsYM(l.year,l.month)),d[1]++,d[2]=c+1,n.attr(\"lay-ymd\",d.join(\"-\")).html(d[2]),r.mark(n,d).limit(n,{year:d[0],month:d[1]-1,date:d[2]},e)}),w(f[0]).attr(\"lay-ym\",l.year+\"-\"+(l.month+1)),w(f[1]).attr(\"lay-ym\",l.year+\"-\"+(l.month+1)),\"cn\"===s.lang?(w(f[0]).attr(\"lay-type\",\"year\").html(l.year+\"年\"),w(f[1]).attr(\"lay-type\",\"month\").html(l.month+1+\"月\")):(w(f[0]).attr(\"lay-type\",\"month\").html(m.month[l.month]),w(f[1]).attr(\"lay-type\",\"year\").html(l.year)),u&&(s.range&&(e?r.endDate=r.endDate||{year:l.year+(\"year\"===s.type?1:0),month:l.month+(\"month\"===s.type?0:-1)}:r.startDate=r.startDate||{year:l.year,month:l.month},e&&(r.listYM=[[r.startDate.year,r.startDate.month+1],[r.endDate.year,r.endDate.month+1]],r.list(s.type,0).list(s.type,1),\"time\"===s.type?r.setBtnStatus(\"时间\",w.extend({},r.systemDate(),r.startTime),w.extend({},r.systemDate(),r.endTime)):r.setBtnStatus(!0))),s.range||(r.listYM=[[l.year,l.month+1]],r.list(s.type,0))),s.range&&!e){var p=r.getAsYM(l.year,l.month);r.calendar(w.extend({},l,{year:p[0],month:p[1]}))}return s.range||r.limit(w(r.footer).find(g),null,0,[\"hours\",\"minutes\",\"seconds\"]),s.range&&e&&!u&&r.stampRange(),r},T.prototype.list=function(e,t){var n=this,a=n.config,i=a.dateTime,r=n.lang(),l=a.range&&\"date\"!==a.type&&\"datetime\"!==a.type,d=w.elem(\"ul\",{\"class\":m+\" \"+{year:\"laydate-year-list\",month:\"laydate-month-list\",time:\"laydate-time-list\"}[e]}),c=n.elemHeader[t],u=w(c[2]).find(\"span\"),h=n.elemCont[t||0],y=w(h).find(\".\"+m)[0],f=\"cn\"===a.lang,p=f?\"年\":\"\",T=n.listYM[t]||{},C=[\"hours\",\"minutes\",\"seconds\"],x=[\"startTime\",\"endTime\"][t];if(T[0]<1&&(T[0]=1),\"year\"===e){var M,b=M=T[0]-7;b<1&&(b=M=1),w.each(new Array(15),function(e){var i=w.elem(\"li\",{\"lay-ym\":M}),r={year:M};M==T[0]&&w(i).addClass(o),i.innerHTML=M+p,d.appendChild(i),M<n.firstDate.year?(r.month=a.min.month,r.date=a.min.date):M>=n.firstDate.year&&(r.month=a.max.month,r.date=a.max.date),n.limit(w(i),r,t),M++}),w(u[f?0:1]).attr(\"lay-ym\",M-8+\"-\"+T[1]).html(b+p+\" - \"+(M-1+p))}else if(\"month\"===e)w.each(new Array(12),function(e){var i=w.elem(\"li\",{\"lay-ym\":e}),s={year:T[0],month:e};e+1==T[1]&&w(i).addClass(o),i.innerHTML=r.month[e]+(f?\"月\":\"\"),d.appendChild(i),T[0]<n.firstDate.year?s.date=a.min.date:T[0]>=n.firstDate.year&&(s.date=a.max.date),n.limit(w(i),s,t)}),w(u[f?0:1]).attr(\"lay-ym\",T[0]+\"-\"+T[1]).html(T[0]+p);else if(\"time\"===e){var E=function(){w(d).find(\"ol\").each(function(e,a){w(a).find(\"li\").each(function(a,i){n.limit(w(i),[{hours:a},{hours:n[x].hours,minutes:a},{hours:n[x].hours,minutes:n[x].minutes,seconds:a}][e],t,[[\"hours\"],[\"hours\",\"minutes\"],[\"hours\",\"minutes\",\"seconds\"]][e])})}),a.range||n.limit(w(n.footer).find(g),n[x],0,[\"hours\",\"minutes\",\"seconds\"])};a.range?n[x]||(n[x]={hours:0,minutes:0,seconds:0}):n[x]=i,w.each([24,60,60],function(e,t){var a=w.elem(\"li\"),i=[\"<p>\"+r.time[e]+\"</p><ol>\"];w.each(new Array(t),function(t){i.push(\"<li\"+(n[x][C[e]]===t?' class=\"'+o+'\"':\"\")+\">\"+w.digit(t,2)+\"</li>\")}),a.innerHTML=i.join(\"\")+\"</ol>\",d.appendChild(a)}),E()}if(y&&h.removeChild(y),h.appendChild(d),\"year\"===e||\"month\"===e)w(n.elemMain[t]).addClass(\"laydate-ym-show\"),w(d).find(\"li\").on(\"click\",function(){var r=0|w(this).attr(\"lay-ym\");if(!w(this).hasClass(s)){if(0===t)i[e]=r,l&&(n.startDate[e]=r),n.limit(w(n.footer).find(g),null,0);else if(l)n.endDate[e]=r;else{var c=\"year\"===e?n.getAsYM(r,T[1]-1,\"sub\"):n.getAsYM(T[0],r,\"sub\");w.extend(i,{year:c[0],month:c[1]})}\"year\"===a.type||\"month\"===a.type?(w(d).find(\".\"+o).removeClass(o),w(this).addClass(o),\"month\"===a.type&&\"year\"===e&&(n.listYM[t][0]=r,l&&(n[[\"startDate\",\"endDate\"][t]].year=r),n.list(\"month\",t))):(n.checkDate(\"limit\").calendar(),n.closeList()),n.setBtnStatus(),a.range||n.done(null,\"change\"),w(n.footer).find(D).removeClass(s)}});else{var S=w.elem(\"span\",{\"class\":v}),k=function(){w(d).find(\"ol\").each(function(e){var t=this,a=w(t).find(\"li\");t.scrollTop=30*(n[x][C[e]]-2),t.scrollTop<=0&&a.each(function(e,n){if(!w(this).hasClass(s))return t.scrollTop=30*(e-2),!0})})},H=w(c[2]).find(\".\"+v);k(),S.innerHTML=a.range?[r.startTime,r.endTime][t]:r.timeTips,w(n.elemMain[t]).addClass(\"laydate-time-show\"),H[0]&&H.remove(),c[2].appendChild(S),w(d).find(\"ol\").each(function(e){var t=this;w(t).find(\"li\").on(\"click\",function(){var r=0|this.innerHTML;w(this).hasClass(s)||(a.range?n[x][C[e]]=r:i[C[e]]=r,w(t).find(\".\"+o).removeClass(o),w(this).addClass(o),E(),k(),(n.endDate||\"time\"===a.type)&&n.done(null,\"change\"),n.setBtnStatus())})})}return n},T.prototype.listYM=[],T.prototype.closeList=function(){var e=this;e.config;w.each(e.elemCont,function(t,n){w(this).find(\".\"+m).remove(),w(e.elemMain[t]).removeClass(\"laydate-ym-show laydate-time-show\")}),w(e.elem).find(\".\"+v).remove()},T.prototype.setBtnStatus=function(e,t,n){var a,i=this,r=i.config,o=w(i.footer).find(g),d=r.range&&\"date\"!==r.type&&\"time\"!==r.type;d&&(t=t||i.startDate,n=n||i.endDate,a=i.newDate(t).getTime()>i.newDate(n).getTime(),i.limit(null,t)||i.limit(null,n)?o.addClass(s):o[a?\"addClass\":\"removeClass\"](s),e&&a&&i.hint(\"string\"==typeof e?l.replace(/日期/g,e):l))},T.prototype.parse=function(e,t){var n=this,a=n.config,i=t||(e?w.extend({},n.endDate,n.endTime):a.range?w.extend({},n.startDate,n.startTime):a.dateTime),r=n.format.concat();return w.each(r,function(e,t){/yyyy|y/.test(t)?r[e]=w.digit(i.year,t.length):/MM|M/.test(t)?r[e]=w.digit(i.month+1,t.length):/dd|d/.test(t)?r[e]=w.digit(i.date,t.length):/HH|H/.test(t)?r[e]=w.digit(i.hours,t.length):/mm|m/.test(t)?r[e]=w.digit(i.minutes,t.length):/ss|s/.test(t)&&(r[e]=w.digit(i.seconds,t.length))}),a.range&&!e?r.join(\"\")+\" \"+a.range+\" \"+n.parse(1):r.join(\"\")},T.prototype.newDate=function(e){return e=e||{},new Date(e.year||1,e.month||0,e.date||1,e.hours||0,e.minutes||0,e.seconds||0)},T.prototype.setValue=function(e){var t=this,n=t.config,a=t.bindElem||n.elem[0],i=t.isInput(a)?\"val\":\"html\";return\"static\"===n.position||w(a)[i](e||\"\"),this},T.prototype.stampRange=function(){var e,t,n=this,a=n.config,i=w(n.elem).find(\"td\");if(a.range&&!n.endDate&&w(n.footer).find(g).addClass(s),n.endDate)return e=n.newDate({year:n.startDate.year,month:n.startDate.month,date:n.startDate.date}).getTime(),t=n.newDate({year:n.endDate.year,month:n.endDate.month,date:n.endDate.date}).getTime(),e>t?n.hint(l):void w.each(i,function(a,i){var r=w(i).attr(\"lay-ymd\").split(\"-\"),s=n.newDate({year:r[0],month:r[1]-1,date:r[2]}).getTime();w(i).removeClass(u+\" \"+o),s!==e&&s!==t||w(i).addClass(w(i).hasClass(y)||w(i).hasClass(f)?u:o),s>e&&s<t&&w(i).addClass(u)})},T.prototype.done=function(e,t){var n=this,a=n.config,i=w.extend({},n.startDate?w.extend(n.startDate,n.startTime):a.dateTime),r=w.extend({},w.extend(n.endDate,n.endTime));return w.each([i,r],function(e,t){\"month\"in t&&w.extend(t,{month:t.month+1})}),e=e||[n.parse(),i,r],\"function\"==typeof a[t||\"done\"]&&a[t||\"done\"].apply(a,e),n},T.prototype.choose=function(e){var t=this,n=t.config,a=n.dateTime,i=w(t.elem).find(\"td\"),r=e.attr(\"lay-ymd\").split(\"-\"),l=function(e){new Date;e&&w.extend(a,r),n.range&&(t.startDate?w.extend(t.startDate,r):t.startDate=w.extend({},r,t.startTime),t.startYMD=r)};if(r={year:0|r[0],month:(0|r[1])-1,date:0|r[2]},!e.hasClass(s))if(n.range){if(w.each([\"startTime\",\"endTime\"],function(e,n){t[n]=t[n]||{hours:0,minutes:0,seconds:0}}),t.endState)l(),delete t.endState,delete t.endDate,t.startState=!0,i.removeClass(o+\" \"+u),e.addClass(o);else if(t.startState){if(e.addClass(o),t.endDate?w.extend(t.endDate,r):t.endDate=w.extend({},r,t.endTime),t.newDate(r).getTime()<t.newDate(t.startYMD).getTime()){var d=w.extend({},t.endDate,{hours:t.startDate.hours,minutes:t.startDate.minutes,seconds:t.startDate.seconds});w.extend(t.endDate,t.startDate,{hours:t.endDate.hours,minutes:t.endDate.minutes,seconds:t.endDate.seconds}),t.startDate=d}n.showBottom||t.done(),t.stampRange(),t.endState=!0,t.done(null,\"change\")}else e.addClass(o),l(),t.startState=!0;w(t.footer).find(g)[t.endDate?\"removeClass\":\"addClass\"](s)}else\"static\"===n.position?(l(!0),t.calendar().done().done(null,\"change\")):\"date\"===n.type?(l(!0),t.setValue(t.parse()).remove().done()):\"datetime\"===n.type&&(l(!0),t.calendar().done(null,\"change\"))},T.prototype.tool=function(e,t){var n=this,a=n.config,i=a.dateTime,r=\"static\"===a.position,o={datetime:function(){w(e).hasClass(s)||(n.list(\"time\",0),a.range&&n.list(\"time\",1),w(e).attr(\"lay-type\",\"date\").html(n.lang().dateTips))},date:function(){n.closeList(),w(e).attr(\"lay-type\",\"datetime\").html(n.lang().timeTips)},clear:function(){n.setValue(\"\").remove(),r&&(w.extend(i,n.firstDate),n.calendar()),a.range&&(delete n.startState,delete n.endState,delete n.endDate,delete n.startTime,delete n.endTime),n.done([\"\",{},{}])},now:function(){var e=new Date;w.extend(i,n.systemDate(),{hours:e.getHours(),minutes:e.getMinutes(),seconds:e.getSeconds()}),n.setValue(n.parse()).remove(),r&&n.calendar(),n.done()},confirm:function(){if(a.range){if(!n.endDate)return n.hint(\"请先选择日期范围\");if(w(e).hasClass(s))return n.hint(\"time\"===a.type?l.replace(/日期/g,\"时间\"):l)}else if(w(e).hasClass(s))return n.hint(\"不在有效日期或时间范围内\");n.done(),n.setValue(n.parse()).remove()}};o[t]&&o[t]()},T.prototype.change=function(e){var t=this,n=t.config,a=n.dateTime,i=n.range&&(\"year\"===n.type||\"month\"===n.type),r=t.elemCont[e||0],o=t.listYM[e],s=function(s){var l=[\"startDate\",\"endDate\"][e],d=w(r).find(\".laydate-year-list\")[0],c=w(r).find(\".laydate-month-list\")[0];return d&&(o[0]=s?o[0]-15:o[0]+15,t.list(\"year\",e)),c&&(s?o[0]--:o[0]++,t.list(\"month\",e)),(d||c)&&(w.extend(a,{year:o[0]}),i&&(t[l].year=o[0]),n.range||t.done(null,\"change\"),t.setBtnStatus(),n.range||t.limit(w(t.footer).find(g),{year:o[0]})),d||c};return{prevYear:function(){s(\"sub\")||(a.year--,t.checkDate(\"limit\").calendar(),n.range||t.done(null,\"change\"))},prevMonth:function(){var e=t.getAsYM(a.year,a.month,\"sub\");w.extend(a,{year:e[0],month:e[1]}),t.checkDate(\"limit\").calendar(),n.range||t.done(null,\"change\")},nextMonth:function(){var e=t.getAsYM(a.year,a.month);w.extend(a,{year:e[0],month:e[1]}),t.checkDate(\"limit\").calendar(),n.range||t.done(null,\"change\")},nextYear:function(){s()||(a.year++,t.checkDate(\"limit\").calendar(),n.range||t.done(null,\"change\"))}}},T.prototype.changeEvent=function(){var e=this;e.config;w(e.elem).on(\"click\",function(e){w.stope(e)}),w.each(e.elemHeader,function(t,n){w(n[0]).on(\"click\",function(n){e.change(t).prevYear()}),w(n[1]).on(\"click\",function(n){e.change(t).prevMonth()}),w(n[2]).find(\"span\").on(\"click\",function(n){var a=w(this),i=a.attr(\"lay-ym\"),r=a.attr(\"lay-type\");i&&(i=i.split(\"-\"),e.listYM[t]=[0|i[0],0|i[1]],e.list(r,t),w(e.footer).find(D).addClass(s))}),w(n[3]).on(\"click\",function(n){e.change(t).nextMonth()}),w(n[4]).on(\"click\",function(n){e.change(t).nextYear()})}),w.each(e.table,function(t,n){var a=w(n).find(\"td\");a.on(\"click\",function(){e.choose(w(this))})}),w(e.footer).find(\"span\").on(\"click\",function(){var t=w(this).attr(\"lay-type\");e.tool(this,t)})},T.prototype.isInput=function(e){return/input|textarea/.test(e.tagName.toLocaleLowerCase())},T.prototype.events=function(){var e=this,t=e.config,n=function(n,a){n.on(t.trigger,function(){a&&(e.bindElem=this),e.render()})};t.elem[0]&&!t.elem[0].eventHandler&&(n(t.elem,\"bind\"),n(t.eventElem),w(document).on(\"click\",function(n){n.target!==t.elem[0]&&n.target!==t.eventElem[0]&&n.target!==w(t.closeStop)[0]&&e.remove()}).on(\"keydown\",function(t){13===t.keyCode&&w(\"#\"+e.elemID)[0]&&e.elemID===T.thisElem&&(t.preventDefault(),w(e.footer).find(g)[0].click())}),w(window).on(\"resize\",function(){return!(!e.elem||!w(r)[0])&&void e.position()}),t.elem[0].eventHandler=!0)},n.render=function(e){var t=new T(e);return a.call(t)},n.getEndDate=function(e,t){var n=new Date;return n.setFullYear(t||n.getFullYear(),e||n.getMonth()+1,1),new Date(n.getTime()-864e5).getDate()},window.lay=window.lay||w,e?(n.ready(),layui.define(function(e){n.path=layui.cache.dir,e(i,n)})):\"function\"==typeof define&&define.amd?define(function(){return n}):function(){n.ready(),window.laydate=n}()}();!function(e,t){\"object\"==typeof module&&\"object\"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error(\"jQuery requires a window with a document\");return t(e)}:t(e)}(\"undefined\"!=typeof window?window:this,function(e,t){function n(e){var t=!!e&&\"length\"in e&&e.length,n=pe.type(e);return\"function\"!==n&&!pe.isWindow(e)&&(\"array\"===n||0===t||\"number\"==typeof t&&t>0&&t-1 in e)}function r(e,t,n){if(pe.isFunction(t))return pe.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return pe.grep(e,function(e){return e===t!==n});if(\"string\"==typeof t){if(Ce.test(t))return pe.filter(t,e,n);t=pe.filter(t,e)}return pe.grep(e,function(e){return pe.inArray(e,t)>-1!==n})}function i(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}function o(e){var t={};return pe.each(e.match(De)||[],function(e,n){t[n]=!0}),t}function a(){re.addEventListener?(re.removeEventListener(\"DOMContentLoaded\",s),e.removeEventListener(\"load\",s)):(re.detachEvent(\"onreadystatechange\",s),e.detachEvent(\"onload\",s))}function s(){(re.addEventListener||\"load\"===e.event.type||\"complete\"===re.readyState)&&(a(),pe.ready())}function u(e,t,n){if(void 0===n&&1===e.nodeType){var r=\"data-\"+t.replace(_e,\"-$1\").toLowerCase();if(n=e.getAttribute(r),\"string\"==typeof n){try{n=\"true\"===n||\"false\"!==n&&(\"null\"===n?null:+n+\"\"===n?+n:qe.test(n)?pe.parseJSON(n):n)}catch(i){}pe.data(e,t,n)}else n=void 0}return n}function l(e){var t;for(t in e)if((\"data\"!==t||!pe.isEmptyObject(e[t]))&&\"toJSON\"!==t)return!1;return!0}function c(e,t,n,r){if(He(e)){var i,o,a=pe.expando,s=e.nodeType,u=s?pe.cache:e,l=s?e[a]:e[a]&&a;if(l&&u[l]&&(r||u[l].data)||void 0!==n||\"string\"!=typeof t)return l||(l=s?e[a]=ne.pop()||pe.guid++:a),u[l]||(u[l]=s?{}:{toJSON:pe.noop}),\"object\"!=typeof t&&\"function\"!=typeof t||(r?u[l]=pe.extend(u[l],t):u[l].data=pe.extend(u[l].data,t)),o=u[l],r||(o.data||(o.data={}),o=o.data),void 0!==n&&(o[pe.camelCase(t)]=n),\"string\"==typeof t?(i=o[t],null==i&&(i=o[pe.camelCase(t)])):i=o,i}}function f(e,t,n){if(He(e)){var r,i,o=e.nodeType,a=o?pe.cache:e,s=o?e[pe.expando]:pe.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){pe.isArray(t)?t=t.concat(pe.map(t,pe.camelCase)):t in r?t=[t]:(t=pe.camelCase(t),t=t in r?[t]:t.split(\" \")),i=t.length;for(;i--;)delete r[t[i]];if(n?!l(r):!pe.isEmptyObject(r))return}(n||(delete a[s].data,l(a[s])))&&(o?pe.cleanData([e],!0):fe.deleteExpando||a!=a.window?delete a[s]:a[s]=void 0)}}}function d(e,t,n,r){var i,o=1,a=20,s=r?function(){return r.cur()}:function(){return pe.css(e,t,\"\")},u=s(),l=n&&n[3]||(pe.cssNumber[t]?\"\":\"px\"),c=(pe.cssNumber[t]||\"px\"!==l&&+u)&&Me.exec(pe.css(e,t));if(c&&c[3]!==l){l=l||c[3],n=n||[],c=+u||1;do o=o||\".5\",c/=o,pe.style(e,t,c+l);while(o!==(o=s()/u)&&1!==o&&--a)}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}function p(e){var t=ze.split(\"|\"),n=e.createDocumentFragment();if(n.createElement)for(;t.length;)n.createElement(t.pop());return n}function h(e,t){var n,r,i=0,o=\"undefined\"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||\"*\"):\"undefined\"!=typeof e.querySelectorAll?e.querySelectorAll(t||\"*\"):void 0;if(!o)for(o=[],n=e.childNodes||e;null!=(r=n[i]);i++)!t||pe.nodeName(r,t)?o.push(r):pe.merge(o,h(r,t));return void 0===t||t&&pe.nodeName(e,t)?pe.merge([e],o):o}function g(e,t){for(var n,r=0;null!=(n=e[r]);r++)pe._data(n,\"globalEval\",!t||pe._data(t[r],\"globalEval\"))}function m(e){Be.test(e.type)&&(e.defaultChecked=e.checked)}function y(e,t,n,r,i){for(var o,a,s,u,l,c,f,d=e.length,y=p(t),v=[],x=0;x<d;x++)if(a=e[x],a||0===a)if(\"object\"===pe.type(a))pe.merge(v,a.nodeType?[a]:a);else if(Ue.test(a)){for(u=u||y.appendChild(t.createElement(\"div\")),l=(We.exec(a)||[\"\",\"\"])[1].toLowerCase(),f=Xe[l]||Xe._default,u.innerHTML=f[1]+pe.htmlPrefilter(a)+f[2],o=f[0];o--;)u=u.lastChild;if(!fe.leadingWhitespace&&$e.test(a)&&v.push(t.createTextNode($e.exec(a)[0])),!fe.tbody)for(a=\"table\"!==l||Ve.test(a)?\"<table>\"!==f[1]||Ve.test(a)?0:u:u.firstChild,o=a&&a.childNodes.length;o--;)pe.nodeName(c=a.childNodes[o],\"tbody\")&&!c.childNodes.length&&a.removeChild(c);for(pe.merge(v,u.childNodes),u.textContent=\"\";u.firstChild;)u.removeChild(u.firstChild);u=y.lastChild}else v.push(t.createTextNode(a));for(u&&y.removeChild(u),fe.appendChecked||pe.grep(h(v,\"input\"),m),x=0;a=v[x++];)if(r&&pe.inArray(a,r)>-1)i&&i.push(a);else if(s=pe.contains(a.ownerDocument,a),u=h(y.appendChild(a),\"script\"),s&&g(u),n)for(o=0;a=u[o++];)Ie.test(a.type||\"\")&&n.push(a);return u=null,y}function v(){return!0}function x(){return!1}function b(){try{return re.activeElement}catch(e){}}function w(e,t,n,r,i,o){var a,s;if(\"object\"==typeof t){\"string\"!=typeof n&&(r=r||n,n=void 0);for(s in t)w(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&(\"string\"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),i===!1)i=x;else if(!i)return e;return 1===o&&(a=i,i=function(e){return pe().off(e),a.apply(this,arguments)},i.guid=a.guid||(a.guid=pe.guid++)),e.each(function(){pe.event.add(this,t,i,r,n)})}function T(e,t){return pe.nodeName(e,\"table\")&&pe.nodeName(11!==t.nodeType?t:t.firstChild,\"tr\")?e.getElementsByTagName(\"tbody\")[0]||e.appendChild(e.ownerDocument.createElement(\"tbody\")):e}function C(e){return e.type=(null!==pe.find.attr(e,\"type\"))+\"/\"+e.type,e}function E(e){var t=it.exec(e.type);return t?e.type=t[1]:e.removeAttribute(\"type\"),e}function N(e,t){if(1===t.nodeType&&pe.hasData(e)){var n,r,i,o=pe._data(e),a=pe._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;r<i;r++)pe.event.add(t,n,s[n][r])}a.data&&(a.data=pe.extend({},a.data))}}function k(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!fe.noCloneEvent&&t[pe.expando]){i=pe._data(t);for(r in i.events)pe.removeEvent(t,r,i.handle);t.removeAttribute(pe.expando)}\"script\"===n&&t.text!==e.text?(C(t).text=e.text,E(t)):\"object\"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),fe.html5Clone&&e.innerHTML&&!pe.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):\"input\"===n&&Be.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):\"option\"===n?t.defaultSelected=t.selected=e.defaultSelected:\"input\"!==n&&\"textarea\"!==n||(t.defaultValue=e.defaultValue)}}function S(e,t,n,r){t=oe.apply([],t);var i,o,a,s,u,l,c=0,f=e.length,d=f-1,p=t[0],g=pe.isFunction(p);if(g||f>1&&\"string\"==typeof p&&!fe.checkClone&&rt.test(p))return e.each(function(i){var o=e.eq(i);g&&(t[0]=p.call(this,i,o.html())),S(o,t,n,r)});if(f&&(l=y(t,e[0].ownerDocument,!1,e,r),i=l.firstChild,1===l.childNodes.length&&(l=i),i||r)){for(s=pe.map(h(l,\"script\"),C),a=s.length;c<f;c++)o=l,c!==d&&(o=pe.clone(o,!0,!0),a&&pe.merge(s,h(o,\"script\"))),n.call(e[c],o,c);if(a)for(u=s[s.length-1].ownerDocument,pe.map(s,E),c=0;c<a;c++)o=s[c],Ie.test(o.type||\"\")&&!pe._data(o,\"globalEval\")&&pe.contains(u,o)&&(o.src?pe._evalUrl&&pe._evalUrl(o.src):pe.globalEval((o.text||o.textContent||o.innerHTML||\"\").replace(ot,\"\")));l=i=null}return e}function A(e,t,n){for(var r,i=t?pe.filter(t,e):e,o=0;null!=(r=i[o]);o++)n||1!==r.nodeType||pe.cleanData(h(r)),r.parentNode&&(n&&pe.contains(r.ownerDocument,r)&&g(h(r,\"script\")),r.parentNode.removeChild(r));return e}function D(e,t){var n=pe(t.createElement(e)).appendTo(t.body),r=pe.css(n[0],\"display\");return n.detach(),r}function j(e){var t=re,n=lt[e];return n||(n=D(e,t),\"none\"!==n&&n||(ut=(ut||pe(\"<iframe frameborder='0' width='0' height='0'/>\")).appendTo(t.documentElement),t=(ut[0].contentWindow||ut[0].contentDocument).document,t.write(),t.close(),n=D(e,t),ut.detach()),lt[e]=n),n}function L(e,t){return{get:function(){return e()?void delete this.get:(this.get=t).apply(this,arguments)}}}function H(e){if(e in Et)return e;for(var t=e.charAt(0).toUpperCase()+e.slice(1),n=Ct.length;n--;)if(e=Ct[n]+t,e in Et)return e}function q(e,t){for(var n,r,i,o=[],a=0,s=e.length;a<s;a++)r=e[a],r.style&&(o[a]=pe._data(r,\"olddisplay\"),n=r.style.display,t?(o[a]||\"none\"!==n||(r.style.display=\"\"),\"\"===r.style.display&&Re(r)&&(o[a]=pe._data(r,\"olddisplay\",j(r.nodeName)))):(i=Re(r),(n&&\"none\"!==n||!i)&&pe._data(r,\"olddisplay\",i?n:pe.css(r,\"display\"))));for(a=0;a<s;a++)r=e[a],r.style&&(t&&\"none\"!==r.style.display&&\"\"!==r.style.display||(r.style.display=t?o[a]||\"\":\"none\"));return e}function _(e,t,n){var r=bt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||\"px\"):t}function F(e,t,n,r,i){for(var o=n===(r?\"border\":\"content\")?4:\"width\"===t?1:0,a=0;o<4;o+=2)\"margin\"===n&&(a+=pe.css(e,n+Oe[o],!0,i)),r?(\"content\"===n&&(a-=pe.css(e,\"padding\"+Oe[o],!0,i)),\"margin\"!==n&&(a-=pe.css(e,\"border\"+Oe[o]+\"Width\",!0,i))):(a+=pe.css(e,\"padding\"+Oe[o],!0,i),\"padding\"!==n&&(a+=pe.css(e,\"border\"+Oe[o]+\"Width\",!0,i)));return a}function M(t,n,r){var i=!0,o=\"width\"===n?t.offsetWidth:t.offsetHeight,a=ht(t),s=fe.boxSizing&&\"border-box\"===pe.css(t,\"boxSizing\",!1,a);if(re.msFullscreenElement&&e.top!==e&&t.getClientRects().length&&(o=Math.round(100*t.getBoundingClientRect()[n])),o<=0||null==o){if(o=gt(t,n,a),(o<0||null==o)&&(o=t.style[n]),ft.test(o))return o;i=s&&(fe.boxSizingReliable()||o===t.style[n]),o=parseFloat(o)||0}return o+F(t,n,r||(s?\"border\":\"content\"),i,a)+\"px\"}function O(e,t,n,r,i){return new O.prototype.init(e,t,n,r,i)}function R(){return e.setTimeout(function(){Nt=void 0}),Nt=pe.now()}function P(e,t){var n,r={height:e},i=0;for(t=t?1:0;i<4;i+=2-t)n=Oe[i],r[\"margin\"+n]=r[\"padding\"+n]=e;return t&&(r.opacity=r.width=e),r}function B(e,t,n){for(var r,i=($.tweeners[t]||[]).concat($.tweeners[\"*\"]),o=0,a=i.length;o<a;o++)if(r=i[o].call(n,t,e))return r}function W(e,t,n){var r,i,o,a,s,u,l,c,f=this,d={},p=e.style,h=e.nodeType&&Re(e),g=pe._data(e,\"fxshow\");n.queue||(s=pe._queueHooks(e,\"fx\"),null==s.unqueued&&(s.unqueued=0,u=s.empty.fire,s.empty.fire=function(){s.unqueued||u()}),s.unqueued++,f.always(function(){f.always(function(){s.unqueued--,pe.queue(e,\"fx\").length||s.empty.fire()})})),1===e.nodeType&&(\"height\"in t||\"width\"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],l=pe.css(e,\"display\"),c=\"none\"===l?pe._data(e,\"olddisplay\")||j(e.nodeName):l,\"inline\"===c&&\"none\"===pe.css(e,\"float\")&&(fe.inlineBlockNeedsLayout&&\"inline\"!==j(e.nodeName)?p.zoom=1:p.display=\"inline-block\")),n.overflow&&(p.overflow=\"hidden\",fe.shrinkWrapBlocks()||f.always(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t)if(i=t[r],St.exec(i)){if(delete t[r],o=o||\"toggle\"===i,i===(h?\"hide\":\"show\")){if(\"show\"!==i||!g||void 0===g[r])continue;h=!0}d[r]=g&&g[r]||pe.style(e,r)}else l=void 0;if(pe.isEmptyObject(d))\"inline\"===(\"none\"===l?j(e.nodeName):l)&&(p.display=l);else{g?\"hidden\"in g&&(h=g.hidden):g=pe._data(e,\"fxshow\",{}),o&&(g.hidden=!h),h?pe(e).show():f.done(function(){pe(e).hide()}),f.done(function(){var t;pe._removeData(e,\"fxshow\");for(t in d)pe.style(e,t,d[t])});for(r in d)a=B(h?g[r]:0,r,f),r in g||(g[r]=a.start,h&&(a.end=a.start,a.start=\"width\"===r||\"height\"===r?1:0))}}function I(e,t){var n,r,i,o,a;for(n in e)if(r=pe.camelCase(n),i=t[r],o=e[n],pe.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),a=pe.cssHooks[r],a&&\"expand\"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}function $(e,t,n){var r,i,o=0,a=$.prefilters.length,s=pe.Deferred().always(function(){delete u.elem}),u=function(){if(i)return!1;for(var t=Nt||R(),n=Math.max(0,l.startTime+l.duration-t),r=n/l.duration||0,o=1-r,a=0,u=l.tweens.length;a<u;a++)l.tweens[a].run(o);return s.notifyWith(e,[l,o,n]),o<1&&u?n:(s.resolveWith(e,[l]),!1)},l=s.promise({elem:e,props:pe.extend({},t),opts:pe.extend(!0,{specialEasing:{},easing:pe.easing._default},n),originalProperties:t,originalOptions:n,startTime:Nt||R(),duration:n.duration,tweens:[],createTween:function(t,n){var r=pe.Tween(e,l.opts,t,n,l.opts.specialEasing[t]||l.opts.easing);return l.tweens.push(r),r},stop:function(t){var n=0,r=t?l.tweens.length:0;if(i)return this;for(i=!0;n<r;n++)l.tweens[n].run(1);return t?(s.notifyWith(e,[l,1,0]),s.resolveWith(e,[l,t])):s.rejectWith(e,[l,t]),this}}),c=l.props;for(I(c,l.opts.specialEasing);o<a;o++)if(r=$.prefilters[o].call(l,e,c,l.opts))return pe.isFunction(r.stop)&&(pe._queueHooks(l.elem,l.opts.queue).stop=pe.proxy(r.stop,r)),r;return pe.map(c,B,l),pe.isFunction(l.opts.start)&&l.opts.start.call(e,l),pe.fx.timer(pe.extend(u,{elem:e,anim:l,queue:l.opts.queue})),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always)}function z(e){return pe.attr(e,\"class\")||\"\"}function X(e){return function(t,n){\"string\"!=typeof t&&(n=t,t=\"*\");var r,i=0,o=t.toLowerCase().match(De)||[];if(pe.isFunction(n))for(;r=o[i++];)\"+\"===r.charAt(0)?(r=r.slice(1)||\"*\",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function U(e,t,n,r){function i(s){var u;return o[s]=!0,pe.each(e[s]||[],function(e,s){var l=s(t,n,r);return\"string\"!=typeof l||a||o[l]?a?!(u=l):void 0:(t.dataTypes.unshift(l),i(l),!1)}),u}var o={},a=e===Qt;return i(t.dataTypes[0])||!o[\"*\"]&&i(\"*\")}function V(e,t){var n,r,i=pe.ajaxSettings.flatOptions||{};for(r in t)void 0!==t[r]&&((i[r]?e:n||(n={}))[r]=t[r]);return n&&pe.extend(!0,e,n),e}function Y(e,t,n){for(var r,i,o,a,s=e.contents,u=e.dataTypes;\"*\"===u[0];)u.shift(),void 0===i&&(i=e.mimeType||t.getResponseHeader(\"Content-Type\"));if(i)for(a in s)if(s[a]&&s[a].test(i)){u.unshift(a);break}if(u[0]in n)o=u[0];else{for(a in n){if(!u[0]||e.converters[a+\" \"+u[0]]){o=a;break}r||(r=a)}o=o||r}if(o)return o!==u[0]&&u.unshift(o),n[o]}function J(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];for(o=c.shift();o;)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if(\"*\"===o)o=u;else if(\"*\"!==u&&u!==o){if(a=l[u+\" \"+o]||l[\"* \"+o],!a)for(i in l)if(s=i.split(\" \"),s[1]===o&&(a=l[u+\" \"+s[0]]||l[\"* \"+s[0]])){a===!0?a=l[i]:l[i]!==!0&&(o=s[0],c.unshift(s[1]));break}if(a!==!0)if(a&&e[\"throws\"])t=a(t);else try{t=a(t)}catch(f){return{state:\"parsererror\",error:a?f:\"No conversion from \"+u+\" to \"+o}}}return{state:\"success\",data:t}}function G(e){return e.style&&e.style.display||pe.css(e,\"display\")}function K(e){for(;e&&1===e.nodeType;){if(\"none\"===G(e)||\"hidden\"===e.type)return!0;e=e.parentNode}return!1}function Q(e,t,n,r){var i;if(pe.isArray(t))pe.each(t,function(t,i){n||rn.test(e)?r(e,i):Q(e+\"[\"+(\"object\"==typeof i&&null!=i?t:\"\")+\"]\",i,n,r)});else if(n||\"object\"!==pe.type(t))r(e,t);else for(i in t)Q(e+\"[\"+i+\"]\",t[i],n,r)}function Z(){try{return new e.XMLHttpRequest}catch(t){}}function ee(){try{return new e.ActiveXObject(\"Microsoft.XMLHTTP\")}catch(t){}}function te(e){return pe.isWindow(e)?e:9===e.nodeType&&(e.defaultView||e.parentWindow)}var ne=[],re=e.document,ie=ne.slice,oe=ne.concat,ae=ne.push,se=ne.indexOf,ue={},le=ue.toString,ce=ue.hasOwnProperty,fe={},de=\"1.12.3\",pe=function(e,t){return new pe.fn.init(e,t)},he=/^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g,ge=/^-ms-/,me=/-([\\da-z])/gi,ye=function(e,t){return t.toUpperCase()};pe.fn=pe.prototype={jquery:de,constructor:pe,selector:\"\",length:0,toArray:function(){return ie.call(this)},get:function(e){return null!=e?e<0?this[e+this.length]:this[e]:ie.call(this)},pushStack:function(e){var t=pe.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e){return pe.each(this,e)},map:function(e){return this.pushStack(pe.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(ie.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:ae,sort:ne.sort,splice:ne.splice},pe.extend=pe.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for(\"boolean\"==typeof a&&(l=a,a=arguments[s]||{},s++),\"object\"==typeof a||pe.isFunction(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(i=arguments[s]))for(r in i)e=a[r],n=i[r],a!==n&&(l&&n&&(pe.isPlainObject(n)||(t=pe.isArray(n)))?(t?(t=!1,o=e&&pe.isArray(e)?e:[]):o=e&&pe.isPlainObject(e)?e:{},a[r]=pe.extend(l,o,n)):void 0!==n&&(a[r]=n));return a},pe.extend({expando:\"jQuery\"+(de+Math.random()).replace(/\\D/g,\"\"),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isFunction:function(e){return\"function\"===pe.type(e)},isArray:Array.isArray||function(e){return\"array\"===pe.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){var t=e&&e.toString();return!pe.isArray(e)&&t-parseFloat(t)+1>=0},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},isPlainObject:function(e){var t;if(!e||\"object\"!==pe.type(e)||e.nodeType||pe.isWindow(e))return!1;try{if(e.constructor&&!ce.call(e,\"constructor\")&&!ce.call(e.constructor.prototype,\"isPrototypeOf\"))return!1}catch(n){return!1}if(!fe.ownFirst)for(t in e)return ce.call(e,t);for(t in e);return void 0===t||ce.call(e,t)},type:function(e){return null==e?e+\"\":\"object\"==typeof e||\"function\"==typeof e?ue[le.call(e)]||\"object\":typeof e},globalEval:function(t){t&&pe.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(ge,\"ms-\").replace(me,ye)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t){var r,i=0;if(n(e))for(r=e.length;i<r&&t.call(e[i],i,e[i])!==!1;i++);else for(i in e)if(t.call(e[i],i,e[i])===!1)break;return e},trim:function(e){return null==e?\"\":(e+\"\").replace(he,\"\")},makeArray:function(e,t){var r=t||[];return null!=e&&(n(Object(e))?pe.merge(r,\"string\"==typeof e?[e]:e):ae.call(r,e)),r},inArray:function(e,t,n){var r;if(t){if(se)return se.call(t,e,n);for(r=t.length,n=n?n<0?Math.max(0,r+n):n:0;n<r;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;)e[i++]=t[r++];if(n!==n)for(;void 0!==t[r];)e[i++]=t[r++];return e.length=i,e},grep:function(e,t,n){for(var r,i=[],o=0,a=e.length,s=!n;o<a;o++)r=!t(e[o],o),r!==s&&i.push(e[o]);return i},map:function(e,t,r){var i,o,a=0,s=[];if(n(e))for(i=e.length;a<i;a++)o=t(e[a],a,r),null!=o&&s.push(o);else for(a in e)o=t(e[a],a,r),null!=o&&s.push(o);return oe.apply([],s)},guid:1,proxy:function(e,t){var n,r,i;if(\"string\"==typeof t&&(i=e[t],t=e,e=i),pe.isFunction(e))return n=ie.call(arguments,2),r=function(){return e.apply(t||this,n.concat(ie.call(arguments)))},r.guid=e.guid=e.guid||pe.guid++,r},now:function(){return+new Date},support:fe}),\"function\"==typeof Symbol&&(pe.fn[Symbol.iterator]=ne[Symbol.iterator]),pe.each(\"Boolean Number String Function Array Date RegExp Object Error Symbol\".split(\" \"),function(e,t){ue[\"[object \"+t+\"]\"]=t.toLowerCase()});var ve=function(e){function t(e,t,n,r){var i,o,a,s,u,l,f,p,h=t&&t.ownerDocument,g=t?t.nodeType:9;if(n=n||[],\"string\"!=typeof e||!e||1!==g&&9!==g&&11!==g)return n;if(!r&&((t?t.ownerDocument||t:B)!==H&&L(t),t=t||H,_)){if(11!==g&&(l=ye.exec(e)))if(i=l[1]){if(9===g){if(!(a=t.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(h&&(a=h.getElementById(i))&&R(t,a)&&a.id===i)return n.push(a),n}else{if(l[2])return Q.apply(n,t.getElementsByTagName(e)),n;if((i=l[3])&&w.getElementsByClassName&&t.getElementsByClassName)return Q.apply(n,t.getElementsByClassName(i)),n}if(w.qsa&&!X[e+\" \"]&&(!F||!F.test(e))){if(1!==g)h=t,p=e;else if(\"object\"!==t.nodeName.toLowerCase()){for((s=t.getAttribute(\"id\"))?s=s.replace(xe,\"\\\\$&\"):t.setAttribute(\"id\",s=P),f=N(e),o=f.length,u=de.test(s)?\"#\"+s:\"[id='\"+s+\"']\";o--;)f[o]=u+\" \"+d(f[o]);p=f.join(\",\"),h=ve.test(e)&&c(t.parentNode)||t}if(p)try{return Q.apply(n,h.querySelectorAll(p)),n}catch(m){}finally{s===P&&t.removeAttribute(\"id\")}}}return S(e.replace(se,\"$1\"),t,n,r)}function n(){function e(n,r){return t.push(n+\" \")>T.cacheLength&&delete e[t.shift()],e[n+\" \"]=r}var t=[];return e}function r(e){return e[P]=!0,e}function i(e){var t=H.createElement(\"div\");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function o(e,t){for(var n=e.split(\"|\"),r=n.length;r--;)T.attrHandle[n[r]]=t}function a(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||V)-(~e.sourceIndex||V);if(r)return r;if(n)for(;n=n.nextSibling;)if(n===t)return-1;return e?1:-1}function s(e){return function(t){var n=t.nodeName.toLowerCase();return\"input\"===n&&t.type===e}}function u(e){return function(t){var n=t.nodeName.toLowerCase();return(\"input\"===n||\"button\"===n)&&t.type===e}}function l(e){return r(function(t){return t=+t,r(function(n,r){for(var i,o=e([],n.length,t),a=o.length;a--;)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function c(e){return e&&\"undefined\"!=typeof e.getElementsByTagName&&e}function f(){}function d(e){for(var t=0,n=e.length,r=\"\";t<n;t++)r+=e[t].value;return r}function p(e,t,n){var r=t.dir,i=n&&\"parentNode\"===r,o=I++;return t.first?function(t,n,o){for(;t=t[r];)if(1===t.nodeType||i)return e(t,n,o)}:function(t,n,a){var s,u,l,c=[W,o];if(a){for(;t=t[r];)if((1===t.nodeType||i)&&e(t,n,a))return!0}else for(;t=t[r];)if(1===t.nodeType||i){if(l=t[P]||(t[P]={}),u=l[t.uniqueID]||(l[t.uniqueID]={}),(s=u[r])&&s[0]===W&&s[1]===o)return c[2]=s[2];if(u[r]=c,c[2]=e(t,n,a))return!0}}}function h(e){return e.length>1?function(t,n,r){for(var i=e.length;i--;)if(!e[i](t,n,r))return!1;return!0}:e[0]}function g(e,n,r){for(var i=0,o=n.length;i<o;i++)t(e,n[i],r);return r}function m(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;s<u;s++)(o=e[s])&&(n&&!n(o,r,i)||(a.push(o),l&&t.push(s)));return a}function y(e,t,n,i,o,a){return i&&!i[P]&&(i=y(i)),o&&!o[P]&&(o=y(o,a)),r(function(r,a,s,u){var l,c,f,d=[],p=[],h=a.length,y=r||g(t||\"*\",s.nodeType?[s]:s,[]),v=!e||!r&&t?y:m(y,d,e,s,u),x=n?o||(r?e:h||i)?[]:a:v;if(n&&n(v,x,s,u),i)for(l=m(x,p),i(l,[],s,u),c=l.length;c--;)(f=l[c])&&(x[p[c]]=!(v[p[c]]=f));if(r){if(o||e){if(o){for(l=[],c=x.length;c--;)(f=x[c])&&l.push(v[c]=f);o(null,x=[],l,u)}for(c=x.length;c--;)(f=x[c])&&(l=o?ee(r,f):d[c])>-1&&(r[l]=!(a[l]=f))}}else x=m(x===a?x.splice(h,x.length):x),o?o(null,a,x,u):Q.apply(a,x)})}function v(e){for(var t,n,r,i=e.length,o=T.relative[e[0].type],a=o||T.relative[\" \"],s=o?1:0,u=p(function(e){return e===t},a,!0),l=p(function(e){return ee(t,e)>-1},a,!0),c=[function(e,n,r){var i=!o&&(r||n!==A)||((t=n).nodeType?u(e,n,r):l(e,n,r));return t=null,i}];s<i;s++)if(n=T.relative[e[s].type])c=[p(h(c),n)];else{if(n=T.filter[e[s].type].apply(null,e[s].matches),n[P]){for(r=++s;r<i&&!T.relative[e[r].type];r++);return y(s>1&&h(c),s>1&&d(e.slice(0,s-1).concat({value:\" \"===e[s-2].type?\"*\":\"\"})).replace(se,\"$1\"),n,s<r&&v(e.slice(s,r)),r<i&&v(e=e.slice(r)),r<i&&d(e))}c.push(n)}return h(c)}function x(e,n){var i=n.length>0,o=e.length>0,a=function(r,a,s,u,l){var c,f,d,p=0,h=\"0\",g=r&&[],y=[],v=A,x=r||o&&T.find.TAG(\"*\",l),b=W+=null==v?1:Math.random()||.1,w=x.length;for(l&&(A=a===H||a||l);h!==w&&null!=(c=x[h]);h++){if(o&&c){for(f=0,a||c.ownerDocument===H||(L(c),s=!_);d=e[f++];)if(d(c,a||H,s)){u.push(c);break}l&&(W=b)}i&&((c=!d&&c)&&p--,r&&g.push(c))}if(p+=h,i&&h!==p){for(f=0;d=n[f++];)d(g,y,a,s);if(r){if(p>0)for(;h--;)g[h]||y[h]||(y[h]=G.call(u));y=m(y)}Q.apply(u,y),l&&!r&&y.length>0&&p+n.length>1&&t.uniqueSort(u)}return l&&(W=b,A=v),g};return i?r(a):a}var b,w,T,C,E,N,k,S,A,D,j,L,H,q,_,F,M,O,R,P=\"sizzle\"+1*new Date,B=e.document,W=0,I=0,$=n(),z=n(),X=n(),U=function(e,t){return e===t&&(j=!0),0},V=1<<31,Y={}.hasOwnProperty,J=[],G=J.pop,K=J.push,Q=J.push,Z=J.slice,ee=function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},te=\"checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped\",ne=\"[\\\\x20\\\\t\\\\r\\\\n\\\\f]\",re=\"(?:\\\\\\\\.|[\\\\w-]|[^\\\\x00-\\\\xa0])+\",ie=\"\\\\[\"+ne+\"*(\"+re+\")(?:\"+ne+\"*([*^$|!~]?=)\"+ne+\"*(?:'((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\"|(\"+re+\"))|)\"+ne+\"*\\\\]\",oe=\":(\"+re+\")(?:\\\\((('((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\")|((?:\\\\\\\\.|[^\\\\\\\\()[\\\\]]|\"+ie+\")*)|.*)\\\\)|)\",ae=new RegExp(ne+\"+\",\"g\"),se=new RegExp(\"^\"+ne+\"+|((?:^|[^\\\\\\\\])(?:\\\\\\\\.)*)\"+ne+\"+$\",\"g\"),ue=new RegExp(\"^\"+ne+\"*,\"+ne+\"*\"),le=new RegExp(\"^\"+ne+\"*([>+~]|\"+ne+\")\"+ne+\"*\"),ce=new RegExp(\"=\"+ne+\"*([^\\\\]'\\\"]*?)\"+ne+\"*\\\\]\",\"g\"),fe=new RegExp(oe),de=new RegExp(\"^\"+re+\"$\"),pe={ID:new RegExp(\"^#(\"+re+\")\"),CLASS:new RegExp(\"^\\\\.(\"+re+\")\"),TAG:new RegExp(\"^(\"+re+\"|[*])\"),ATTR:new RegExp(\"^\"+ie),PSEUDO:new RegExp(\"^\"+oe),CHILD:new RegExp(\"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\\\(\"+ne+\"*(even|odd|(([+-]|)(\\\\d*)n|)\"+ne+\"*(?:([+-]|)\"+ne+\"*(\\\\d+)|))\"+ne+\"*\\\\)|)\",\"i\"),bool:new RegExp(\"^(?:\"+te+\")$\",\"i\"),needsContext:new RegExp(\"^\"+ne+\"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\\\(\"+ne+\"*((?:-\\\\d)?\\\\d*)\"+ne+\"*\\\\)|)(?=[^-]|$)\",\"i\")},he=/^(?:input|select|textarea|button)$/i,ge=/^h\\d$/i,me=/^[^{]+\\{\\s*\\[native \\w/,ye=/^(?:#([\\w-]+)|(\\w+)|\\.([\\w-]+))$/,ve=/[+~]/,xe=/'|\\\\/g,be=new RegExp(\"\\\\\\\\([\\\\da-f]{1,6}\"+ne+\"?|(\"+ne+\")|.)\",\"ig\"),we=function(e,t,n){var r=\"0x\"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},Te=function(){L()};try{Q.apply(J=Z.call(B.childNodes),B.childNodes),J[B.childNodes.length].nodeType}catch(Ce){Q={apply:J.length?function(e,t){K.apply(e,Z.call(t))}:function(e,t){for(var n=e.length,r=0;e[n++]=t[r++];);e.length=n-1}}}w=t.support={},E=t.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&\"HTML\"!==t.nodeName},L=t.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:B;return r!==H&&9===r.nodeType&&r.documentElement?(H=r,q=H.documentElement,_=!E(H),(n=H.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener(\"unload\",Te,!1):n.attachEvent&&n.attachEvent(\"onunload\",Te)),w.attributes=i(function(e){return e.className=\"i\",!e.getAttribute(\"className\")}),w.getElementsByTagName=i(function(e){return e.appendChild(H.createComment(\"\")),!e.getElementsByTagName(\"*\").length}),w.getElementsByClassName=me.test(H.getElementsByClassName),w.getById=i(function(e){return q.appendChild(e).id=P,!H.getElementsByName||!H.getElementsByName(P).length}),w.getById?(T.find.ID=function(e,t){if(\"undefined\"!=typeof t.getElementById&&_){var n=t.getElementById(e);return n?[n]:[]}},T.filter.ID=function(e){var t=e.replace(be,we);return function(e){return e.getAttribute(\"id\")===t}}):(delete T.find.ID,T.filter.ID=function(e){var t=e.replace(be,we);return function(e){var n=\"undefined\"!=typeof e.getAttributeNode&&e.getAttributeNode(\"id\");return n&&n.value===t}}),T.find.TAG=w.getElementsByTagName?function(e,t){return\"undefined\"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):w.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if(\"*\"===e){for(;n=o[i++];)1===n.nodeType&&r.push(n);return r}return o},T.find.CLASS=w.getElementsByClassName&&function(e,t){if(\"undefined\"!=typeof t.getElementsByClassName&&_)return t.getElementsByClassName(e)},M=[],F=[],(w.qsa=me.test(H.querySelectorAll))&&(i(function(e){q.appendChild(e).innerHTML=\"<a id='\"+P+\"'></a><select id='\"+P+\"-\\r\\\\' msallowcapture=''><option selected=''></option></select>\",e.querySelectorAll(\"[msallowcapture^='']\").length&&F.push(\"[*^$]=\"+ne+\"*(?:''|\\\"\\\")\"),e.querySelectorAll(\"[selected]\").length||F.push(\"\\\\[\"+ne+\"*(?:value|\"+te+\")\"),e.querySelectorAll(\"[id~=\"+P+\"-]\").length||F.push(\"~=\"),e.querySelectorAll(\":checked\").length||F.push(\":checked\"),e.querySelectorAll(\"a#\"+P+\"+*\").length||F.push(\".#.+[+~]\")}),i(function(e){var t=H.createElement(\"input\");t.setAttribute(\"type\",\"hidden\"),e.appendChild(t).setAttribute(\"name\",\"D\"),e.querySelectorAll(\"[name=d]\").length&&F.push(\"name\"+ne+\"*[*^$|!~]?=\"),e.querySelectorAll(\":enabled\").length||F.push(\":enabled\",\":disabled\"),e.querySelectorAll(\"*,:x\"),F.push(\",.*:\")})),(w.matchesSelector=me.test(O=q.matches||q.webkitMatchesSelector||q.mozMatchesSelector||q.oMatchesSelector||q.msMatchesSelector))&&i(function(e){w.disconnectedMatch=O.call(e,\"div\"),O.call(e,\"[s!='']:x\"),M.push(\"!=\",oe)}),F=F.length&&new RegExp(F.join(\"|\")),M=M.length&&new RegExp(M.join(\"|\")),t=me.test(q.compareDocumentPosition),R=t||me.test(q.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},U=t?function(e,t){if(e===t)return j=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n?n:(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1,1&n||!w.sortDetached&&t.compareDocumentPosition(e)===n?e===H||e.ownerDocument===B&&R(B,e)?-1:t===H||t.ownerDocument===B&&R(B,t)?1:D?ee(D,e)-ee(D,t):0:4&n?-1:1)}:function(e,t){if(e===t)return j=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,s=[e],u=[t];if(!i||!o)return e===H?-1:t===H?1:i?-1:o?1:D?ee(D,e)-ee(D,t):0;if(i===o)return a(e,t);for(n=e;n=n.parentNode;)s.unshift(n);for(n=t;n=n.parentNode;)u.unshift(n);for(;s[r]===u[r];)r++;return r?a(s[r],u[r]):s[r]===B?-1:u[r]===B?1:0},H):H},t.matches=function(e,n){return t(e,null,null,n)},t.matchesSelector=function(e,n){if((e.ownerDocument||e)!==H&&L(e),n=n.replace(ce,\"='$1']\"),w.matchesSelector&&_&&!X[n+\" \"]&&(!M||!M.test(n))&&(!F||!F.test(n)))try{var r=O.call(e,n);if(r||w.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(i){}return t(n,H,null,[e]).length>0},t.contains=function(e,t){return(e.ownerDocument||e)!==H&&L(e),R(e,t)},t.attr=function(e,t){(e.ownerDocument||e)!==H&&L(e);var n=T.attrHandle[t.toLowerCase()],r=n&&Y.call(T.attrHandle,t.toLowerCase())?n(e,t,!_):void 0;return void 0!==r?r:w.attributes||!_?e.getAttribute(t):(r=e.getAttributeNode(t))&&r.specified?r.value:null},t.error=function(e){throw new Error(\"Syntax error, unrecognized expression: \"+e)},t.uniqueSort=function(e){var t,n=[],r=0,i=0;if(j=!w.detectDuplicates,D=!w.sortStable&&e.slice(0),e.sort(U),j){for(;t=e[i++];)t===e[i]&&(r=n.push(i));for(;r--;)e.splice(n[r],1)}return D=null,e},C=t.getText=function(e){var t,n=\"\",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if(\"string\"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=C(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r++];)n+=C(t);return n},T=t.selectors={cacheLength:50,createPseudo:r,match:pe,attrHandle:{},find:{},relative:{\">\":{dir:\"parentNode\",first:!0},\" \":{dir:\"parentNode\"},\"+\":{dir:\"previousSibling\",first:!0},\"~\":{dir:\"previousSibling\"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(be,we),e[3]=(e[3]||e[4]||e[5]||\"\").replace(be,we),\"~=\"===e[2]&&(e[3]=\" \"+e[3]+\" \"),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),\"nth\"===e[1].slice(0,3)?(e[3]||t.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*(\"even\"===e[3]||\"odd\"===e[3])),e[5]=+(e[7]+e[8]||\"odd\"===e[3])):e[3]&&t.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return pe.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||\"\":n&&fe.test(n)&&(t=N(n,!0))&&(t=n.indexOf(\")\",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(be,we).toLowerCase();return\"*\"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=$[e+\" \"];return t||(t=new RegExp(\"(^|\"+ne+\")\"+e+\"(\"+ne+\"|$)\"))&&$(e,function(e){return t.test(\"string\"==typeof e.className&&e.className||\"undefined\"!=typeof e.getAttribute&&e.getAttribute(\"class\")||\"\")})},ATTR:function(e,n,r){return function(i){var o=t.attr(i,e);return null==o?\"!=\"===n:!n||(o+=\"\",\"=\"===n?o===r:\"!=\"===n?o!==r:\"^=\"===n?r&&0===o.indexOf(r):\"*=\"===n?r&&o.indexOf(r)>-1:\"$=\"===n?r&&o.slice(-r.length)===r:\"~=\"===n?(\" \"+o.replace(ae,\" \")+\" \").indexOf(r)>-1:\"|=\"===n&&(o===r||o.slice(0,r.length+1)===r+\"-\"))}},CHILD:function(e,t,n,r,i){var o=\"nth\"!==e.slice(0,3),a=\"last\"!==e.slice(-4),s=\"of-type\"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,d,p,h,g=o!==a?\"nextSibling\":\"previousSibling\",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!u&&!s,x=!1;if(m){if(o){for(;g;){for(d=t;d=d[g];)if(s?d.nodeName.toLowerCase()===y:1===d.nodeType)return!1;h=g=\"only\"===e&&!h&&\"nextSibling\"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){for(d=m,f=d[P]||(d[P]={}),c=f[d.uniqueID]||(f[d.uniqueID]={}),\nl=c[e]||[],p=l[0]===W&&l[1],x=p&&l[2],d=p&&m.childNodes[p];d=++p&&d&&d[g]||(x=p=0)||h.pop();)if(1===d.nodeType&&++x&&d===t){c[e]=[W,p,x];break}}else if(v&&(d=t,f=d[P]||(d[P]={}),c=f[d.uniqueID]||(f[d.uniqueID]={}),l=c[e]||[],p=l[0]===W&&l[1],x=p),x===!1)for(;(d=++p&&d&&d[g]||(x=p=0)||h.pop())&&((s?d.nodeName.toLowerCase()!==y:1!==d.nodeType)||!++x||(v&&(f=d[P]||(d[P]={}),c=f[d.uniqueID]||(f[d.uniqueID]={}),c[e]=[W,x]),d!==t)););return x-=i,x===r||x%r===0&&x/r>=0}}},PSEUDO:function(e,n){var i,o=T.pseudos[e]||T.setFilters[e.toLowerCase()]||t.error(\"unsupported pseudo: \"+e);return o[P]?o(n):o.length>1?(i=[e,e,\"\",n],T.setFilters.hasOwnProperty(e.toLowerCase())?r(function(e,t){for(var r,i=o(e,n),a=i.length;a--;)r=ee(e,i[a]),e[r]=!(t[r]=i[a])}):function(e){return o(e,0,i)}):o}},pseudos:{not:r(function(e){var t=[],n=[],i=k(e.replace(se,\"$1\"));return i[P]?r(function(e,t,n,r){for(var o,a=i(e,null,r,[]),s=e.length;s--;)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,r,o){return t[0]=e,i(t,null,o,n),t[0]=null,!n.pop()}}),has:r(function(e){return function(n){return t(e,n).length>0}}),contains:r(function(e){return e=e.replace(be,we),function(t){return(t.textContent||t.innerText||C(t)).indexOf(e)>-1}}),lang:r(function(e){return de.test(e||\"\")||t.error(\"unsupported lang: \"+e),e=e.replace(be,we).toLowerCase(),function(t){var n;do if(n=_?t.lang:t.getAttribute(\"xml:lang\")||t.getAttribute(\"lang\"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+\"-\");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===q},focus:function(e){return e===H.activeElement&&(!H.hasFocus||H.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return\"input\"===t&&!!e.checked||\"option\"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!T.pseudos.empty(e)},header:function(e){return ge.test(e.nodeName)},input:function(e){return he.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return\"input\"===t&&\"button\"===e.type||\"button\"===t},text:function(e){var t;return\"input\"===e.nodeName.toLowerCase()&&\"text\"===e.type&&(null==(t=e.getAttribute(\"type\"))||\"text\"===t.toLowerCase())},first:l(function(){return[0]}),last:l(function(e,t){return[t-1]}),eq:l(function(e,t,n){return[n<0?n+t:n]}),even:l(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:l(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:l(function(e,t,n){for(var r=n<0?n+t:n;--r>=0;)e.push(r);return e}),gt:l(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}},T.pseudos.nth=T.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})T.pseudos[b]=s(b);for(b in{submit:!0,reset:!0})T.pseudos[b]=u(b);return f.prototype=T.filters=T.pseudos,T.setFilters=new f,N=t.tokenize=function(e,n){var r,i,o,a,s,u,l,c=z[e+\" \"];if(c)return n?0:c.slice(0);for(s=e,u=[],l=T.preFilter;s;){r&&!(i=ue.exec(s))||(i&&(s=s.slice(i[0].length)||s),u.push(o=[])),r=!1,(i=le.exec(s))&&(r=i.shift(),o.push({value:r,type:i[0].replace(se,\" \")}),s=s.slice(r.length));for(a in T.filter)!(i=pe[a].exec(s))||l[a]&&!(i=l[a](i))||(r=i.shift(),o.push({value:r,type:a,matches:i}),s=s.slice(r.length));if(!r)break}return n?s.length:s?t.error(e):z(e,u).slice(0)},k=t.compile=function(e,t){var n,r=[],i=[],o=X[e+\" \"];if(!o){for(t||(t=N(e)),n=t.length;n--;)o=v(t[n]),o[P]?r.push(o):i.push(o);o=X(e,x(i,r)),o.selector=e}return o},S=t.select=function(e,t,n,r){var i,o,a,s,u,l=\"function\"==typeof e&&e,f=!r&&N(e=l.selector||e);if(n=n||[],1===f.length){if(o=f[0]=f[0].slice(0),o.length>2&&\"ID\"===(a=o[0]).type&&w.getById&&9===t.nodeType&&_&&T.relative[o[1].type]){if(t=(T.find.ID(a.matches[0].replace(be,we),t)||[])[0],!t)return n;l&&(t=t.parentNode),e=e.slice(o.shift().value.length)}for(i=pe.needsContext.test(e)?0:o.length;i--&&(a=o[i],!T.relative[s=a.type]);)if((u=T.find[s])&&(r=u(a.matches[0].replace(be,we),ve.test(o[0].type)&&c(t.parentNode)||t))){if(o.splice(i,1),e=r.length&&d(o),!e)return Q.apply(n,r),n;break}}return(l||k(e,f))(r,t,!_,n,!t||ve.test(e)&&c(t.parentNode)||t),n},w.sortStable=P.split(\"\").sort(U).join(\"\")===P,w.detectDuplicates=!!j,L(),w.sortDetached=i(function(e){return 1&e.compareDocumentPosition(H.createElement(\"div\"))}),i(function(e){return e.innerHTML=\"<a href='#'></a>\",\"#\"===e.firstChild.getAttribute(\"href\")})||o(\"type|href|height|width\",function(e,t,n){if(!n)return e.getAttribute(t,\"type\"===t.toLowerCase()?1:2)}),w.attributes&&i(function(e){return e.innerHTML=\"<input/>\",e.firstChild.setAttribute(\"value\",\"\"),\"\"===e.firstChild.getAttribute(\"value\")})||o(\"value\",function(e,t,n){if(!n&&\"input\"===e.nodeName.toLowerCase())return e.defaultValue}),i(function(e){return null==e.getAttribute(\"disabled\")})||o(te,function(e,t,n){var r;if(!n)return e[t]===!0?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),t}(e);pe.find=ve,pe.expr=ve.selectors,pe.expr[\":\"]=pe.expr.pseudos,pe.uniqueSort=pe.unique=ve.uniqueSort,pe.text=ve.getText,pe.isXMLDoc=ve.isXML,pe.contains=ve.contains;var xe=function(e,t,n){for(var r=[],i=void 0!==n;(e=e[t])&&9!==e.nodeType;)if(1===e.nodeType){if(i&&pe(e).is(n))break;r.push(e)}return r},be=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},we=pe.expr.match.needsContext,Te=/^<([\\w-]+)\\s*\\/?>(?:<\\/\\1>|)$/,Ce=/^.[^:#\\[\\.,]*$/;pe.filter=function(e,t,n){var r=t[0];return n&&(e=\":not(\"+e+\")\"),1===t.length&&1===r.nodeType?pe.find.matchesSelector(r,e)?[r]:[]:pe.find.matches(e,pe.grep(t,function(e){return 1===e.nodeType}))},pe.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if(\"string\"!=typeof e)return this.pushStack(pe(e).filter(function(){for(t=0;t<i;t++)if(pe.contains(r[t],this))return!0}));for(t=0;t<i;t++)pe.find(e,r[t],n);return n=this.pushStack(i>1?pe.unique(n):n),n.selector=this.selector?this.selector+\" \"+e:e,n},filter:function(e){return this.pushStack(r(this,e||[],!1))},not:function(e){return this.pushStack(r(this,e||[],!0))},is:function(e){return!!r(this,\"string\"==typeof e&&we.test(e)?pe(e):e||[],!1).length}});var Ee,Ne=/^(?:\\s*(<[\\w\\W]+>)[^>]*|#([\\w-]*))$/,ke=pe.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||Ee,\"string\"==typeof e){if(r=\"<\"===e.charAt(0)&&\">\"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:Ne.exec(e),!r||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof pe?t[0]:t,pe.merge(this,pe.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:re,!0)),Te.test(r[1])&&pe.isPlainObject(t))for(r in t)pe.isFunction(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}if(i=re.getElementById(r[2]),i&&i.parentNode){if(i.id!==r[2])return Ee.find(e);this.length=1,this[0]=i}return this.context=re,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):pe.isFunction(e)?\"undefined\"!=typeof n.ready?n.ready(e):e(pe):(void 0!==e.selector&&(this.selector=e.selector,this.context=e.context),pe.makeArray(e,this))};ke.prototype=pe.fn,Ee=pe(re);var Se=/^(?:parents|prev(?:Until|All))/,Ae={children:!0,contents:!0,next:!0,prev:!0};pe.fn.extend({has:function(e){var t,n=pe(e,this),r=n.length;return this.filter(function(){for(t=0;t<r;t++)if(pe.contains(this,n[t]))return!0})},closest:function(e,t){for(var n,r=0,i=this.length,o=[],a=we.test(e)||\"string\"!=typeof e?pe(e,t||this.context):0;r<i;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(n.nodeType<11&&(a?a.index(n)>-1:1===n.nodeType&&pe.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?pe.uniqueSort(o):o)},index:function(e){return e?\"string\"==typeof e?pe.inArray(this[0],pe(e)):pe.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(pe.uniqueSort(pe.merge(this.get(),pe(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),pe.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return xe(e,\"parentNode\")},parentsUntil:function(e,t,n){return xe(e,\"parentNode\",n)},next:function(e){return i(e,\"nextSibling\")},prev:function(e){return i(e,\"previousSibling\")},nextAll:function(e){return xe(e,\"nextSibling\")},prevAll:function(e){return xe(e,\"previousSibling\")},nextUntil:function(e,t,n){return xe(e,\"nextSibling\",n)},prevUntil:function(e,t,n){return xe(e,\"previousSibling\",n)},siblings:function(e){return be((e.parentNode||{}).firstChild,e)},children:function(e){return be(e.firstChild)},contents:function(e){return pe.nodeName(e,\"iframe\")?e.contentDocument||e.contentWindow.document:pe.merge([],e.childNodes)}},function(e,t){pe.fn[e]=function(n,r){var i=pe.map(this,t,n);return\"Until\"!==e.slice(-5)&&(r=n),r&&\"string\"==typeof r&&(i=pe.filter(r,i)),this.length>1&&(Ae[e]||(i=pe.uniqueSort(i)),Se.test(e)&&(i=i.reverse())),this.pushStack(i)}});var De=/\\S+/g;pe.Callbacks=function(e){e=\"string\"==typeof e?o(e):pe.extend({},e);var t,n,r,i,a=[],s=[],u=-1,l=function(){for(i=e.once,r=t=!0;s.length;u=-1)for(n=s.shift();++u<a.length;)a[u].apply(n[0],n[1])===!1&&e.stopOnFalse&&(u=a.length,n=!1);e.memory||(n=!1),t=!1,i&&(a=n?[]:\"\")},c={add:function(){return a&&(n&&!t&&(u=a.length-1,s.push(n)),function r(t){pe.each(t,function(t,n){pe.isFunction(n)?e.unique&&c.has(n)||a.push(n):n&&n.length&&\"string\"!==pe.type(n)&&r(n)})}(arguments),n&&!t&&l()),this},remove:function(){return pe.each(arguments,function(e,t){for(var n;(n=pe.inArray(t,a,n))>-1;)a.splice(n,1),n<=u&&u--}),this},has:function(e){return e?pe.inArray(e,a)>-1:a.length>0},empty:function(){return a&&(a=[]),this},disable:function(){return i=s=[],a=n=\"\",this},disabled:function(){return!a},lock:function(){return i=!0,n||c.disable(),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=n||[],n=[e,n.slice?n.slice():n],s.push(n),t||l()),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},pe.extend({Deferred:function(e){var t=[[\"resolve\",\"done\",pe.Callbacks(\"once memory\"),\"resolved\"],[\"reject\",\"fail\",pe.Callbacks(\"once memory\"),\"rejected\"],[\"notify\",\"progress\",pe.Callbacks(\"memory\")]],n=\"pending\",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return pe.Deferred(function(n){pe.each(t,function(t,o){var a=pe.isFunction(e[t])&&e[t];i[o[1]](function(){var e=a&&a.apply(this,arguments);e&&pe.isFunction(e.promise)?e.promise().progress(n.notify).done(n.resolve).fail(n.reject):n[o[0]+\"With\"](this===r?n.promise():this,a?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?pe.extend(e,r):r}},i={};return r.pipe=r.then,pe.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+\"With\"](this===i?r:this,arguments),this},i[o[0]+\"With\"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t,n,r,i=0,o=ie.call(arguments),a=o.length,s=1!==a||e&&pe.isFunction(e.promise)?a:0,u=1===s?e:pe.Deferred(),l=function(e,n,r){return function(i){n[e]=this,r[e]=arguments.length>1?ie.call(arguments):i,r===t?u.notifyWith(n,r):--s||u.resolveWith(n,r)}};if(a>1)for(t=new Array(a),n=new Array(a),r=new Array(a);i<a;i++)o[i]&&pe.isFunction(o[i].promise)?o[i].promise().progress(l(i,n,t)).done(l(i,r,o)).fail(u.reject):--s;return s||u.resolveWith(r,o),u.promise()}});var je;pe.fn.ready=function(e){return pe.ready.promise().done(e),this},pe.extend({isReady:!1,readyWait:1,holdReady:function(e){e?pe.readyWait++:pe.ready(!0)},ready:function(e){(e===!0?--pe.readyWait:pe.isReady)||(pe.isReady=!0,e!==!0&&--pe.readyWait>0||(je.resolveWith(re,[pe]),pe.fn.triggerHandler&&(pe(re).triggerHandler(\"ready\"),pe(re).off(\"ready\"))))}}),pe.ready.promise=function(t){if(!je)if(je=pe.Deferred(),\"complete\"===re.readyState||\"loading\"!==re.readyState&&!re.documentElement.doScroll)e.setTimeout(pe.ready);else if(re.addEventListener)re.addEventListener(\"DOMContentLoaded\",s),e.addEventListener(\"load\",s);else{re.attachEvent(\"onreadystatechange\",s),e.attachEvent(\"onload\",s);var n=!1;try{n=null==e.frameElement&&re.documentElement}catch(r){}n&&n.doScroll&&!function i(){if(!pe.isReady){try{n.doScroll(\"left\")}catch(t){return e.setTimeout(i,50)}a(),pe.ready()}}()}return je.promise(t)},pe.ready.promise();var Le;for(Le in pe(fe))break;fe.ownFirst=\"0\"===Le,fe.inlineBlockNeedsLayout=!1,pe(function(){var e,t,n,r;n=re.getElementsByTagName(\"body\")[0],n&&n.style&&(t=re.createElement(\"div\"),r=re.createElement(\"div\"),r.style.cssText=\"position:absolute;border:0;width:0;height:0;top:0;left:-9999px\",n.appendChild(r).appendChild(t),\"undefined\"!=typeof t.style.zoom&&(t.style.cssText=\"display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1\",fe.inlineBlockNeedsLayout=e=3===t.offsetWidth,e&&(n.style.zoom=1)),n.removeChild(r))}),function(){var e=re.createElement(\"div\");fe.deleteExpando=!0;try{delete e.test}catch(t){fe.deleteExpando=!1}e=null}();var He=function(e){var t=pe.noData[(e.nodeName+\" \").toLowerCase()],n=+e.nodeType||1;return(1===n||9===n)&&(!t||t!==!0&&e.getAttribute(\"classid\")===t)},qe=/^(?:\\{[\\w\\W]*\\}|\\[[\\w\\W]*\\])$/,_e=/([A-Z])/g;pe.extend({cache:{},noData:{\"applet \":!0,\"embed \":!0,\"object \":\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\"},hasData:function(e){return e=e.nodeType?pe.cache[e[pe.expando]]:e[pe.expando],!!e&&!l(e)},data:function(e,t,n){return c(e,t,n)},removeData:function(e,t){return f(e,t)},_data:function(e,t,n){return c(e,t,n,!0)},_removeData:function(e,t){return f(e,t,!0)}}),pe.fn.extend({data:function(e,t){var n,r,i,o=this[0],a=o&&o.attributes;if(void 0===e){if(this.length&&(i=pe.data(o),1===o.nodeType&&!pe._data(o,\"parsedAttrs\"))){for(n=a.length;n--;)a[n]&&(r=a[n].name,0===r.indexOf(\"data-\")&&(r=pe.camelCase(r.slice(5)),u(o,r,i[r])));pe._data(o,\"parsedAttrs\",!0)}return i}return\"object\"==typeof e?this.each(function(){pe.data(this,e)}):arguments.length>1?this.each(function(){pe.data(this,e,t)}):o?u(o,e,pe.data(o,e)):void 0},removeData:function(e){return this.each(function(){pe.removeData(this,e)})}}),pe.extend({queue:function(e,t,n){var r;if(e)return t=(t||\"fx\")+\"queue\",r=pe._data(e,t),n&&(!r||pe.isArray(n)?r=pe._data(e,t,pe.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||\"fx\";var n=pe.queue(e,t),r=n.length,i=n.shift(),o=pe._queueHooks(e,t),a=function(){pe.dequeue(e,t)};\"inprogress\"===i&&(i=n.shift(),r--),i&&(\"fx\"===t&&n.unshift(\"inprogress\"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+\"queueHooks\";return pe._data(e,n)||pe._data(e,n,{empty:pe.Callbacks(\"once memory\").add(function(){pe._removeData(e,t+\"queue\"),pe._removeData(e,n)})})}}),pe.fn.extend({queue:function(e,t){var n=2;return\"string\"!=typeof e&&(t=e,e=\"fx\",n--),arguments.length<n?pe.queue(this[0],e):void 0===t?this:this.each(function(){var n=pe.queue(this,e,t);pe._queueHooks(this,e),\"fx\"===e&&\"inprogress\"!==n[0]&&pe.dequeue(this,e)})},dequeue:function(e){return this.each(function(){pe.dequeue(this,e)})},clearQueue:function(e){return this.queue(e||\"fx\",[])},promise:function(e,t){var n,r=1,i=pe.Deferred(),o=this,a=this.length,s=function(){--r||i.resolveWith(o,[o])};for(\"string\"!=typeof e&&(t=e,e=void 0),e=e||\"fx\";a--;)n=pe._data(o[a],e+\"queueHooks\"),n&&n.empty&&(r++,n.empty.add(s));return s(),i.promise(t)}}),function(){var e;fe.shrinkWrapBlocks=function(){if(null!=e)return e;e=!1;var t,n,r;return n=re.getElementsByTagName(\"body\")[0],n&&n.style?(t=re.createElement(\"div\"),r=re.createElement(\"div\"),r.style.cssText=\"position:absolute;border:0;width:0;height:0;top:0;left:-9999px\",n.appendChild(r).appendChild(t),\"undefined\"!=typeof t.style.zoom&&(t.style.cssText=\"-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:1px;width:1px;zoom:1\",t.appendChild(re.createElement(\"div\")).style.width=\"5px\",e=3!==t.offsetWidth),n.removeChild(r),e):void 0}}();var Fe=/[+-]?(?:\\d*\\.|)\\d+(?:[eE][+-]?\\d+|)/.source,Me=new RegExp(\"^(?:([+-])=|)(\"+Fe+\")([a-z%]*)$\",\"i\"),Oe=[\"Top\",\"Right\",\"Bottom\",\"Left\"],Re=function(e,t){return e=t||e,\"none\"===pe.css(e,\"display\")||!pe.contains(e.ownerDocument,e)},Pe=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if(\"object\"===pe.type(n)){i=!0;for(s in n)Pe(e,t,s,n[s],!0,o,a)}else if(void 0!==r&&(i=!0,pe.isFunction(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(pe(e),n)})),t))for(;s<u;s++)t(e[s],n,a?r:r.call(e[s],s,t(e[s],n)));return i?e:l?t.call(e):u?t(e[0],n):o},Be=/^(?:checkbox|radio)$/i,We=/<([\\w:-]+)/,Ie=/^$|\\/(?:java|ecma)script/i,$e=/^\\s+/,ze=\"abbr|article|aside|audio|bdi|canvas|data|datalist|details|dialog|figcaption|figure|footer|header|hgroup|main|mark|meter|nav|output|picture|progress|section|summary|template|time|video\";!function(){var e=re.createElement(\"div\"),t=re.createDocumentFragment(),n=re.createElement(\"input\");e.innerHTML=\"  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>\",fe.leadingWhitespace=3===e.firstChild.nodeType,fe.tbody=!e.getElementsByTagName(\"tbody\").length,fe.htmlSerialize=!!e.getElementsByTagName(\"link\").length,fe.html5Clone=\"<:nav></:nav>\"!==re.createElement(\"nav\").cloneNode(!0).outerHTML,n.type=\"checkbox\",n.checked=!0,t.appendChild(n),fe.appendChecked=n.checked,e.innerHTML=\"<textarea>x</textarea>\",fe.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue,t.appendChild(e),n=re.createElement(\"input\"),n.setAttribute(\"type\",\"radio\"),n.setAttribute(\"checked\",\"checked\"),n.setAttribute(\"name\",\"t\"),e.appendChild(n),fe.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,fe.noCloneEvent=!!e.addEventListener,e[pe.expando]=1,fe.attributes=!e.getAttribute(pe.expando)}();var Xe={option:[1,\"<select multiple='multiple'>\",\"</select>\"],legend:[1,\"<fieldset>\",\"</fieldset>\"],area:[1,\"<map>\",\"</map>\"],param:[1,\"<object>\",\"</object>\"],thead:[1,\"<table>\",\"</table>\"],tr:[2,\"<table><tbody>\",\"</tbody></table>\"],col:[2,\"<table><tbody></tbody><colgroup>\",\"</colgroup></table>\"],td:[3,\"<table><tbody><tr>\",\"</tr></tbody></table>\"],_default:fe.htmlSerialize?[0,\"\",\"\"]:[1,\"X<div>\",\"</div>\"]};Xe.optgroup=Xe.option,Xe.tbody=Xe.tfoot=Xe.colgroup=Xe.caption=Xe.thead,Xe.th=Xe.td;var Ue=/<|&#?\\w+;/,Ve=/<tbody/i;!function(){var t,n,r=re.createElement(\"div\");for(t in{submit:!0,change:!0,focusin:!0})n=\"on\"+t,(fe[t]=n in e)||(r.setAttribute(n,\"t\"),fe[t]=r.attributes[n].expando===!1);r=null}();var Ye=/^(?:input|select|textarea)$/i,Je=/^key/,Ge=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ke=/^(?:focusinfocus|focusoutblur)$/,Qe=/^([^.]*)(?:\\.(.+)|)/;pe.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,f,d,p,h,g,m=pe._data(e);if(m){for(n.handler&&(u=n,n=u.handler,i=u.selector),n.guid||(n.guid=pe.guid++),(a=m.events)||(a=m.events={}),(c=m.handle)||(c=m.handle=function(e){return\"undefined\"==typeof pe||e&&pe.event.triggered===e.type?void 0:pe.event.dispatch.apply(c.elem,arguments)},c.elem=e),t=(t||\"\").match(De)||[\"\"],s=t.length;s--;)o=Qe.exec(t[s])||[],p=g=o[1],h=(o[2]||\"\").split(\".\").sort(),p&&(l=pe.event.special[p]||{},p=(i?l.delegateType:l.bindType)||p,l=pe.event.special[p]||{},f=pe.extend({type:p,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&pe.expr.match.needsContext.test(i),namespace:h.join(\".\")},u),(d=a[p])||(d=a[p]=[],d.delegateCount=0,l.setup&&l.setup.call(e,r,h,c)!==!1||(e.addEventListener?e.addEventListener(p,c,!1):e.attachEvent&&e.attachEvent(\"on\"+p,c))),l.add&&(l.add.call(e,f),f.handler.guid||(f.handler.guid=n.guid)),i?d.splice(d.delegateCount++,0,f):d.push(f),pe.event.global[p]=!0);e=null}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,d,p,h,g,m=pe.hasData(e)&&pe._data(e);if(m&&(c=m.events)){for(t=(t||\"\").match(De)||[\"\"],l=t.length;l--;)if(s=Qe.exec(t[l])||[],p=g=s[1],h=(s[2]||\"\").split(\".\").sort(),p){for(f=pe.event.special[p]||{},p=(r?f.delegateType:f.bindType)||p,d=c[p]||[],s=s[2]&&new RegExp(\"(^|\\\\.)\"+h.join(\"\\\\.(?:.*\\\\.|)\")+\"(\\\\.|$)\"),u=o=d.length;o--;)a=d[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&(\"**\"!==r||!a.selector)||(d.splice(o,1),a.selector&&d.delegateCount--,f.remove&&f.remove.call(e,a));u&&!d.length&&(f.teardown&&f.teardown.call(e,h,m.handle)!==!1||pe.removeEvent(e,p,m.handle),delete c[p])}else for(p in c)pe.event.remove(e,p+t[l],n,r,!0);pe.isEmptyObject(c)&&(delete m.handle,pe._removeData(e,\"events\"))}},trigger:function(t,n,r,i){var o,a,s,u,l,c,f,d=[r||re],p=ce.call(t,\"type\")?t.type:t,h=ce.call(t,\"namespace\")?t.namespace.split(\".\"):[];if(s=c=r=r||re,3!==r.nodeType&&8!==r.nodeType&&!Ke.test(p+pe.event.triggered)&&(p.indexOf(\".\")>-1&&(h=p.split(\".\"),p=h.shift(),h.sort()),a=p.indexOf(\":\")<0&&\"on\"+p,t=t[pe.expando]?t:new pe.Event(p,\"object\"==typeof t&&t),t.isTrigger=i?2:3,t.namespace=h.join(\".\"),t.rnamespace=t.namespace?new RegExp(\"(^|\\\\.)\"+h.join(\"\\\\.(?:.*\\\\.|)\")+\"(\\\\.|$)\"):null,t.result=void 0,t.target||(t.target=r),n=null==n?[t]:pe.makeArray(n,[t]),l=pe.event.special[p]||{},i||!l.trigger||l.trigger.apply(r,n)!==!1)){if(!i&&!l.noBubble&&!pe.isWindow(r)){for(u=l.delegateType||p,Ke.test(u+p)||(s=s.parentNode);s;s=s.parentNode)d.push(s),c=s;c===(r.ownerDocument||re)&&d.push(c.defaultView||c.parentWindow||e)}for(f=0;(s=d[f++])&&!t.isPropagationStopped();)t.type=f>1?u:l.bindType||p,o=(pe._data(s,\"events\")||{})[t.type]&&pe._data(s,\"handle\"),o&&o.apply(s,n),o=a&&s[a],o&&o.apply&&He(s)&&(t.result=o.apply(s,n),t.result===!1&&t.preventDefault());if(t.type=p,!i&&!t.isDefaultPrevented()&&(!l._default||l._default.apply(d.pop(),n)===!1)&&He(r)&&a&&r[p]&&!pe.isWindow(r)){c=r[a],c&&(r[a]=null),pe.event.triggered=p;try{r[p]()}catch(g){}pe.event.triggered=void 0,c&&(r[a]=c)}return t.result}},dispatch:function(e){e=pe.event.fix(e);var t,n,r,i,o,a=[],s=ie.call(arguments),u=(pe._data(this,\"events\")||{})[e.type]||[],l=pe.event.special[e.type]||{};if(s[0]=e,e.delegateTarget=this,!l.preDispatch||l.preDispatch.call(this,e)!==!1){for(a=pe.event.handlers.call(this,e,u),t=0;(i=a[t++])&&!e.isPropagationStopped();)for(e.currentTarget=i.elem,n=0;(o=i.handlers[n++])&&!e.isImmediatePropagationStopped();)e.rnamespace&&!e.rnamespace.test(o.namespace)||(e.handleObj=o,e.data=o.data,r=((pe.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,s),void 0!==r&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()));return l.postDispatch&&l.postDispatch.call(this,e),e.result}},handlers:function(e,t){var n,r,i,o,a=[],s=t.delegateCount,u=e.target;if(s&&u.nodeType&&(\"click\"!==e.type||isNaN(e.button)||e.button<1))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||\"click\"!==e.type)){for(r=[],n=0;n<s;n++)o=t[n],i=o.selector+\" \",void 0===r[i]&&(r[i]=o.needsContext?pe(i,this).index(u)>-1:pe.find(i,this,null,[u]).length),r[i]&&r.push(o);r.length&&a.push({elem:u,handlers:r})}return s<t.length&&a.push({elem:this,handlers:t.slice(s)}),a},fix:function(e){if(e[pe.expando])return e;var t,n,r,i=e.type,o=e,a=this.fixHooks[i];for(a||(this.fixHooks[i]=a=Ge.test(i)?this.mouseHooks:Je.test(i)?this.keyHooks:{}),r=a.props?this.props.concat(a.props):this.props,e=new pe.Event(o),t=r.length;t--;)n=r[t],e[n]=o[n];return e.target||(e.target=o.srcElement||re),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,a.filter?a.filter(e,o):e},props:\"altKey bubbles cancelable ctrlKey currentTarget detail eventPhase metaKey relatedTarget shiftKey target timeStamp view which\".split(\" \"),fixHooks:{},keyHooks:{props:\"char charCode key keyCode\".split(\" \"),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:\"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement\".split(\" \"),filter:function(e,t){var n,r,i,o=t.button,a=t.fromElement;return null==e.pageX&&null!=t.clientX&&(r=e.target.ownerDocument||re,i=r.documentElement,n=r.body,e.pageX=t.clientX+(i&&i.scrollLeft||n&&n.scrollLeft||0)-(i&&i.clientLeft||n&&n.clientLeft||0),e.pageY=t.clientY+(i&&i.scrollTop||n&&n.scrollTop||0)-(i&&i.clientTop||n&&n.clientTop||0)),!e.relatedTarget&&a&&(e.relatedTarget=a===e.target?t.toElement:a),e.which||void 0===o||(e.which=1&o?1:2&o?3:4&o?2:0),e}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==b()&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:\"focusin\"},blur:{trigger:function(){if(this===b()&&this.blur)return this.blur(),!1},delegateType:\"focusout\"},click:{trigger:function(){if(pe.nodeName(this,\"input\")&&\"checkbox\"===this.type&&this.click)return this.click(),!1},_default:function(e){return pe.nodeName(e.target,\"a\")}},beforeunload:{postDispatch:function(e){void 0!==e.result&&e.originalEvent&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n){var r=pe.extend(new pe.Event,n,{type:e,isSimulated:!0});pe.event.trigger(r,null,t),r.isDefaultPrevented()&&n.preventDefault()}},pe.removeEvent=re.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n)}:function(e,t,n){var r=\"on\"+t;e.detachEvent&&(\"undefined\"==typeof e[r]&&(e[r]=null),e.detachEvent(r,n))},pe.Event=function(e,t){return this instanceof pe.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||void 0===e.defaultPrevented&&e.returnValue===!1?v:x):this.type=e,t&&pe.extend(this,t),this.timeStamp=e&&e.timeStamp||pe.now(),void(this[pe.expando]=!0)):new pe.Event(e,t)},pe.Event.prototype={constructor:pe.Event,isDefaultPrevented:x,isPropagationStopped:x,isImmediatePropagationStopped:x,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=v,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=v,e&&!this.isSimulated&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){var e=this.originalEvent;this.isImmediatePropagationStopped=v,e&&e.stopImmediatePropagation&&e.stopImmediatePropagation(),this.stopPropagation()}},pe.each({mouseenter:\"mouseover\",mouseleave:\"mouseout\",pointerenter:\"pointerover\",pointerleave:\"pointerout\"},function(e,t){pe.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return i&&(i===r||pe.contains(r,i))||(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),fe.submit||(pe.event.special.submit={setup:function(){return!pe.nodeName(this,\"form\")&&void pe.event.add(this,\"click._submit keypress._submit\",function(e){var t=e.target,n=pe.nodeName(t,\"input\")||pe.nodeName(t,\"button\")?pe.prop(t,\"form\"):void 0;n&&!pe._data(n,\"submit\")&&(pe.event.add(n,\"submit._submit\",function(e){e._submitBubble=!0}),pe._data(n,\"submit\",!0))})},postDispatch:function(e){e._submitBubble&&(delete e._submitBubble,this.parentNode&&!e.isTrigger&&pe.event.simulate(\"submit\",this.parentNode,e))},teardown:function(){return!pe.nodeName(this,\"form\")&&void pe.event.remove(this,\"._submit\")}}),fe.change||(pe.event.special.change={setup:function(){return Ye.test(this.nodeName)?(\"checkbox\"!==this.type&&\"radio\"!==this.type||(pe.event.add(this,\"propertychange._change\",function(e){\"checked\"===e.originalEvent.propertyName&&(this._justChanged=!0)}),pe.event.add(this,\"click._change\",function(e){this._justChanged&&!e.isTrigger&&(this._justChanged=!1),pe.event.simulate(\"change\",this,e)})),!1):void pe.event.add(this,\"beforeactivate._change\",function(e){var t=e.target;Ye.test(t.nodeName)&&!pe._data(t,\"change\")&&(pe.event.add(t,\"change._change\",function(e){!this.parentNode||e.isSimulated||e.isTrigger||pe.event.simulate(\"change\",this.parentNode,e)}),pe._data(t,\"change\",!0))})},handle:function(e){var t=e.target;if(this!==t||e.isSimulated||e.isTrigger||\"radio\"!==t.type&&\"checkbox\"!==t.type)return e.handleObj.handler.apply(this,arguments)},teardown:function(){return pe.event.remove(this,\"._change\"),!Ye.test(this.nodeName)}}),fe.focusin||pe.each({focus:\"focusin\",blur:\"focusout\"},function(e,t){var n=function(e){pe.event.simulate(t,e.target,pe.event.fix(e))};pe.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=pe._data(r,t);i||r.addEventListener(e,n,!0),pe._data(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=pe._data(r,t)-1;i?pe._data(r,t,i):(r.removeEventListener(e,n,!0),pe._removeData(r,t))}}}),pe.fn.extend({on:function(e,t,n,r){return w(this,e,t,n,r)},one:function(e,t,n,r){return w(this,e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,pe(e.delegateTarget).off(r.namespace?r.origType+\".\"+r.namespace:r.origType,r.selector,r.handler),this;if(\"object\"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return t!==!1&&\"function\"!=typeof t||(n=t,t=void 0),n===!1&&(n=x),this.each(function(){pe.event.remove(this,e,n,t)})},trigger:function(e,t){return this.each(function(){pe.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return pe.event.trigger(e,t,n,!0)}});var Ze=/ jQuery\\d+=\"(?:null|\\d+)\"/g,et=new RegExp(\"<(?:\"+ze+\")[\\\\s/>]\",\"i\"),tt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\\w:-]+)[^>]*)\\/>/gi,nt=/<script|<style|<link/i,rt=/checked\\s*(?:[^=]|=\\s*.checked.)/i,it=/^true\\/(.*)/,ot=/^\\s*<!(?:\\[CDATA\\[|--)|(?:\\]\\]|--)>\\s*$/g,at=p(re),st=at.appendChild(re.createElement(\"div\"));pe.extend({htmlPrefilter:function(e){return e.replace(tt,\"<$1></$2>\")},clone:function(e,t,n){var r,i,o,a,s,u=pe.contains(e.ownerDocument,e);if(fe.html5Clone||pe.isXMLDoc(e)||!et.test(\"<\"+e.nodeName+\">\")?o=e.cloneNode(!0):(st.innerHTML=e.outerHTML,st.removeChild(o=st.firstChild)),!(fe.noCloneEvent&&fe.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||pe.isXMLDoc(e)))for(r=h(o),s=h(e),a=0;null!=(i=s[a]);++a)r[a]&&k(i,r[a]);if(t)if(n)for(s=s||h(e),r=r||h(o),a=0;null!=(i=s[a]);a++)N(i,r[a]);else N(e,o);return r=h(o,\"script\"),r.length>0&&g(r,!u&&h(e,\"script\")),r=s=i=null,o},cleanData:function(e,t){for(var n,r,i,o,a=0,s=pe.expando,u=pe.cache,l=fe.attributes,c=pe.event.special;null!=(n=e[a]);a++)if((t||He(n))&&(i=n[s],o=i&&u[i])){if(o.events)for(r in o.events)c[r]?pe.event.remove(n,r):pe.removeEvent(n,r,o.handle);u[i]&&(delete u[i],l||\"undefined\"==typeof n.removeAttribute?n[s]=void 0:n.removeAttribute(s),ne.push(i))}}}),pe.fn.extend({domManip:S,detach:function(e){return A(this,e,!0)},remove:function(e){return A(this,e)},text:function(e){return Pe(this,function(e){return void 0===e?pe.text(this):this.empty().append((this[0]&&this[0].ownerDocument||re).createTextNode(e))},null,e,arguments.length)},append:function(){return S(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=T(this,e);t.appendChild(e)}})},prepend:function(){return S(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=T(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return S(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return S(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++){for(1===e.nodeType&&pe.cleanData(h(e,!1));e.firstChild;)e.removeChild(e.firstChild);e.options&&pe.nodeName(e,\"select\")&&(e.options.length=0)}return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return pe.clone(this,e,t)})},html:function(e){return Pe(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e)return 1===t.nodeType?t.innerHTML.replace(Ze,\"\"):void 0;if(\"string\"==typeof e&&!nt.test(e)&&(fe.htmlSerialize||!et.test(e))&&(fe.leadingWhitespace||!$e.test(e))&&!Xe[(We.exec(e)||[\"\",\"\"])[1].toLowerCase()]){e=pe.htmlPrefilter(e);try{for(;n<r;n++)t=this[n]||{},1===t.nodeType&&(pe.cleanData(h(t,!1)),t.innerHTML=e);t=0}catch(i){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=[];return S(this,arguments,function(t){var n=this.parentNode;pe.inArray(this,e)<0&&(pe.cleanData(h(this)),\nn&&n.replaceChild(t,this))},e)}}),pe.each({appendTo:\"append\",prependTo:\"prepend\",insertBefore:\"before\",insertAfter:\"after\",replaceAll:\"replaceWith\"},function(e,t){pe.fn[e]=function(e){for(var n,r=0,i=[],o=pe(e),a=o.length-1;r<=a;r++)n=r===a?this:this.clone(!0),pe(o[r])[t](n),ae.apply(i,n.get());return this.pushStack(i)}});var ut,lt={HTML:\"block\",BODY:\"block\"},ct=/^margin/,ft=new RegExp(\"^(\"+Fe+\")(?!px)[a-z%]+$\",\"i\"),dt=function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i},pt=re.documentElement;!function(){function t(){var t,c,f=re.documentElement;f.appendChild(u),l.style.cssText=\"-webkit-box-sizing:border-box;box-sizing:border-box;position:relative;display:block;margin:auto;border:1px;padding:1px;top:1%;width:50%\",n=i=s=!1,r=a=!0,e.getComputedStyle&&(c=e.getComputedStyle(l),n=\"1%\"!==(c||{}).top,s=\"2px\"===(c||{}).marginLeft,i=\"4px\"===(c||{width:\"4px\"}).width,l.style.marginRight=\"50%\",r=\"4px\"===(c||{marginRight:\"4px\"}).marginRight,t=l.appendChild(re.createElement(\"div\")),t.style.cssText=l.style.cssText=\"-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0\",t.style.marginRight=t.style.width=\"0\",l.style.width=\"1px\",a=!parseFloat((e.getComputedStyle(t)||{}).marginRight),l.removeChild(t)),l.style.display=\"none\",o=0===l.getClientRects().length,o&&(l.style.display=\"\",l.innerHTML=\"<table><tr><td></td><td>t</td></tr></table>\",t=l.getElementsByTagName(\"td\"),t[0].style.cssText=\"margin:0;border:0;padding:0;display:none\",o=0===t[0].offsetHeight,o&&(t[0].style.display=\"\",t[1].style.display=\"none\",o=0===t[0].offsetHeight)),f.removeChild(u)}var n,r,i,o,a,s,u=re.createElement(\"div\"),l=re.createElement(\"div\");l.style&&(l.style.cssText=\"float:left;opacity:.5\",fe.opacity=\"0.5\"===l.style.opacity,fe.cssFloat=!!l.style.cssFloat,l.style.backgroundClip=\"content-box\",l.cloneNode(!0).style.backgroundClip=\"\",fe.clearCloneStyle=\"content-box\"===l.style.backgroundClip,u=re.createElement(\"div\"),u.style.cssText=\"border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute\",l.innerHTML=\"\",u.appendChild(l),fe.boxSizing=\"\"===l.style.boxSizing||\"\"===l.style.MozBoxSizing||\"\"===l.style.WebkitBoxSizing,pe.extend(fe,{reliableHiddenOffsets:function(){return null==n&&t(),o},boxSizingReliable:function(){return null==n&&t(),i},pixelMarginRight:function(){return null==n&&t(),r},pixelPosition:function(){return null==n&&t(),n},reliableMarginRight:function(){return null==n&&t(),a},reliableMarginLeft:function(){return null==n&&t(),s}}))}();var ht,gt,mt=/^(top|right|bottom|left)$/;e.getComputedStyle?(ht=function(t){var n=t.ownerDocument.defaultView;return n&&n.opener||(n=e),n.getComputedStyle(t)},gt=function(e,t,n){var r,i,o,a,s=e.style;return n=n||ht(e),a=n?n.getPropertyValue(t)||n[t]:void 0,\"\"!==a&&void 0!==a||pe.contains(e.ownerDocument,e)||(a=pe.style(e,t)),n&&!fe.pixelMarginRight()&&ft.test(a)&&ct.test(t)&&(r=s.width,i=s.minWidth,o=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=i,s.maxWidth=o),void 0===a?a:a+\"\"}):pt.currentStyle&&(ht=function(e){return e.currentStyle},gt=function(e,t,n){var r,i,o,a,s=e.style;return n=n||ht(e),a=n?n[t]:void 0,null==a&&s&&s[t]&&(a=s[t]),ft.test(a)&&!mt.test(t)&&(r=s.left,i=e.runtimeStyle,o=i&&i.left,o&&(i.left=e.currentStyle.left),s.left=\"fontSize\"===t?\"1em\":a,a=s.pixelLeft+\"px\",s.left=r,o&&(i.left=o)),void 0===a?a:a+\"\"||\"auto\"});var yt=/alpha\\([^)]*\\)/i,vt=/opacity\\s*=\\s*([^)]*)/i,xt=/^(none|table(?!-c[ea]).+)/,bt=new RegExp(\"^(\"+Fe+\")(.*)$\",\"i\"),wt={position:\"absolute\",visibility:\"hidden\",display:\"block\"},Tt={letterSpacing:\"0\",fontWeight:\"400\"},Ct=[\"Webkit\",\"O\",\"Moz\",\"ms\"],Et=re.createElement(\"div\").style;pe.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=gt(e,\"opacity\");return\"\"===n?\"1\":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{\"float\":fe.cssFloat?\"cssFloat\":\"styleFloat\"},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=pe.camelCase(t),u=e.style;if(t=pe.cssProps[s]||(pe.cssProps[s]=H(s)||s),a=pe.cssHooks[t]||pe.cssHooks[s],void 0===n)return a&&\"get\"in a&&void 0!==(i=a.get(e,!1,r))?i:u[t];if(o=typeof n,\"string\"===o&&(i=Me.exec(n))&&i[1]&&(n=d(e,t,i),o=\"number\"),null!=n&&n===n&&(\"number\"===o&&(n+=i&&i[3]||(pe.cssNumber[s]?\"\":\"px\")),fe.clearCloneStyle||\"\"!==n||0!==t.indexOf(\"background\")||(u[t]=\"inherit\"),!(a&&\"set\"in a&&void 0===(n=a.set(e,n,r)))))try{u[t]=n}catch(l){}}},css:function(e,t,n,r){var i,o,a,s=pe.camelCase(t);return t=pe.cssProps[s]||(pe.cssProps[s]=H(s)||s),a=pe.cssHooks[t]||pe.cssHooks[s],a&&\"get\"in a&&(o=a.get(e,!0,n)),void 0===o&&(o=gt(e,t,r)),\"normal\"===o&&t in Tt&&(o=Tt[t]),\"\"===n||n?(i=parseFloat(o),n===!0||isFinite(i)?i||0:o):o}}),pe.each([\"height\",\"width\"],function(e,t){pe.cssHooks[t]={get:function(e,n,r){if(n)return xt.test(pe.css(e,\"display\"))&&0===e.offsetWidth?dt(e,wt,function(){return M(e,t,r)}):M(e,t,r)},set:function(e,n,r){var i=r&&ht(e);return _(e,n,r?F(e,t,r,fe.boxSizing&&\"border-box\"===pe.css(e,\"boxSizing\",!1,i),i):0)}}}),fe.opacity||(pe.cssHooks.opacity={get:function(e,t){return vt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||\"\")?.01*parseFloat(RegExp.$1)+\"\":t?\"1\":\"\"},set:function(e,t){var n=e.style,r=e.currentStyle,i=pe.isNumeric(t)?\"alpha(opacity=\"+100*t+\")\":\"\",o=r&&r.filter||n.filter||\"\";n.zoom=1,(t>=1||\"\"===t)&&\"\"===pe.trim(o.replace(yt,\"\"))&&n.removeAttribute&&(n.removeAttribute(\"filter\"),\"\"===t||r&&!r.filter)||(n.filter=yt.test(o)?o.replace(yt,i):o+\" \"+i)}}),pe.cssHooks.marginRight=L(fe.reliableMarginRight,function(e,t){if(t)return dt(e,{display:\"inline-block\"},gt,[e,\"marginRight\"])}),pe.cssHooks.marginLeft=L(fe.reliableMarginLeft,function(e,t){if(t)return(parseFloat(gt(e,\"marginLeft\"))||(pe.contains(e.ownerDocument,e)?e.getBoundingClientRect().left-dt(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}):0))+\"px\"}),pe.each({margin:\"\",padding:\"\",border:\"Width\"},function(e,t){pe.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o=\"string\"==typeof n?n.split(\" \"):[n];r<4;r++)i[e+Oe[r]+t]=o[r]||o[r-2]||o[0];return i}},ct.test(e)||(pe.cssHooks[e+t].set=_)}),pe.fn.extend({css:function(e,t){return Pe(this,function(e,t,n){var r,i,o={},a=0;if(pe.isArray(t)){for(r=ht(e),i=t.length;a<i;a++)o[t[a]]=pe.css(e,t[a],!1,r);return o}return void 0!==n?pe.style(e,t,n):pe.css(e,t)},e,t,arguments.length>1)},show:function(){return q(this,!0)},hide:function(){return q(this)},toggle:function(e){return\"boolean\"==typeof e?e?this.show():this.hide():this.each(function(){Re(this)?pe(this).show():pe(this).hide()})}}),pe.Tween=O,O.prototype={constructor:O,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||pe.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(pe.cssNumber[n]?\"\":\"px\")},cur:function(){var e=O.propHooks[this.prop];return e&&e.get?e.get(this):O.propHooks._default.get(this)},run:function(e){var t,n=O.propHooks[this.prop];return this.options.duration?this.pos=t=pe.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):O.propHooks._default.set(this),this}},O.prototype.init.prototype=O.prototype,O.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=pe.css(e.elem,e.prop,\"\"),t&&\"auto\"!==t?t:0)},set:function(e){pe.fx.step[e.prop]?pe.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[pe.cssProps[e.prop]]&&!pe.cssHooks[e.prop]?e.elem[e.prop]=e.now:pe.style(e.elem,e.prop,e.now+e.unit)}}},O.propHooks.scrollTop=O.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},pe.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:\"swing\"},pe.fx=O.prototype.init,pe.fx.step={};var Nt,kt,St=/^(?:toggle|show|hide)$/,At=/queueHooks$/;pe.Animation=pe.extend($,{tweeners:{\"*\":[function(e,t){var n=this.createTween(e,t);return d(n.elem,e,Me.exec(t),n),n}]},tweener:function(e,t){pe.isFunction(e)?(t=e,e=[\"*\"]):e=e.match(De);for(var n,r=0,i=e.length;r<i;r++)n=e[r],$.tweeners[n]=$.tweeners[n]||[],$.tweeners[n].unshift(t)},prefilters:[W],prefilter:function(e,t){t?$.prefilters.unshift(e):$.prefilters.push(e)}}),pe.speed=function(e,t,n){var r=e&&\"object\"==typeof e?pe.extend({},e):{complete:n||!n&&t||pe.isFunction(e)&&e,duration:e,easing:n&&t||t&&!pe.isFunction(t)&&t};return r.duration=pe.fx.off?0:\"number\"==typeof r.duration?r.duration:r.duration in pe.fx.speeds?pe.fx.speeds[r.duration]:pe.fx.speeds._default,null!=r.queue&&r.queue!==!0||(r.queue=\"fx\"),r.old=r.complete,r.complete=function(){pe.isFunction(r.old)&&r.old.call(this),r.queue&&pe.dequeue(this,r.queue)},r},pe.fn.extend({fadeTo:function(e,t,n,r){return this.filter(Re).css(\"opacity\",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=pe.isEmptyObject(e),o=pe.speed(t,n,r),a=function(){var t=$(this,pe.extend({},e),o);(i||pe._data(this,\"finish\"))&&t.stop(!0)};return a.finish=a,i||o.queue===!1?this.each(a):this.queue(o.queue,a)},stop:function(e,t,n){var r=function(e){var t=e.stop;delete e.stop,t(n)};return\"string\"!=typeof e&&(n=t,t=e,e=void 0),t&&e!==!1&&this.queue(e||\"fx\",[]),this.each(function(){var t=!0,i=null!=e&&e+\"queueHooks\",o=pe.timers,a=pe._data(this);if(i)a[i]&&a[i].stop&&r(a[i]);else for(i in a)a[i]&&a[i].stop&&At.test(i)&&r(a[i]);for(i=o.length;i--;)o[i].elem!==this||null!=e&&o[i].queue!==e||(o[i].anim.stop(n),t=!1,o.splice(i,1));!t&&n||pe.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||\"fx\"),this.each(function(){var t,n=pe._data(this),r=n[e+\"queue\"],i=n[e+\"queueHooks\"],o=pe.timers,a=r?r.length:0;for(n.finish=!0,pe.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;t<a;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}}),pe.each([\"toggle\",\"show\",\"hide\"],function(e,t){var n=pe.fn[t];pe.fn[t]=function(e,r,i){return null==e||\"boolean\"==typeof e?n.apply(this,arguments):this.animate(P(t,!0),e,r,i)}}),pe.each({slideDown:P(\"show\"),slideUp:P(\"hide\"),slideToggle:P(\"toggle\"),fadeIn:{opacity:\"show\"},fadeOut:{opacity:\"hide\"},fadeToggle:{opacity:\"toggle\"}},function(e,t){pe.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),pe.timers=[],pe.fx.tick=function(){var e,t=pe.timers,n=0;for(Nt=pe.now();n<t.length;n++)e=t[n],e()||t[n]!==e||t.splice(n--,1);t.length||pe.fx.stop(),Nt=void 0},pe.fx.timer=function(e){pe.timers.push(e),e()?pe.fx.start():pe.timers.pop()},pe.fx.interval=13,pe.fx.start=function(){kt||(kt=e.setInterval(pe.fx.tick,pe.fx.interval))},pe.fx.stop=function(){e.clearInterval(kt),kt=null},pe.fx.speeds={slow:600,fast:200,_default:400},pe.fn.delay=function(t,n){return t=pe.fx?pe.fx.speeds[t]||t:t,n=n||\"fx\",this.queue(n,function(n,r){var i=e.setTimeout(n,t);r.stop=function(){e.clearTimeout(i)}})},function(){var e,t=re.createElement(\"input\"),n=re.createElement(\"div\"),r=re.createElement(\"select\"),i=r.appendChild(re.createElement(\"option\"));n=re.createElement(\"div\"),n.setAttribute(\"className\",\"t\"),n.innerHTML=\"  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>\",e=n.getElementsByTagName(\"a\")[0],t.setAttribute(\"type\",\"checkbox\"),n.appendChild(t),e=n.getElementsByTagName(\"a\")[0],e.style.cssText=\"top:1px\",fe.getSetAttribute=\"t\"!==n.className,fe.style=/top/.test(e.getAttribute(\"style\")),fe.hrefNormalized=\"/a\"===e.getAttribute(\"href\"),fe.checkOn=!!t.value,fe.optSelected=i.selected,fe.enctype=!!re.createElement(\"form\").enctype,r.disabled=!0,fe.optDisabled=!i.disabled,t=re.createElement(\"input\"),t.setAttribute(\"value\",\"\"),fe.input=\"\"===t.getAttribute(\"value\"),t.value=\"t\",t.setAttribute(\"type\",\"radio\"),fe.radioValue=\"t\"===t.value}();var Dt=/\\r/g,jt=/[\\x20\\t\\r\\n\\f]+/g;pe.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=pe.isFunction(e),this.each(function(n){var i;1===this.nodeType&&(i=r?e.call(this,n,pe(this).val()):e,null==i?i=\"\":\"number\"==typeof i?i+=\"\":pe.isArray(i)&&(i=pe.map(i,function(e){return null==e?\"\":e+\"\"})),t=pe.valHooks[this.type]||pe.valHooks[this.nodeName.toLowerCase()],t&&\"set\"in t&&void 0!==t.set(this,i,\"value\")||(this.value=i))});if(i)return t=pe.valHooks[i.type]||pe.valHooks[i.nodeName.toLowerCase()],t&&\"get\"in t&&void 0!==(n=t.get(i,\"value\"))?n:(n=i.value,\"string\"==typeof n?n.replace(Dt,\"\"):null==n?\"\":n)}}}),pe.extend({valHooks:{option:{get:function(e){var t=pe.find.attr(e,\"value\");return null!=t?t:pe.trim(pe.text(e)).replace(jt,\" \")}},select:{get:function(e){for(var t,n,r=e.options,i=e.selectedIndex,o=\"select-one\"===e.type||i<0,a=o?null:[],s=o?i+1:r.length,u=i<0?s:o?i:0;u<s;u++)if(n=r[u],(n.selected||u===i)&&(fe.optDisabled?!n.disabled:null===n.getAttribute(\"disabled\"))&&(!n.parentNode.disabled||!pe.nodeName(n.parentNode,\"optgroup\"))){if(t=pe(n).val(),o)return t;a.push(t)}return a},set:function(e,t){for(var n,r,i=e.options,o=pe.makeArray(t),a=i.length;a--;)if(r=i[a],pe.inArray(pe.valHooks.option.get(r),o)>-1)try{r.selected=n=!0}catch(s){r.scrollHeight}else r.selected=!1;return n||(e.selectedIndex=-1),i}}}}),pe.each([\"radio\",\"checkbox\"],function(){pe.valHooks[this]={set:function(e,t){if(pe.isArray(t))return e.checked=pe.inArray(pe(e).val(),t)>-1}},fe.checkOn||(pe.valHooks[this].get=function(e){return null===e.getAttribute(\"value\")?\"on\":e.value})});var Lt,Ht,qt=pe.expr.attrHandle,_t=/^(?:checked|selected)$/i,Ft=fe.getSetAttribute,Mt=fe.input;pe.fn.extend({attr:function(e,t){return Pe(this,pe.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){pe.removeAttr(this,e)})}}),pe.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return\"undefined\"==typeof e.getAttribute?pe.prop(e,t,n):(1===o&&pe.isXMLDoc(e)||(t=t.toLowerCase(),i=pe.attrHooks[t]||(pe.expr.match.bool.test(t)?Ht:Lt)),void 0!==n?null===n?void pe.removeAttr(e,t):i&&\"set\"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+\"\"),n):i&&\"get\"in i&&null!==(r=i.get(e,t))?r:(r=pe.find.attr(e,t),null==r?void 0:r))},attrHooks:{type:{set:function(e,t){if(!fe.radioValue&&\"radio\"===t&&pe.nodeName(e,\"input\")){var n=e.value;return e.setAttribute(\"type\",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(De);if(o&&1===e.nodeType)for(;n=o[i++];)r=pe.propFix[n]||n,pe.expr.match.bool.test(n)?Mt&&Ft||!_t.test(n)?e[r]=!1:e[pe.camelCase(\"default-\"+n)]=e[r]=!1:pe.attr(e,n,\"\"),e.removeAttribute(Ft?n:r)}}),Ht={set:function(e,t,n){return t===!1?pe.removeAttr(e,n):Mt&&Ft||!_t.test(n)?e.setAttribute(!Ft&&pe.propFix[n]||n,n):e[pe.camelCase(\"default-\"+n)]=e[n]=!0,n}},pe.each(pe.expr.match.bool.source.match(/\\w+/g),function(e,t){var n=qt[t]||pe.find.attr;Mt&&Ft||!_t.test(t)?qt[t]=function(e,t,r){var i,o;return r||(o=qt[t],qt[t]=i,i=null!=n(e,t,r)?t.toLowerCase():null,qt[t]=o),i}:qt[t]=function(e,t,n){if(!n)return e[pe.camelCase(\"default-\"+t)]?t.toLowerCase():null}}),Mt&&Ft||(pe.attrHooks.value={set:function(e,t,n){return pe.nodeName(e,\"input\")?void(e.defaultValue=t):Lt&&Lt.set(e,t,n)}}),Ft||(Lt={set:function(e,t,n){var r=e.getAttributeNode(n);if(r||e.setAttributeNode(r=e.ownerDocument.createAttribute(n)),r.value=t+=\"\",\"value\"===n||t===e.getAttribute(n))return t}},qt.id=qt.name=qt.coords=function(e,t,n){var r;if(!n)return(r=e.getAttributeNode(t))&&\"\"!==r.value?r.value:null},pe.valHooks.button={get:function(e,t){var n=e.getAttributeNode(t);if(n&&n.specified)return n.value},set:Lt.set},pe.attrHooks.contenteditable={set:function(e,t,n){Lt.set(e,\"\"!==t&&t,n)}},pe.each([\"width\",\"height\"],function(e,t){pe.attrHooks[t]={set:function(e,n){if(\"\"===n)return e.setAttribute(t,\"auto\"),n}}})),fe.style||(pe.attrHooks.style={get:function(e){return e.style.cssText||void 0},set:function(e,t){return e.style.cssText=t+\"\"}});var Ot=/^(?:input|select|textarea|button|object)$/i,Rt=/^(?:a|area)$/i;pe.fn.extend({prop:function(e,t){return Pe(this,pe.prop,e,t,arguments.length>1)},removeProp:function(e){return e=pe.propFix[e]||e,this.each(function(){try{this[e]=void 0,delete this[e]}catch(t){}})}}),pe.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&pe.isXMLDoc(e)||(t=pe.propFix[t]||t,i=pe.propHooks[t]),void 0!==n?i&&\"set\"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&\"get\"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=pe.find.attr(e,\"tabindex\");return t?parseInt(t,10):Ot.test(e.nodeName)||Rt.test(e.nodeName)&&e.href?0:-1}}},propFix:{\"for\":\"htmlFor\",\"class\":\"className\"}}),fe.hrefNormalized||pe.each([\"href\",\"src\"],function(e,t){pe.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),fe.optSelected||(pe.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),pe.each([\"tabIndex\",\"readOnly\",\"maxLength\",\"cellSpacing\",\"cellPadding\",\"rowSpan\",\"colSpan\",\"useMap\",\"frameBorder\",\"contentEditable\"],function(){pe.propFix[this.toLowerCase()]=this}),fe.enctype||(pe.propFix.enctype=\"encoding\");var Pt=/[\\t\\r\\n\\f]/g;pe.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(pe.isFunction(e))return this.each(function(t){pe(this).addClass(e.call(this,t,z(this)))});if(\"string\"==typeof e&&e)for(t=e.match(De)||[];n=this[u++];)if(i=z(n),r=1===n.nodeType&&(\" \"+i+\" \").replace(Pt,\" \")){for(a=0;o=t[a++];)r.indexOf(\" \"+o+\" \")<0&&(r+=o+\" \");s=pe.trim(r),i!==s&&pe.attr(n,\"class\",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(pe.isFunction(e))return this.each(function(t){pe(this).removeClass(e.call(this,t,z(this)))});if(!arguments.length)return this.attr(\"class\",\"\");if(\"string\"==typeof e&&e)for(t=e.match(De)||[];n=this[u++];)if(i=z(n),r=1===n.nodeType&&(\" \"+i+\" \").replace(Pt,\" \")){for(a=0;o=t[a++];)for(;r.indexOf(\" \"+o+\" \")>-1;)r=r.replace(\" \"+o+\" \",\" \");s=pe.trim(r),i!==s&&pe.attr(n,\"class\",s)}return this},toggleClass:function(e,t){var n=typeof e;return\"boolean\"==typeof t&&\"string\"===n?t?this.addClass(e):this.removeClass(e):pe.isFunction(e)?this.each(function(n){pe(this).toggleClass(e.call(this,n,z(this),t),t)}):this.each(function(){var t,r,i,o;if(\"string\"===n)for(r=0,i=pe(this),o=e.match(De)||[];t=o[r++];)i.hasClass(t)?i.removeClass(t):i.addClass(t);else void 0!==e&&\"boolean\"!==n||(t=z(this),t&&pe._data(this,\"__className__\",t),pe.attr(this,\"class\",t||e===!1?\"\":pe._data(this,\"__className__\")||\"\"))})},hasClass:function(e){var t,n,r=0;for(t=\" \"+e+\" \";n=this[r++];)if(1===n.nodeType&&(\" \"+z(n)+\" \").replace(Pt,\" \").indexOf(t)>-1)return!0;return!1}}),pe.each(\"blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu\".split(\" \"),function(e,t){pe.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),pe.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}});var Bt=e.location,Wt=pe.now(),It=/\\?/,$t=/(,)|(\\[|{)|(}|])|\"(?:[^\"\\\\\\r\\n]|\\\\[\"\\\\\\/bfnrt]|\\\\u[\\da-fA-F]{4})*\"\\s*:?|true|false|null|-?(?!0\\d)\\d+(?:\\.\\d+|)(?:[eE][+-]?\\d+|)/g;pe.parseJSON=function(t){if(e.JSON&&e.JSON.parse)return e.JSON.parse(t+\"\");var n,r=null,i=pe.trim(t+\"\");return i&&!pe.trim(i.replace($t,function(e,t,i,o){return n&&t&&(r=0),0===r?e:(n=i||t,r+=!o-!i,\"\")}))?Function(\"return \"+i)():pe.error(\"Invalid JSON: \"+t)},pe.parseXML=function(t){var n,r;if(!t||\"string\"!=typeof t)return null;try{e.DOMParser?(r=new e.DOMParser,n=r.parseFromString(t,\"text/xml\")):(n=new e.ActiveXObject(\"Microsoft.XMLDOM\"),n.async=\"false\",n.loadXML(t))}catch(i){n=void 0}return n&&n.documentElement&&!n.getElementsByTagName(\"parsererror\").length||pe.error(\"Invalid XML: \"+t),n};var zt=/#.*$/,Xt=/([?&])_=[^&]*/,Ut=/^(.*?):[ \\t]*([^\\r\\n]*)\\r?$/gm,Vt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Yt=/^(?:GET|HEAD)$/,Jt=/^\\/\\//,Gt=/^([\\w.+-]+:)(?:\\/\\/(?:[^\\/?#]*@|)([^\\/?#:]*)(?::(\\d+)|)|)/,Kt={},Qt={},Zt=\"*/\".concat(\"*\"),en=Bt.href,tn=Gt.exec(en.toLowerCase())||[];pe.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:en,type:\"GET\",isLocal:Vt.test(tn[1]),global:!0,processData:!0,async:!0,contentType:\"application/x-www-form-urlencoded; charset=UTF-8\",accepts:{\"*\":Zt,text:\"text/plain\",html:\"text/html\",xml:\"application/xml, text/xml\",json:\"application/json, text/javascript\"},contents:{xml:/\\bxml\\b/,html:/\\bhtml/,json:/\\bjson\\b/},responseFields:{xml:\"responseXML\",text:\"responseText\",json:\"responseJSON\"},converters:{\"* text\":String,\"text html\":!0,\"text json\":pe.parseJSON,\"text xml\":pe.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?V(V(e,pe.ajaxSettings),t):V(pe.ajaxSettings,e)},ajaxPrefilter:X(Kt),ajaxTransport:X(Qt),ajax:function(t,n){function r(t,n,r,i){var o,f,v,x,w,C=n;2!==b&&(b=2,u&&e.clearTimeout(u),c=void 0,s=i||\"\",T.readyState=t>0?4:0,o=t>=200&&t<300||304===t,r&&(x=Y(d,T,r)),x=J(d,x,T,o),o?(d.ifModified&&(w=T.getResponseHeader(\"Last-Modified\"),w&&(pe.lastModified[a]=w),w=T.getResponseHeader(\"etag\"),w&&(pe.etag[a]=w)),204===t||\"HEAD\"===d.type?C=\"nocontent\":304===t?C=\"notmodified\":(C=x.state,f=x.data,v=x.error,o=!v)):(v=C,!t&&C||(C=\"error\",t<0&&(t=0))),T.status=t,T.statusText=(n||C)+\"\",o?g.resolveWith(p,[f,C,T]):g.rejectWith(p,[T,C,v]),T.statusCode(y),y=void 0,l&&h.trigger(o?\"ajaxSuccess\":\"ajaxError\",[T,d,o?f:v]),m.fireWith(p,[T,C]),l&&(h.trigger(\"ajaxComplete\",[T,d]),--pe.active||pe.event.trigger(\"ajaxStop\")))}\"object\"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,a,s,u,l,c,f,d=pe.ajaxSetup({},n),p=d.context||d,h=d.context&&(p.nodeType||p.jquery)?pe(p):pe.event,g=pe.Deferred(),m=pe.Callbacks(\"once memory\"),y=d.statusCode||{},v={},x={},b=0,w=\"canceled\",T={readyState:0,getResponseHeader:function(e){var t;if(2===b){if(!f)for(f={};t=Ut.exec(s);)f[t[1].toLowerCase()]=t[2];t=f[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===b?s:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return b||(e=x[n]=x[n]||e,v[e]=t),this},overrideMimeType:function(e){return b||(d.mimeType=e),this},statusCode:function(e){var t;if(e)if(b<2)for(t in e)y[t]=[y[t],e[t]];else T.always(e[T.status]);return this},abort:function(e){var t=e||w;return c&&c.abort(t),r(0,t),this}};if(g.promise(T).complete=m.add,T.success=T.done,T.error=T.fail,d.url=((t||d.url||en)+\"\").replace(zt,\"\").replace(Jt,tn[1]+\"//\"),d.type=n.method||n.type||d.method||d.type,d.dataTypes=pe.trim(d.dataType||\"*\").toLowerCase().match(De)||[\"\"],null==d.crossDomain&&(i=Gt.exec(d.url.toLowerCase()),d.crossDomain=!(!i||i[1]===tn[1]&&i[2]===tn[2]&&(i[3]||(\"http:\"===i[1]?\"80\":\"443\"))===(tn[3]||(\"http:\"===tn[1]?\"80\":\"443\")))),d.data&&d.processData&&\"string\"!=typeof d.data&&(d.data=pe.param(d.data,d.traditional)),U(Kt,d,n,T),2===b)return T;l=pe.event&&d.global,l&&0===pe.active++&&pe.event.trigger(\"ajaxStart\"),d.type=d.type.toUpperCase(),d.hasContent=!Yt.test(d.type),a=d.url,d.hasContent||(d.data&&(a=d.url+=(It.test(a)?\"&\":\"?\")+d.data,delete d.data),d.cache===!1&&(d.url=Xt.test(a)?a.replace(Xt,\"$1_=\"+Wt++):a+(It.test(a)?\"&\":\"?\")+\"_=\"+Wt++)),d.ifModified&&(pe.lastModified[a]&&T.setRequestHeader(\"If-Modified-Since\",pe.lastModified[a]),pe.etag[a]&&T.setRequestHeader(\"If-None-Match\",pe.etag[a])),(d.data&&d.hasContent&&d.contentType!==!1||n.contentType)&&T.setRequestHeader(\"Content-Type\",d.contentType),T.setRequestHeader(\"Accept\",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(\"*\"!==d.dataTypes[0]?\", \"+Zt+\"; q=0.01\":\"\"):d.accepts[\"*\"]);for(o in d.headers)T.setRequestHeader(o,d.headers[o]);if(d.beforeSend&&(d.beforeSend.call(p,T,d)===!1||2===b))return T.abort();w=\"abort\";for(o in{success:1,error:1,complete:1})T[o](d[o]);if(c=U(Qt,d,n,T)){if(T.readyState=1,l&&h.trigger(\"ajaxSend\",[T,d]),2===b)return T;d.async&&d.timeout>0&&(u=e.setTimeout(function(){T.abort(\"timeout\")},d.timeout));try{b=1,c.send(v,r)}catch(C){if(!(b<2))throw C;r(-1,C)}}else r(-1,\"No Transport\");return T},getJSON:function(e,t,n){return pe.get(e,t,n,\"json\")},getScript:function(e,t){return pe.get(e,void 0,t,\"script\")}}),pe.each([\"get\",\"post\"],function(e,t){pe[t]=function(e,n,r,i){return pe.isFunction(n)&&(i=i||r,r=n,n=void 0),pe.ajax(pe.extend({url:e,type:t,dataType:i,data:n,success:r},pe.isPlainObject(e)&&e))}}),pe._evalUrl=function(e){return pe.ajax({url:e,type:\"GET\",dataType:\"script\",cache:!0,async:!1,global:!1,\"throws\":!0})},pe.fn.extend({wrapAll:function(e){if(pe.isFunction(e))return this.each(function(t){pe(this).wrapAll(e.call(this,t))});if(this[0]){var t=pe(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){for(var e=this;e.firstChild&&1===e.firstChild.nodeType;)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return pe.isFunction(e)?this.each(function(t){pe(this).wrapInner(e.call(this,t))}):this.each(function(){var t=pe(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=pe.isFunction(e);return this.each(function(n){pe(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){pe.nodeName(this,\"body\")||pe(this).replaceWith(this.childNodes)}).end()}}),pe.expr.filters.hidden=function(e){return fe.reliableHiddenOffsets()?e.offsetWidth<=0&&e.offsetHeight<=0&&!e.getClientRects().length:K(e)},pe.expr.filters.visible=function(e){return!pe.expr.filters.hidden(e)};var nn=/%20/g,rn=/\\[\\]$/,on=/\\r?\\n/g,an=/^(?:submit|button|image|reset|file)$/i,sn=/^(?:input|select|textarea|keygen)/i;pe.param=function(e,t){var n,r=[],i=function(e,t){t=pe.isFunction(t)?t():null==t?\"\":t,r[r.length]=encodeURIComponent(e)+\"=\"+encodeURIComponent(t)};if(void 0===t&&(t=pe.ajaxSettings&&pe.ajaxSettings.traditional),pe.isArray(e)||e.jquery&&!pe.isPlainObject(e))pe.each(e,function(){i(this.name,this.value)});else for(n in e)Q(n,e[n],t,i);return r.join(\"&\").replace(nn,\"+\")},pe.fn.extend({serialize:function(){return pe.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=pe.prop(this,\"elements\");return e?pe.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!pe(this).is(\":disabled\")&&sn.test(this.nodeName)&&!an.test(e)&&(this.checked||!Be.test(e))}).map(function(e,t){var n=pe(this).val();return null==n?null:pe.isArray(n)?pe.map(n,function(e){return{name:t.name,value:e.replace(on,\"\\r\\n\")}}):{name:t.name,value:n.replace(on,\"\\r\\n\")}}).get()}}),pe.ajaxSettings.xhr=void 0!==e.ActiveXObject?function(){return this.isLocal?ee():re.documentMode>8?Z():/^(get|post|head|put|delete|options)$/i.test(this.type)&&Z()||ee()}:Z;var un=0,ln={},cn=pe.ajaxSettings.xhr();e.attachEvent&&e.attachEvent(\"onunload\",function(){for(var e in ln)ln[e](void 0,!0)}),fe.cors=!!cn&&\"withCredentials\"in cn,cn=fe.ajax=!!cn,cn&&pe.ajaxTransport(function(t){if(!t.crossDomain||fe.cors){var n;return{send:function(r,i){var o,a=t.xhr(),s=++un;if(a.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(o in t.xhrFields)a[o]=t.xhrFields[o];t.mimeType&&a.overrideMimeType&&a.overrideMimeType(t.mimeType),t.crossDomain||r[\"X-Requested-With\"]||(r[\"X-Requested-With\"]=\"XMLHttpRequest\");for(o in r)void 0!==r[o]&&a.setRequestHeader(o,r[o]+\"\");a.send(t.hasContent&&t.data||null),n=function(e,r){var o,u,l;if(n&&(r||4===a.readyState))if(delete ln[s],n=void 0,a.onreadystatechange=pe.noop,r)4!==a.readyState&&a.abort();else{l={},o=a.status,\"string\"==typeof a.responseText&&(l.text=a.responseText);try{u=a.statusText}catch(c){u=\"\"}o||!t.isLocal||t.crossDomain?1223===o&&(o=204):o=l.text?200:404}l&&i(o,u,l,a.getAllResponseHeaders())},t.async?4===a.readyState?e.setTimeout(n):a.onreadystatechange=ln[s]=n:n()},abort:function(){n&&n(void 0,!0)}}}}),pe.ajaxSetup({accepts:{script:\"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript\"},contents:{script:/\\b(?:java|ecma)script\\b/},converters:{\"text script\":function(e){return pe.globalEval(e),e}}}),pe.ajaxPrefilter(\"script\",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type=\"GET\",e.global=!1)}),pe.ajaxTransport(\"script\",function(e){if(e.crossDomain){var t,n=re.head||pe(\"head\")[0]||re.documentElement;return{send:function(r,i){t=re.createElement(\"script\"),t.async=!0,e.scriptCharset&&(t.charset=e.scriptCharset),t.src=e.url,t.onload=t.onreadystatechange=function(e,n){(n||!t.readyState||/loaded|complete/.test(t.readyState))&&(t.onload=t.onreadystatechange=null,t.parentNode&&t.parentNode.removeChild(t),t=null,n||i(200,\"success\"))},n.insertBefore(t,n.firstChild)},abort:function(){t&&t.onload(void 0,!0)}}}});var fn=[],dn=/(=)\\?(?=&|$)|\\?\\?/;pe.ajaxSetup({jsonp:\"callback\",jsonpCallback:function(){var e=fn.pop()||pe.expando+\"_\"+Wt++;return this[e]=!0,e}}),pe.ajaxPrefilter(\"json jsonp\",function(t,n,r){var i,o,a,s=t.jsonp!==!1&&(dn.test(t.url)?\"url\":\"string\"==typeof t.data&&0===(t.contentType||\"\").indexOf(\"application/x-www-form-urlencoded\")&&dn.test(t.data)&&\"data\");if(s||\"jsonp\"===t.dataTypes[0])return i=t.jsonpCallback=pe.isFunction(t.jsonpCallback)?t.jsonpCallback():t.jsonpCallback,s?t[s]=t[s].replace(dn,\"$1\"+i):t.jsonp!==!1&&(t.url+=(It.test(t.url)?\"&\":\"?\")+t.jsonp+\"=\"+i),t.converters[\"script json\"]=function(){return a||pe.error(i+\" was not called\"),a[0]},t.dataTypes[0]=\"json\",o=e[i],e[i]=function(){a=arguments},r.always(function(){void 0===o?pe(e).removeProp(i):e[i]=o,t[i]&&(t.jsonpCallback=n.jsonpCallback,fn.push(i)),a&&pe.isFunction(o)&&o(a[0]),a=o=void 0}),\"script\"}),pe.parseHTML=function(e,t,n){if(!e||\"string\"!=typeof e)return null;\"boolean\"==typeof t&&(n=t,t=!1),t=t||re;var r=Te.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=y([e],t,i),i&&i.length&&pe(i).remove(),pe.merge([],r.childNodes))};var pn=pe.fn.load;return pe.fn.load=function(e,t,n){if(\"string\"!=typeof e&&pn)return pn.apply(this,arguments);var r,i,o,a=this,s=e.indexOf(\" \");return s>-1&&(r=pe.trim(e.slice(s,e.length)),e=e.slice(0,s)),pe.isFunction(t)?(n=t,t=void 0):t&&\"object\"==typeof t&&(i=\"POST\"),a.length>0&&pe.ajax({url:e,type:i||\"GET\",dataType:\"html\",data:t}).done(function(e){o=arguments,a.html(r?pe(\"<div>\").append(pe.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},pe.each([\"ajaxStart\",\"ajaxStop\",\"ajaxComplete\",\"ajaxError\",\"ajaxSuccess\",\"ajaxSend\"],function(e,t){pe.fn[t]=function(e){return this.on(t,e)}}),pe.expr.filters.animated=function(e){return pe.grep(pe.timers,function(t){return e===t.elem}).length},pe.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l,c=pe.css(e,\"position\"),f=pe(e),d={};\"static\"===c&&(e.style.position=\"relative\"),s=f.offset(),o=pe.css(e,\"top\"),u=pe.css(e,\"left\"),l=(\"absolute\"===c||\"fixed\"===c)&&pe.inArray(\"auto\",[o,u])>-1,l?(r=f.position(),a=r.top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),pe.isFunction(t)&&(t=t.call(e,n,pe.extend({},s))),null!=t.top&&(d.top=t.top-s.top+a),null!=t.left&&(d.left=t.left-s.left+i),\"using\"in t?t.using.call(e,d):f.css(d)}},pe.fn.extend({offset:function(e){if(arguments.length)return void 0===e?this:this.each(function(t){pe.offset.setOffset(this,e,t)});var t,n,r={top:0,left:0},i=this[0],o=i&&i.ownerDocument;if(o)return t=o.documentElement,pe.contains(t,i)?(\"undefined\"!=typeof i.getBoundingClientRect&&(r=i.getBoundingClientRect()),n=te(o),{top:r.top+(n.pageYOffset||t.scrollTop)-(t.clientTop||0),left:r.left+(n.pageXOffset||t.scrollLeft)-(t.clientLeft||0)}):r},position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return\"fixed\"===pe.css(r,\"position\")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),pe.nodeName(e[0],\"html\")||(n=e.offset()),n.top+=pe.css(e[0],\"borderTopWidth\",!0),n.left+=pe.css(e[0],\"borderLeftWidth\",!0)),{top:t.top-n.top-pe.css(r,\"marginTop\",!0),left:t.left-n.left-pe.css(r,\"marginLeft\",!0)}}},offsetParent:function(){return this.map(function(){\nfor(var e=this.offsetParent;e&&!pe.nodeName(e,\"html\")&&\"static\"===pe.css(e,\"position\");)e=e.offsetParent;return e||pt})}}),pe.each({scrollLeft:\"pageXOffset\",scrollTop:\"pageYOffset\"},function(e,t){var n=/Y/.test(t);pe.fn[e]=function(r){return Pe(this,function(e,r,i){var o=te(e);return void 0===i?o?t in o?o[t]:o.document.documentElement[r]:e[r]:void(o?o.scrollTo(n?pe(o).scrollLeft():i,n?i:pe(o).scrollTop()):e[r]=i)},e,r,arguments.length,null)}}),pe.each([\"top\",\"left\"],function(e,t){pe.cssHooks[t]=L(fe.pixelPosition,function(e,n){if(n)return n=gt(e,t),ft.test(n)?pe(e).position()[t]+\"px\":n})}),pe.each({Height:\"height\",Width:\"width\"},function(e,t){pe.each({padding:\"inner\"+e,content:t,\"\":\"outer\"+e},function(n,r){pe.fn[r]=function(r,i){var o=arguments.length&&(n||\"boolean\"!=typeof r),a=n||(r===!0||i===!0?\"margin\":\"border\");return Pe(this,function(t,n,r){var i;return pe.isWindow(t)?t.document.documentElement[\"client\"+e]:9===t.nodeType?(i=t.documentElement,Math.max(t.body[\"scroll\"+e],i[\"scroll\"+e],t.body[\"offset\"+e],i[\"offset\"+e],i[\"client\"+e])):void 0===r?pe.css(t,n,a):pe.style(t,n,r,a)},t,o?r:void 0,o,null)}})}),pe.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,\"**\"):this.off(t,e||\"**\",n)}}),pe.fn.size=function(){return this.length},pe.fn.andSelf=pe.fn.addBack,layui.define(function(e){layui.$=pe,e(\"jquery\",pe)}),pe});!function(e,t){\"use strict\";var i,n,a=e.layui&&layui.define,o={getPath:function(){var e=document.currentScript?document.currentScript.src:function(){for(var e,t=document.scripts,i=t.length-1,n=i;n>0;n--)if(\"interactive\"===t[n].readyState){e=t[n].src;break}return e||t[i].src}();return e.substring(0,e.lastIndexOf(\"/\")+1)}(),config:{},end:{},minIndex:0,minLeft:[],btn:[\"&#x786E;&#x5B9A;\",\"&#x53D6;&#x6D88;\"],type:[\"dialog\",\"page\",\"iframe\",\"loading\",\"tips\"],getStyle:function(t,i){var n=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return n[n.getPropertyValue?\"getPropertyValue\":\"getAttribute\"](i)},link:function(t,i,n){if(r.path){var a=document.getElementsByTagName(\"head\")[0],s=document.createElement(\"link\");\"string\"==typeof i&&(n=i);var l=(n||t).replace(/\\.|\\//g,\"\"),f=\"layuicss-\"+l,c=0;s.rel=\"stylesheet\",s.href=r.path+t,s.id=f,document.getElementById(f)||a.appendChild(s),\"function\"==typeof i&&!function u(){return++c>80?e.console&&console.error(\"layer.css: Invalid\"):void(1989===parseInt(o.getStyle(document.getElementById(f),\"width\"))?i():setTimeout(u,100))}()}}},r={v:\"3.1.1\",ie:function(){var t=navigator.userAgent.toLowerCase();return!!(e.ActiveXObject||\"ActiveXObject\"in e)&&((t.match(/msie\\s(\\d+)/)||[])[1]||\"11\")}(),index:e.layer&&e.layer.v?1e5:0,path:o.getPath,config:function(e,t){return e=e||{},r.cache=o.config=i.extend({},o.config,e),r.path=o.config.path||r.path,\"string\"==typeof e.extend&&(e.extend=[e.extend]),o.config.path&&r.ready(),e.extend?(a?layui.addcss(\"modules/layer/\"+e.extend):o.link(\"theme/\"+e.extend),this):this},ready:function(e){var t=\"layer\",i=\"\",n=(a?\"modules/layer/\":\"theme/\")+\"default/layer.css?v=\"+r.v+i;return a?layui.addcss(n,e,t):o.link(n,e,t),this},alert:function(e,t,n){var a=\"function\"==typeof t;return a&&(n=t),r.open(i.extend({content:e,yes:n},a?{}:t))},confirm:function(e,t,n,a){var s=\"function\"==typeof t;return s&&(a=n,n=t),r.open(i.extend({content:e,btn:o.btn,yes:n,btn2:a},s?{}:t))},msg:function(e,n,a){var s=\"function\"==typeof n,f=o.config.skin,c=(f?f+\" \"+f+\"-msg\":\"\")||\"layui-layer-msg\",u=l.anim.length-1;return s&&(a=n),r.open(i.extend({content:e,time:3e3,shade:!1,skin:c,title:!1,closeBtn:!1,btn:!1,resize:!1,end:a},s&&!o.config.skin?{skin:c+\" layui-layer-hui\",anim:u}:function(){return n=n||{},(n.icon===-1||n.icon===t&&!o.config.skin)&&(n.skin=c+\" \"+(n.skin||\"layui-layer-hui\")),n}()))},load:function(e,t){return r.open(i.extend({type:3,icon:e||0,resize:!1,shade:.01},t))},tips:function(e,t,n){return r.open(i.extend({type:4,content:[e,t],closeBtn:!1,time:3e3,shade:!1,resize:!1,fixed:!1,maxWidth:210},n))}},s=function(e){var t=this;t.index=++r.index,t.config=i.extend({},t.config,o.config,e),document.body?t.creat():setTimeout(function(){t.creat()},30)};s.pt=s.prototype;var l=[\"layui-layer\",\".layui-layer-title\",\".layui-layer-main\",\".layui-layer-dialog\",\"layui-layer-iframe\",\"layui-layer-content\",\"layui-layer-btn\",\"layui-layer-close\"];l.anim=[\"layer-anim-00\",\"layer-anim-01\",\"layer-anim-02\",\"layer-anim-03\",\"layer-anim-04\",\"layer-anim-05\",\"layer-anim-06\"],s.pt.config={type:0,shade:.3,fixed:!0,move:l[1],title:\"&#x4FE1;&#x606F;\",offset:\"auto\",area:\"auto\",closeBtn:1,time:0,zIndex:19891014,maxWidth:360,anim:0,isOutAnim:!0,icon:-1,moveType:1,resize:!0,scrollbar:!0,tips:2},s.pt.vessel=function(e,t){var n=this,a=n.index,r=n.config,s=r.zIndex+a,f=\"object\"==typeof r.title,c=r.maxmin&&(1===r.type||2===r.type),u=r.title?'<div class=\"layui-layer-title\" style=\"'+(f?r.title[1]:\"\")+'\">'+(f?r.title[0]:r.title)+\"</div>\":\"\";return r.zIndex=s,t([r.shade?'<div class=\"layui-layer-shade\" id=\"layui-layer-shade'+a+'\" times=\"'+a+'\" style=\"'+(\"z-index:\"+(s-1)+\"; \")+'\"></div>':\"\",'<div class=\"'+l[0]+(\" layui-layer-\"+o.type[r.type])+(0!=r.type&&2!=r.type||r.shade?\"\":\" layui-layer-border\")+\" \"+(r.skin||\"\")+'\" id=\"'+l[0]+a+'\" type=\"'+o.type[r.type]+'\" times=\"'+a+'\" showtime=\"'+r.time+'\" conType=\"'+(e?\"object\":\"string\")+'\" style=\"z-index: '+s+\"; width:\"+r.area[0]+\";height:\"+r.area[1]+(r.fixed?\"\":\";position:absolute;\")+'\">'+(e&&2!=r.type?\"\":u)+'<div id=\"'+(r.id||\"\")+'\" class=\"layui-layer-content'+(0==r.type&&r.icon!==-1?\" layui-layer-padding\":\"\")+(3==r.type?\" layui-layer-loading\"+r.icon:\"\")+'\">'+(0==r.type&&r.icon!==-1?'<i class=\"layui-layer-ico layui-layer-ico'+r.icon+'\"></i>':\"\")+(1==r.type&&e?\"\":r.content||\"\")+'</div><span class=\"layui-layer-setwin\">'+function(){var e=c?'<a class=\"layui-layer-min\" href=\"javascript:;\"><cite></cite></a><a class=\"layui-layer-ico layui-layer-max\" href=\"javascript:;\"></a>':\"\";return r.closeBtn&&(e+='<a class=\"layui-layer-ico '+l[7]+\" \"+l[7]+(r.title?r.closeBtn:4==r.type?\"1\":\"2\")+'\" href=\"javascript:;\"></a>'),e}()+\"</span>\"+(r.btn?function(){var e=\"\";\"string\"==typeof r.btn&&(r.btn=[r.btn]);for(var t=0,i=r.btn.length;t<i;t++)e+='<a class=\"'+l[6]+t+'\">'+r.btn[t]+\"</a>\";return'<div class=\"'+l[6]+\" layui-layer-btn-\"+(r.btnAlign||\"\")+'\">'+e+\"</div>\"}():\"\")+(r.resize?'<span class=\"layui-layer-resize\"></span>':\"\")+\"</div>\"],u,i('<div class=\"layui-layer-move\"></div>')),n},s.pt.creat=function(){var e=this,t=e.config,a=e.index,s=t.content,f=\"object\"==typeof s,c=i(\"body\");if(!t.id||!i(\"#\"+t.id)[0]){switch(\"string\"==typeof t.area&&(t.area=\"auto\"===t.area?[\"\",\"\"]:[t.area,\"\"]),t.shift&&(t.anim=t.shift),6==r.ie&&(t.fixed=!1),t.type){case 0:t.btn=\"btn\"in t?t.btn:o.btn[0],r.closeAll(\"dialog\");break;case 2:var s=t.content=f?t.content:[t.content||\"\",\"auto\"];t.content='<iframe scrolling=\"'+(t.content[1]||\"auto\")+'\" allowtransparency=\"true\" id=\"'+l[4]+a+'\" name=\"'+l[4]+a+'\" onload=\"this.className=\\'\\';\" class=\"layui-layer-load\" frameborder=\"0\" src=\"'+t.content[0]+'\"></iframe>';break;case 3:delete t.title,delete t.closeBtn,t.icon===-1&&0===t.icon,r.closeAll(\"loading\");break;case 4:f||(t.content=[t.content,\"body\"]),t.follow=t.content[1],t.content=t.content[0]+'<i class=\"layui-layer-TipsG\"></i>',delete t.title,t.tips=\"object\"==typeof t.tips?t.tips:[t.tips,!0],t.tipsMore||r.closeAll(\"tips\")}if(e.vessel(f,function(n,r,u){c.append(n[0]),f?function(){2==t.type||4==t.type?function(){i(\"body\").append(n[1])}():function(){s.parents(\".\"+l[0])[0]||(s.data(\"display\",s.css(\"display\")).show().addClass(\"layui-layer-wrap\").wrap(n[1]),i(\"#\"+l[0]+a).find(\".\"+l[5]).before(r))}()}():c.append(n[1]),i(\".layui-layer-move\")[0]||c.append(o.moveElem=u),e.layero=i(\"#\"+l[0]+a),t.scrollbar||l.html.css(\"overflow\",\"hidden\").attr(\"layer-full\",a)}).auto(a),i(\"#layui-layer-shade\"+e.index).css({\"background-color\":t.shade[1]||\"#000\",opacity:t.shade[0]||t.shade}),2==t.type&&6==r.ie&&e.layero.find(\"iframe\").attr(\"src\",s[0]),4==t.type?e.tips():e.offset(),t.fixed&&n.on(\"resize\",function(){e.offset(),(/^\\d+%$/.test(t.area[0])||/^\\d+%$/.test(t.area[1]))&&e.auto(a),4==t.type&&e.tips()}),t.time<=0||setTimeout(function(){r.close(e.index)},t.time),e.move().callback(),l.anim[t.anim]){var u=\"layer-anim \"+l.anim[t.anim];e.layero.addClass(u).one(\"webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend\",function(){i(this).removeClass(u)})}t.isOutAnim&&e.layero.data(\"isOutAnim\",!0)}},s.pt.auto=function(e){var t=this,a=t.config,o=i(\"#\"+l[0]+e);\"\"===a.area[0]&&a.maxWidth>0&&(r.ie&&r.ie<8&&a.btn&&o.width(o.innerWidth()),o.outerWidth()>a.maxWidth&&o.width(a.maxWidth));var s=[o.innerWidth(),o.innerHeight()],f=o.find(l[1]).outerHeight()||0,c=o.find(\".\"+l[6]).outerHeight()||0,u=function(e){e=o.find(e),e.height(s[1]-f-c-2*(0|parseFloat(e.css(\"padding-top\"))))};switch(a.type){case 2:u(\"iframe\");break;default:\"\"===a.area[1]?a.maxHeight>0&&o.outerHeight()>a.maxHeight?(s[1]=a.maxHeight,u(\".\"+l[5])):a.fixed&&s[1]>=n.height()&&(s[1]=n.height(),u(\".\"+l[5])):u(\".\"+l[5])}return t},s.pt.offset=function(){var e=this,t=e.config,i=e.layero,a=[i.outerWidth(),i.outerHeight()],o=\"object\"==typeof t.offset;e.offsetTop=(n.height()-a[1])/2,e.offsetLeft=(n.width()-a[0])/2,o?(e.offsetTop=t.offset[0],e.offsetLeft=t.offset[1]||e.offsetLeft):\"auto\"!==t.offset&&(\"t\"===t.offset?e.offsetTop=0:\"r\"===t.offset?e.offsetLeft=n.width()-a[0]:\"b\"===t.offset?e.offsetTop=n.height()-a[1]:\"l\"===t.offset?e.offsetLeft=0:\"lt\"===t.offset?(e.offsetTop=0,e.offsetLeft=0):\"lb\"===t.offset?(e.offsetTop=n.height()-a[1],e.offsetLeft=0):\"rt\"===t.offset?(e.offsetTop=0,e.offsetLeft=n.width()-a[0]):\"rb\"===t.offset?(e.offsetTop=n.height()-a[1],e.offsetLeft=n.width()-a[0]):e.offsetTop=t.offset),t.fixed||(e.offsetTop=/%$/.test(e.offsetTop)?n.height()*parseFloat(e.offsetTop)/100:parseFloat(e.offsetTop),e.offsetLeft=/%$/.test(e.offsetLeft)?n.width()*parseFloat(e.offsetLeft)/100:parseFloat(e.offsetLeft),e.offsetTop+=n.scrollTop(),e.offsetLeft+=n.scrollLeft()),i.attr(\"minLeft\")&&(e.offsetTop=n.height()-(i.find(l[1]).outerHeight()||0),e.offsetLeft=i.css(\"left\")),i.css({top:e.offsetTop,left:e.offsetLeft})},s.pt.tips=function(){var e=this,t=e.config,a=e.layero,o=[a.outerWidth(),a.outerHeight()],r=i(t.follow);r[0]||(r=i(\"body\"));var s={width:r.outerWidth(),height:r.outerHeight(),top:r.offset().top,left:r.offset().left},f=a.find(\".layui-layer-TipsG\"),c=t.tips[0];t.tips[1]||f.remove(),s.autoLeft=function(){s.left+o[0]-n.width()>0?(s.tipLeft=s.left+s.width-o[0],f.css({right:12,left:\"auto\"})):s.tipLeft=s.left},s.where=[function(){s.autoLeft(),s.tipTop=s.top-o[1]-10,f.removeClass(\"layui-layer-TipsB\").addClass(\"layui-layer-TipsT\").css(\"border-right-color\",t.tips[1])},function(){s.tipLeft=s.left+s.width+10,s.tipTop=s.top,f.removeClass(\"layui-layer-TipsL\").addClass(\"layui-layer-TipsR\").css(\"border-bottom-color\",t.tips[1])},function(){s.autoLeft(),s.tipTop=s.top+s.height+10,f.removeClass(\"layui-layer-TipsT\").addClass(\"layui-layer-TipsB\").css(\"border-right-color\",t.tips[1])},function(){s.tipLeft=s.left-o[0]-10,s.tipTop=s.top,f.removeClass(\"layui-layer-TipsR\").addClass(\"layui-layer-TipsL\").css(\"border-bottom-color\",t.tips[1])}],s.where[c-1](),1===c?s.top-(n.scrollTop()+o[1]+16)<0&&s.where[2]():2===c?n.width()-(s.left+s.width+o[0]+16)>0||s.where[3]():3===c?s.top-n.scrollTop()+s.height+o[1]+16-n.height()>0&&s.where[0]():4===c&&o[0]+16-s.left>0&&s.where[1](),a.find(\".\"+l[5]).css({\"background-color\":t.tips[1],\"padding-right\":t.closeBtn?\"30px\":\"\"}),a.css({left:s.tipLeft-(t.fixed?n.scrollLeft():0),top:s.tipTop-(t.fixed?n.scrollTop():0)})},s.pt.move=function(){var e=this,t=e.config,a=i(document),s=e.layero,l=s.find(t.move),f=s.find(\".layui-layer-resize\"),c={};return t.move&&l.css(\"cursor\",\"move\"),l.on(\"mousedown\",function(e){e.preventDefault(),t.move&&(c.moveStart=!0,c.offset=[e.clientX-parseFloat(s.css(\"left\")),e.clientY-parseFloat(s.css(\"top\"))],o.moveElem.css(\"cursor\",\"move\").show())}),f.on(\"mousedown\",function(e){e.preventDefault(),c.resizeStart=!0,c.offset=[e.clientX,e.clientY],c.area=[s.outerWidth(),s.outerHeight()],o.moveElem.css(\"cursor\",\"se-resize\").show()}),a.on(\"mousemove\",function(i){if(c.moveStart){var a=i.clientX-c.offset[0],o=i.clientY-c.offset[1],l=\"fixed\"===s.css(\"position\");if(i.preventDefault(),c.stX=l?0:n.scrollLeft(),c.stY=l?0:n.scrollTop(),!t.moveOut){var f=n.width()-s.outerWidth()+c.stX,u=n.height()-s.outerHeight()+c.stY;a<c.stX&&(a=c.stX),a>f&&(a=f),o<c.stY&&(o=c.stY),o>u&&(o=u)}s.css({left:a,top:o})}if(t.resize&&c.resizeStart){var a=i.clientX-c.offset[0],o=i.clientY-c.offset[1];i.preventDefault(),r.style(e.index,{width:c.area[0]+a,height:c.area[1]+o}),c.isResize=!0,t.resizing&&t.resizing(s)}}).on(\"mouseup\",function(e){c.moveStart&&(delete c.moveStart,o.moveElem.hide(),t.moveEnd&&t.moveEnd(s)),c.resizeStart&&(delete c.resizeStart,o.moveElem.hide())}),e},s.pt.callback=function(){function e(){var e=a.cancel&&a.cancel(t.index,n);e===!1||r.close(t.index)}var t=this,n=t.layero,a=t.config;t.openLayer(),a.success&&(2==a.type?n.find(\"iframe\").on(\"load\",function(){a.success(n,t.index)}):a.success(n,t.index)),6==r.ie&&t.IE6(n),n.find(\".\"+l[6]).children(\"a\").on(\"click\",function(){var e=i(this).index();if(0===e)a.yes?a.yes(t.index,n):a.btn1?a.btn1(t.index,n):r.close(t.index);else{var o=a[\"btn\"+(e+1)]&&a[\"btn\"+(e+1)](t.index,n);o===!1||r.close(t.index)}}),n.find(\".\"+l[7]).on(\"click\",e),a.shadeClose&&i(\"#layui-layer-shade\"+t.index).on(\"click\",function(){r.close(t.index)}),n.find(\".layui-layer-min\").on(\"click\",function(){var e=a.min&&a.min(n);e===!1||r.min(t.index,a)}),n.find(\".layui-layer-max\").on(\"click\",function(){i(this).hasClass(\"layui-layer-maxmin\")?(r.restore(t.index),a.restore&&a.restore(n)):(r.full(t.index,a),setTimeout(function(){a.full&&a.full(n)},100))}),a.end&&(o.end[t.index]=a.end)},o.reselect=function(){i.each(i(\"select\"),function(e,t){var n=i(this);n.parents(\".\"+l[0])[0]||1==n.attr(\"layer\")&&i(\".\"+l[0]).length<1&&n.removeAttr(\"layer\").show(),n=null})},s.pt.IE6=function(e){i(\"select\").each(function(e,t){var n=i(this);n.parents(\".\"+l[0])[0]||\"none\"===n.css(\"display\")||n.attr({layer:\"1\"}).hide(),n=null})},s.pt.openLayer=function(){var e=this;r.zIndex=e.config.zIndex,r.setTop=function(e){var t=function(){r.zIndex++,e.css(\"z-index\",r.zIndex+1)};return r.zIndex=parseInt(e[0].style.zIndex),e.on(\"mousedown\",t),r.zIndex}},o.record=function(e){var t=[e.width(),e.height(),e.position().top,e.position().left+parseFloat(e.css(\"margin-left\"))];e.find(\".layui-layer-max\").addClass(\"layui-layer-maxmin\"),e.attr({area:t})},o.rescollbar=function(e){l.html.attr(\"layer-full\")==e&&(l.html[0].style.removeProperty?l.html[0].style.removeProperty(\"overflow\"):l.html[0].style.removeAttribute(\"overflow\"),l.html.removeAttr(\"layer-full\"))},e.layer=r,r.getChildFrame=function(e,t){return t=t||i(\".\"+l[4]).attr(\"times\"),i(\"#\"+l[0]+t).find(\"iframe\").contents().find(e)},r.getFrameIndex=function(e){return i(\"#\"+e).parents(\".\"+l[4]).attr(\"times\")},r.iframeAuto=function(e){if(e){var t=r.getChildFrame(\"html\",e).outerHeight(),n=i(\"#\"+l[0]+e),a=n.find(l[1]).outerHeight()||0,o=n.find(\".\"+l[6]).outerHeight()||0;n.css({height:t+a+o}),n.find(\"iframe\").css({height:t})}},r.iframeSrc=function(e,t){i(\"#\"+l[0]+e).find(\"iframe\").attr(\"src\",t)},r.style=function(e,t,n){var a=i(\"#\"+l[0]+e),r=a.find(\".layui-layer-content\"),s=a.attr(\"type\"),f=a.find(l[1]).outerHeight()||0,c=a.find(\".\"+l[6]).outerHeight()||0;a.attr(\"minLeft\");s!==o.type[3]&&s!==o.type[4]&&(n||(parseFloat(t.width)<=260&&(t.width=260),parseFloat(t.height)-f-c<=64&&(t.height=64+f+c)),a.css(t),c=a.find(\".\"+l[6]).outerHeight(),s===o.type[2]?a.find(\"iframe\").css({height:parseFloat(t.height)-f-c}):r.css({height:parseFloat(t.height)-f-c-parseFloat(r.css(\"padding-top\"))-parseFloat(r.css(\"padding-bottom\"))}))},r.min=function(e,t){var a=i(\"#\"+l[0]+e),s=a.find(l[1]).outerHeight()||0,f=a.attr(\"minLeft\")||181*o.minIndex+\"px\",c=a.css(\"position\");o.record(a),o.minLeft[0]&&(f=o.minLeft[0],o.minLeft.shift()),a.attr(\"position\",c),r.style(e,{width:180,height:s,left:f,top:n.height()-s,position:\"fixed\",overflow:\"hidden\"},!0),a.find(\".layui-layer-min\").hide(),\"page\"===a.attr(\"type\")&&a.find(l[4]).hide(),o.rescollbar(e),a.attr(\"minLeft\")||o.minIndex++,a.attr(\"minLeft\",f)},r.restore=function(e){var t=i(\"#\"+l[0]+e),n=t.attr(\"area\").split(\",\");t.attr(\"type\");r.style(e,{width:parseFloat(n[0]),height:parseFloat(n[1]),top:parseFloat(n[2]),left:parseFloat(n[3]),position:t.attr(\"position\"),overflow:\"visible\"},!0),t.find(\".layui-layer-max\").removeClass(\"layui-layer-maxmin\"),t.find(\".layui-layer-min\").show(),\"page\"===t.attr(\"type\")&&t.find(l[4]).show(),o.rescollbar(e)},r.full=function(e){var t,a=i(\"#\"+l[0]+e);o.record(a),l.html.attr(\"layer-full\")||l.html.css(\"overflow\",\"hidden\").attr(\"layer-full\",e),clearTimeout(t),t=setTimeout(function(){var t=\"fixed\"===a.css(\"position\");r.style(e,{top:t?0:n.scrollTop(),left:t?0:n.scrollLeft(),width:n.width(),height:n.height()},!0),a.find(\".layui-layer-min\").hide()},100)},r.title=function(e,t){var n=i(\"#\"+l[0]+(t||r.index)).find(l[1]);n.html(e)},r.close=function(e){var t=i(\"#\"+l[0]+e),n=t.attr(\"type\"),a=\"layer-anim-close\";if(t[0]){var s=\"layui-layer-wrap\",f=function(){if(n===o.type[1]&&\"object\"===t.attr(\"conType\")){t.children(\":not(.\"+l[5]+\")\").remove();for(var a=t.find(\".\"+s),r=0;r<2;r++)a.unwrap();a.css(\"display\",a.data(\"display\")).removeClass(s)}else{if(n===o.type[2])try{var f=i(\"#\"+l[4]+e)[0];f.contentWindow.document.write(\"\"),f.contentWindow.close(),t.find(\".\"+l[5])[0].removeChild(f)}catch(c){}t[0].innerHTML=\"\",t.remove()}\"function\"==typeof o.end[e]&&o.end[e](),delete o.end[e]};t.data(\"isOutAnim\")&&t.addClass(\"layer-anim \"+a),i(\"#layui-layer-moves, #layui-layer-shade\"+e).remove(),6==r.ie&&o.reselect(),o.rescollbar(e),t.attr(\"minLeft\")&&(o.minIndex--,o.minLeft.push(t.attr(\"minLeft\"))),r.ie&&r.ie<10||!t.data(\"isOutAnim\")?f():setTimeout(function(){f()},200)}},r.closeAll=function(e){i.each(i(\".\"+l[0]),function(){var t=i(this),n=e?t.attr(\"type\")===e:1;n&&r.close(t.attr(\"times\")),n=null})};var f=r.cache||{},c=function(e){return f.skin?\" \"+f.skin+\" \"+f.skin+\"-\"+e:\"\"};r.prompt=function(e,t){var a=\"\";if(e=e||{},\"function\"==typeof e&&(t=e),e.area){var o=e.area;a='style=\"width: '+o[0]+\"; height: \"+o[1]+';\"',delete e.area}var s,l=2==e.formType?'<textarea class=\"layui-layer-input\"'+a+\"></textarea>\":function(){return'<input type=\"'+(1==e.formType?\"password\":\"text\")+'\" class=\"layui-layer-input\">'}(),f=e.success;return delete e.success,r.open(i.extend({type:1,btn:[\"&#x786E;&#x5B9A;\",\"&#x53D6;&#x6D88;\"],content:l,skin:\"layui-layer-prompt\"+c(\"prompt\"),maxWidth:n.width(),success:function(t){s=t.find(\".layui-layer-input\"),s.val(e.value||\"\").focus(),\"function\"==typeof f&&f(t)},resize:!1,yes:function(i){var n=s.val();\"\"===n?s.focus():n.length>(e.maxlength||500)?r.tips(\"&#x6700;&#x591A;&#x8F93;&#x5165;\"+(e.maxlength||500)+\"&#x4E2A;&#x5B57;&#x6570;\",s,{tips:1}):t&&t(n,i,s)}},e))},r.tab=function(e){e=e||{};var t=e.tab||{},n=\"layui-this\",a=e.success;return delete e.success,r.open(i.extend({type:1,skin:\"layui-layer-tab\"+c(\"tab\"),resize:!1,title:function(){var e=t.length,i=1,a=\"\";if(e>0)for(a='<span class=\"'+n+'\">'+t[0].title+\"</span>\";i<e;i++)a+=\"<span>\"+t[i].title+\"</span>\";return a}(),content:'<ul class=\"layui-layer-tabmain\">'+function(){var e=t.length,i=1,a=\"\";if(e>0)for(a='<li class=\"layui-layer-tabli '+n+'\">'+(t[0].content||\"no content\")+\"</li>\";i<e;i++)a+='<li class=\"layui-layer-tabli\">'+(t[i].content||\"no  content\")+\"</li>\";return a}()+\"</ul>\",success:function(t){var o=t.find(\".layui-layer-title\").children(),r=t.find(\".layui-layer-tabmain\").children();o.on(\"mousedown\",function(t){t.stopPropagation?t.stopPropagation():t.cancelBubble=!0;var a=i(this),o=a.index();a.addClass(n).siblings().removeClass(n),r.eq(o).show().siblings().hide(),\"function\"==typeof e.change&&e.change(o)}),\"function\"==typeof a&&a(t)}},e))},r.photos=function(t,n,a){function o(e,t,i){var n=new Image;return n.src=e,n.complete?t(n):(n.onload=function(){n.onload=null,t(n)},void(n.onerror=function(e){n.onerror=null,i(e)}))}var s={};if(t=t||{},t.photos){var l=t.photos.constructor===Object,f=l?t.photos:{},u=f.data||[],d=f.start||0;s.imgIndex=(0|d)+1,t.img=t.img||\"img\";var y=t.success;if(delete t.success,l){if(0===u.length)return r.msg(\"&#x6CA1;&#x6709;&#x56FE;&#x7247;\")}else{var p=i(t.photos),h=function(){u=[],p.find(t.img).each(function(e){var t=i(this);t.attr(\"layer-index\",e),u.push({alt:t.attr(\"alt\"),pid:t.attr(\"layer-pid\"),src:t.attr(\"layer-src\")||t.attr(\"src\"),thumb:t.attr(\"src\")})})};if(h(),0===u.length)return;if(n||p.on(\"click\",t.img,function(){var e=i(this),n=e.attr(\"layer-index\");r.photos(i.extend(t,{photos:{start:n,data:u,tab:t.tab},full:t.full}),!0),h()}),!n)return}s.imgprev=function(e){s.imgIndex--,s.imgIndex<1&&(s.imgIndex=u.length),s.tabimg(e)},s.imgnext=function(e,t){s.imgIndex++,s.imgIndex>u.length&&(s.imgIndex=1,t)||s.tabimg(e)},s.keyup=function(e){if(!s.end){var t=e.keyCode;e.preventDefault(),37===t?s.imgprev(!0):39===t?s.imgnext(!0):27===t&&r.close(s.index)}},s.tabimg=function(e){if(!(u.length<=1))return f.start=s.imgIndex-1,r.close(s.index),r.photos(t,!0,e)},s.event=function(){s.bigimg.hover(function(){s.imgsee.show()},function(){s.imgsee.hide()}),s.bigimg.find(\".layui-layer-imgprev\").on(\"click\",function(e){e.preventDefault(),s.imgprev()}),s.bigimg.find(\".layui-layer-imgnext\").on(\"click\",function(e){e.preventDefault(),s.imgnext()}),i(document).on(\"keyup\",s.keyup)},s.loadi=r.load(1,{shade:!(\"shade\"in t)&&.9,scrollbar:!1}),o(u[d].src,function(n){r.close(s.loadi),s.index=r.open(i.extend({type:1,id:\"layui-layer-photos\",area:function(){var a=[n.width,n.height],o=[i(e).width()-100,i(e).height()-100];if(!t.full&&(a[0]>o[0]||a[1]>o[1])){var r=[a[0]/o[0],a[1]/o[1]];r[0]>r[1]?(a[0]=a[0]/r[0],a[1]=a[1]/r[0]):r[0]<r[1]&&(a[0]=a[0]/r[1],a[1]=a[1]/r[1])}return[a[0]+\"px\",a[1]+\"px\"]}(),title:!1,shade:.9,shadeClose:!0,closeBtn:!1,move:\".layui-layer-phimg img\",moveType:1,scrollbar:!1,moveOut:!0,isOutAnim:!1,skin:\"layui-layer-photos\"+c(\"photos\"),content:'<div class=\"layui-layer-phimg\"><img src=\"'+u[d].src+'\" alt=\"'+(u[d].alt||\"\")+'\" layer-pid=\"'+u[d].pid+'\"><div class=\"layui-layer-imgsee\">'+(u.length>1?'<span class=\"layui-layer-imguide\"><a href=\"javascript:;\" class=\"layui-layer-iconext layui-layer-imgprev\"></a><a href=\"javascript:;\" class=\"layui-layer-iconext layui-layer-imgnext\"></a></span>':\"\")+'<div class=\"layui-layer-imgbar\" style=\"display:'+(a?\"block\":\"\")+'\"><span class=\"layui-layer-imgtit\"><a href=\"javascript:;\">'+(u[d].alt||\"\")+\"</a><em>\"+s.imgIndex+\"/\"+u.length+\"</em></span></div></div></div>\",success:function(e,i){s.bigimg=e.find(\".layui-layer-phimg\"),s.imgsee=e.find(\".layui-layer-imguide,.layui-layer-imgbar\"),s.event(e),t.tab&&t.tab(u[d],e),\"function\"==typeof y&&y(e)},end:function(){s.end=!0,i(document).off(\"keyup\",s.keyup)}},t))},function(){r.close(s.loadi),r.msg(\"&#x5F53;&#x524D;&#x56FE;&#x7247;&#x5730;&#x5740;&#x5F02;&#x5E38;<br>&#x662F;&#x5426;&#x7EE7;&#x7EED;&#x67E5;&#x770B;&#x4E0B;&#x4E00;&#x5F20;&#xFF1F;\",{time:3e4,btn:[\"&#x4E0B;&#x4E00;&#x5F20;\",\"&#x4E0D;&#x770B;&#x4E86;\"],yes:function(){u.length>1&&s.imgnext(!0,!0)}})})}},o.run=function(t){i=t,n=i(e),l.html=i(\"html\"),r.open=function(e){var t=new s(e);return t.index}},e.layui&&layui.define?(r.ready(),layui.define(\"jquery\",function(t){r.path=layui.cache.dir,o.run(layui.$),e.layer=r,t(\"layer\",r)})):\"function\"==typeof define&&define.amd?define([\"jquery\"],function(){return o.run(e.jQuery),r}):function(){o.run(e.jQuery),r.ready()}()}(window);layui.define(\"jquery\",function(t){\"use strict\";var a=layui.$,i=(layui.hint(),layui.device()),e=\"element\",l=\"layui-this\",n=\"layui-show\",s=function(){this.config={}};s.prototype.set=function(t){var i=this;return a.extend(!0,i.config,t),i},s.prototype.on=function(t,a){return layui.onevent.call(this,e,t,a)},s.prototype.tabAdd=function(t,i){var e=\".layui-tab-title\",l=a(\".layui-tab[lay-filter=\"+t+\"]\"),n=l.children(e),s=n.children(\".layui-tab-bar\"),o=l.children(\".layui-tab-content\"),r='<li lay-id=\"'+(i.id||\"\")+'\"'+(i.attr?' lay-attr=\"'+i.attr+'\"':\"\")+\">\"+(i.title||\"unnaming\")+\"</li>\";return s[0]?s.before(r):n.append(r),o.append('<div class=\"layui-tab-item\">'+(i.content||\"\")+\"</div>\"),f.hideTabMore(!0),f.tabAuto(),this},s.prototype.tabDelete=function(t,i){var e=\".layui-tab-title\",l=a(\".layui-tab[lay-filter=\"+t+\"]\"),n=l.children(e),s=n.find('>li[lay-id=\"'+i+'\"]');return f.tabDelete(null,s),this},s.prototype.tabChange=function(t,i){var e=\".layui-tab-title\",l=a(\".layui-tab[lay-filter=\"+t+\"]\"),n=l.children(e),s=n.find('>li[lay-id=\"'+i+'\"]');return f.tabClick.call(s[0],null,null,s),this},s.prototype.tab=function(t){t=t||{},b.on(\"click\",t.headerElem,function(i){var e=a(this).index();f.tabClick.call(this,i,e,null,t)})},s.prototype.progress=function(t,i){var e=\"layui-progress\",l=a(\".\"+e+\"[lay-filter=\"+t+\"]\"),n=l.find(\".\"+e+\"-bar\"),s=n.find(\".\"+e+\"-text\");return n.css(\"width\",i),s.text(i),this};var o=\".layui-nav\",r=\"layui-nav-item\",c=\"layui-nav-bar\",u=\"layui-nav-tree\",d=\"layui-nav-child\",y=\"layui-nav-more\",h=\"layui-anim layui-anim-upbit\",f={tabClick:function(t,i,s,o){o=o||{};var r=s||a(this),i=i||r.parent().children(\"li\").index(r),c=o.headerElem?r.parent():r.parents(\".layui-tab\").eq(0),u=o.bodyElem?a(o.bodyElem):c.children(\".layui-tab-content\").children(\".layui-tab-item\"),d=r.find(\"a\"),y=c.attr(\"lay-filter\");\"javascript:;\"!==d.attr(\"href\")&&\"_blank\"===d.attr(\"target\")||(r.addClass(l).siblings().removeClass(l),u.eq(i).addClass(n).siblings().removeClass(n)),layui.event.call(this,e,\"tab(\"+y+\")\",{elem:c,index:i})},tabDelete:function(t,i){var n=i||a(this).parent(),s=n.index(),o=n.parents(\".layui-tab\").eq(0),r=o.children(\".layui-tab-content\").children(\".layui-tab-item\"),c=o.attr(\"lay-filter\");n.hasClass(l)&&(n.next()[0]?f.tabClick.call(n.next()[0],null,s+1):n.prev()[0]&&f.tabClick.call(n.prev()[0],null,s-1)),n.remove(),r.eq(s).remove(),setTimeout(function(){f.tabAuto()},50),layui.event.call(this,e,\"tabDelete(\"+c+\")\",{elem:o,index:s})},tabAuto:function(){var t=\"layui-tab-more\",e=\"layui-tab-bar\",l=\"layui-tab-close\",n=this;a(\".layui-tab\").each(function(){var s=a(this),o=s.children(\".layui-tab-title\"),r=(s.children(\".layui-tab-content\").children(\".layui-tab-item\"),'lay-stope=\"tabmore\"'),c=a('<span class=\"layui-unselect layui-tab-bar\" '+r+\"><i \"+r+' class=\"layui-icon\">&#xe61a;</i></span>');if(n===window&&8!=i.ie&&f.hideTabMore(!0),s.attr(\"lay-allowClose\")&&o.find(\"li\").each(function(){var t=a(this);if(!t.find(\".\"+l)[0]){var i=a('<i class=\"layui-icon layui-unselect '+l+'\">&#x1006;</i>');i.on(\"click\",f.tabDelete),t.append(i)}}),\"string\"!=typeof s.attr(\"lay-unauto\"))if(o.prop(\"scrollWidth\")>o.outerWidth()+1){if(o.find(\".\"+e)[0])return;o.append(c),s.attr(\"overflow\",\"\"),c.on(\"click\",function(a){o[this.title?\"removeClass\":\"addClass\"](t),this.title=this.title?\"\":\"收缩\"})}else o.find(\".\"+e).remove(),s.removeAttr(\"overflow\")})},hideTabMore:function(t){var i=a(\".layui-tab-title\");t!==!0&&\"tabmore\"===a(t.target).attr(\"lay-stope\")||(i.removeClass(\"layui-tab-more\"),i.find(\".layui-tab-bar\").attr(\"title\",\"\"))},clickThis:function(){var t=a(this),i=t.parents(o),n=i.attr(\"lay-filter\"),s=t.parent(),c=t.siblings(\".\"+d),y=\"string\"==typeof s.attr(\"lay-unselect\");\"javascript:;\"!==t.attr(\"href\")&&\"_blank\"===t.attr(\"target\")||y||c[0]||(i.find(\".\"+l).removeClass(l),s.addClass(l)),i.hasClass(u)&&(c.removeClass(h),c[0]&&(s[\"none\"===c.css(\"display\")?\"addClass\":\"removeClass\"](r+\"ed\"),\"all\"===i.attr(\"lay-shrink\")&&s.siblings().removeClass(r+\"ed\"))),layui.event.call(this,e,\"nav(\"+n+\")\",t)},collapse:function(){var t=a(this),i=t.find(\".layui-colla-icon\"),l=t.siblings(\".layui-colla-content\"),s=t.parents(\".layui-collapse\").eq(0),o=s.attr(\"lay-filter\"),r=\"none\"===l.css(\"display\");if(\"string\"==typeof s.attr(\"lay-accordion\")){var c=s.children(\".layui-colla-item\").children(\".\"+n);c.siblings(\".layui-colla-title\").children(\".layui-colla-icon\").html(\"&#xe602;\"),c.removeClass(n)}l[r?\"addClass\":\"removeClass\"](n),i.html(r?\"&#xe61a;\":\"&#xe602;\"),layui.event.call(this,e,\"collapse(\"+o+\")\",{title:t,content:l,show:r})}};s.prototype.init=function(t,e){var l=function(){return e?'[lay-filter=\"'+e+'\"]':\"\"}(),s={tab:function(){f.tabAuto.call({})},nav:function(){var t=200,e={},s={},p={},b=function(l,o,r){var c=a(this),f=c.find(\".\"+d);o.hasClass(u)?l.css({top:c.position().top,height:c.children(\"a\").outerHeight(),opacity:1}):(f.addClass(h),l.css({left:c.position().left+parseFloat(c.css(\"marginLeft\")),top:c.position().top+c.height()-l.height()}),e[r]=setTimeout(function(){l.css({width:c.width(),opacity:1})},i.ie&&i.ie<10?0:t),clearTimeout(p[r]),\"block\"===f.css(\"display\")&&clearTimeout(s[r]),s[r]=setTimeout(function(){f.addClass(n),c.find(\".\"+y).addClass(y+\"d\")},300))};a(o+l).each(function(i){var l=a(this),o=a('<span class=\"'+c+'\"></span>'),h=l.find(\".\"+r);l.find(\".\"+c)[0]||(l.append(o),h.on(\"mouseenter\",function(){b.call(this,o,l,i)}).on(\"mouseleave\",function(){l.hasClass(u)||(clearTimeout(s[i]),s[i]=setTimeout(function(){l.find(\".\"+d).removeClass(n),l.find(\".\"+y).removeClass(y+\"d\")},300))}),l.on(\"mouseleave\",function(){clearTimeout(e[i]),p[i]=setTimeout(function(){l.hasClass(u)?o.css({height:0,top:o.position().top+o.height()/2,opacity:0}):o.css({width:0,left:o.position().left+o.width()/2,opacity:0})},t)})),h.find(\"a\").each(function(){var t=a(this),i=(t.parent(),t.siblings(\".\"+d));i[0]&&!t.children(\".\"+y)[0]&&t.append('<span class=\"'+y+'\"></span>'),t.off(\"click\",f.clickThis).on(\"click\",f.clickThis)})})},breadcrumb:function(){var t=\".layui-breadcrumb\";a(t+l).each(function(){var t=a(this),i=\"lay-separator\",e=t.attr(i)||\"/\",l=t.find(\"a\");l.next(\"span[\"+i+\"]\")[0]||(l.each(function(t){t!==l.length-1&&a(this).after(\"<span \"+i+\">\"+e+\"</span>\")}),t.css(\"visibility\",\"visible\"))})},progress:function(){var t=\"layui-progress\";a(\".\"+t+l).each(function(){var i=a(this),e=i.find(\".layui-progress-bar\"),l=e.attr(\"lay-percent\");e.css(\"width\",function(){return/^.+\\/.+$/.test(l)?100*new Function(\"return \"+l)()+\"%\":l}()),i.attr(\"lay-showPercent\")&&setTimeout(function(){e.html('<span class=\"'+t+'-text\">'+l+\"</span>\")},350)})},collapse:function(){var t=\"layui-collapse\";a(\".\"+t+l).each(function(){var t=a(this).find(\".layui-colla-item\");t.each(function(){var t=a(this),i=t.find(\".layui-colla-title\"),e=t.find(\".layui-colla-content\"),l=\"none\"===e.css(\"display\");i.find(\".layui-colla-icon\").remove(),i.append('<i class=\"layui-icon layui-colla-icon\">'+(l?\"&#xe602;\":\"&#xe61a;\")+\"</i>\"),i.off(\"click\",f.collapse).on(\"click\",f.collapse)})})}};return s[t]?s[t]():layui.each(s,function(t,a){a()})},s.prototype.render=s.prototype.init;var p=new s,b=a(document);p.render();var v=\".layui-tab-title li\";b.on(\"click\",v,f.tabClick),b.on(\"click\",f.hideTabMore),a(window).on(\"resize\",f.tabAuto),t(e,p)});layui.define(\"layer\",function(e){\"use strict\";var i=layui.$,t=layui.layer,n=layui.hint(),a=layui.device(),o={config:{},set:function(e){var t=this;return t.config=i.extend({},t.config,e),t},on:function(e,i){return layui.onevent.call(this,r,e,i)}},l=function(){var e=this;return{upload:function(i){e.upload.call(e,i)},config:e.config}},r=\"upload\",u=\"layui-upload-file\",c=\"layui-upload-form\",f=\"layui-upload-iframe\",s=\"layui-upload-choose\",p=function(e){var t=this;t.config=i.extend({},t.config,o.config,e),t.render()};p.prototype.config={accept:\"images\",exts:\"\",auto:!0,bindAction:\"\",url:\"\",field:\"file\",method:\"post\",data:{},drag:!0,size:0,number:0,multiple:!1},p.prototype.render=function(e){var t=this,e=t.config;e.elem=i(e.elem),e.bindAction=i(e.bindAction),t.file(),t.events()},p.prototype.file=function(){var e=this,t=e.config,n=e.elemFile=i(['<input class=\"'+u+'\" type=\"file\" accept=\"'+t.acceptMime+'\" name=\"'+t.field+'\"',t.multiple?\" multiple\":\"\",\">\"].join(\"\")),o=t.elem.next();(o.hasClass(u)||o.hasClass(c))&&o.remove(),a.ie&&a.ie<10&&t.elem.wrap('<div class=\"layui-upload-wrap\"></div>'),e.isFile()?(e.elemFile=t.elem,t.field=t.elem[0].name):t.elem.after(n),a.ie&&a.ie<10&&e.initIE()},p.prototype.initIE=function(){var e=this,t=e.config,n=i('<iframe id=\"'+f+'\" class=\"'+f+'\" name=\"'+f+'\" frameborder=\"0\"></iframe>'),a=i(['<form target=\"'+f+'\" class=\"'+c+'\" method=\"post\" key=\"set-mine\" enctype=\"multipart/form-data\" action=\"'+t.url+'\">',\"</form>\"].join(\"\"));i(\"#\"+f)[0]||i(\"body\").append(n),t.elem.next().hasClass(c)||(e.elemFile.wrap(a),t.elem.next(\".\"+c).append(function(){var e=[];return layui.each(t.data,function(i,t){t=\"function\"==typeof t?t():t,e.push('<input type=\"hidden\" name=\"'+i+'\" value=\"'+t+'\">')}),e.join(\"\")}()))},p.prototype.msg=function(e){return t.msg(e,{icon:2,shift:6})},p.prototype.isFile=function(){var e=this.config.elem[0];if(e)return\"input\"===e.tagName.toLocaleLowerCase()&&\"file\"===e.type},p.prototype.preview=function(e){var i=this;window.FileReader&&layui.each(i.chooseFiles,function(i,t){var n=new FileReader;n.readAsDataURL(t),n.onload=function(){e&&e(i,t,this.result)}})},p.prototype.upload=function(e,t){var n,o=this,l=o.config,r=o.elemFile[0],u=function(){var t=0,n=0,a=e||o.files||o.chooseFiles||r.files,u=function(){l.multiple&&t+n===o.fileLength&&\"function\"==typeof l.allDone&&l.allDone({total:o.fileLength,successful:t,aborted:n})};layui.each(a,function(e,a){var r=new FormData;r.append(l.field,a),layui.each(l.data,function(e,i){i=\"function\"==typeof i?i():i,r.append(e,i)}),i.ajax({url:l.url,type:\"post\",data:r,contentType:!1,processData:!1,dataType:\"json\",headers:l.headers||{},success:function(i){t++,d(e,i),u()},error:function(){n++,o.msg(\"请求上传接口出现异常\"),m(e),u()}})})},c=function(){var e=i(\"#\"+f);o.elemFile.parent().submit(),clearInterval(p.timer),p.timer=setInterval(function(){var i,t=e.contents().find(\"body\");try{i=t.text()}catch(n){o.msg(\"获取上传后的响应信息出现异常\"),clearInterval(p.timer),m()}i&&(clearInterval(p.timer),t.html(\"\"),d(0,i))},30)},d=function(e,i){if(o.elemFile.next(\".\"+s).remove(),r.value=\"\",\"object\"!=typeof i)try{i=JSON.parse(i)}catch(t){return i={},o.msg(\"请对上传接口返回有效JSON\")}\"function\"==typeof l.done&&l.done(i,e||0,function(e){o.upload(e)})},m=function(e){l.auto&&(r.value=\"\"),\"function\"==typeof l.error&&l.error(e||0,function(e){o.upload(e)})},h=l.exts,v=function(){var i=[];return layui.each(e||o.chooseFiles,function(e,t){i.push(t.name)}),i}(),g={preview:function(e){o.preview(e)},upload:function(e,i){var t={};t[e]=i,o.upload(t)},pushFile:function(){return o.files=o.files||{},layui.each(o.chooseFiles,function(e,i){o.files[e]=i}),o.files},resetFile:function(e,i,t){var n=new File([i],t);o.files=o.files||{},o.files[e]=n}},y=function(){if(\"choose\"!==t&&!l.auto||(l.choose&&l.choose(g),\"choose\"!==t))return l.before&&l.before(g),a.ie?a.ie>9?u():c():void u()};if(v=0===v.length?r.value.match(/[^\\/\\\\]+\\..+/g)||[]||\"\":v,0!==v.length){switch(l.accept){case\"file\":if(h&&!RegExp(\"\\\\w\\\\.(\"+h+\")$\",\"i\").test(escape(v)))return o.msg(\"选择的文件中包含不支持的格式\"),r.value=\"\";break;case\"video\":if(!RegExp(\"\\\\w\\\\.(\"+(h||\"avi|mp4|wma|rmvb|rm|flash|3gp|flv\")+\")$\",\"i\").test(escape(v)))return o.msg(\"选择的视频中包含不支持的格式\"),r.value=\"\";break;case\"audio\":if(!RegExp(\"\\\\w\\\\.(\"+(h||\"mp3|wav|mid\")+\")$\",\"i\").test(escape(v)))return o.msg(\"选择的音频中包含不支持的格式\"),r.value=\"\";break;default:if(layui.each(v,function(e,i){RegExp(\"\\\\w\\\\.(\"+(h||\"jpg|png|gif|bmp|jpeg$\")+\")\",\"i\").test(escape(i))||(n=!0)}),n)return o.msg(\"选择的图片中包含不支持的格式\"),r.value=\"\"}if(o.fileLength=function(){var i=0,t=e||o.files||o.chooseFiles||r.files;return layui.each(t,function(){i++}),i}(),l.number&&o.fileLength>l.number)return o.msg(\"同时最多只能上传的数量为：\"+l.number);if(l.size>0&&!(a.ie&&a.ie<10)){var F;if(layui.each(o.chooseFiles,function(e,i){if(i.size>1024*l.size){var t=l.size/1024;t=t>=1?t.toFixed(2)+\"MB\":l.size+\"KB\",r.value=\"\",F=t}}),F)return o.msg(\"文件不能超过\"+F)}y()}},p.prototype.events=function(){var e=this,t=e.config,o=function(i){e.chooseFiles={},layui.each(i,function(i,t){var n=(new Date).getTime();e.chooseFiles[n+\"-\"+i]=t})},l=function(i,n){var a=e.elemFile,o=i.length>1?i.length+\"个文件\":(i[0]||{}).name||a[0].value.match(/[^\\/\\\\]+\\..+/g)||[]||\"\";a.next().hasClass(s)&&a.next().remove(),e.upload(null,\"choose\"),e.isFile()||t.choose||a.after('<span class=\"layui-inline '+s+'\">'+o+\"</span>\")};t.elem.off(\"upload.start\").on(\"upload.start\",function(){var a=i(this),o=a.attr(\"lay-data\");if(o)try{o=new Function(\"return \"+o)(),e.config=i.extend({},t,o)}catch(l){n.error(\"Upload element property lay-data configuration item has a syntax error: \"+o)}e.config.item=a,e.elemFile[0].click()}),a.ie&&a.ie<10||t.elem.off(\"upload.over\").on(\"upload.over\",function(){var e=i(this);e.attr(\"lay-over\",\"\")}).off(\"upload.leave\").on(\"upload.leave\",function(){var e=i(this);e.removeAttr(\"lay-over\")}).off(\"upload.drop\").on(\"upload.drop\",function(n,a){var r=i(this),u=a.originalEvent.dataTransfer.files||[];r.removeAttr(\"lay-over\"),o(u),t.auto?e.upload(u):l(u)}),e.elemFile.off(\"upload.change\").on(\"upload.change\",function(){var i=this.files||[];o(i),t.auto?e.upload():l(i)}),t.bindAction.off(\"upload.action\").on(\"upload.action\",function(){e.upload()}),t.elem.data(\"haveEvents\")||(e.elemFile.on(\"change\",function(){i(this).trigger(\"upload.change\")}),t.elem.on(\"click\",function(){e.isFile()||i(this).trigger(\"upload.start\")}),t.drag&&t.elem.on(\"dragover\",function(e){e.preventDefault(),i(this).trigger(\"upload.over\")}).on(\"dragleave\",function(e){i(this).trigger(\"upload.leave\")}).on(\"drop\",function(e){e.preventDefault(),i(this).trigger(\"upload.drop\",e)}),t.bindAction.on(\"click\",function(){i(this).trigger(\"upload.action\")}),t.elem.data(\"haveEvents\",!0))},o.render=function(e){var i=new p(e);return l.call(i)},e(r,o)});layui.define(\"jquery\",function(e){\"use strict\";var i=layui.jquery,t={config:{},index:layui.slider?layui.slider.index+1e4:0,set:function(e){var t=this;return t.config=i.extend({},t.config,e),t},on:function(e,i){return layui.onevent.call(this,n,e,i)}},a=function(){var e=this,i=e.config;return{setValue:function(i,t){return e.slide(\"set\",i,t||0)},config:i}},n=\"slider\",l=\"layui-disabled\",s=\"layui-slider\",r=\"layui-slider-bar\",o=\"layui-slider-wrap\",u=\"layui-slider-wrap-btn\",d=\"layui-slider-tips\",v=\"layui-slider-input\",c=\"layui-slider-input-txt\",m=\"layui-slider-input-btn\",p=\"layui-slider-hover\",f=function(e){var a=this;a.index=++t.index,a.config=i.extend({},a.config,t.config,e),a.render()};f.prototype.config={type:\"default\",min:0,max:100,value:0,step:1,showstep:!1,tips:!0,input:!1,range:!1,height:200,disabled:!1,theme:\"#009688\"},f.prototype.render=function(){var e=this,t=e.config;if(t.step<1&&(t.step=1),t.max<t.min&&(t.max=t.min+t.step),t.range){t.value=\"object\"==typeof t.value?t.value:[t.min,t.value];var a=Math.min(t.value[0],t.value[1]),n=Math.max(t.value[0],t.value[1]);t.value[0]=a>t.min?a:t.min,t.value[1]=n>t.min?n:t.min,t.value[0]=t.value[0]>t.max?t.max:t.value[0],t.value[1]=t.value[1]>t.max?t.max:t.value[1];var r=Math.floor((t.value[0]-t.min)/(t.max-t.min)*100),v=Math.floor((t.value[1]-t.min)/(t.max-t.min)*100),m=v-r+\"%\";r+=\"%\",v+=\"%\"}else{\"object\"==typeof t.value&&(t.value=Math.min.apply(null,t.value)),t.value<t.min&&(t.value=t.min),t.value>t.max&&(t.value=t.max);var m=Math.floor((t.value-t.min)/(t.max-t.min)*100)+\"%\"}var p=t.disabled?\"#c2c2c2\":t.theme,f='<div class=\"layui-slider '+(\"vertical\"===t.type?\"layui-slider-vertical\":\"\")+'\">'+(t.tips?'<div class=\"layui-slider-tips\"></div>':\"\")+'<div class=\"layui-slider-bar\" style=\"background:'+p+\"; \"+(\"vertical\"===t.type?\"height\":\"width\")+\":\"+m+\";\"+(\"vertical\"===t.type?\"bottom\":\"left\")+\":\"+(r||0)+';\"></div><div class=\"layui-slider-wrap\" style=\"'+(\"vertical\"===t.type?\"bottom\":\"left\")+\":\"+(r||m)+';\"><div class=\"layui-slider-wrap-btn\" style=\"border: 2px solid '+p+';\"></div></div>'+(t.range?'<div class=\"layui-slider-wrap\" style=\"'+(\"vertical\"===t.type?\"bottom\":\"left\")+\":\"+v+';\"><div class=\"layui-slider-wrap-btn\" style=\"border: 2px solid '+p+';\"></div></div>':\"\")+\"</div>\",h=i(t.elem),y=h.next(\".\"+s);if(y[0]&&y.remove(),e.elemTemp=i(f),t.range?(e.elemTemp.find(\".\"+o).eq(0).data(\"value\",t.value[0]),e.elemTemp.find(\".\"+o).eq(1).data(\"value\",t.value[1])):e.elemTemp.find(\".\"+o).data(\"value\",t.value),h.html(e.elemTemp),\"vertical\"===t.type&&e.elemTemp.height(t.height+\"px\"),t.showstep){for(var g=(t.max-t.min)/t.step,b=\"\",x=1;x<g+1;x++){var T=100*x/g;T<100&&(b+='<div class=\"layui-slider-step\" style=\"'+(\"vertical\"===t.type?\"bottom\":\"left\")+\":\"+T+'%\"></div>')}e.elemTemp.append(b)}if(t.input&&!t.range){var w=i('<div class=\"layui-slider-input layui-input\"><div class=\"layui-slider-input-txt\"><input type=\"text\" class=\"layui-input\"></div><div class=\"layui-slider-input-btn\"><i class=\"layui-icon layui-icon-up\"></i><i class=\"layui-icon layui-icon-down\"></i></div></div>');h.css(\"position\",\"relative\"),h.append(w),h.find(\".\"+c).children(\"input\").val(t.value),\"vertical\"===t.type?w.css({left:0,top:-48}):e.elemTemp.css(\"margin-right\",w.outerWidth()+15)}t.disabled?(e.elemTemp.addClass(l),e.elemTemp.find(\".\"+u).addClass(l)):e.slide(),e.elemTemp.find(\".\"+u).on(\"mouseover\",function(){var a=\"vertical\"===t.type?t.height:e.elemTemp[0].offsetWidth,n=e.elemTemp.find(\".\"+o),l=\"vertical\"===t.type?a-i(this).parent()[0].offsetTop-n.height():i(this).parent()[0].offsetLeft,s=l/a*100,r=i(this).parent().data(\"value\"),u=t.setTips?t.setTips(r):r;e.elemTemp.find(\".\"+d).html(u),\"vertical\"===t.type?e.elemTemp.find(\".\"+d).css({bottom:s+\"%\",\"margin-bottom\":\"20px\",display:\"inline-block\"}):e.elemTemp.find(\".\"+d).css({left:s+\"%\",display:\"inline-block\"})}).on(\"mouseout\",function(){e.elemTemp.find(\".\"+d).css(\"display\",\"none\")})},f.prototype.slide=function(e,t,a){var n=this,l=n.config,s=n.elemTemp,f=function(){return\"vertical\"===l.type?l.height:s[0].offsetWidth},h=s.find(\".\"+o),y=s.next(\".\"+v),g=y.children(\".\"+c).children(\"input\").val(),b=100/((l.max-l.min)/Math.ceil(l.step)),x=function(e,i){e=Math.ceil(e)*b>100?Math.ceil(e)*b:Math.round(e)*b,e=e>100?100:e,h.eq(i).css(\"vertical\"===l.type?\"bottom\":\"left\",e+\"%\");var t=T(h[0].offsetLeft),a=l.range?T(h[1].offsetLeft):0;\"vertical\"===l.type?(s.find(\".\"+d).css({bottom:e+\"%\",\"margin-bottom\":\"20px\"}),t=T(f()-h[0].offsetTop-h.height()),a=l.range?T(f()-h[1].offsetTop-h.height()):0):s.find(\".\"+d).css(\"left\",e+\"%\"),t=t>100?100:t,a=a>100?100:a;var n=Math.min(t,a),o=Math.abs(t-a);\"vertical\"===l.type?s.find(\".\"+r).css({height:o+\"%\",bottom:n+\"%\"}):s.find(\".\"+r).css({width:o+\"%\",left:n+\"%\"});var u=l.min+Math.round((l.max-l.min)*e/100);if(g=u,y.children(\".\"+c).children(\"input\").val(g),h.eq(i).data(\"value\",u),u=l.setTips?l.setTips(u):u,s.find(\".\"+d).html(u),l.range){var v=[h.eq(0).data(\"value\"),h.eq(1).data(\"value\")];v[0]>v[1]&&v.reverse()}l.change&&l.change(l.range?v:u)},T=function(e){var i=e/f()*100/b,t=Math.round(i)*b;return e==f()&&(t=Math.ceil(i)*b),t},w=i(['<div class=\"layui-auxiliar-moving\" id=\"LAY-slider-moving\"></div'].join(\"\")),M=function(e,t){var a=function(){t&&t(),w.remove()};i(\"#LAY-slider-moving\")[0]||i(\"body\").append(w),w.on(\"mousemove\",e),w.on(\"mouseup\",a).on(\"mouseleave\",a)};if(\"set\"===e)return x(t,a);s.find(\".\"+u).each(function(e){var t=i(this);t.on(\"mousedown\",function(i){i=i||window.event;var a=t.parent()[0].offsetLeft,n=i.clientX;\"vertical\"===l.type&&(a=f()-t.parent()[0].offsetTop-h.height(),n=i.clientY);var r=function(i){i=i||window.event;var r=a+(\"vertical\"===l.type?n-i.clientY:i.clientX-n);r<0&&(r=0),r>f()&&(r=f());var o=r/f()*100/b;x(o,e),t.addClass(p),s.find(\".\"+d).show(),i.preventDefault()},o=function(){t.removeClass(p),s.find(\".\"+d).hide()};M(r,o)})}),s.on(\"click\",function(e){var t=i(\".\"+u);if(!t.is(event.target)&&0===t.has(event.target).length&&t.length){var a,n=\"vertical\"===l.type?f()-e.clientY+i(this).offset().top:e.clientX-i(this).offset().left;n<0&&(n=0),n>f()&&(n=f());var s=n/f()*100/b;a=l.range?\"vertical\"===l.type?Math.abs(n-parseInt(i(h[0]).css(\"bottom\")))>Math.abs(n-parseInt(i(h[1]).css(\"bottom\")))?1:0:Math.abs(n-h[0].offsetLeft)>Math.abs(n-h[1].offsetLeft)?1:0:0,x(s,a),e.preventDefault()}}),y.hover(function(){var e=i(this);e.children(\".\"+m).fadeIn(\"fast\")},function(){var e=i(this);e.children(\".\"+m).fadeOut(\"fast\")}),y.children(\".\"+m).children(\"i\").each(function(e){i(this).on(\"click\",function(){g=1==e?g-l.step<l.min?l.min:Number(g)-l.step:Number(g)+l.step>l.max?l.max:Number(g)+l.step;var i=(g-l.min)/(l.max-l.min)*100/b;x(i,0)})});var q=function(){var e=this.value;e=isNaN(e)?0:e,e=e<l.min?l.min:e,e=e>l.max?l.max:e,this.value=e;var i=(e-l.min)/(l.max-l.min)*100/b;x(i,0)};y.children(\".\"+c).children(\"input\").on(\"keydown\",function(e){13===e.keyCode&&(e.preventDefault(),q.call(this))}).on(\"change\",q)},f.prototype.events=function(){var e=this;e.config},t.render=function(e){var i=new f(e);return a.call(i)},e(n,t)});layui.define(\"jquery\",function(e){\"use strict\";var i=layui.jquery,o={config:{},index:layui.colorpicker?layui.colorpicker.index+1e4:0,set:function(e){var o=this;return o.config=i.extend({},o.config,e),o},on:function(e,i){return layui.onevent.call(this,\"colorpicker\",e,i)}},r=function(){var e=this,i=e.config;return{config:i}},t=\"colorpicker\",n=\"layui-show\",l=\"layui-colorpicker\",c=\".layui-colorpicker-main\",a=\"layui-icon-down\",s=\"layui-icon-close\",f=\"layui-colorpicker-trigger-span\",d=\"layui-colorpicker-trigger-i\",u=\"layui-colorpicker-side\",p=\"layui-colorpicker-side-slider\",g=\"layui-colorpicker-basis\",v=\"layui-colorpicker-alpha-bgcolor\",h=\"layui-colorpicker-alpha-slider\",m=\"layui-colorpicker-basis-cursor\",b=\"layui-colorpicker-main-input\",k=function(e){var i={h:0,s:0,b:0},o=Math.min(e.r,e.g,e.b),r=Math.max(e.r,e.g,e.b),t=r-o;return i.b=r,i.s=0!=r?255*t/r:0,0!=i.s?e.r==r?i.h=(e.g-e.b)/t:e.g==r?i.h=2+(e.b-e.r)/t:i.h=4+(e.r-e.g)/t:i.h=-1,r==o&&(i.h=0),i.h*=60,i.h<0&&(i.h+=360),i.s*=100/255,i.b*=100/255,i},y=function(e){var e=e.indexOf(\"#\")>-1?e.substring(1):e;if(3==e.length){var i=e.split(\"\");e=i[0]+i[0]+i[1]+i[1]+i[2]+i[2]}e=parseInt(e,16);var o={r:e>>16,g:(65280&e)>>8,b:255&e};return k(o)},x=function(e){var i={},o=e.h,r=255*e.s/100,t=255*e.b/100;if(0==r)i.r=i.g=i.b=t;else{var n=t,l=(255-r)*t/255,c=(n-l)*(o%60)/60;360==o&&(o=0),o<60?(i.r=n,i.b=l,i.g=l+c):o<120?(i.g=n,i.b=l,i.r=n-c):o<180?(i.g=n,i.r=l,i.b=l+c):o<240?(i.b=n,i.r=l,i.g=n-c):o<300?(i.b=n,i.g=l,i.r=l+c):o<360?(i.r=n,i.g=l,i.b=n-c):(i.r=0,i.g=0,i.b=0)}return{r:Math.round(i.r),g:Math.round(i.g),b:Math.round(i.b)}},C=function(e){var o=x(e),r=[o.r.toString(16),o.g.toString(16),o.b.toString(16)];return i.each(r,function(e,i){1==i.length&&(r[e]=\"0\"+i)}),r.join(\"\")},P=function(e){var i=/[0-9]{1,3}/g,o=e.match(i)||[];return{r:o[0],g:o[1],b:o[2]}},B=i(window),w=i(document),D=function(e){var r=this;r.index=++o.index,r.config=i.extend({},r.config,o.config,e),r.render()};D.prototype.config={color:\"\",size:null,alpha:!1,format:\"hex\",predefine:!1,colors:[\"#009688\",\"#5FB878\",\"#1E9FFF\",\"#FF5722\",\"#FFB800\",\"#01AAED\",\"#999\",\"#c00\",\"#ff8c00\",\"#ffd700\",\"#90ee90\",\"#00ced1\",\"#1e90ff\",\"#c71585\",\"rgb(0, 186, 189)\",\"rgb(255, 120, 0)\",\"rgb(250, 212, 0)\",\"#393D49\",\"rgba(0,0,0,.5)\",\"rgba(255, 69, 0, 0.68)\",\"rgba(144, 240, 144, 0.5)\",\"rgba(31, 147, 255, 0.73)\"]},D.prototype.render=function(){var e=this,o=e.config,r=i(['<div class=\"layui-unselect layui-colorpicker\">',\"<span \"+(\"rgb\"==o.format&&o.alpha?'class=\"layui-colorpicker-trigger-bgcolor\"':\"\")+\">\",'<span class=\"layui-colorpicker-trigger-span\" ','lay-type=\"'+(\"rgb\"==o.format?o.alpha?\"rgba\":\"torgb\":\"\")+'\" ','style=\"'+function(){var e=\"\";return o.color?(e=o.color,(o.color.match(/[0-9]{1,3}/g)||[]).length>3&&(o.alpha&&\"rgb\"==o.format||(e=\"#\"+C(k(P(o.color))))),\"background: \"+e):e}()+'\">','<i class=\"layui-icon layui-colorpicker-trigger-i '+(o.color?a:s)+'\"></i>',\"</span>\",\"</span>\",\"</div>\"].join(\"\")),t=i(o.elem);o.size&&r.addClass(\"layui-colorpicker-\"+o.size),t.addClass(\"layui-inline\").html(e.elemColorBox=r),e.color=e.elemColorBox.find(\".\"+f)[0].style.background,e.events()},D.prototype.renderPicker=function(){var e=this,o=e.config,r=e.elemColorBox[0],t=e.elemPicker=i(['<div id=\"layui-colorpicker'+e.index+'\" data-index=\"'+e.index+'\" class=\"layui-anim layui-anim-upbit layui-colorpicker-main\">','<div class=\"layui-colorpicker-main-wrapper\">','<div class=\"layui-colorpicker-basis\">','<div class=\"layui-colorpicker-basis-white\"></div>','<div class=\"layui-colorpicker-basis-black\"></div>','<div class=\"layui-colorpicker-basis-cursor\"></div>',\"</div>\",'<div class=\"layui-colorpicker-side\">','<div class=\"layui-colorpicker-side-slider\"></div>',\"</div>\",\"</div>\",'<div class=\"layui-colorpicker-main-alpha '+(o.alpha?n:\"\")+'\">','<div class=\"layui-colorpicker-alpha-bgcolor\">','<div class=\"layui-colorpicker-alpha-slider\"></div>',\"</div>\",\"</div>\",function(){if(o.predefine){var e=['<div class=\"layui-colorpicker-main-pre\">'];return layui.each(o.colors,function(i,o){e.push(['<div class=\"layui-colorpicker-pre'+((o.match(/[0-9]{1,3}/g)||[]).length>3?\" layui-colorpicker-pre-isalpha\":\"\")+'\">','<div style=\"background:'+o+'\"></div>',\"</div>\"].join(\"\"))}),e.push(\"</div>\"),e.join(\"\")}return\"\"}(),'<div class=\"layui-colorpicker-main-input\">','<div class=\"layui-inline\">','<input type=\"text\" class=\"layui-input\">',\"</div>\",'<div class=\"layui-btn-container\">','<button class=\"layui-btn layui-btn-primary layui-btn-sm\" colorpicker-events=\"clear\">清空</button>','<button class=\"layui-btn layui-btn-sm\" colorpicker-events=\"confirm\">确定</button>',\"</div\",\"</div>\",\"</div>\"].join(\"\"));e.elemColorBox.find(\".\"+f)[0];i(c)[0]&&i(c).data(\"index\")==e.index?e.removePicker(D.thisElemInd):(e.removePicker(D.thisElemInd),i(\"body\").append(t)),D.thisElemInd=e.index,D.thisColor=r.style.background,e.position(),e.pickerEvents()},D.prototype.removePicker=function(e){var o=this;o.config;return i(\"#layui-colorpicker\"+(e||o.index)).remove(),o},D.prototype.position=function(){var e=this,i=e.config,o=e.bindElem||e.elemColorBox[0],r=e.elemPicker[0],t=o.getBoundingClientRect(),n=r.offsetWidth,l=r.offsetHeight,c=function(e){return e=e?\"scrollLeft\":\"scrollTop\",document.body[e]|document.documentElement[e]},a=function(e){return document.documentElement[e?\"clientWidth\":\"clientHeight\"]},s=5,f=t.left,d=t.bottom;f-=(n-o.offsetWidth)/2,d+=s,f+n+s>a(\"width\")?f=a(\"width\")-n-s:f<s&&(f=s),d+l+s>a()&&(d=t.top>l?t.top-l:a()-l,d-=2*s),i.position&&(r.style.position=i.position),r.style.left=f+(\"fixed\"===i.position?0:c(1))+\"px\",r.style.top=d+(\"fixed\"===i.position?0:c())+\"px\"},D.prototype.val=function(){var e=this,i=(e.config,e.elemColorBox.find(\".\"+f)),o=e.elemPicker.find(\".\"+b),r=i[0],t=r.style.backgroundColor;if(t){var n=k(P(t)),l=i.attr(\"lay-type\");if(e.select(n.h,n.s,n.b),\"torgb\"===l&&o.find(\"input\").val(t),\"rgba\"===l){var c=P(t);if(3==(t.match(/[0-9]{1,3}/g)||[]).length)o.find(\"input\").val(\"rgba(\"+c.r+\", \"+c.g+\", \"+c.b+\", 1)\"),e.elemPicker.find(\".\"+h).css(\"left\",280);else{o.find(\"input\").val(t);var a=280*t.slice(t.lastIndexOf(\",\")+1,t.length-1);e.elemPicker.find(\".\"+h).css(\"left\",a)}e.elemPicker.find(\".\"+v)[0].style.background=\"linear-gradient(to right, rgba(\"+c.r+\", \"+c.g+\", \"+c.b+\", 0), rgb(\"+c.r+\", \"+c.g+\", \"+c.b+\"))\"}}else e.select(0,100,100),o.find(\"input\").val(\"\"),e.elemPicker.find(\".\"+v)[0].style.background=\"\",e.elemPicker.find(\".\"+h).css(\"left\",280)},D.prototype.side=function(){var e=this,o=e.config,r=e.elemColorBox.find(\".\"+f),t=r.attr(\"lay-type\"),n=e.elemPicker.find(\".\"+u),l=e.elemPicker.find(\".\"+p),c=e.elemPicker.find(\".\"+g),y=e.elemPicker.find(\".\"+m),C=e.elemPicker.find(\".\"+v),w=e.elemPicker.find(\".\"+h),D=l[0].offsetTop/180*360,E=100-(y[0].offsetTop+3)/180*100,H=(y[0].offsetLeft+3)/260*100,W=Math.round(w[0].offsetLeft/280*100)/100,j=e.elemColorBox.find(\".\"+d),F=e.elemPicker.find(\".layui-colorpicker-pre\").children(\"div\"),L=function(i,n,l,c){e.select(i,n,l);var f=x({h:i,s:n,b:l});if(j.addClass(a).removeClass(s),r[0].style.background=\"rgb(\"+f.r+\", \"+f.g+\", \"+f.b+\")\",\"torgb\"===t&&e.elemPicker.find(\".\"+b).find(\"input\").val(\"rgb(\"+f.r+\", \"+f.g+\", \"+f.b+\")\"),\"rgba\"===t){var d=0;d=280*c,w.css(\"left\",d),e.elemPicker.find(\".\"+b).find(\"input\").val(\"rgba(\"+f.r+\", \"+f.g+\", \"+f.b+\", \"+c+\")\"),r[0].style.background=\"rgba(\"+f.r+\", \"+f.g+\", \"+f.b+\", \"+c+\")\",C[0].style.background=\"linear-gradient(to right, rgba(\"+f.r+\", \"+f.g+\", \"+f.b+\", 0), rgb(\"+f.r+\", \"+f.g+\", \"+f.b+\"))\"}o.change&&o.change(e.elemPicker.find(\".\"+b).find(\"input\").val())},M=i(['<div class=\"layui-auxiliar-moving\" id=\"LAY-colorpicker-moving\"></div'].join(\"\")),Y=function(e){i(\"#LAY-colorpicker-moving\")[0]||i(\"body\").append(M),M.on(\"mousemove\",e),M.on(\"mouseup\",function(){M.remove()}).on(\"mouseleave\",function(){M.remove()})};l.on(\"mousedown\",function(e){var i=this.offsetTop,o=e.clientY,r=function(e){var r=i+(e.clientY-o),t=n[0].offsetHeight;r<0&&(r=0),r>t&&(r=t);var l=r/180*360;D=l,L(l,H,E,W),e.preventDefault()};Y(r),e.preventDefault()}),n.on(\"click\",function(e){var o=e.clientY-i(this).offset().top;o<0&&(o=0),o>this.offsetHeight&&(o=this.offsetHeight);var r=o/180*360;D=r,L(r,H,E,W),e.preventDefault()}),y.on(\"mousedown\",function(e){var i=this.offsetTop,o=this.offsetLeft,r=e.clientY,t=e.clientX,n=function(e){var n=i+(e.clientY-r),l=o+(e.clientX-t),a=c[0].offsetHeight-3,s=c[0].offsetWidth-3;n<-3&&(n=-3),n>a&&(n=a),l<-3&&(l=-3),l>s&&(l=s);var f=(l+3)/260*100,d=100-(n+3)/180*100;E=d,H=f,L(D,f,d,W),e.preventDefault()};layui.stope(e),Y(n),e.preventDefault()}),c.on(\"mousedown\",function(e){var o=e.clientY-i(this).offset().top-3+B.scrollTop(),r=e.clientX-i(this).offset().left-3+B.scrollLeft();o<-3&&(o=-3),o>this.offsetHeight-3&&(o=this.offsetHeight-3),r<-3&&(r=-3),r>this.offsetWidth-3&&(r=this.offsetWidth-3);var t=(r+3)/260*100,n=100-(o+3)/180*100;E=n,H=t,L(D,t,n,W),e.preventDefault(),y.trigger(e,\"mousedown\")}),w.on(\"mousedown\",function(e){var i=this.offsetLeft,o=e.clientX,r=function(e){var r=i+(e.clientX-o),t=C[0].offsetWidth;r<0&&(r=0),r>t&&(r=t);var n=Math.round(r/280*100)/100;W=n,L(D,H,E,n),e.preventDefault()};Y(r),e.preventDefault()}),C.on(\"click\",function(e){var o=e.clientX-i(this).offset().left;o<0&&(o=0),o>this.offsetWidth&&(o=this.offsetWidth);var r=Math.round(o/280*100)/100;W=r,L(D,H,E,r),e.preventDefault()}),F.each(function(){i(this).on(\"click\",function(){i(this).parent(\".layui-colorpicker-pre\").addClass(\"selected\").siblings().removeClass(\"selected\");var e,o=this.style.backgroundColor,r=k(P(o)),t=o.slice(o.lastIndexOf(\",\")+1,o.length-1);D=r.h,H=r.s,E=r.b,3==(o.match(/[0-9]{1,3}/g)||[]).length&&(t=1),W=t,e=280*t,L(r.h,r.s,r.b,t)})})},D.prototype.select=function(e,i,o,r){var t=this,n=(t.config,C({h:e,s:100,b:100})),l=C({h:e,s:i,b:o}),c=e/360*180,a=180-o/100*180-3,s=i/100*260-3;t.elemPicker.find(\".\"+p).css(\"top\",c),t.elemPicker.find(\".\"+g)[0].style.background=\"#\"+n,t.elemPicker.find(\".\"+m).css({top:a,left:s}),\"change\"!==r&&t.elemPicker.find(\".\"+b).find(\"input\").val(\"#\"+l)},D.prototype.pickerEvents=function(){var e=this,o=e.config,r=e.elemColorBox.find(\".\"+f),t=e.elemPicker.find(\".\"+b+\" input\"),n={clear:function(i){r[0].style.background=\"\",e.elemColorBox.find(\".\"+d).removeClass(a).addClass(s),e.color=\"\",o.done&&o.done(\"\"),e.removePicker()},confirm:function(i,n){var l=t.val(),c=l,f={};if(l.indexOf(\",\")>-1){if(f=k(P(l)),e.select(f.h,f.s,f.b),r[0].style.background=c=\"#\"+C(f),(l.match(/[0-9]{1,3}/g)||[]).length>3&&\"rgba\"===r.attr(\"lay-type\")){var u=280*l.slice(l.lastIndexOf(\",\")+1,l.length-1);e.elemPicker.find(\".\"+h).css(\"left\",u),r[0].style.background=l,c=l}}else f=y(l),r[0].style.background=c=\"#\"+C(f),e.elemColorBox.find(\".\"+d).removeClass(s).addClass(a);return\"change\"===n?(e.select(f.h,f.s,f.b,n),void(o.change&&o.change(c))):(e.color=l,o.done&&o.done(l),void e.removePicker())}};e.elemPicker.on(\"click\",\"*[colorpicker-events]\",function(){var e=i(this),o=e.attr(\"colorpicker-events\");n[o]&&n[o].call(this,e)}),t.on(\"keyup\",function(e){var o=i(this);n.confirm.call(this,o,13===e.keyCode?null:\"change\")})},D.prototype.events=function(){var e=this,o=e.config,r=e.elemColorBox.find(\".\"+f);e.elemColorBox.on(\"click\",function(){e.renderPicker(),i(c)[0]&&(e.val(),e.side())}),o.elem[0]&&!e.elemColorBox[0].eventHandler&&(w.on(\"click\",function(o){if(!i(o.target).hasClass(l)&&!i(o.target).parents(\".\"+l)[0]&&!i(o.target).hasClass(c.replace(/\\./g,\"\"))&&!i(o.target).parents(c)[0]&&e.elemPicker){if(e.color){var t=k(P(e.color));e.select(t.h,t.s,t.b)}else e.elemColorBox.find(\".\"+d).removeClass(a).addClass(s);r[0].style.background=e.color||\"\",e.removePicker()}}),B.on(\"resize\",function(){return!(!e.elemPicker||!i(c)[0])&&void e.position()}),e.elemColorBox[0].eventHandler=!0)},o.render=function(e){var i=new D(e);return r.call(i)},e(t,o)});layui.define(\"layer\",function(e){\"use strict\";var t=layui.$,i=layui.layer,a=layui.hint(),n=layui.device(),l=\"form\",r=\".layui-form\",s=\"layui-this\",o=\"layui-hide\",c=\"layui-disabled\",u=function(){this.config={verify:{required:[/[\\S]+/,\"必填项不能为空\"],phone:[/^1\\d{10}$/,\"请输入正确的手机号\"],email:[/^([a-zA-Z0-9_\\.\\-])+\\@(([a-zA-Z0-9\\-])+\\.)+([a-zA-Z0-9]{2,4})+$/,\"邮箱格式不正确\"],url:[/(^#)|(^http(s*):\\/\\/[^\\s]+\\.[^\\s]+)/,\"链接格式不正确\"],number:function(e){if(!e||isNaN(e))return\"只能填写数字\"},date:[/^(\\d{4})[-\\/](\\d{1}|0\\d{1}|1[0-2])([-\\/](\\d{1}|0\\d{1}|[1-2][0-9]|3[0-1]))*$/,\"日期格式不正确\"],identity:[/(^\\d{15}$)|(^\\d{17}(x|X|\\d)$)/,\"请输入正确的身份证号\"]}}};u.prototype.set=function(e){var i=this;return t.extend(!0,i.config,e),i},u.prototype.verify=function(e){var i=this;return t.extend(!0,i.config.verify,e),i},u.prototype.on=function(e,t){return layui.onevent.call(this,l,e,t)},u.prototype.val=function(e,i){var a=t(r+'[lay-filter=\"'+e+'\"]');a.each(function(e,a){var n=t(this);layui.each(i,function(e,t){var i,a=n.find('[name=\"'+e+'\"]');a[0]&&(i=a[0].type,\"checkbox\"===i?a[0].checked=t:\"radio\"===i?a.each(function(){this.value===t&&(this.checked=!0)}):a.val(t))})}),f.render(null,e)},u.prototype.render=function(e,i){var n=this,u=t(r+function(){return i?'[lay-filter=\"'+i+'\"]':\"\"}()),d={select:function(){var e,i=\"请选择\",a=\"layui-form-select\",n=\"layui-select-title\",r=\"layui-select-none\",d=\"\",f=u.find(\"select\"),v=function(i,l){t(i.target).parent().hasClass(n)&&!l||(t(\".\"+a).removeClass(a+\"ed \"+a+\"up\"),e&&d&&e.val(d)),e=null},y=function(i,u,f){var y,p=t(this),m=i.find(\".\"+n),k=m.find(\"input\"),x=i.find(\"dl\"),g=x.children(\"dd\"),b=this.selectedIndex;if(!u){var C=function(){var e=i.offset().top+i.outerHeight()+5-h.scrollTop(),t=x.outerHeight();b=p[0].selectedIndex,i.addClass(a+\"ed\"),g.removeClass(o),y=null,g.eq(b).addClass(s).siblings().removeClass(s),e+t>h.height()&&e>=t&&i.addClass(a+\"up\"),$()},w=function(e){i.removeClass(a+\"ed \"+a+\"up\"),k.blur(),y=null,e||T(k.val(),function(e){var i=p[0].selectedIndex;e&&(d=t(p[0].options[i]).html(),0===i&&d===k.attr(\"placeholder\")&&(d=\"\"),k.val(d||\"\"))})},$=function(){var e=x.children(\"dd.\"+s);if(e[0]){var t=e.position().top,i=x.height(),a=e.height();t>i&&x.scrollTop(t+x.scrollTop()-i+a-5),t<0&&x.scrollTop(t+x.scrollTop()-5)}};m.on(\"click\",function(e){i.hasClass(a+\"ed\")?w():(v(e,!0),C()),x.find(\".\"+r).remove()}),m.find(\".layui-edge\").on(\"click\",function(){k.focus()}),k.on(\"keyup\",function(e){var t=e.keyCode;9===t&&C()}).on(\"keydown\",function(e){var t=e.keyCode;9===t&&w();var i=function(t,a){var n,l;e.preventDefault();var r=function(){var e=x.children(\"dd.\"+s);if(x.children(\"dd.\"+o)[0]&&\"next\"===t){var i=x.children(\"dd:not(.\"+o+\",.\"+c+\")\"),n=i.eq(0).index();if(n>=0&&n<e.index()&&!i.hasClass(s))return i.eq(0).prev()[0]?i.eq(0).prev():x.children(\":last\")}return a&&a[0]?a:y&&y[0]?y:e}();return l=r[t](),n=r[t](\"dd:not(.\"+o+\")\"),l[0]?(y=r[t](),n[0]&&!n.hasClass(c)||!y[0]?(n.addClass(s).siblings().removeClass(s),void $()):i(t,y)):y=null};38===t&&i(\"prev\"),40===t&&i(\"next\"),13===t&&(e.preventDefault(),x.children(\"dd.\"+s).trigger(\"click\"))});var T=function(e,i,a){var n=0;layui.each(g,function(){var i=t(this),l=i.text(),r=l.indexOf(e)===-1;(\"\"===e||\"blur\"===a?e!==l:r)&&n++,\"keyup\"===a&&i[r?\"addClass\":\"removeClass\"](o)});var l=n===g.length;return i(l),l},j=function(e){var t=this.value,i=e.keyCode;return 9!==i&&13!==i&&37!==i&&38!==i&&39!==i&&40!==i&&(T(t,function(e){e?x.find(\".\"+r)[0]||x.append('<p class=\"'+r+'\">无匹配项</p>'):x.find(\".\"+r).remove()},\"keyup\"),\"\"===t&&x.find(\".\"+r).remove(),void $())};f&&k.on(\"keyup\",j).on(\"blur\",function(i){var a=p[0].selectedIndex;e=k,d=t(p[0].options[a]).html(),0===a&&d===k.attr(\"placeholder\")&&(d=\"\"),setTimeout(function(){T(k.val(),function(e){d||k.val(\"\")},\"blur\")},200)}),g.on(\"click\",function(){var e=t(this),a=e.attr(\"lay-value\"),n=p.attr(\"lay-filter\");return!e.hasClass(c)&&(e.hasClass(\"layui-select-tips\")?k.val(\"\"):(k.val(e.text()),e.addClass(s)),e.siblings().removeClass(s),p.val(a).removeClass(\"layui-form-danger\"),layui.event.call(this,l,\"select(\"+n+\")\",{elem:p[0],value:a,othis:i}),w(!0),!1)}),i.find(\"dl>dt\").on(\"click\",function(e){return!1}),t(document).off(\"click\",v).on(\"click\",v)}};f.each(function(e,l){var r=t(this),o=r.next(\".\"+a),u=this.disabled,d=l.value,f=t(l.options[l.selectedIndex]),v=l.options[0];if(\"string\"==typeof r.attr(\"lay-ignore\"))return r.show();var h=\"string\"==typeof r.attr(\"lay-search\"),p=v?v.value?i:v.innerHTML||i:i,m=t(['<div class=\"'+(h?\"\":\"layui-unselect \")+a,(u?\" layui-select-disabled\":\"\")+'\">','<div class=\"'+n+'\">','<input type=\"text\" placeholder=\"'+p+'\" '+('value=\"'+(d?f.html():\"\")+'\"')+(h?\"\":\" readonly\")+' class=\"layui-input'+(h?\"\":\" layui-unselect\")+(u?\" \"+c:\"\")+'\">','<i class=\"layui-edge\"></i></div>','<dl class=\"layui-anim layui-anim-upbit'+(r.find(\"optgroup\")[0]?\" layui-select-group\":\"\")+'\">',function(e){var t=[];return layui.each(e,function(e,a){0!==e||a.value?\"optgroup\"===a.tagName.toLowerCase()?t.push(\"<dt>\"+a.label+\"</dt>\"):t.push('<dd lay-value=\"'+a.value+'\" class=\"'+(d===a.value?s:\"\")+(a.disabled?\" \"+c:\"\")+'\">'+a.innerHTML+\"</dd>\"):t.push('<dd lay-value=\"\" class=\"layui-select-tips\">'+(a.innerHTML||i)+\"</dd>\")}),0===t.length&&t.push('<dd lay-value=\"\" class=\"'+c+'\">没有选项</dd>'),t.join(\"\")}(r.find(\"*\"))+\"</dl>\",\"</div>\"].join(\"\"));o[0]&&o.remove(),r.after(m),y.call(this,m,u,h)})},checkbox:function(){var e={checkbox:[\"layui-form-checkbox\",\"layui-form-checked\",\"checkbox\"],_switch:[\"layui-form-switch\",\"layui-form-onswitch\",\"switch\"]},i=u.find(\"input[type=checkbox]\"),a=function(e,i){var a=t(this);e.on(\"click\",function(){var t=a.attr(\"lay-filter\"),n=(a.attr(\"lay-text\")||\"\").split(\"|\");a[0].disabled||(a[0].checked?(a[0].checked=!1,e.removeClass(i[1]).find(\"em\").text(n[1])):(a[0].checked=!0,e.addClass(i[1]).find(\"em\").text(n[0])),layui.event.call(a[0],l,i[2]+\"(\"+t+\")\",{elem:a[0],value:a[0].value,othis:e}))})};i.each(function(i,n){var l=t(this),r=l.attr(\"lay-skin\"),s=(l.attr(\"lay-text\")||\"\").split(\"|\"),o=this.disabled;\"switch\"===r&&(r=\"_\"+r);var u=e[r]||e.checkbox;if(\"string\"==typeof l.attr(\"lay-ignore\"))return l.show();var d=l.next(\".\"+u[0]),f=t(['<div class=\"layui-unselect '+u[0],n.checked?\" \"+u[1]:\"\",o?\" layui-checkbox-disbaled \"+c:\"\",'\"',r?' lay-skin=\"'+r+'\"':\"\",\">\",function(){var e=n.title.replace(/\\s/g,\"\"),t={checkbox:[e?\"<span>\"+n.title+\"</span>\":\"\",'<i class=\"layui-icon layui-icon-ok\"></i>'].join(\"\"),_switch:\"<em>\"+((n.checked?s[0]:s[1])||\"\")+\"</em><i></i>\"};return t[r]||t.checkbox}(),\"</div>\"].join(\"\"));d[0]&&d.remove(),l.after(f),a.call(this,f,u)})},radio:function(){var e=\"layui-form-radio\",i=[\"&#xe643;\",\"&#xe63f;\"],a=u.find(\"input[type=radio]\"),n=function(a){var n=t(this),s=\"layui-anim-scaleSpring\";a.on(\"click\",function(){var o=n[0].name,c=n.parents(r),u=n.attr(\"lay-filter\"),d=c.find(\"input[name=\"+o.replace(/(\\.|#|\\[|\\])/g,\"\\\\$1\")+\"]\");n[0].disabled||(layui.each(d,function(){var a=t(this).next(\".\"+e);this.checked=!1,a.removeClass(e+\"ed\"),a.find(\".layui-icon\").removeClass(s).html(i[1])}),n[0].checked=!0,a.addClass(e+\"ed\"),a.find(\".layui-icon\").addClass(s).html(i[0]),layui.event.call(n[0],l,\"radio(\"+u+\")\",{elem:n[0],value:n[0].value,othis:a}))})};a.each(function(a,l){var r=t(this),s=r.next(\".\"+e),o=this.disabled;if(\"string\"==typeof r.attr(\"lay-ignore\"))return r.show();s[0]&&s.remove();var u=t(['<div class=\"layui-unselect '+e,l.checked?\" \"+e+\"ed\":\"\",(o?\" layui-radio-disbaled \"+c:\"\")+'\">','<i class=\"layui-anim layui-icon\">'+i[l.checked?0:1]+\"</i>\",\"<div>\"+function(){var e=l.title||\"\";return\"string\"==typeof r.next().attr(\"lay-radio\")&&(e=r.next().html(),r.next().remove()),e}()+\"</div>\",\"</div>\"].join(\"\"));r.after(u),n.call(this,u)})}};return e?d[e]?d[e]():a.error(\"不支持的\"+e+\"表单渲染\"):layui.each(d,function(e,t){t()}),n};var d=function(){var e=t(this),a=f.config.verify,s=null,o=\"layui-form-danger\",c={},u=e.parents(r),d=u.find(\"*[lay-verify]\"),v=e.parents(\"form\")[0],h=u.find(\"input,select,textarea\"),y=e.attr(\"lay-filter\");if(layui.each(d,function(e,l){var r=t(this),c=r.attr(\"lay-verify\").split(\"|\"),u=r.attr(\"lay-verType\"),d=r.val();if(r.removeClass(o),layui.each(c,function(e,t){var c,f=\"\",v=\"function\"==typeof a[t];if(a[t]){var c=v?f=a[t](d,l):!a[t][0].test(d);if(f=f||a[t][1],c)return\"tips\"===u?i.tips(f,function(){return\"string\"==typeof r.attr(\"lay-ignore\")||\"select\"!==l.tagName.toLowerCase()&&!/^checkbox|radio$/.test(l.type)?r:r.next()}(),{tips:1}):\"alert\"===u?i.alert(f,{title:\"提示\",shadeClose:!0}):i.msg(f,{icon:5,shift:6}),n.android||n.ios||l.focus(),r.addClass(o),s=!0}}),s)return s}),s)return!1;var p={};return layui.each(h,function(e,t){if(t.name=(t.name||\"\").replace(/^\\s*|\\s*&/,\"\"),t.name){if(/^.*\\[\\]$/.test(t.name)){var i=t.name.match(/^(.*)\\[\\]$/g)[0];p[i]=0|p[i],t.name=t.name.replace(/^(.*)\\[\\]$/,\"$1[\"+p[i]++ +\"]\")}/^checkbox|radio$/.test(t.type)&&!t.checked||(c[t.name]=t.value)}}),layui.event.call(this,l,\"submit(\"+y+\")\",{elem:this,form:v,field:c})},f=new u,v=t(document),h=t(window);f.render(),v.on(\"reset\",r,function(){var e=t(this).attr(\"lay-filter\");setTimeout(function(){f.render(null,e)},50)}),v.on(\"submit\",r,d).on(\"click\",\"*[lay-submit]\",d),e(l,f)});layui.define(\"jquery\",function(e){\"use strict\";var o=layui.$,a=layui.hint(),i=\"layui-tree-enter\",r=function(e){this.options=e},t={arrow:[\"&#xe623;\",\"&#xe625;\"],checkbox:[\"&#xe626;\",\"&#xe627;\"],radio:[\"&#xe62b;\",\"&#xe62a;\"],branch:[\"&#xe622;\",\"&#xe624;\"],leaf:\"&#xe621;\"};r.prototype.init=function(e){var o=this;e.addClass(\"layui-box layui-tree\"),o.options.skin&&e.addClass(\"layui-tree-skin-\"+o.options.skin),o.tree(e),o.on(e)},r.prototype.tree=function(e,a){var i=this,r=i.options,n=a||r.nodes;layui.each(n,function(a,n){var l=n.children&&n.children.length>0,c=o('<ul class=\"'+(n.spread?\"layui-show\":\"\")+'\"></ul>'),s=o([\"<li \"+(n.spread?'data-spread=\"'+n.spread+'\"':\"\")+\">\",function(){return l?'<i class=\"layui-icon layui-tree-spread\">'+(n.spread?t.arrow[1]:t.arrow[0])+\"</i>\":\"\"}(),function(){return r.check?'<i class=\"layui-icon layui-tree-check\">'+(\"checkbox\"===r.check?t.checkbox[0]:\"radio\"===r.check?t.radio[0]:\"\")+\"</i>\":\"\"}(),function(){return'<a href=\"'+(n.href||\"javascript:;\")+'\" '+(r.target&&n.href?'target=\"'+r.target+'\"':\"\")+\">\"+('<i class=\"layui-icon layui-tree-'+(l?\"branch\":\"leaf\")+'\">'+(l?n.spread?t.branch[1]:t.branch[0]:t.leaf)+\"</i>\")+(\"<cite>\"+(n.name||\"未命名\")+\"</cite></a>\")}(),\"</li>\"].join(\"\"));l&&(s.append(c),i.tree(c,n.children)),e.append(s),\"function\"==typeof r.click&&i.click(s,n),i.spread(s,n),r.drag&&i.drag(s,n)})},r.prototype.click=function(e,o){var a=this,i=a.options;e.children(\"a\").on(\"click\",function(e){layui.stope(e),i.click(o)})},r.prototype.spread=function(e,o){var a=this,i=(a.options,e.children(\".layui-tree-spread\")),r=e.children(\"ul\"),n=e.children(\"a\"),l=function(){e.data(\"spread\")?(e.data(\"spread\",null),r.removeClass(\"layui-show\"),i.html(t.arrow[0]),n.find(\".layui-icon\").html(t.branch[0])):(e.data(\"spread\",!0),r.addClass(\"layui-show\"),i.html(t.arrow[1]),n.find(\".layui-icon\").html(t.branch[1]))};r[0]&&(i.on(\"click\",l),n.on(\"dblclick\",l))},r.prototype.on=function(e){var a=this,r=a.options,t=\"layui-tree-drag\";e.find(\"i\").on(\"selectstart\",function(e){return!1}),r.drag&&o(document).on(\"mousemove\",function(e){var i=a.move;if(i.from){var r=(i.to,o('<div class=\"layui-box '+t+'\"></div>'));e.preventDefault(),o(\".\"+t)[0]||o(\"body\").append(r);var n=o(\".\"+t)[0]?o(\".\"+t):r;n.addClass(\"layui-show\").html(i.from.elem.children(\"a\").html()),n.css({left:e.pageX+10,top:e.pageY+10})}}).on(\"mouseup\",function(){var e=a.move;e.from&&(e.from.elem.children(\"a\").removeClass(i),e.to&&e.to.elem.children(\"a\").removeClass(i),a.move={},o(\".\"+t).remove())})},r.prototype.move={},r.prototype.drag=function(e,a){var r=this,t=(r.options,e.children(\"a\")),n=function(){var t=o(this),n=r.move;n.from&&(n.to={item:a,elem:e},t.addClass(i))};t.on(\"mousedown\",function(){var o=r.move;o.from={item:a,elem:e}}),t.on(\"mouseenter\",n).on(\"mousemove\",n).on(\"mouseleave\",function(){var e=o(this),a=r.move;a.from&&(delete a.to,e.removeClass(i))})},e(\"tree\",function(e){var i=new r(e=e||{}),t=o(e.elem);return t[0]?void i.init(t):a.error(\"layui.tree 没有找到\"+e.elem+\"元素\")})});layui.define([\"laytpl\",\"laypage\",\"layer\",\"form\",\"util\"],function(e){\"use strict\";var t=layui.$,i=layui.laytpl,a=layui.laypage,l=layui.layer,n=layui.form,o=(layui.util,layui.hint()),r=layui.device(),d={config:{checkName:\"LAY_CHECKED\",indexName:\"LAY_TABLE_INDEX\"},cache:{},index:layui.table?layui.table.index+1e4:0,set:function(e){var i=this;return i.config=t.extend({},i.config,e),i},on:function(e,t){return layui.onevent.call(this,u,e,t)}},c=function(){var e=this,t=e.config,i=t.id||t.index;return i&&(c.that[i]=e,c.config[i]=t),{reload:function(t){e.reload.call(e,t)},setColsWidth:function(){e.setColsWidth.call(e)},resize:function(){e.resize.call(e)},config:t}},s=function(e){var t=c.config[e];return t||o.error(\"The ID option was not found in the table instance\"),t||null},u=\"table\",h=\".layui-table\",y=\"layui-hide\",f=\"layui-none\",p=\"layui-table-view\",v=\".layui-table-tool\",m=\".layui-table-box\",g=\".layui-table-init\",b=\".layui-table-header\",x=\".layui-table-body\",k=\".layui-table-main\",C=\".layui-table-fixed\",w=\".layui-table-fixed-l\",T=\".layui-table-fixed-r\",A=\".layui-table-total\",L=\".layui-table-page\",S=\".layui-table-sort\",N=\"layui-table-edit\",W=\"layui-table-hover\",_=function(e){var t='{{#if(item2.colspan){}} colspan=\"{{item2.colspan}}\"{{#} if(item2.rowspan){}} rowspan=\"{{item2.rowspan}}\"{{#}}}';return e=e||{},['<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" class=\"layui-table\" ','{{# if(d.data.skin){ }}lay-skin=\"{{d.data.skin}}\"{{# } }} {{# if(d.data.size){ }}lay-size=\"{{d.data.size}}\"{{# } }} {{# if(d.data.even){ }}lay-even{{# } }}>',\"<thead>\",\"{{# layui.each(d.data.cols, function(i1, item1){ }}\",\"<tr>\",\"{{# layui.each(item1, function(i2, item2){ }}\",'{{# if(item2.fixed && item2.fixed !== \"right\"){ left = true; } }}','{{# if(item2.fixed === \"right\"){ right = true; } }}',function(){return e.fixed&&\"right\"!==e.fixed?'{{# if(item2.fixed && item2.fixed !== \"right\"){ }}':\"right\"===e.fixed?'{{# if(item2.fixed === \"right\"){ }}':\"\"}(),\"{{# var isSort = !(item2.colGroup) && item2.sort; }}\",'<th data-field=\"{{ item2.field||i2 }}\" data-key=\"{{d.index}}-{{i1}}-{{i2}}\" {{# if( item2.parentKey){ }}data-parentkey=\"{{ item2.parentKey }}\"{{# } }} {{# if(item2.minWidth){ }}data-minwidth=\"{{item2.minWidth}}\"{{# } }} '+t+' {{# if(item2.unresize || item2.colGroup){ }}data-unresize=\"true\"{{# } }} class=\"{{# if(item2.hide){ }}layui-hide{{# } }}{{# if(isSort){ }} layui-unselect{{# } }}{{# if(!item2.field){ }} layui-table-col-special{{# } }}\">','<div class=\"layui-table-cell laytable-cell-',\"{{# if(item2.colGroup){ }}\",\"group\",\"{{# } else { }}\",\"{{d.index}}-{{i1}}-{{i2}}\",'{{# if(item2.type !== \"normal\"){ }}',\" laytable-cell-{{ item2.type }}\",\"{{# } }}\",\"{{# } }}\",'\" {{#if(item2.align){}}align=\"{{item2.align}}\"{{#}}}>','{{# if(item2.type === \"checkbox\"){ }}','<input type=\"checkbox\" name=\"layTableCheckbox\" lay-skin=\"primary\" lay-filter=\"layTableAllChoose\" {{# if(item2[d.data.checkName]){ }}checked{{# }; }}>',\"{{# } else { }}\",'<span>{{item2.title||\"\"}}</span>',\"{{# if(isSort){ }}\",'<span class=\"layui-table-sort layui-inline\"><i class=\"layui-edge layui-table-sort-asc\" title=\"升序\"></i><i class=\"layui-edge layui-table-sort-desc\" title=\"降序\"></i></span>',\"{{# } }}\",\"{{# } }}\",\"</div>\",\"</th>\",e.fixed?\"{{# }; }}\":\"\",\"{{# }); }}\",\"</tr>\",\"{{# }); }}\",\"</thead>\",\"</table>\"].join(\"\")},E=['<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" class=\"layui-table\" ','{{# if(d.data.skin){ }}lay-skin=\"{{d.data.skin}}\"{{# } }} {{# if(d.data.size){ }}lay-size=\"{{d.data.size}}\"{{# } }} {{# if(d.data.even){ }}lay-even{{# } }}>',\"<tbody></tbody>\",\"</table>\"].join(\"\"),z=['<div class=\"layui-form layui-border-box {{d.VIEW_CLASS}}\" lay-filter=\"LAY-table-{{d.index}}\" lay-id=\"{{ d.data.id }}\" style=\"{{# if(d.data.width){ }}width:{{d.data.width}}px;{{# } }} {{# if(d.data.height){ }}height:{{d.data.height}}px;{{# } }}\">',\"{{# if(d.data.toolbar){ }}\",'<div class=\"layui-table-tool\">','<div class=\"layui-table-tool-temp\"></div>','<div class=\"layui-table-tool-self\"></div>',\"</div>\",\"{{# } }}\",'<div class=\"layui-table-box\">',\"{{# if(d.data.loading){ }}\",'<div class=\"layui-table-init\" style=\"background-color: #fff;\">','<i class=\"layui-icon layui-icon-loading layui-icon\"></i>',\"</div>\",\"{{# } }}\",\"{{# var left, right; }}\",'<div class=\"layui-table-header\">',_(),\"</div>\",'<div class=\"layui-table-body layui-table-main\">',E,\"</div>\",\"{{# if(left){ }}\",'<div class=\"layui-table-fixed layui-table-fixed-l\">','<div class=\"layui-table-header\">',_({fixed:!0}),\"</div>\",'<div class=\"layui-table-body\">',E,\"</div>\",\"</div>\",\"{{# }; }}\",\"{{# if(right){ }}\",'<div class=\"layui-table-fixed layui-table-fixed-r\">','<div class=\"layui-table-header\">',_({fixed:\"right\"}),'<div class=\"layui-table-mend\"></div>',\"</div>\",'<div class=\"layui-table-body\">',E,\"</div>\",\"</div>\",\"{{# }; }}\",\"</div>\",\"{{# if(d.data.totalRow){ }}\",'<div class=\"layui-table-total\">','<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" class=\"layui-table\" ','{{# if(d.data.skin){ }}lay-skin=\"{{d.data.skin}}\"{{# } }} {{# if(d.data.size){ }}lay-size=\"{{d.data.size}}\"{{# } }} {{# if(d.data.even){ }}lay-even{{# } }}>','<tbody><tr><td><div class=\"layui-table-cell\" style=\"visibility: hidden;\">Total</div></td></tr></tbody>',\"</table>\",\"</div>\",\"{{# } }}\",\"{{# if(d.data.page){ }}\",'<div class=\"layui-table-page\">','<div id=\"layui-table-page{{d.index}}\"></div>',\"</div>\",\"{{# } }}\",\"<style>\",\"{{# layui.each(d.data.cols, function(i1, item1){\",\"layui.each(item1, function(i2, item2){ }}\",\".laytable-cell-{{d.index}}-{{i1}}-{{i2}}{ \",\"{{# if(item2.width){ }}\",\"width: {{item2.width}}px;\",\"{{# } }}\",\" }\",\"{{# });\",\"}); }}\",\"</style>\",\"</div>\"].join(\"\"),H=t(window),R=t(document),F=function(e){var i=this;i.index=++d.index,i.config=t.extend({},i.config,d.config,e),i.render()};F.prototype.config={limit:10,loading:!0,cellMinWidth:60,defaultToolbar:[\"filter\",\"exports\",\"print\"],autoSort:!0,text:{none:\"无数据\"}},F.prototype.render=function(){var e=this,a=e.config;if(a.elem=t(a.elem),a.where=a.where||{},a.id=a.id||a.elem.attr(\"id\")||e.index,a.request=t.extend({pageName:\"page\",limitName:\"limit\"},a.request),a.response=t.extend({statusName:\"code\",statusCode:0,msgName:\"msg\",dataName:\"data\",countName:\"count\"},a.response),\"object\"==typeof a.page&&(a.limit=a.page.limit||a.limit,a.limits=a.page.limits||a.limits,e.page=a.page.curr=a.page.curr||1,delete a.page.elem,delete a.page.jump),!a.elem[0])return e;a.height&&/^full-\\d+$/.test(a.height)&&(e.fullHeightGap=a.height.split(\"-\")[1],a.height=H.height()-e.fullHeightGap),e.setInit();var l=a.elem,n=l.next(\".\"+p),o=e.elem=t(i(z).render({VIEW_CLASS:p,data:a,index:e.index}));if(a.index=e.index,n[0]&&n.remove(),l.after(o),e.layTool=o.find(v),e.layBox=o.find(m),e.layHeader=o.find(b),e.layMain=o.find(k),e.layBody=o.find(x),e.layFixed=o.find(C),e.layFixLeft=o.find(w),e.layFixRight=o.find(T),e.layTotal=o.find(A),e.layPage=o.find(L),e.renderToolbar(),e.fullSize(),a.cols.length>1){var r=e.layFixed.find(b).find(\"th\");r.height(e.layHeader.height()-1-parseFloat(r.css(\"padding-top\"))-parseFloat(r.css(\"padding-bottom\")))}e.pullData(e.page),e.events()},F.prototype.initOpts=function(e){var t=this,i=(t.config,{checkbox:48,radio:48,space:15,numbers:40});e.checkbox&&(e.type=\"checkbox\"),e.space&&(e.type=\"space\"),e.type||(e.type=\"normal\"),\"normal\"!==e.type&&(e.unresize=!0,e.width=e.width||i[e.type])},F.prototype.setInit=function(e){var t=this,i=t.config;return i.clientWidth=i.width||function(){var e=function(t){var a,l;t=t||i.elem.parent(),a=t.width();try{l=\"none\"===t.css(\"display\")}catch(n){}return!t[0]||a&&!l?a:e(t.parent())};return e()}(),\"width\"===e?i.clientWidth:void layui.each(i.cols,function(e,a){layui.each(a,function(l,n){if(!n)return void a.splice(l,1);if(n.key=e+\"-\"+l,n.hide=n.hide||!1,n.colGroup||n.colspan>1){var o=0;layui.each(i.cols[e+1],function(t,i){i.HAS_PARENT||o>1&&o==n.colspan||(i.HAS_PARENT=!0,i.parentKey=e+\"-\"+l,o+=parseInt(i.colspan>1?i.colspan:1))}),n.colGroup=!0}t.initOpts(n)})})},F.prototype.renderToolbar=function(){var e=this,a=e.config,l=['<div class=\"layui-inline\" lay-event=\"add\"><i class=\"layui-icon layui-icon-add-1\"></i></div>','<div class=\"layui-inline\" lay-event=\"update\"><i class=\"layui-icon layui-icon-edit\"></i></div>','<div class=\"layui-inline\" lay-event=\"delete\"><i class=\"layui-icon layui-icon-delete\"></i></div>'].join(\"\"),n=e.layTool.find(\".layui-table-tool-temp\");if(\"default\"===a.toolbar)n.html(l);else if(\"string\"==typeof a.toolbar){var o=t(a.toolbar).html()||\"\";o&&n.html(i(o).render(a))}var r={filter:{title:\"筛选列\",layEvent:\"LAYTABLE_COLS\",icon:\"layui-icon-cols\"},exports:{title:\"导出\",layEvent:\"LAYTABLE_EXPORT\",icon:\"layui-icon-export\"},print:{title:\"打印\",layEvent:\"LAYTABLE_PRINT\",icon:\"layui-icon-print\"}},d=[];\"object\"==typeof a.defaultToolbar&&layui.each(a.defaultToolbar,function(e,t){var i=r[t];i&&d.push('<div class=\"layui-inline\" title=\"'+i.title+'\" lay-event=\"'+i.layEvent+'\"><i class=\"layui-icon '+i.icon+'\"></i></div>')}),e.layTool.find(\".layui-table-tool-self\").html(d.join(\"\"))},F.prototype.setParentCol=function(e,t){var i=this,a=i.config,l=i.layHeader.find('th[data-key=\"'+a.index+\"-\"+t+'\"]'),n=parseInt(l.attr(\"colspan\"))||0;if(l[0]){var o=t.split(\"-\"),r=a.cols[o[0]][o[1]];e?n--:n++,l.attr(\"colspan\",n),l[n<1?\"addClass\":\"removeClass\"](y),r.colspan=n,r.hide=n<1;var d=l.data(\"parentkey\");d&&i.setParentCol(e,d)}},F.prototype.setColsPatch=function(){var e=this,t=e.config;layui.each(t.cols,function(t,i){layui.each(i,function(t,i){i.hide&&e.setParentCol(i.hide,i.parentKey)})})},F.prototype.setColsWidth=function(){var e=this,t=e.config,i=0,a=0,l=0,n=0,o=e.setInit(\"width\");e.eachCols(function(e,t){t.hide||i++}),o=o-function(){return\"line\"===t.skin||\"nob\"===t.skin?2:i+1}()-e.getScrollWidth(e.layMain[0])-1;var r=function(e){layui.each(t.cols,function(i,r){layui.each(r,function(i,d){var c=0,s=d.minWidth||t.cellMinWidth;return d?void(d.colGroup||d.hide||(e?l&&l<s&&(a--,c=s):(c=d.width||0,/\\d+%$/.test(c)?(c=Math.floor(parseFloat(c)/100*o),c<s&&(c=s)):c||(d.width=c=0,a++)),d.hide&&(c=0),n+=c)):void r.splice(i,1)})}),o>n&&a&&(l=(o-n)/a)};r(),r(!0),e.autoColNums=a,e.eachCols(function(i,a){var n=a.minWidth||t.cellMinWidth;a.colGroup||a.hide||(0===a.width?e.getCssRule(t.index+\"-\"+a.key,function(e){e.style.width=Math.floor(l>=n?l:n)+\"px\"}):/\\d+%$/.test(a.width)&&e.getCssRule(t.index+\"-\"+a.key,function(e){e.style.width=Math.floor(parseFloat(a.width)/100*o)+\"px\"}))});var d=e.layMain.width()-e.getScrollWidth(e.layMain[0])-e.layMain.children(\"table\").outerWidth();if(e.autoColNums&&d>=-i&&d<=i){var c=function(t){var i;return t=t||e.layHeader.eq(0).find(\"thead th:last-child\"),i=t.data(\"field\"),!i&&t.prev()[0]?c(t.prev()):t},s=c(),u=s.data(\"key\");e.getCssRule(u,function(t){var i=t.style.width||s.outerWidth();t.style.width=parseFloat(i)+d+\"px\",e.layMain.height()-e.layMain.prop(\"clientHeight\")>0&&(t.style.width=parseFloat(t.style.width)-1+\"px\")})}e.loading(!0)},F.prototype.resize=function(){var e=this;e.fullSize(),e.setColsWidth(),e.scrollPatch()},F.prototype.reload=function(e){var i=this;i.config.data&&i.config.data.constructor===Array&&delete i.config.data,i.config=t.extend({},i.config,e),i.render()},F.prototype.page=1,F.prototype.pullData=function(e){var i=this,a=i.config,l=a.request,n=a.response,o=function(){\"object\"==typeof a.initSort&&i.sort(a.initSort.field,a.initSort.type)};if(i.startTime=(new Date).getTime(),a.url){var r={};r[l.pageName]=e,r[l.limitName]=a.limit;var d=t.extend(r,a.where);a.contentType&&0==a.contentType.indexOf(\"application/json\")&&(d=JSON.stringify(d)),t.ajax({type:a.method||\"get\",url:a.url,contentType:a.contentType,data:d,dataType:\"json\",headers:a.headers||{},success:function(t){\"function\"==typeof a.parseData&&(t=a.parseData(t)||t),t[n.statusName]!=n.statusCode?(i.renderForm(),i.layMain.html('<div class=\"'+f+'\">'+(t[n.msgName]||\"返回的数据不符合规范，正确的成功状态码 (\"+n.statusName+\") 应为：\"+n.statusCode)+\"</div>\")):(i.renderData(t,e,t[n.countName]),o(),a.time=(new Date).getTime()-i.startTime+\" ms\"),i.setColsWidth(),\"function\"==typeof a.done&&a.done(t,e,t[n.countName])},error:function(e,t){i.layMain.html('<div class=\"'+f+'\">数据接口请求异常：'+t+\"</div>\"),i.renderForm(),i.setColsWidth()}})}else if(a.data&&a.data.constructor===Array){var c={},s=e*a.limit-a.limit;c[n.dataName]=a.data.concat().splice(s,a.limit),c[n.countName]=a.data.length,i.renderData(c,e,a.data.length),o(),i.setColsWidth(),\"function\"==typeof a.done&&a.done(c,e,c[n.countName])}},F.prototype.eachCols=function(e){var t=this;return d.eachCols(null,e,t.config.cols),t},F.prototype.renderData=function(e,n,o,r){var c=this,s=c.config,u=e[s.response.dataName]||[],h=[],p=[],v=[],m=function(){var e;return!r&&c.sortKey?c.sort(c.sortKey.field,c.sortKey.sort,!0):(layui.each(u,function(a,l){var o=[],u=[],f=[],m=a+s.limit*(n-1)+1;0!==l.length&&(r||(l[d.config.indexName]=a),c.eachCols(function(n,r){var c=r.field||n,h=s.index+\"-\"+r.key,p=l[c];if(void 0!==p&&null!==p||(p=\"\"),!r.colGroup){var v=['<td data-field=\"'+c+'\" data-key=\"'+h+'\" '+function(){var e=[];return r.edit&&e.push('data-edit=\"'+r.edit+'\"'),r.align&&e.push('align=\"'+r.align+'\"'),r.templet&&e.push('data-content=\"'+p+'\"'),r.toolbar&&e.push('data-off=\"true\"'),r.event&&e.push('lay-event=\"'+r.event+'\"'),r.style&&e.push('style=\"'+r.style+'\"'),r.minWidth&&e.push('data-minwidth=\"'+r.minWidth+'\"'),e.join(\" \")}()+' class=\"'+function(){var e=[];return r.hide&&e.push(y),r.field||e.push(\"layui-table-col-special\"),e.join(\" \")}()+'\">','<div class=\"layui-table-cell laytable-cell-'+function(){return\"normal\"===r.type?h:h+\" laytable-cell-\"+r.type}()+'\">'+function(){var n=t.extend(!0,{LAY_INDEX:m},l),o=d.config.checkName;switch(r.type){case\"checkbox\":return'<input type=\"checkbox\" name=\"layTableCheckbox\" lay-skin=\"primary\" '+function(){return r[o]?(l[o]=r[o],r[o]?\"checked\":\"\"):n[o]?\"checked\":\"\"}()+\">\";case\"radio\":return n[o]&&(e=a),'<input type=\"radio\" name=\"layTableRadio_'+s.index+'\" '+(n[o]?\"checked\":\"\")+' lay-type=\"layTableRadio\">';case\"numbers\":return m}return r.toolbar?i(t(r.toolbar).html()||\"\").render(n):r.templet?function(){return\"function\"==typeof r.templet?r.templet(n):i(t(r.templet).html()||String(p)).render(n)}():p}(),\"</div></td>\"].join(\"\");o.push(v),r.fixed&&\"right\"!==r.fixed&&u.push(v),\"right\"===r.fixed&&f.push(v)}}),h.push('<tr data-index=\"'+a+'\">'+o.join(\"\")+\"</tr>\"),p.push('<tr data-index=\"'+a+'\">'+u.join(\"\")+\"</tr>\"),v.push('<tr data-index=\"'+a+'\">'+f.join(\"\")+\"</tr>\"))}),c.layBody.scrollTop(0),c.layMain.find(\".\"+f).remove(),c.layMain.find(\"tbody\").html(h.join(\"\")),c.layFixLeft.find(\"tbody\").html(p.join(\"\")),c.layFixRight.find(\"tbody\").html(v.join(\"\")),c.renderForm(),\"number\"==typeof e&&c.setThisRowChecked(e),c.syncCheckAll(),c.haveInit?c.scrollPatch():setTimeout(function(){c.scrollPatch()},50),c.haveInit=!0,l.close(c.tipsIndex),s.HAS_SET_COLS_PATCH||c.setColsPatch(),void(s.HAS_SET_COLS_PATCH=!0))};return c.key=s.id||s.index,d.cache[c.key]=u,c.layPage[0==o||0===u.length&&1==n?\"addClass\":\"removeClass\"](y),r?m():0===u.length?(c.renderForm(),c.layFixed.remove(),c.layMain.find(\"tbody\").html(\"\"),c.layMain.find(\".\"+f).remove(),c.layMain.append('<div class=\"'+f+'\">'+s.text.none+\"</div>\")):(m(),c.renderTotal(u),void(s.page&&(s.page=t.extend({elem:\"layui-table-page\"+s.index,count:o,limit:s.limit,limits:s.limits||[10,20,30,40,50,60,70,80,90],groups:3,layout:[\"prev\",\"page\",\"next\",\"skip\",\"count\",\"limit\"],prev:'<i class=\"layui-icon\">&#xe603;</i>',next:'<i class=\"layui-icon\">&#xe602;</i>',jump:function(e,t){t||(c.page=e.curr,s.limit=e.limit,c.loading(),c.pullData(e.curr))}},s.page),s.page.count=o,a.render(s.page))))},F.prototype.renderTotal=function(e){var t=this,i=t.config,a={};if(i.totalRow){layui.each(e,function(e,i){0!==i.length&&t.eachCols(function(e,t){var l=t.field||e,n=i[l];t.totalRow&&(a[l]=(a[l]||0)+(parseFloat(n)||0))})});var l=[];t.eachCols(function(e,t){var n=t.field||e,o=['<td data-field=\"'+n+'\" data-key=\"'+i.index+\"-\"+t.key+'\" '+function(){var e=[];return t.align&&e.push('align=\"'+t.align+'\"'),t.style&&e.push('style=\"'+t.style+'\"'),t.minWidth&&e.push('data-minwidth=\"'+t.minWidth+'\"'),e.join(\" \")}()+' class=\"'+function(){var e=[];return t.hide&&e.push(y),t.field||e.push(\"layui-table-col-special\"),e.join(\" \")}()+'\">','<div class=\"layui-table-cell laytable-cell-'+function(){var e=i.index+\"-\"+t.key;return\"normal\"===t.type?e:e+\" laytable-cell-\"+t.type}()+'\">'+function(){var e=t.totalRowText||\"\";return t.totalRow?parseFloat(a[n]).toFixed(2)||e:e}(),\"</div></td>\"].join(\"\");l.push(o)}),t.layTotal.find(\"tbody\").html(\"<tr>\"+l.join(\"\")+\"</tr>\")}},F.prototype.getColElem=function(e,t){var i=this,a=i.config;return e.eq(0).find(\".laytable-cell-\"+(a.index+\"-\"+t)+\":eq(0)\")},F.prototype.renderForm=function(e){n.render(e,\"LAY-table-\"+this.index)},F.prototype.setThisRowChecked=function(e){var t=this,i=(t.config,\"layui-table-click\"),a=t.layBody.find('tr[data-index=\"'+e+'\"]');a.addClass(i).siblings(\"tr\").removeClass(i)},F.prototype.sort=function(e,i,a,l){var n,r,c=this,s={},h=c.config,y=h.elem.attr(\"lay-filter\"),f=d.cache[c.key];\"string\"==typeof e&&c.layHeader.find(\"th\").each(function(i,a){var l=t(this),o=l.data(\"field\");if(o===e)return e=l,n=o,!1});try{var n=n||e.data(\"field\"),p=e.data(\"key\");if(c.sortKey&&!a&&n===c.sortKey.field&&i===c.sortKey.sort)return;var v=c.layHeader.find(\"th .laytable-cell-\"+p).find(S);c.layHeader.find(\"th\").find(S).removeAttr(\"lay-sort\"),v.attr(\"lay-sort\",i||null),c.layFixed.find(\"th\")}catch(m){return o.error(\"Table modules: Did not match to field\")}c.sortKey={field:n,sort:i},h.autoSort&&(\"asc\"===i?r=layui.sort(f,n):\"desc\"===i?r=layui.sort(f,n,!0):(r=layui.sort(f,d.config.indexName),delete c.sortKey)),s[h.response.dataName]=r||f,c.renderData(s,c.page,c.count,!0),l&&layui.event.call(e,u,\"sort(\"+y+\")\",{field:n,type:i})},F.prototype.loading=function(e){var i=this,a=i.config;a.loading&&(e?(i.layInit&&i.layInit.remove(),delete i.layInit,i.layBox.find(g).remove()):(i.layInit=t(['<div class=\"layui-table-init\">','<i class=\"layui-icon layui-icon-loading layui-icon\"></i>',\"</div>\"].join(\"\")),i.layBox.append(i.layInit)))},F.prototype.setCheckData=function(e,t){var i=this,a=i.config,l=d.cache[i.key];l[e]&&l[e].constructor!==Array&&(l[e][a.checkName]=t)},F.prototype.syncCheckAll=function(){var e=this,t=e.config,i=e.layHeader.find('input[name=\"layTableCheckbox\"]'),a=function(i){return e.eachCols(function(e,a){\"checkbox\"===a.type&&(a[t.checkName]=i)}),i};i[0]&&(d.checkStatus(e.key).isAll?(i[0].checked||(i.prop(\"checked\",!0),e.renderForm(\"checkbox\")),a(!0)):(i[0].checked&&(i.prop(\"checked\",!1),e.renderForm(\"checkbox\")),a(!1)))},F.prototype.getCssRule=function(e,t){var i=this,a=i.elem.find(\"style\")[0],l=a.sheet||a.styleSheet||{},n=l.cssRules||l.rules;layui.each(n,function(i,a){if(a.selectorText===\".laytable-cell-\"+e)return t(a),!0})},F.prototype.fullSize=function(){var e,t=this,i=t.config,a=i.height;t.fullHeightGap&&(a=H.height()-t.fullHeightGap,a<135&&(a=135),t.elem.css(\"height\",a)),a&&(e=parseFloat(a)-(t.layHeader.outerHeight()||38),i.toolbar&&(e-=t.layTool.outerHeight()||50),i.totalRow&&(e-=t.layTotal.outerHeight()||40),i.page&&(e=e-(t.layPage.outerHeight()||41)-2),t.layMain.css(\"height\",e))},F.prototype.getScrollWidth=function(e){var t=0;return e?t=e.offsetWidth-e.clientWidth:(e=document.createElement(\"div\"),e.style.width=\"100px\",e.style.height=\"100px\",e.style.overflowY=\"scroll\",document.body.appendChild(e),t=e.offsetWidth-e.clientWidth,document.body.removeChild(e)),t},F.prototype.scrollPatch=function(){var e=this,i=e.layMain.children(\"table\"),a=e.layMain.width()-e.layMain.prop(\"clientWidth\"),l=e.layMain.height()-e.layMain.prop(\"clientHeight\"),n=(e.getScrollWidth(e.layMain[0]),i.outerWidth()-e.layMain.width()),o=function(e){if(a&&l){if(e=e.eq(0),!e.find(\".layui-table-patch\")[0]){var i=t('<th class=\"layui-table-patch\"><div class=\"layui-table-cell\"></div></th>');i.find(\"div\").css({width:a}),e.find(\"tr\").append(i)}}else e.find(\".layui-table-patch\").remove()};o(e.layHeader),o(e.layTotal);var r=e.layMain.height(),d=r-l;e.layFixed.find(x).css(\"height\",i.height()>=d?d:\"auto\"),e.layFixRight[n>0?\"removeClass\":\"addClass\"](y),e.layFixRight.css(\"right\",a-1)},F.prototype.events=function(){var e,a=this,o=a.config,c=t(\"body\"),s={},h=a.layHeader.find(\"th\"),f=\".layui-table-cell\",p=o.elem.attr(\"lay-filter\");a.layTool.on(\"click\",\"*[lay-event]\",function(e){var i=t(this),c=i.attr(\"lay-event\"),s=function(e){var l=t(e.list),n=t('<ul class=\"layui-table-tool-panel\"></ul>');n.html(l),o.height&&n.css(\"max-height\",o.height-(a.layTool.outerHeight()||50)),i.find(\".layui-table-tool-panel\")[0]||i.append(n),a.renderForm(),n.on(\"click\",function(e){layui.stope(e)}),e.done&&e.done(n,l)};switch(layui.stope(e),R.trigger(\"table.tool.panel.remove\"),l.close(a.tipsIndex),c){case\"LAYTABLE_COLS\":s({list:function(){var e=[];return a.eachCols(function(t,i){i.field&&\"normal\"==i.type&&e.push('<li><input type=\"checkbox\" name=\"'+i.field+'\" data-key=\"'+i.key+'\" data-parentkey=\"'+(i.parentKey||\"\")+'\" lay-skin=\"primary\" '+(i.hide?\"\":\"checked\")+' title=\"'+(i.title||i.field)+'\" lay-filter=\"LAY_TABLE_TOOL_COLS\"></li>')}),e.join(\"\")}(),done:function(){n.on(\"checkbox(LAY_TABLE_TOOL_COLS)\",function(e){var i=t(e.elem),l=this.checked,n=i.data(\"key\"),r=i.data(\"parentkey\");layui.each(o.cols,function(e,t){layui.each(t,function(t,i){if(e+\"-\"+t===n){var d=i.hide;i.hide=!l,a.elem.find('*[data-key=\"'+o.index+\"-\"+n+'\"]')[l?\"removeClass\":\"addClass\"](y),d!=i.hide&&a.setParentCol(!l,r),a.resize()}})})})}});break;case\"LAYTABLE_EXPORT\":r.ie?l.tips(\"导出功能不支持 IE，请用 Chrome 等高级浏览器导出\",this,{tips:3}):s({list:function(){return['<li data-type=\"csv\">导出到 Csv 文件</li>','<li data-type=\"xls\">导出到 Excel 文件</li>'].join(\"\")}(),done:function(e,i){i.on(\"click\",function(){var e=t(this).data(\"type\");d.exportFile(o.id,null,e)})}});break;case\"LAYTABLE_PRINT\":var h=window.open(\"打印窗口\",\"_blank\"),f=[\"<style>\",\"body{font-size: 12px; color: #666;}\",\"table{width: 100%; border-collapse: collapse; border-spacing: 0;}\",\"th,td{line-height: 20px; padding: 9px 15px; border: 1px solid #ccc; text-align: left; font-size: 12px; color: #666;}\",\"a{color: #666; text-decoration:none;}\",\"*.layui-hide{display: none}\",\"</style>\"].join(\"\"),v=t(a.layHeader.html());v.append(a.layMain.find(\"table\").html()),v.find(\"th.layui-table-patch\").remove(),v.find(\".layui-table-col-special\").remove(),h.document.write(f+v.prop(\"outerHTML\")),h.document.close(),h.print(),h.close()}layui.event.call(this,u,\"toolbar(\"+p+\")\",t.extend({event:c,config:o},{}))}),h.on(\"mousemove\",function(e){var i=t(this),a=i.offset().left,l=e.clientX-a;i.data(\"unresize\")||s.resizeStart||(s.allowResize=i.width()-l<=10,c.css(\"cursor\",s.allowResize?\"col-resize\":\"\"))}).on(\"mouseleave\",function(){t(this);s.resizeStart||c.css(\"cursor\",\"\")}).on(\"mousedown\",function(e){var i=t(this);if(s.allowResize){var l=i.data(\"key\");e.preventDefault(),s.resizeStart=!0,s.offset=[e.clientX,e.clientY],a.getCssRule(l,function(e){var t=e.style.width||i.outerWidth();s.rule=e,s.ruleWidth=parseFloat(t),s.minWidth=i.data(\"minwidth\")||o.cellMinWidth})}}),R.on(\"mousemove\",function(t){if(s.resizeStart){if(t.preventDefault(),s.rule){var i=s.ruleWidth+t.clientX-s.offset[0];i<s.minWidth&&(i=s.minWidth),s.rule.style.width=i+\"px\",l.close(a.tipsIndex)}e=1}}).on(\"mouseup\",function(t){s.resizeStart&&(s={},c.css(\"cursor\",\"\"),a.scrollPatch()),2===e&&(e=null)}),h.on(\"click\",function(i){var l,n=t(this),o=n.find(S),r=o.attr(\"lay-sort\");return o[0]&&1!==e?(l=\"asc\"===r?\"desc\":\"desc\"===r?null:\"asc\",void a.sort(n,l,null,!0)):e=2}).find(S+\" .layui-edge \").on(\"click\",function(e){var i=t(this),l=i.index(),n=i.parents(\"th\").eq(0).data(\"field\");layui.stope(e),0===l?a.sort(n,\"asc\",null,!0):a.sort(n,\"desc\",null,!0)});var v=function(e){var l=t(this),n=l.parents(\"tr\").eq(0).data(\"index\"),o=a.layBody.find('tr[data-index=\"'+n+'\"]'),r=d.cache[a.key][n];return t.extend({tr:o,data:d.clearCacheKey(r),del:function(){d.cache[a.key][n]=[],o.remove(),a.scrollPatch()},update:function(e){e=e||{},layui.each(e,function(e,l){if(e in r){var n,d=o.children('td[data-field=\"'+e+'\"]');r[e]=l,a.eachCols(function(t,i){i.field==e&&i.templet&&(n=i.templet)}),d.children(f).html(function(){return n?function(){return\"function\"==typeof n?n(r):i(t(n).html()||l).render(r)}():l}()),d.data(\"content\",l)}})}},e)};a.elem.on(\"click\",'input[name=\"layTableCheckbox\"]+',function(){var e=t(this).prev(),i=a.layBody.find('input[name=\"layTableCheckbox\"]'),l=e.parents(\"tr\").eq(0).data(\"index\"),n=e[0].checked,o=\"layTableAllChoose\"===e.attr(\"lay-filter\");o?(i.each(function(e,t){t.checked=n,a.setCheckData(e,n)}),a.syncCheckAll(),a.renderForm(\"checkbox\")):(a.setCheckData(l,n),a.syncCheckAll()),layui.event.call(e[0],u,\"checkbox(\"+p+\")\",v.call(e[0],{checked:n,type:o?\"all\":\"one\"}))}),a.elem.on(\"click\",'input[lay-type=\"layTableRadio\"]+',function(){var e=t(this).prev(),i=e[0].checked,l=d.cache[a.key],n=e.parents(\"tr\").eq(0).data(\"index\");layui.each(l,function(e,t){n===e?t.LAY_CHECKED=!0:delete t.LAY_CHECKED}),a.setThisRowChecked(n),layui.event.call(this,u,\"radio(\"+p+\")\",v.call(this,{checked:i}))}),a.layBody.on(\"mouseenter\",\"tr\",function(){var e=t(this),i=e.index();a.layBody.find(\"tr:eq(\"+i+\")\").addClass(W)}).on(\"mouseleave\",\"tr\",function(){var e=t(this),i=e.index();a.layBody.find(\"tr:eq(\"+i+\")\").removeClass(W)}).on(\"click\",\"tr\",function(){m.call(this,\"row\")}).on(\"dblclick\",\"tr\",function(){m.call(this,\"rowDouble\")});var m=function(e){var i=t(this);layui.event.call(this,u,e+\"(\"+p+\")\",v.call(i.children(\"td\")[0]))};a.layBody.on(\"change\",\".\"+N,function(){var e=t(this),i=this.value,l=e.parent().data(\"field\"),n=e.parents(\"tr\").eq(0).data(\"index\"),o=d.cache[a.key][n];o[l]=i,layui.event.call(this,u,\"edit(\"+p+\")\",v.call(this,{value:i,field:l}))}).on(\"blur\",\".\"+N,function(){var e,l=t(this),n=l.parent().data(\"field\"),o=l.parents(\"tr\").eq(0).data(\"index\"),r=d.cache[a.key][o];a.eachCols(function(t,i){i.field==n&&i.templet&&(e=i.templet)}),l.siblings(f).html(function(a){return e?function(){return\"function\"==typeof e?e(r):i(t(e).html()||this.value).render(r)}():a}(this.value)),l.parent().data(\"content\",this.value),l.remove()}),a.layBody.on(\"click\",\"td\",function(e){var i=t(this),a=(i.data(\"field\"),i.data(\"edit\")),l=i.children(f);if(!i.data(\"off\")&&a){var n=t('<input class=\"layui-input '+N+'\">');return n[0].value=i.data(\"content\")||l.text(),i.find(\".\"+N)[0]||i.append(n),n.focus(),void layui.stope(e)}}).on(\"mouseenter\",\"td\",function(){b.call(this)}).on(\"mouseleave\",\"td\",function(){b.call(this,\"hide\")});var g=\"layui-table-grid-down\",b=function(e){var i=t(this),a=i.children(f);if(e)i.find(\".layui-table-grid-down\").remove();else if(a.prop(\"scrollWidth\")>a.outerWidth()){if(a.find(\".\"+g)[0])return;i.append('<div class=\"'+g+'\"><i class=\"layui-icon layui-icon-down\"></i></div>')}};a.layBody.on(\"click\",\".\"+g,function(e){var i=t(this),n=i.parent(),d=n.children(f);a.tipsIndex=l.tips(['<div class=\"layui-table-tips-main\" style=\"margin-top: -'+(d.height()+16)+\"px;\"+function(){return\"sm\"===o.size?\"padding: 4px 15px; font-size: 12px;\":\"lg\"===o.size?\"padding: 14px 15px;\":\"\"}()+'\">',d.html(),\"</div>\",'<i class=\"layui-icon layui-table-tips-c layui-icon-close\"></i>'].join(\"\"),d[0],{tips:[3,\"\"],time:-1,anim:-1,maxWidth:r.ios||r.android?300:a.elem.width()/2,isOutAnim:!1,skin:\"layui-table-tips\",success:function(e,t){e.find(\".layui-table-tips-c\").on(\"click\",function(){l.close(t)})}}),layui.stope(e)}),a.layBody.on(\"click\",\"*[lay-event]\",function(){var e=t(this),i=e.parents(\"tr\").eq(0).data(\"index\");layui.event.call(this,u,\"tool(\"+p+\")\",v.call(this,{event:e.attr(\"lay-event\")})),a.setThisRowChecked(i)}),a.layMain.on(\"scroll\",function(){var e=t(this),i=e.scrollLeft(),n=e.scrollTop();a.layHeader.scrollLeft(i),a.layTotal.scrollLeft(i),a.layFixed.find(x).scrollTop(n),l.close(a.tipsIndex)}),R.on(\"click\",function(){R.trigger(\"table.remove.tool.panel\")}),R.on(\"table.remove.tool.panel\",function(){t(\".layui-table-tool-panel\").remove()}),H.on(\"resize\",function(){a.resize()})},d.init=function(e,i){i=i||{};var a=this,l=t(e?'table[lay-filter=\"'+e+'\"]':h+\"[lay-data]\"),n=\"Table element property lay-data configuration item has a syntax error: \";return l.each(function(){var a=t(this),l=a.attr(\"lay-data\");try{l=new Function(\"return \"+l)()}catch(r){o.error(n+l)}var c=[],s=t.extend({elem:this,cols:[],data:[],skin:a.attr(\"lay-skin\"),size:a.attr(\"lay-size\"),even:\"string\"==typeof a.attr(\"lay-even\")},d.config,i,l);e&&a.hide(),a.find(\"thead>tr\").each(function(e){s.cols[e]=[],t(this).children().each(function(i){var a=t(this),l=a.attr(\"lay-data\");try{l=new Function(\"return \"+l)()}catch(r){return o.error(n+l)}var d=t.extend({title:a.text(),colspan:a.attr(\"colspan\")||0,rowspan:a.attr(\"rowspan\")||0},l);d.colspan<2&&c.push(d),s.cols[e].push(d)})}),a.find(\"tbody>tr\").each(function(e){var i=t(this),a={};i.children(\"td\").each(function(e,i){var l=t(this),n=l.data(\"field\");if(n)return a[n]=l.html()}),layui.each(c,function(e,t){var l=i.children(\"td\").eq(e);a[t.field]=l.html()}),s.data[e]=a}),d.render(s)}),a},c.that={},c.config={},d.eachCols=function(e,i,a){var l=c.config[e]||{},n=[],o=0;a=t.extend(!0,[],a||l.cols),layui.each(a,function(e,t){layui.each(t,function(t,i){if(i.colGroup){var l=0;o++,i.CHILD_COLS=[],layui.each(a[e+1],function(e,t){t.PARENT_COL_INDEX||l>1&&l==i.colspan||(t.PARENT_COL_INDEX=o,i.CHILD_COLS.push(t),l+=parseInt(t.colspan>1?t.colspan:1))})}i.PARENT_COL_INDEX||n.push(i)})});var r=function(e){layui.each(e||n,function(e,t){return t.CHILD_COLS?r(t.CHILD_COLS):void(\"function\"==typeof i&&i(e,t))})};r()},d.checkStatus=function(e){var t=0,i=0,a=[],l=d.cache[e]||[];return layui.each(l,function(e,l){return l.constructor===Array?void i++:void(l[d.config.checkName]&&(t++,a.push(d.clearCacheKey(l))))}),{data:a,isAll:!!l.length&&t===l.length-i}},d.exportFile=function(e,t,i){t=t||d.clearCacheKey(d.cache[e]),i=i||\"csv\";var a=c.config[e]||{},l={csv:\"text/csv\",xls:\"application/vnd.ms-excel\"}[i],n=document.createElement(\"a\");return r.ie?o.error(\"IE_NOT_SUPPORT_EXPORTS\"):(n.href=\"data:\"+l+\";charset=utf-8,\\ufeff\"+encodeURIComponent(function(){var i=[],a=[];return layui.each(t,function(t,l){var n=[];\"object\"==typeof e?(layui.each(e,function(e,a){0==t&&i.push(a||\"\")}),layui.each(d.clearCacheKey(l),function(e,t){n.push(t)})):d.eachCols(e,function(e,a){a.field&&\"normal\"==a.type&&!a.hide&&(0==t&&i.push(a.title||\"\"),n.push(l[a.field]))}),a.push(n.join(\",\"))}),i.join(\",\")+\"\\r\\n\"+a.join(\"\\r\\n\")}()),n.download=(a.title||\"table_\"+(a.index||\"\"))+\".\"+i,document.body.appendChild(n),n.click(),void document.body.removeChild(n))},d.resize=function(e){if(e){var t=s(e);if(!t)return;c.that[e].resize()}else layui.each(c.that,function(){this.resize()})},d.reload=function(e,i){i=i||{};var a=s(e);if(a)return i.data&&i.data.constructor===Array&&delete a.data,d.render(t.extend(!0,{},a,i))},d.render=function(e){var t=new F(e);return c.call(t)},d.clearCacheKey=function(e){return e=t.extend({},e),delete e[d.config.checkName],delete e[d.config.indexName],e},d.init(),e(u,d)});layui.define(\"jquery\",function(e){\"use strict\";var i=layui.$,n=(layui.hint(),layui.device(),{config:{},set:function(e){var n=this;return n.config=i.extend({},n.config,e),n},on:function(e,i){return layui.onevent.call(this,t,e,i)}}),t=\"carousel\",a=\"layui-this\",l=\">*[carousel-item]>*\",o=\"layui-carousel-left\",r=\"layui-carousel-right\",d=\"layui-carousel-prev\",s=\"layui-carousel-next\",u=\"layui-carousel-arrow\",c=\"layui-carousel-ind\",m=function(e){var t=this;t.config=i.extend({},t.config,n.config,e),t.render()};m.prototype.config={width:\"600px\",height:\"280px\",full:!1,arrow:\"hover\",indicator:\"inside\",autoplay:!0,interval:3e3,anim:\"\",trigger:\"click\",index:0},m.prototype.render=function(){var e=this,n=e.config;n.elem=i(n.elem),n.elem[0]&&(e.elemItem=n.elem.find(l),n.index<0&&(n.index=0),n.index>=e.elemItem.length&&(n.index=e.elemItem.length-1),n.interval<800&&(n.interval=800),n.full?n.elem.css({position:\"fixed\",width:\"100%\",height:\"100%\",zIndex:9999}):n.elem.css({width:n.width,height:n.height}),n.elem.attr(\"lay-anim\",n.anim),e.elemItem.eq(n.index).addClass(a),e.elemItem.length<=1||(e.indicator(),e.arrow(),e.autoplay(),e.events()))},m.prototype.reload=function(e){var n=this;clearInterval(n.timer),n.config=i.extend({},n.config,e),n.render()},m.prototype.prevIndex=function(){var e=this,i=e.config,n=i.index-1;return n<0&&(n=e.elemItem.length-1),n},m.prototype.nextIndex=function(){var e=this,i=e.config,n=i.index+1;return n>=e.elemItem.length&&(n=0),n},m.prototype.addIndex=function(e){var i=this,n=i.config;e=e||1,n.index=n.index+e,n.index>=i.elemItem.length&&(n.index=0)},m.prototype.subIndex=function(e){var i=this,n=i.config;e=e||1,n.index=n.index-e,n.index<0&&(n.index=i.elemItem.length-1)},m.prototype.autoplay=function(){var e=this,i=e.config;i.autoplay&&(e.timer=setInterval(function(){e.slide()},i.interval))},m.prototype.arrow=function(){var e=this,n=e.config,t=i(['<button class=\"layui-icon '+u+'\" lay-type=\"sub\">'+(\"updown\"===n.anim?\"&#xe619;\":\"&#xe603;\")+\"</button>\",'<button class=\"layui-icon '+u+'\" lay-type=\"add\">'+(\"updown\"===n.anim?\"&#xe61a;\":\"&#xe602;\")+\"</button>\"].join(\"\"));n.elem.attr(\"lay-arrow\",n.arrow),n.elem.find(\".\"+u)[0]&&n.elem.find(\".\"+u).remove(),n.elem.append(t),t.on(\"click\",function(){var n=i(this),t=n.attr(\"lay-type\");e.slide(t)})},m.prototype.indicator=function(){var e=this,n=e.config,t=e.elemInd=i(['<div class=\"'+c+'\"><ul>',function(){var i=[];return layui.each(e.elemItem,function(e){i.push(\"<li\"+(n.index===e?' class=\"layui-this\"':\"\")+\"></li>\")}),i.join(\"\")}(),\"</ul></div>\"].join(\"\"));n.elem.attr(\"lay-indicator\",n.indicator),n.elem.find(\".\"+c)[0]&&n.elem.find(\".\"+c).remove(),n.elem.append(t),\"updown\"===n.anim&&t.css(\"margin-top\",-(t.height()/2)),t.find(\"li\").on(\"hover\"===n.trigger?\"mouseover\":n.trigger,function(){var t=i(this),a=t.index();a>n.index?e.slide(\"add\",a-n.index):a<n.index&&e.slide(\"sub\",n.index-a)})},m.prototype.slide=function(e,i){var n=this,l=n.elemItem,u=n.config,c=u.index,m=u.elem.attr(\"lay-filter\");n.haveSlide||(\"sub\"===e?(n.subIndex(i),l.eq(u.index).addClass(d),setTimeout(function(){l.eq(c).addClass(r),l.eq(u.index).addClass(r)},50)):(n.addIndex(i),l.eq(u.index).addClass(s),setTimeout(function(){l.eq(c).addClass(o),l.eq(u.index).addClass(o)},50)),setTimeout(function(){l.removeClass(a+\" \"+d+\" \"+s+\" \"+o+\" \"+r),l.eq(u.index).addClass(a),n.haveSlide=!1},300),n.elemInd.find(\"li\").eq(u.index).addClass(a).siblings().removeClass(a),n.haveSlide=!0,layui.event.call(this,t,\"change(\"+m+\")\",{index:u.index,prevIndex:c,item:l.eq(u.index)}))},m.prototype.events=function(){var e=this,i=e.config;i.elem.data(\"haveEvents\")||(i.elem.on(\"mouseenter\",function(){clearInterval(e.timer)}).on(\"mouseleave\",function(){e.autoplay()}),i.elem.data(\"haveEvents\",!0))},n.render=function(e){var i=new m(e);return i},e(t,n)});layui.define(\"jquery\",function(e){\"use strict\";var a=layui.jquery,i={config:{},index:layui.rate?layui.rate.index+1e4:0,set:function(e){var i=this;return i.config=a.extend({},i.config,e),i},on:function(e,a){return layui.onevent.call(this,n,e,a)}},l=function(){var e=this,a=e.config;return{setvalue:function(a){e.setvalue.call(e,a)},config:a}},n=\"rate\",t=\"layui-rate\",o=\"layui-icon-rate\",s=\"layui-icon-rate-solid\",u=\"layui-icon-rate-half\",r=\"layui-icon-rate-solid layui-icon-rate-half\",c=\"layui-icon-rate-solid layui-icon-rate\",f=\"layui-icon-rate layui-icon-rate-half\",v=function(e){var l=this;l.index=++i.index,l.config=a.extend({},l.config,i.config,e),l.render()};v.prototype.config={length:5,text:!1,readonly:!1,half:!1,value:0,theme:\"\"},v.prototype.render=function(){var e=this,i=e.config,l=i.theme?'style=\"color: '+i.theme+';\"':\"\";i.elem=a(i.elem),parseInt(i.value)!==i.value&&(i.half||(i.value=Math.ceil(i.value)-i.value<.5?Math.ceil(i.value):Math.floor(i.value)));for(var n='<ul class=\"layui-rate\" '+(i.readonly?\"readonly\":\"\")+\">\",u=1;u<=i.length;u++){var r='<li class=\"layui-inline\"><i class=\"layui-icon '+(u>Math.floor(i.value)?o:s)+'\" '+l+\"></i></li>\";i.half&&parseInt(i.value)!==i.value&&u==Math.ceil(i.value)?n=n+'<li><i class=\"layui-icon layui-icon-rate-half\" '+l+\"></i></li>\":n+=r}n+=\"</ul>\"+(i.text?'<span class=\"layui-inline\">'+i.value+\"星\":\"\")+\"</span>\";var c=i.elem,f=c.next(\".\"+t);f[0]&&f.remove(),e.elemTemp=a(n),i.span=e.elemTemp.next(\"span\"),i.setText&&i.setText(i.value),c.html(e.elemTemp),c.addClass(\"layui-inline\"),i.readonly||e.action()},v.prototype.setvalue=function(e){var a=this,i=a.config;i.value=e,a.render()},v.prototype.action=function(){var e=this,i=e.config,l=e.elemTemp,n=l.find(\"i\").width();l.children(\"li\").each(function(e){var t=e+1,v=a(this);v.on(\"click\",function(e){if(i.value=t,i.half){var o=e.pageX-a(this).offset().left;o<=n/2&&(i.value=i.value-.5)}i.text&&l.next(\"span\").text(i.value+\"星\"),i.choose&&i.choose(i.value),i.setText&&i.setText(i.value)}),v.on(\"mousemove\",function(e){if(l.find(\"i\").each(function(){a(this).addClass(o).removeClass(r)}),l.find(\"i:lt(\"+t+\")\").each(function(){a(this).addClass(s).removeClass(f)}),i.half){var c=e.pageX-a(this).offset().left;c<=n/2&&v.children(\"i\").addClass(u).removeClass(s)}}),v.on(\"mouseleave\",function(){l.find(\"i\").each(function(){a(this).addClass(o).removeClass(r)}),l.find(\"i:lt(\"+Math.floor(i.value)+\")\").each(function(){a(this).addClass(s).removeClass(f)}),i.half&&parseInt(i.value)!==i.value&&l.children(\"li:eq(\"+Math.floor(i.value)+\")\").children(\"i\").addClass(u).removeClass(c)})})},v.prototype.events=function(){var e=this;e.config},i.render=function(e){var a=new v(e);return l.call(a)},e(n,i)});layui.define(\"jquery\",function(t){\"use strict\";var e=layui.$,i={fixbar:function(t){var i,a,n=\"layui-fixbar\",r=\"layui-fixbar-top\",o=e(document),l=e(\"body\");t=e.extend({showHeight:200},t),t.bar1=t.bar1===!0?\"&#xe606;\":t.bar1,t.bar2=t.bar2===!0?\"&#xe607;\":t.bar2,t.bgcolor=t.bgcolor?\"background-color:\"+t.bgcolor:\"\";var c=[t.bar1,t.bar2,\"&#xe604;\"],g=e(['<ul class=\"'+n+'\">',t.bar1?'<li class=\"layui-icon\" lay-type=\"bar1\" style=\"'+t.bgcolor+'\">'+c[0]+\"</li>\":\"\",t.bar2?'<li class=\"layui-icon\" lay-type=\"bar2\" style=\"'+t.bgcolor+'\">'+c[1]+\"</li>\":\"\",'<li class=\"layui-icon '+r+'\" lay-type=\"top\" style=\"'+t.bgcolor+'\">'+c[2]+\"</li>\",\"</ul>\"].join(\"\")),s=g.find(\".\"+r),u=function(){var e=o.scrollTop();e>=t.showHeight?i||(s.show(),i=1):i&&(s.hide(),i=0)};e(\".\"+n)[0]||(\"object\"==typeof t.css&&g.css(t.css),l.append(g),u(),g.find(\"li\").on(\"click\",function(){var i=e(this),a=i.attr(\"lay-type\");\"top\"===a&&e(\"html,body\").animate({scrollTop:0},200),t.click&&t.click.call(this,a)}),o.on(\"scroll\",function(){clearTimeout(a),a=setTimeout(function(){u()},100)}))},countdown:function(t,e,i){var a=this,n=\"function\"==typeof e,r=new Date(t).getTime(),o=new Date(!e||n?(new Date).getTime():e).getTime(),l=r-o,c=[Math.floor(l/864e5),Math.floor(l/36e5)%24,Math.floor(l/6e4)%60,Math.floor(l/1e3)%60];n&&(i=e);var g=setTimeout(function(){a.countdown(t,o+1e3,i)},1e3);return i&&i(l>0?c:[0,0,0,0],e,g),l<=0&&clearTimeout(g),g},timeAgo:function(t,e){var i=this,a=[[],[]],n=(new Date).getTime()-new Date(t).getTime();return n>6912e5?(n=new Date(t),a[0][0]=i.digit(n.getFullYear(),4),a[0][1]=i.digit(n.getMonth()+1),a[0][2]=i.digit(n.getDate()),e||(a[1][0]=i.digit(n.getHours()),a[1][1]=i.digit(n.getMinutes()),a[1][2]=i.digit(n.getSeconds())),a[0].join(\"-\")+\" \"+a[1].join(\":\")):n>=864e5?(n/1e3/60/60/24|0)+\"天前\":n>=36e5?(n/1e3/60/60|0)+\"小时前\":n>=12e4?(n/1e3/60|0)+\"分钟前\":n<0?\"未来\":\"刚刚\"},digit:function(t,e){var i=\"\";t=String(t),e=e||2;for(var a=t.length;a<e;a++)i+=\"0\";return t<Math.pow(10,e)?i+(0|t):t},toDateString:function(t,e){var i=this,a=new Date(t||new Date),n=[i.digit(a.getFullYear(),4),i.digit(a.getMonth()+1),i.digit(a.getDate())],r=[i.digit(a.getHours()),i.digit(a.getMinutes()),i.digit(a.getSeconds())];return e=e||\"yyyy-MM-dd HH:mm:ss\",e.replace(/yyyy/g,n[0]).replace(/MM/g,n[1]).replace(/dd/g,n[2]).replace(/HH/g,r[0]).replace(/mm/g,r[1]).replace(/ss/g,r[2])},escape:function(t){return String(t||\"\").replace(/&(?!#?[a-zA-Z0-9]+;)/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(/'/g,\"&#39;\").replace(/\"/g,\"&quot;\")}};!function(t,e,i){\"$:nomunge\";function a(){n=e[l](function(){r.each(function(){var e=t(this),i=e.width(),a=e.height(),n=t.data(this,g);(i!==n.w||a!==n.h)&&e.trigger(c,[n.w=i,n.h=a])}),a()},o[s])}var n,r=t([]),o=t.resize=t.extend(t.resize,{}),l=\"setTimeout\",c=\"resize\",g=c+\"-special-event\",s=\"delay\",u=\"throttleWindow\";o[s]=250,o[u]=!0,t.event.special[c]={setup:function(){if(!o[u]&&this[l])return!1;var e=t(this);r=r.add(e),t.data(this,g,{w:e.width(),h:e.height()}),1===r.length&&a()},teardown:function(){if(!o[u]&&this[l])return!1;var e=t(this);r=r.not(e),e.removeData(g),r.length||clearTimeout(n)},add:function(e){function a(e,a,r){var o=t(this),l=t.data(this,g)||{};l.w=a!==i?a:o.width(),l.h=r!==i?r:o.height(),n.apply(this,arguments)}if(!o[u]&&this[l])return!1;var n;return t.isFunction(e)?(n=e,a):(n=e.handler,void(e.handler=a))}}}(e,window),t(\"util\",i)});layui.define(\"jquery\",function(e){\"use strict\";var l=layui.$,o=function(e){},t='<i class=\"layui-anim layui-anim-rotate layui-anim-loop layui-icon \">&#xe63e;</i>';o.prototype.load=function(e){var o,i,n,r,a=this,c=0;e=e||{};var f=l(e.elem);if(f[0]){var m=l(e.scrollElem||document),u=e.mb||50,s=!(\"isAuto\"in e)||e.isAuto,v=e.end||\"没有更多了\",y=e.scrollElem&&e.scrollElem!==document,d=\"<cite>加载更多</cite>\",h=l('<div class=\"layui-flow-more\"><a href=\"javascript:;\">'+d+\"</a></div>\");f.find(\".layui-flow-more\")[0]||f.append(h);var p=function(e,t){e=l(e),h.before(e),t=0==t||null,t?h.html(v):h.find(\"a\").html(d),i=t,o=null,n&&n()},g=function(){o=!0,h.find(\"a\").html(t),\"function\"==typeof e.done&&e.done(++c,p)};if(g(),h.find(\"a\").on(\"click\",function(){l(this);i||o||g()}),e.isLazyimg)var n=a.lazyimg({elem:e.elem+\" img\",scrollElem:e.scrollElem});return s?(m.on(\"scroll\",function(){var e=l(this),t=e.scrollTop();r&&clearTimeout(r),i||(r=setTimeout(function(){var i=y?e.height():l(window).height(),n=y?e.prop(\"scrollHeight\"):document.documentElement.scrollHeight;n-t-i<=u&&(o||g())},100))}),a):a}},o.prototype.lazyimg=function(e){var o,t=this,i=0;e=e||{};var n=l(e.scrollElem||document),r=e.elem||\"img\",a=e.scrollElem&&e.scrollElem!==document,c=function(e,l){var o=n.scrollTop(),r=o+l,c=a?function(){return e.offset().top-n.offset().top+o}():e.offset().top;if(c>=o&&c<=r&&!e.attr(\"src\")){var m=e.attr(\"lay-src\");layui.img(m,function(){var l=t.lazyimg.elem.eq(i);e.attr(\"src\",m).removeAttr(\"lay-src\"),l[0]&&f(l),i++})}},f=function(e,o){var f=a?(o||n).height():l(window).height(),m=n.scrollTop(),u=m+f;if(t.lazyimg.elem=l(r),e)c(e,f);else for(var s=0;s<t.lazyimg.elem.length;s++){var v=t.lazyimg.elem.eq(s),y=a?function(){return v.offset().top-n.offset().top+m}():v.offset().top;if(c(v,f),i=s,y>u)break}};if(f(),!o){var m;n.on(\"scroll\",function(){var e=l(this);m&&clearTimeout(m),m=setTimeout(function(){f(null,e)},50)}),o=!0}return f},e(\"flow\",new o)});layui.define([\"layer\",\"form\"],function(t){\"use strict\";var e=layui.$,i=layui.layer,a=layui.form,l=(layui.hint(),layui.device()),n=\"layedit\",o=\"layui-show\",r=\"layui-disabled\",c=function(){var t=this;t.index=0,t.config={tool:[\"strong\",\"italic\",\"underline\",\"del\",\"|\",\"left\",\"center\",\"right\",\"|\",\"link\",\"unlink\",\"face\",\"image\"],hideTool:[],height:280}};c.prototype.set=function(t){var i=this;return e.extend(!0,i.config,t),i},c.prototype.on=function(t,e){return layui.onevent(n,t,e)},c.prototype.build=function(t,i){i=i||{};var a=this,n=a.config,r=\"layui-layedit\",c=e(\"string\"==typeof t?\"#\"+t:t),u=\"LAY_layedit_\"+ ++a.index,d=c.next(\".\"+r),y=e.extend({},n,i),f=function(){var t=[],e={};return layui.each(y.hideTool,function(t,i){e[i]=!0}),layui.each(y.tool,function(i,a){C[a]&&!e[a]&&t.push(C[a])}),t.join(\"\")}(),m=e(['<div class=\"'+r+'\">','<div class=\"layui-unselect layui-layedit-tool\">'+f+\"</div>\",'<div class=\"layui-layedit-iframe\">','<iframe id=\"'+u+'\" name=\"'+u+'\" textarea=\"'+t+'\" frameborder=\"0\"></iframe>',\"</div>\",\"</div>\"].join(\"\"));return l.ie&&l.ie<8?c.removeClass(\"layui-hide\").addClass(o):(d[0]&&d.remove(),s.call(a,m,c[0],y),c.addClass(\"layui-hide\").after(m),a.index)},c.prototype.getContent=function(t){var e=u(t);if(e[0])return d(e[0].document.body.innerHTML)},c.prototype.getText=function(t){var i=u(t);if(i[0])return e(i[0].document.body).text()},c.prototype.setContent=function(t,i,a){var l=u(t);l[0]&&(a?e(l[0].document.body).append(i):e(l[0].document.body).html(i),layedit.sync(t))},c.prototype.sync=function(t){var i=u(t);if(i[0]){var a=e(\"#\"+i[1].attr(\"textarea\"));a.val(d(i[0].document.body.innerHTML))}},c.prototype.getSelection=function(t){var e=u(t);if(e[0]){var i=m(e[0].document);return document.selection?i.text:i.toString()}};var s=function(t,i,a){var l=this,n=t.find(\"iframe\");n.css({height:a.height}).on(\"load\",function(){var o=n.contents(),r=n.prop(\"contentWindow\"),c=o.find(\"head\"),s=e([\"<style>\",\"*{margin: 0; padding: 0;}\",\"body{padding: 10px; line-height: 20px; overflow-x: hidden; word-wrap: break-word; font: 14px Helvetica Neue,Helvetica,PingFang SC,Microsoft YaHei,Tahoma,Arial,sans-serif; -webkit-box-sizing: border-box !important; -moz-box-sizing: border-box !important; box-sizing: border-box !important;}\",\"a{color:#01AAED; text-decoration:none;}a:hover{color:#c00}\",\"p{margin-bottom: 10px;}\",\"img{display: inline-block; border: none; vertical-align: middle;}\",\"pre{margin: 10px 0; padding: 10px; line-height: 20px; border: 1px solid #ddd; border-left-width: 6px; background-color: #F2F2F2; color: #333; font-family: Courier New; font-size: 12px;}\",\"</style>\"].join(\"\")),u=o.find(\"body\");c.append(s),u.attr(\"contenteditable\",\"true\").css({\"min-height\":a.height}).html(i.value||\"\"),y.apply(l,[r,n,i,a]),g.call(l,r,t,a)})},u=function(t){var i=e(\"#LAY_layedit_\"+t),a=i.prop(\"contentWindow\");return[a,i]},d=function(t){return 8==l.ie&&(t=t.replace(/<.+>/g,function(t){return t.toLowerCase()})),t},y=function(t,a,n,o){var r=t.document,c=e(r.body);c.on(\"keydown\",function(t){var e=t.keyCode;if(13===e){var a=m(r),l=p(a),n=l.parentNode;if(\"pre\"===n.tagName.toLowerCase()){if(t.shiftKey)return;return i.msg(\"请暂时用shift+enter\"),!1}r.execCommand(\"formatBlock\",!1,\"<p>\")}}),e(n).parents(\"form\").on(\"submit\",function(){var t=c.html();8==l.ie&&(t=t.replace(/<.+>/g,function(t){return t.toLowerCase()})),n.value=t}),c.on(\"paste\",function(e){r.execCommand(\"formatBlock\",!1,\"<p>\"),setTimeout(function(){f.call(t,c),n.value=c.html()},100)})},f=function(t){var i=this;i.document;t.find(\"*[style]\").each(function(){var t=this.style.textAlign;this.removeAttribute(\"style\"),e(this).css({\"text-align\":t||\"\"})}),t.find(\"table\").addClass(\"layui-table\"),t.find(\"script,link\").remove()},m=function(t){return t.selection?t.selection.createRange():t.getSelection().getRangeAt(0)},p=function(t){return t.endContainer||t.parentElement().childNodes[0]},v=function(t,i,a){var l=this.document,n=document.createElement(t);for(var o in i)n.setAttribute(o,i[o]);if(n.removeAttribute(\"text\"),l.selection){var r=a.text||i.text;if(\"a\"===t&&!r)return;r&&(n.innerHTML=r),a.pasteHTML(e(n).prop(\"outerHTML\")),a.select()}else{var r=a.toString()||i.text;if(\"a\"===t&&!r)return;r&&(n.innerHTML=r),a.deleteContents(),a.insertNode(n)}},h=function(t,i){var a=this.document,l=\"layedit-tool-active\",n=p(m(a)),o=function(e){return t.find(\".layedit-tool-\"+e)};i&&i[i.hasClass(l)?\"removeClass\":\"addClass\"](l),t.find(\">i\").removeClass(l),o(\"unlink\").addClass(r),e(n).parents().each(function(){var t=this.tagName.toLowerCase(),e=this.style.textAlign;\"b\"!==t&&\"strong\"!==t||o(\"b\").addClass(l),\"i\"!==t&&\"em\"!==t||o(\"i\").addClass(l),\"u\"===t&&o(\"u\").addClass(l),\"strike\"===t&&o(\"d\").addClass(l),\"p\"===t&&(\"center\"===e?o(\"center\").addClass(l):\"right\"===e?o(\"right\").addClass(l):o(\"left\").addClass(l)),\"a\"===t&&(o(\"link\").addClass(l),o(\"unlink\").removeClass(r))})},g=function(t,a,l){var n=t.document,o=e(n.body),c={link:function(i){var a=p(i),l=e(a).parent();b.call(o,{href:l.attr(\"href\"),target:l.attr(\"target\")},function(e){var a=l[0];\"A\"===a.tagName?a.href=e.url:v.call(t,\"a\",{target:e.target,href:e.url,text:e.url},i)})},unlink:function(t){n.execCommand(\"unlink\")},face:function(e){x.call(this,function(i){v.call(t,\"img\",{src:i.src,alt:i.alt},e)})},image:function(a){var n=this;layui.use(\"upload\",function(o){var r=l.uploadImage||{};o.render({url:r.url,method:r.type,elem:e(n).find(\"input\")[0],done:function(e){0==e.code?(e.data=e.data||{},v.call(t,\"img\",{src:e.data.src,alt:e.data.title},a)):i.msg(e.msg||\"上传失败\")}})})},code:function(e){k.call(o,function(i){v.call(t,\"pre\",{text:i.code,\"lay-lang\":i.lang},e)})},help:function(){i.open({type:2,title:\"帮助\",area:[\"600px\",\"380px\"],shadeClose:!0,shade:.1,skin:\"layui-layer-msg\",content:[\"http://www.layui.com/about/layedit/help.html\",\"no\"]})}},s=a.find(\".layui-layedit-tool\"),u=function(){var i=e(this),a=i.attr(\"layedit-event\"),l=i.attr(\"lay-command\");if(!i.hasClass(r)){o.focus();var u=m(n);u.commonAncestorContainer;l?(n.execCommand(l),/justifyLeft|justifyCenter|justifyRight/.test(l)&&n.execCommand(\"formatBlock\",!1,\"<p>\"),setTimeout(function(){o.focus()},10)):c[a]&&c[a].call(this,u),h.call(t,s,i)}},d=/image/;s.find(\">i\").on(\"mousedown\",function(){var t=e(this),i=t.attr(\"layedit-event\");d.test(i)||u.call(this)}).on(\"click\",function(){var t=e(this),i=t.attr(\"layedit-event\");d.test(i)&&u.call(this)}),o.on(\"click\",function(){h.call(t,s),i.close(x.index)})},b=function(t,e){var l=this,n=i.open({type:1,id:\"LAY_layedit_link\",area:\"350px\",shade:.05,shadeClose:!0,moveType:1,title:\"超链接\",skin:\"layui-layer-msg\",content:['<ul class=\"layui-form\" style=\"margin: 15px;\">','<li class=\"layui-form-item\">','<label class=\"layui-form-label\" style=\"width: 60px;\">URL</label>','<div class=\"layui-input-block\" style=\"margin-left: 90px\">','<input name=\"url\" lay-verify=\"url\" value=\"'+(t.href||\"\")+'\" autofocus=\"true\" autocomplete=\"off\" class=\"layui-input\">',\"</div>\",\"</li>\",'<li class=\"layui-form-item\">','<label class=\"layui-form-label\" style=\"width: 60px;\">打开方式</label>','<div class=\"layui-input-block\" style=\"margin-left: 90px\">','<input type=\"radio\" name=\"target\" value=\"_self\" class=\"layui-input\" title=\"当前窗口\"'+(\"_self\"!==t.target&&t.target?\"\":\"checked\")+\">\",'<input type=\"radio\" name=\"target\" value=\"_blank\" class=\"layui-input\" title=\"新窗口\" '+(\"_blank\"===t.target?\"checked\":\"\")+\">\",\"</div>\",\"</li>\",'<li class=\"layui-form-item\" style=\"text-align: center;\">','<button type=\"button\" lay-submit lay-filter=\"layedit-link-yes\" class=\"layui-btn\"> 确定 </button>','<button style=\"margin-left: 20px;\" type=\"button\" class=\"layui-btn layui-btn-primary\"> 取消 </button>',\"</li>\",\"</ul>\"].join(\"\"),success:function(t,n){var o=\"submit(layedit-link-yes)\";a.render(\"radio\"),t.find(\".layui-btn-primary\").on(\"click\",function(){i.close(n),l.focus()}),a.on(o,function(t){i.close(b.index),e&&e(t.field)})}});b.index=n},x=function(t){var a=function(){var t=[\"[微笑]\",\"[嘻嘻]\",\"[哈哈]\",\"[可爱]\",\"[可怜]\",\"[挖鼻]\",\"[吃惊]\",\"[害羞]\",\"[挤眼]\",\"[闭嘴]\",\"[鄙视]\",\"[爱你]\",\"[泪]\",\"[偷笑]\",\"[亲亲]\",\"[生病]\",\"[太开心]\",\"[白眼]\",\"[右哼哼]\",\"[左哼哼]\",\"[嘘]\",\"[衰]\",\"[委屈]\",\"[吐]\",\"[哈欠]\",\"[抱抱]\",\"[怒]\",\"[疑问]\",\"[馋嘴]\",\"[拜拜]\",\"[思考]\",\"[汗]\",\"[困]\",\"[睡]\",\"[钱]\",\"[失望]\",\"[酷]\",\"[色]\",\"[哼]\",\"[鼓掌]\",\"[晕]\",\"[悲伤]\",\"[抓狂]\",\"[黑线]\",\"[阴险]\",\"[怒骂]\",\"[互粉]\",\"[心]\",\"[伤心]\",\"[猪头]\",\"[熊猫]\",\"[兔子]\",\"[ok]\",\"[耶]\",\"[good]\",\"[NO]\",\"[赞]\",\"[来]\",\"[弱]\",\"[草泥马]\",\"[神马]\",\"[囧]\",\"[浮云]\",\"[给力]\",\"[围观]\",\"[威武]\",\"[奥特曼]\",\"[礼物]\",\"[钟]\",\"[话筒]\",\"[蜡烛]\",\"[蛋糕]\"],e={};return layui.each(t,function(t,i){e[i]=layui.cache.dir+\"images/face/\"+t+\".gif\"}),e}();return x.hide=x.hide||function(t){\"face\"!==e(t.target).attr(\"layedit-event\")&&i.close(x.index)},x.index=i.tips(function(){var t=[];return layui.each(a,function(e,i){t.push('<li title=\"'+e+'\"><img src=\"'+i+'\" alt=\"'+e+'\"></li>')}),'<ul class=\"layui-clear\">'+t.join(\"\")+\"</ul>\"}(),this,{tips:1,time:0,skin:\"layui-box layui-util-face\",maxWidth:500,success:function(l,n){l.css({marginTop:-4,marginLeft:-10}).find(\".layui-clear>li\").on(\"click\",function(){t&&t({src:a[this.title],alt:this.title}),i.close(n)}),e(document).off(\"click\",x.hide).on(\"click\",x.hide)}})},k=function(t){var e=this,l=i.open({type:1,id:\"LAY_layedit_code\",area:\"550px\",shade:.05,shadeClose:!0,moveType:1,title:\"插入代码\",skin:\"layui-layer-msg\",content:['<ul class=\"layui-form layui-form-pane\" style=\"margin: 15px;\">','<li class=\"layui-form-item\">','<label class=\"layui-form-label\">请选择语言</label>','<div class=\"layui-input-block\">','<select name=\"lang\">','<option value=\"JavaScript\">JavaScript</option>','<option value=\"HTML\">HTML</option>','<option value=\"CSS\">CSS</option>','<option value=\"Java\">Java</option>','<option value=\"PHP\">PHP</option>','<option value=\"C#\">C#</option>','<option value=\"Python\">Python</option>','<option value=\"Ruby\">Ruby</option>','<option value=\"Go\">Go</option>',\"</select>\",\"</div>\",\"</li>\",'<li class=\"layui-form-item layui-form-text\">','<label class=\"layui-form-label\">代码</label>','<div class=\"layui-input-block\">','<textarea name=\"code\" lay-verify=\"required\" autofocus=\"true\" class=\"layui-textarea\" style=\"height: 200px;\"></textarea>',\"</div>\",\"</li>\",'<li class=\"layui-form-item\" style=\"text-align: center;\">','<button type=\"button\" lay-submit lay-filter=\"layedit-code-yes\" class=\"layui-btn\"> 确定 </button>','<button style=\"margin-left: 20px;\" type=\"button\" class=\"layui-btn layui-btn-primary\"> 取消 </button>',\"</li>\",\"</ul>\"].join(\"\"),success:function(l,n){var o=\"submit(layedit-code-yes)\";a.render(\"select\"),l.find(\".layui-btn-primary\").on(\"click\",function(){i.close(n),e.focus()}),a.on(o,function(e){i.close(k.index),t&&t(e.field)})}});k.index=l},C={html:'<i class=\"layui-icon layedit-tool-html\" title=\"HTML源代码\" lay-command=\"html\" layedit-event=\"html\"\">&#xe64b;</i><span class=\"layedit-tool-mid\"></span>',strong:'<i class=\"layui-icon layedit-tool-b\" title=\"加粗\" lay-command=\"Bold\" layedit-event=\"b\"\">&#xe62b;</i>',italic:'<i class=\"layui-icon layedit-tool-i\" title=\"斜体\" lay-command=\"italic\" layedit-event=\"i\"\">&#xe644;</i>',underline:'<i class=\"layui-icon layedit-tool-u\" title=\"下划线\" lay-command=\"underline\" layedit-event=\"u\"\">&#xe646;</i>',del:'<i class=\"layui-icon layedit-tool-d\" title=\"删除线\" lay-command=\"strikeThrough\" layedit-event=\"d\"\">&#xe64f;</i>',\"|\":'<span class=\"layedit-tool-mid\"></span>',left:'<i class=\"layui-icon layedit-tool-left\" title=\"左对齐\" lay-command=\"justifyLeft\" layedit-event=\"left\"\">&#xe649;</i>',center:'<i class=\"layui-icon layedit-tool-center\" title=\"居中对齐\" lay-command=\"justifyCenter\" layedit-event=\"center\"\">&#xe647;</i>',right:'<i class=\"layui-icon layedit-tool-right\" title=\"右对齐\" lay-command=\"justifyRight\" layedit-event=\"right\"\">&#xe648;</i>',link:'<i class=\"layui-icon layedit-tool-link\" title=\"插入链接\" layedit-event=\"link\"\">&#xe64c;</i>',unlink:'<i class=\"layui-icon layedit-tool-unlink layui-disabled\" title=\"清除链接\" lay-command=\"unlink\" layedit-event=\"unlink\"\">&#xe64d;</i>',face:'<i class=\"layui-icon layedit-tool-face\" title=\"表情\" layedit-event=\"face\"\">&#xe650;</i>',image:'<i class=\"layui-icon layedit-tool-image\" title=\"图片\" layedit-event=\"image\">&#xe64a;<input type=\"file\" name=\"file\"></i>',code:'<i class=\"layui-icon layedit-tool-code\" title=\"插入代码\" layedit-event=\"code\">&#xe64e;</i>',help:'<i class=\"layui-icon layedit-tool-help\" title=\"帮助\" layedit-event=\"help\">&#xe607;</i>'},w=new c;t(n,w)});layui.define(\"jquery\",function(e){\"use strict\";var a=layui.$,l=\"http://www.layui.com/doc/modules/code.html\";e(\"code\",function(e){var t=[];e=e||{},e.elem=a(e.elem||\".layui-code\"),e.about=!(\"about\"in e)||e.about,e.elem.each(function(){t.push(this)}),layui.each(t.reverse(),function(t,i){var c=a(i),o=c.html();(c.attr(\"lay-encode\")||e.encode)&&(o=o.replace(/&(?!#?[a-zA-Z0-9]+;)/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(/'/g,\"&#39;\").replace(/\"/g,\"&quot;\")),c.html('<ol class=\"layui-code-ol\"><li>'+o.replace(/[\\r\\t\\n]+/g,\"</li><li>\")+\"</li></ol>\"),c.find(\">.layui-code-h3\")[0]||c.prepend('<h3 class=\"layui-code-h3\">'+(c.attr(\"lay-title\")||e.title||\"code\")+(e.about?'<a href=\"'+l+'\" target=\"_blank\">layui.code</a>':\"\")+\"</h3>\");var d=c.find(\">.layui-code-ol\");c.addClass(\"layui-box layui-code-view\"),(c.attr(\"lay-skin\")||e.skin)&&c.addClass(\"layui-code-\"+(c.attr(\"lay-skin\")||e.skin)),(d.find(\"li\").length/100|0)>0&&d.css(\"margin-left\",(d.find(\"li\").length/100|0)+\"px\"),(c.attr(\"lay-height\")||e.height)&&d.css(\"max-height\",c.attr(\"lay-height\")||e.height)})})}).addcss(\"modules/code.css\",\"skincodecss\");\nvar _ajax = layui.$.ajax;\nlayui.$.ajax = function(options){\n     options = options || {};\n     if(location.host === 'demo.spiderflow.org'){\n      options.url = 'http://49.233.182.130:8088/' + options.url;\n     }\n     return _ajax(options);\n}"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/log-viewer.js",
    "content": "function LogViewer(options){\n    options = options || {};\n    this.element = options.element;\n    this.maxLines = options.maxLines || 10;\n    this.onSearchFinish = options.onSearchFinish || function(){};\n    this.bufferSize = this.maxLines * 10;\n    this.logId = options.logId;\n    this.taskId = options.taskId;\n    this.url = options.url;\n    this.buffer = [];\n    this.displayIndex = -1;\n    this.index = -1;\n    this.loading = false;\n    this.reversed = true;\n    this.matchcase = true;\n    this.initEvent();\n    this.init(options.onLoad);\n}\nLogViewer.prototype.init = function(callback){\n    var _this = this;\n    _this.index = -1;\n    this.autoLoad(callback);\n}\nLogViewer.prototype.autoLoad = function(callback){\n    var _this = this;\n    this.loadLines(this.maxLines,function(hasData){\n        if(_this.reversed){\n            _this.displayIndex = _this.buffer.length - _this.maxLines;\n        }else{\n            _this.displayIndex = 0;\n        }\n        _this.render(_this.buffer.slice(_this.displayIndex,_this.displayIndex + _this.maxLines));\n        callback&&callback(hasData);\n    },false);\n}\nLogViewer.prototype.render = function(lines){\n    if(lines.length == 0){\n        return;\n    }\n    this.firstFrom = lines[0].from;\n    this.firstTo = lines[0].to;\n    this.lastFrom = lines[lines.length - 1].from;\n    this.lastTo = lines[lines.length - 1].to;\n    var html = [];\n    if(this.reversed){\n        lines = lines.reverse();\n    }\n    var find = this.keywords === undefined || this.keywords === '';\n    var regx = new RegExp('(' + this.keywords + ')',this.matchcase ? \"ig\" : \"g\");\n    for (var i = 0; i < lines.length; i++) {\n        var text = lines[i].text;\n        if(find == false && (find = text.match(regx))){\n            text = text.replace(regx,'b4430885ba83495_$1_88d1220d37eac831d');\n        }\n        //转义html\n        text = text.replace(/</g,'&lt;');\n        //搜索关键词高亮\n        text = text.replace(/b4430885ba83495_(.*?)_88d1220d37eac831d/g,'<em class=\"search-finded\">$1</em>');\n        html.push('<div class=\"log-row\">' + text + '</div>');\n    }\n    if(this.reversed){\n        html = html.reverse();\n    }\n    this.element.html(html.join(''));\n}\nLogViewer.prototype.search = function(reversed){\n    if(reversed === undefined){\n        reversed = this.reversed;\n    }\n    this.index = reversed ? this.lastFrom : this.firstTo;\n    var _this = this;\n    this.autoLoad(function(hasData){\n        _this.onSearchFinish(hasData);\n    });\n}\nLogViewer.prototype.initEvent = function(){\n    var _this = this;\n    function eventFunc(e){\n        e.stopPropagation();\n        _this.scroll((e.wheelDelta||e.detail) > 0,3);\n        return false;\n    }\n    document.addEventListener('DOMMouseScroll',eventFunc,false);\n    window.onmousewheel = document.onmousewheel = eventFunc;\n    document.addEventListener('keydown', function (e) {\n        e = e || event;\n        var currKey = e.keyCode || e.which || e.charCode;\n        if (currKey === 38 || currKey === 40) {\n            if(_this.keywords){\n                _this.search(currKey === 38);\n            }else{\n                _this.scroll(currKey === 38, 1);\n            }\n        }\n        if (currKey === 33 || currKey === 34) {\n            _this.scroll(currKey === 33, _this.maxLines);\n        }\n        if (currKey === 36 || currKey ===35){\n            _this.reversed = currKey === 35;\n           _this.init();\n        }\n    });\n}\nLogViewer.prototype.setOptions = function(key,value){\n    var _this = this;\n    _this[key] = value;\n}\nLogViewer.prototype.scroll = function(reversed,count){\n    var _this = this;\n    _this.reversed = reversed;\n    var ignore = false;\n    if(reversed){\n        if(this.displayIndex == 0){\n            this.index = this.buffer[0].from;\n            this.loadLines(this.bufferSize,function(hasData){\n                if(hasData){\n                    _this.displayIndex = Math.max(_this.buffer.length - _this.maxLines,0);\n                }\n            },false);\n        }else{\n            _this.displayIndex-=count;\n        }\n    }else{\n        if(this.displayIndex + this.maxLines >= this.buffer.length){\n            this.index = this.buffer[this.buffer.length - 1].to;\n            this.loadLines(this.bufferSize,function(hasData){\n                if(hasData){\n                    _this.displayIndex = 0;\n                }\n            },false);\n        }else{\n            _this.displayIndex+=count;\n        }\n    }\n    this.render(this.buffer.slice(this.displayIndex,this.displayIndex + this.maxLines));\n\n}\nLogViewer.prototype.loadLines = function(count,callback,async){\n    if(this.loading){\n        return;\n    }\n    this.loading = true;\n    var _this = this;\n    $.ajax({\n        url : this.url,\n        async : async,\n        type : 'post',\n        data : {\n            reversed : this.reversed,\n            count : this.bufferSize,\n            id : this.logId,\n            taskId: this.taskId,\n            index : _this.index,\n            keywords : this.keywords,\n            matchcase : this.matchcase,\n            regx : this.regx\n        },\n        dataType : 'json',\n        success : function(json){\n            var hasData = json&&json.data&&json.data.length > 0;\n            if(hasData){\n                _this.buffer = json.data;\n            }\n            callback && callback(hasData);\n            _this.loading = false;\n        }\n    })\n}\n"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/mxgraph/css/common.css",
    "content": "div.mxRubberband {\n\tposition: absolute;\n\toverflow: hidden;\n\tborder-style: solid;\n\tborder-width: 1px;\n\tborder-color: #0000FF;\n\tbackground: #0077FF;\n}\n.mxCellEditor {\n\tbackground: url(data:image/gif;base64,R0lGODlhMAAwAIAAAP///wAAACH5BAEAAAAALAAAAAAwADAAAAIxhI+py+0Po5y02ouz3rz7D4biSJbmiabqyrbuC8fyTNf2jef6zvf+DwwKh8Si8egpAAA7);\n\t_background: url('../images/transparent.gif');\n\tborder-color: transparent;\n\tborder-style: solid;\n\tdisplay: inline-block;\n\tposition: absolute;\n\toverflow: visible;\n\tword-wrap: normal;\n\tborder-width: 0;\n\tmin-width: 1px;\n\tresize: none;\n\tpadding: 0px;\n\tmargin: 0px;\n}\n.mxPlainTextEditor * {\n\tpadding: 0px;\n\tmargin: 0px;\n}\ndiv.mxWindow {\n\t-webkit-box-shadow: 3px 3px 12px #C0C0C0;\n\t-moz-box-shadow: 3px 3px 12px #C0C0C0;\n\tbox-shadow: 3px 3px 12px #C0C0C0;\n\tbackground: url('../images/window.gif');\n\tborder:1px solid #c3c3c3;\n\tposition: absolute;\n\toverflow: hidden;\n\tz-index: 1;\n}\ntable.mxWindow {\n\tborder-collapse: collapse;\n\ttable-layout: fixed;\n  \tfont-family: Arial;\n\tfont-size: 8pt;\n}\ntd.mxWindowTitle {\n\tbackground: url('../images/window-title.gif') repeat-x;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n \ttext-align: center;\n \tfont-weight: bold;\n \toverflow: hidden;\n\theight: 13px;\n\tpadding: 2px;\n \tpadding-top: 4px;\n \tpadding-bottom: 6px;\n \tcolor: black;\n}\ntd.mxWindowPane {\n\tvertical-align: top;\n\tpadding: 0px;\n}\ndiv.mxWindowPane {\n\toverflow: hidden;\n\tposition: relative;\n}\ntd.mxWindowPane td {\n  \tfont-family: Arial;\n\tfont-size: 8pt;\n}\ntd.mxWindowPane input, td.mxWindowPane select, td.mxWindowPane textarea, td.mxWindowPane radio {\n  \tborder-color: #8C8C8C;\n  \tborder-style: solid;\n  \tborder-width: 1px;\n  \tfont-family: Arial;\n\tfont-size: 8pt;\n \tpadding: 1px;\n}\ntd.mxWindowPane button {\n\tbackground: url('../images/button.gif') repeat-x;\n  \tfont-family: Arial;\n  \tfont-size: 8pt;\n  \tpadding: 2px;\n\tfloat: left;\n}\nimg.mxToolbarItem {\n\tmargin-right: 6px;\n\tmargin-bottom: 6px;\n\tborder-width: 1px;\n}\nselect.mxToolbarCombo {\n\tvertical-align: top;\n\tborder-style: inset;\n\tborder-width: 2px;\n}\ndiv.mxToolbarComboContainer {\n\tpadding: 2px;\n}\nimg.mxToolbarMode {\n\tmargin: 2px;\n\tmargin-right: 4px;\n\tmargin-bottom: 4px;\n\tborder-width: 0px;\n}\nimg.mxToolbarModeSelected {\n\tmargin: 0px;\n\tmargin-right: 2px;\n\tmargin-bottom: 2px;\n\tborder-width: 2px;\n\tborder-style: inset;\n}\ndiv.mxTooltip {\n\t-webkit-box-shadow: 3px 3px 12px #C0C0C0;\n\t-moz-box-shadow: 3px 3px 12px #C0C0C0;\n\tbox-shadow: 3px 3px 12px #C0C0C0;\n\tbackground: #FFFFCC;\n\tborder-style: solid;\n\tborder-width: 1px;\n\tborder-color: black;\n\tfont-family: Arial;\n\tfont-size: 8pt;\n\tposition: absolute;\n\tcursor: default;\n\tpadding: 4px;\n\tcolor: black;\n}\ndiv.mxPopupMenu {\n\t-webkit-box-shadow: 3px 3px 12px #C0C0C0;\n\t-moz-box-shadow: 3px 3px 12px #C0C0C0;\n\tbox-shadow: 3px 3px 12px #C0C0C0;\n\tbackground: url('../images/window.gif');\n\tposition: absolute;\n\tborder-style: solid;\n\tborder-width: 1px;\n\tborder-color: black;\n}\ntable.mxPopupMenu {\n\tborder-collapse: collapse;\n\tmargin-top: 1px;\n\tmargin-bottom: 1px;\n}\ntr.mxPopupMenuItem {\n\tcolor: black;\n\tcursor: pointer;\n}\ntr.mxPopupMenuItemHover {\n\tbackground-color: #000066;\n\tcolor: #FFFFFF;\n\tcursor: pointer;\n}\ntd.mxPopupMenuItem {\n\tpadding: 2px 30px 2px 10px;\n\twhite-space: nowrap;\n\tfont-family: Arial;\n\tfont-size: 8pt;\n}\ntd.mxPopupMenuIcon {\n\tbackground-color: #D0D0D0;\n\tpadding: 2px 4px 2px 4px;\n}\n.mxDisabled {\n\topacity: 0.2 !important;\n\tcursor:default !important;\n}\n"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/mxgraph/css/explorer.css",
    "content": "div.mxTooltip {\n\tfilter:progid:DXImageTransform.Microsoft.DropShadow(OffX=4, OffY=4, \n        Color='#A2A2A2', Positive='true');\n}\ndiv.mxPopupMenu {\n\tfilter:progid:DXImageTransform.Microsoft.DropShadow(OffX=4, OffY=4, \n        Color='#C0C0C0', Positive='true');\n}\ndiv.mxWindow {\n\t_filter:progid:DXImageTransform.Microsoft.DropShadow(OffX=4, OffY=4, \n        Color='#C0C0C0', Positive='true');\n}\ntd.mxWindowTitle {\n\t_height: 23px;\n}\n.mxDisabled {\n\tfilter:alpha(opacity=20) !important;\n}\n"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/mxgraph/mxgraph.js",
    "content": "/**\r\n * Copyright (c) 2006-2017, JGraph Ltd\r\n * Copyright (c) 2006-2017, Gaudenz Alder\r\n */\r\nvar mxClient =\r\n{\r\n\t/**\r\n\t * Class: mxClient\r\n\t *\r\n\t * Bootstrapping mechanism for the mxGraph thin client. The production version\r\n\t * of this file contains all code required to run the mxGraph thin client, as\r\n\t * well as global constants to identify the browser and operating system in\r\n\t * use. You may have to load chrome://global/content/contentAreaUtils.js in\r\n\t * your page to disable certain security restrictions in Mozilla.\r\n\t * \r\n\t * Variable: VERSION\r\n\t *\r\n\t * Contains the current version of the mxGraph library. The strings that\r\n\t * communicate versions of mxGraph use the following format.\r\n\t * \r\n\t * versionMajor.versionMinor.buildNumber.revisionNumber\r\n\t * \r\n\t * Current version is 4.0.1.\r\n\t */\r\n\tVERSION: '4.0.1',\r\n\r\n\t/**\r\n\t * Variable: IS_IE\r\n\t *\r\n\t * True if the current browser is Internet Explorer 10 or below. Use <mxClient.IS_IE11>\r\n\t * to detect IE 11.\r\n\t */\r\n\tIS_IE: navigator.userAgent.indexOf('MSIE') >= 0,\r\n\r\n\t/**\r\n\t * Variable: IS_IE6\r\n\t *\r\n\t * True if the current browser is Internet Explorer 6.x.\r\n\t */\r\n\tIS_IE6: navigator.userAgent.indexOf('MSIE 6') >= 0,\r\n\r\n\t/**\r\n\t * Variable: IS_IE11\r\n\t *\r\n\t * True if the current browser is Internet Explorer 11.x.\r\n\t */\r\n\tIS_IE11: !!navigator.userAgent.match(/Trident\\/7\\./),\r\n\r\n\t/**\r\n\t * Variable: IS_EDGE\r\n\t *\r\n\t * True if the current browser is Microsoft Edge.\r\n\t */\r\n\tIS_EDGE: !!navigator.userAgent.match(/Edge\\//),\r\n\r\n\t/**\r\n\t * Variable: IS_QUIRKS\r\n\t *\r\n\t * True if the current browser is Internet Explorer and it is in quirks mode.\r\n\t */\r\n\tIS_QUIRKS: navigator.userAgent.indexOf('MSIE') >= 0 && (document.documentMode == null || document.documentMode == 5),\r\n\r\n\t/**\r\n\t * Variable: IS_EM\r\n\t * \r\n\t * True if the browser is IE11 in enterprise mode (IE8 standards mode).\r\n\t */\r\n\tIS_EM: 'spellcheck' in document.createElement('textarea') && document.documentMode == 8,\r\n\r\n\t/**\r\n\t * Variable: VML_PREFIX\r\n\t * \r\n\t * Prefix for VML namespace in node names. Default is 'v'.\r\n\t */\r\n\tVML_PREFIX: 'v',\r\n\r\n\t/**\r\n\t * Variable: OFFICE_PREFIX\r\n\t * \r\n\t * Prefix for VML office namespace in node names. Default is 'o'.\r\n\t */\r\n\tOFFICE_PREFIX: 'o',\r\n\r\n\t/**\r\n\t * Variable: IS_NS\r\n\t *\r\n\t * True if the current browser is Netscape (including Firefox).\r\n\t */\r\n  \tIS_NS: navigator.userAgent.indexOf('Mozilla/') >= 0 &&\r\n  \t\tnavigator.userAgent.indexOf('MSIE') < 0 &&\r\n  \t\tnavigator.userAgent.indexOf('Edge/') < 0,\r\n\r\n\t/**\r\n\t * Variable: IS_OP\r\n\t *\r\n\t * True if the current browser is Opera.\r\n\t */\r\n  \tIS_OP: navigator.userAgent.indexOf('Opera/') >= 0 ||\r\n  \t\tnavigator.userAgent.indexOf('OPR/') >= 0,\r\n\r\n\t/**\r\n\t * Variable: IS_OT\r\n\t *\r\n\t * True if -o-transform is available as a CSS style, ie for Opera browsers\r\n\t * based on a Presto engine with version 2.5 or later.\r\n\t */\r\n  \tIS_OT: navigator.userAgent.indexOf('Presto/') >= 0 &&\r\n  \t\tnavigator.userAgent.indexOf('Presto/2.4.') < 0 &&\r\n  \t\tnavigator.userAgent.indexOf('Presto/2.3.') < 0 &&\r\n  \t\tnavigator.userAgent.indexOf('Presto/2.2.') < 0 &&\r\n  \t\tnavigator.userAgent.indexOf('Presto/2.1.') < 0 &&\r\n  \t\tnavigator.userAgent.indexOf('Presto/2.0.') < 0 &&\r\n  \t\tnavigator.userAgent.indexOf('Presto/1.') < 0,\r\n  \t\r\n\t/**\r\n\t * Variable: IS_SF\r\n\t *\r\n\t * True if the current browser is Safari.\r\n\t */\r\n  \tIS_SF: navigator.userAgent.indexOf('AppleWebKit/') >= 0 &&\r\n  \t\tnavigator.userAgent.indexOf('Chrome/') < 0 &&\r\n  \t\tnavigator.userAgent.indexOf('Edge/') < 0,\r\n  \t\r\n\t/**\r\n\t * Variable: IS_IOS\r\n\t * \r\n\t * Returns true if the user agent is an iPad, iPhone or iPod.\r\n\t */\r\n  \tIS_IOS: (navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? true : false),\r\n  \t\t\r\n\t/**\r\n\t * Variable: IS_GC\r\n\t *\r\n\t * True if the current browser is Google Chrome.\r\n\t */\r\n  \tIS_GC: navigator.userAgent.indexOf('Chrome/') >= 0 &&\r\n\t\tnavigator.userAgent.indexOf('Edge/') < 0,\r\n\t\r\n\t/**\r\n\t * Variable: IS_CHROMEAPP\r\n\t *\r\n\t * True if the this is running inside a Chrome App.\r\n\t */\r\n  \tIS_CHROMEAPP: window.chrome != null && chrome.app != null && chrome.app.runtime != null,\r\n\t\t\r\n\t/**\r\n\t * Variable: IS_FF\r\n\t *\r\n\t * True if the current browser is Firefox.\r\n\t */\r\n  \tIS_FF: navigator.userAgent.indexOf('Firefox/') >= 0,\r\n  \t\r\n\t/**\r\n\t * Variable: IS_MT\r\n\t *\r\n\t * True if -moz-transform is available as a CSS style. This is the case\r\n\t * for all Firefox-based browsers newer than or equal 3, such as Camino,\r\n\t * Iceweasel, Seamonkey and Iceape.\r\n\t */\r\n  \tIS_MT: (navigator.userAgent.indexOf('Firefox/') >= 0 &&\r\n\t\tnavigator.userAgent.indexOf('Firefox/1.') < 0 &&\r\n  \t\tnavigator.userAgent.indexOf('Firefox/2.') < 0) ||\r\n  \t\t(navigator.userAgent.indexOf('Iceweasel/') >= 0 &&\r\n  \t\tnavigator.userAgent.indexOf('Iceweasel/1.') < 0 &&\r\n  \t\tnavigator.userAgent.indexOf('Iceweasel/2.') < 0) ||\r\n  \t\t(navigator.userAgent.indexOf('SeaMonkey/') >= 0 &&\r\n  \t\tnavigator.userAgent.indexOf('SeaMonkey/1.') < 0) ||\r\n  \t\t(navigator.userAgent.indexOf('Iceape/') >= 0 &&\r\n  \t\tnavigator.userAgent.indexOf('Iceape/1.') < 0),\r\n\r\n\t/**\r\n\t * Variable: IS_VML\r\n\t *\r\n\t * True if the browser supports VML.\r\n\t */\r\n  \tIS_VML: navigator.appName.toUpperCase() == 'MICROSOFT INTERNET EXPLORER',\r\n\r\n\t/**\r\n\t * Variable: IS_SVG\r\n\t *\r\n\t * True if the browser supports SVG.\r\n\t */\r\n  \tIS_SVG: navigator.appName.toUpperCase() != 'MICROSOFT INTERNET EXPLORER',\r\n\r\n\t/**\r\n\t * Variable: NO_FO\r\n\t *\r\n\t * True if foreignObject support is not available. This is the case for\r\n\t * Opera, older SVG-based browsers and all versions of IE.\r\n\t */\r\n  \tNO_FO: !document.createElementNS || document.createElementNS('http://www.w3.org/2000/svg',\r\n  \t\t'foreignObject') != '[object SVGForeignObjectElement]' || navigator.userAgent.indexOf('Opera/') >= 0,\r\n\r\n\t/**\r\n\t * Variable: IS_WIN\r\n\t *\r\n\t * True if the client is a Windows.\r\n\t */\r\n  \tIS_WIN: navigator.appVersion.indexOf('Win') > 0,\r\n\r\n\t/**\r\n\t * Variable: IS_MAC\r\n\t *\r\n\t * True if the client is a Mac.\r\n\t */\r\n  \tIS_MAC: navigator.appVersion.indexOf('Mac') > 0,\r\n\r\n\t/**\r\n\t * Variable: IS_TOUCH\r\n\t * \r\n\t * True if this device supports touchstart/-move/-end events (Apple iOS,\r\n\t * Android, Chromebook and Chrome Browser on touch-enabled devices).\r\n\t */\r\n  \tIS_TOUCH: 'ontouchstart' in document.documentElement,\r\n\r\n\t/**\r\n\t * Variable: IS_POINTER\r\n\t * \r\n\t * True if this device supports Microsoft pointer events (always false on Macs).\r\n\t */\r\n  \tIS_POINTER: window.PointerEvent != null && !(navigator.appVersion.indexOf('Mac') > 0),\r\n\r\n\t/**\r\n\t * Variable: IS_LOCAL\r\n\t *\r\n\t * True if the documents location does not start with http:// or https://.\r\n\t */\r\n  \tIS_LOCAL: document.location.href.indexOf('http://') < 0 &&\r\n  \t\t\t  document.location.href.indexOf('https://') < 0,\r\n\r\n\t/**\r\n\t * Variable: defaultBundles\r\n\t * \r\n\t * Contains the base names of the default bundles if mxLoadResources is false.\r\n\t */\r\n  \tdefaultBundles: [],\r\n\r\n\t/**\r\n\t * Function: isBrowserSupported\r\n\t *\r\n\t * Returns true if the current browser is supported, that is, if\r\n\t * <mxClient.IS_VML> or <mxClient.IS_SVG> is true.\r\n\t * \r\n\t * Example:\r\n\t * \r\n\t * (code)\r\n\t * if (!mxClient.isBrowserSupported())\r\n\t * {\r\n\t *   mxUtils.error('Browser is not supported!', 200, false);\r\n\t * }\r\n\t * (end)\r\n\t */\r\n\tisBrowserSupported: function()\r\n\t{\r\n\t\treturn mxClient.IS_VML || mxClient.IS_SVG;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: link\r\n\t *\r\n\t * Adds a link node to the head of the document. Use this\r\n\t * to add a stylesheet to the page as follows:\r\n\t *\r\n\t * (code)\r\n\t * mxClient.link('stylesheet', filename);\r\n\t * (end)\r\n\t *\r\n\t * where filename is the (relative) URL of the stylesheet. The charset\r\n\t * is hardcoded to ISO-8859-1 and the type is text/css.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * rel - String that represents the rel attribute of the link node.\r\n\t * href - String that represents the href attribute of the link node.\r\n\t * doc - Optional parent document of the link node.\r\n\t * id - unique id for the link element to check if it already exists\r\n\t */\r\n\tlink: function(rel, href, doc, id)\r\n\t{\r\n\t\tdoc = doc || document;\r\n\r\n\t\t// Workaround for Operation Aborted in IE6 if base tag is used in head\r\n\t\tif (mxClient.IS_IE6)\r\n\t\t{\r\n\t\t\tdoc.write('<link rel=\"' + rel + '\" href=\"' + href + '\" charset=\"UTF-8\" type=\"text/css\"/>');\r\n\t\t}\r\n\t\telse\r\n\t\t{\t\r\n\t\t\tvar link = doc.createElement('link');\r\n\t\t\t\r\n\t\t\tlink.setAttribute('rel', rel);\r\n\t\t\tlink.setAttribute('href', href);\r\n\t\t\tlink.setAttribute('charset', 'UTF-8');\r\n\t\t\tlink.setAttribute('type', 'text/css');\r\n\t\t\t\r\n\t\t\tif (id)\r\n\t\t\t{\r\n\t\t\t\tlink.setAttribute('id', id);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar head = doc.getElementsByTagName('head')[0];\r\n\t   \t\thead.appendChild(link);\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: loadResources\r\n\t * \r\n\t * Helper method to load the default bundles if mxLoadResources is false.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * fn - Function to call after all resources have been loaded.\r\n\t * lan - Optional string to pass to <mxResources.add>.\r\n\t */\r\n\tloadResources: function(fn, lan)\r\n\t{\r\n\t\tvar pending = mxClient.defaultBundles.length;\r\n\t\t\r\n\t\tfunction callback()\r\n\t\t{\r\n\t\t\tif (--pending == 0)\r\n\t\t\t{\r\n\t\t\t\tfn();\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tfor (var i = 0; i < mxClient.defaultBundles.length; i++)\r\n\t\t{\r\n\t\t\tmxResources.add(mxClient.defaultBundles[i], lan, callback);\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: include\r\n\t *\r\n\t * Dynamically adds a script node to the document header.\r\n\t * \r\n\t * In production environments, the includes are resolved in the mxClient.js\r\n\t * file to reduce the number of requests required for client startup. This\r\n\t * function should only be used in development environments, but not in\r\n\t * production systems.\r\n\t */\r\n\tinclude: function(src)\r\n\t{\r\n\t\tdocument.write('<script src=\"'+src+'\"></script>');\r\n\t}\r\n};\r\n\r\n/**\r\n * Variable: mxLoadResources\r\n * \r\n * Optional global config variable to toggle loading of the two resource files\r\n * in <mxGraph> and <mxEditor>. Default is true. NOTE: This is a global variable,\r\n * not a variable of mxClient. If this is false, you can use <mxClient.loadResources>\r\n * with its callback to load the default bundles asynchronously.\r\n *\r\n * (code)\r\n * <script type=\"text/javascript\">\r\n * \t\tvar mxLoadResources = false;\r\n * </script>\r\n * <script type=\"text/javascript\" src=\"/path/to/core/directory/js/mxClient.js\"></script>\r\n * (end)\r\n */\r\nif (typeof(mxLoadResources) == 'undefined')\r\n{\r\n\tmxLoadResources = true;\r\n}\r\n\r\n/**\r\n * Variable: mxForceIncludes\r\n * \r\n * Optional global config variable to force loading the JavaScript files in\r\n * development mode. Default is undefined. NOTE: This is a global variable,\r\n * not a variable of mxClient.\r\n *\r\n * (code)\r\n * <script type=\"text/javascript\">\r\n * \t\tvar mxLoadResources = true;\r\n * </script>\r\n * <script type=\"text/javascript\" src=\"/path/to/core/directory/js/mxClient.js\"></script>\r\n * (end)\r\n */\r\nif (typeof(mxForceIncludes) == 'undefined')\r\n{\r\n\tmxForceIncludes = false;\r\n}\r\n\r\n/**\r\n * Variable: mxResourceExtension\r\n * \r\n * Optional global config variable to specify the extension of resource files.\r\n * Default is true. NOTE: This is a global variable, not a variable of mxClient.\r\n *\r\n * (code)\r\n * <script type=\"text/javascript\">\r\n * \t\tvar mxResourceExtension = '.txt';\r\n * </script>\r\n * <script type=\"text/javascript\" src=\"/path/to/core/directory/js/mxClient.js\"></script>\r\n * (end)\r\n */\r\nif (typeof(mxResourceExtension) == 'undefined')\r\n{\r\n\tmxResourceExtension = '.txt';\r\n}\r\n\r\n/**\r\n * Variable: mxLoadStylesheets\r\n * \r\n * Optional global config variable to toggle loading of the CSS files when\r\n * the library is initialized. Default is true. NOTE: This is a global variable,\r\n * not a variable of mxClient.\r\n *\r\n * (code)\r\n * <script type=\"text/javascript\">\r\n * \t\tvar mxLoadStylesheets = false;\r\n * </script>\r\n * <script type=\"text/javascript\" src=\"/path/to/core/directory/js/mxClient.js\"></script>\r\n * (end)\r\n */\r\nif (typeof(mxLoadStylesheets) == 'undefined')\r\n{\r\n\tmxLoadStylesheets = true;\r\n}\r\n\r\n/**\r\n * Variable: basePath\r\n *\r\n * Basepath for all URLs in the core without trailing slash. Default is '.'.\r\n * Set mxBasePath prior to loading the mxClient library as follows to override\r\n * this setting:\r\n *\r\n * (code)\r\n * <script type=\"text/javascript\">\r\n * \t\tmxBasePath = '/path/to/core/directory';\r\n * </script>\r\n * <script type=\"text/javascript\" src=\"/path/to/core/directory/js/mxClient.js\"></script>\r\n * (end)\r\n * \r\n * When using a relative path, the path is relative to the URL of the page that\r\n * contains the assignment. Trailing slashes are automatically removed.\r\n */\r\nif (typeof(mxBasePath) != 'undefined' && mxBasePath.length > 0)\r\n{\r\n\t// Adds a trailing slash if required\r\n\tif (mxBasePath.substring(mxBasePath.length - 1) == '/')\r\n\t{\r\n\t\tmxBasePath = mxBasePath.substring(0, mxBasePath.length - 1);\r\n\t}\r\n\r\n\tmxClient.basePath = mxBasePath;\r\n}\r\nelse\r\n{\r\n\tmxClient.basePath = '.';\r\n}\r\n\r\n/**\r\n * Variable: imageBasePath\r\n *\r\n * Basepath for all images URLs in the core without trailing slash. Default is\r\n * <mxClient.basePath> + '/images'. Set mxImageBasePath prior to loading the\r\n * mxClient library as follows to override this setting:\r\n *\r\n * (code)\r\n * <script type=\"text/javascript\">\r\n * \t\tmxImageBasePath = '/path/to/image/directory';\r\n * </script>\r\n * <script type=\"text/javascript\" src=\"/path/to/core/directory/js/mxClient.js\"></script>\r\n * (end)\r\n * \r\n * When using a relative path, the path is relative to the URL of the page that\r\n * contains the assignment. Trailing slashes are automatically removed.\r\n */\r\nif (typeof(mxImageBasePath) != 'undefined' && mxImageBasePath.length > 0)\r\n{\r\n\t// Adds a trailing slash if required\r\n\tif (mxImageBasePath.substring(mxImageBasePath.length - 1) == '/')\r\n\t{\r\n\t\tmxImageBasePath = mxImageBasePath.substring(0, mxImageBasePath.length - 1);\r\n\t}\r\n\r\n\tmxClient.imageBasePath = mxImageBasePath;\r\n}\r\nelse\r\n{\r\n\tmxClient.imageBasePath = mxClient.basePath + '/images';\t\r\n}\r\n\r\n/**\r\n * Variable: language\r\n *\r\n * Defines the language of the client, eg. en for english, de for german etc.\r\n * The special value 'none' will disable all built-in internationalization and\r\n * resource loading. See <mxResources.getSpecialBundle> for handling identifiers\r\n * with and without a dash.\r\n * \r\n * Set mxLanguage prior to loading the mxClient library as follows to override\r\n * this setting:\r\n *\r\n * (code)\r\n * <script type=\"text/javascript\">\r\n * \t\tmxLanguage = 'en';\r\n * </script>\r\n * <script type=\"text/javascript\" src=\"js/mxClient.js\"></script>\r\n * (end)\r\n * \r\n * If internationalization is disabled, then the following variables should be\r\n * overridden to reflect the current language of the system. These variables are\r\n * cleared when i18n is disabled.\r\n * <mxEditor.askZoomResource>, <mxEditor.lastSavedResource>,\r\n * <mxEditor.currentFileResource>, <mxEditor.propertiesResource>,\r\n * <mxEditor.tasksResource>, <mxEditor.helpResource>, <mxEditor.outlineResource>,\r\n * <mxElbowEdgeHandler.doubleClickOrientationResource>, <mxUtils.errorResource>,\r\n * <mxUtils.closeResource>, <mxGraphSelectionModel.doneResource>,\r\n * <mxGraphSelectionModel.updatingSelectionResource>, <mxGraphView.doneResource>,\r\n * <mxGraphView.updatingDocumentResource>, <mxCellRenderer.collapseExpandResource>,\r\n * <mxGraph.containsValidationErrorsResource> and\r\n * <mxGraph.alreadyConnectedResource>.\r\n */\r\nif (typeof(mxLanguage) != 'undefined' && mxLanguage != null)\r\n{\r\n\tmxClient.language = mxLanguage;\r\n}\r\nelse\r\n{\r\n\tmxClient.language = (mxClient.IS_IE) ? navigator.userLanguage : navigator.language;\r\n}\r\n\r\n/**\r\n * Variable: defaultLanguage\r\n * \r\n * Defines the default language which is used in the common resource files. Any\r\n * resources for this language will only load the common resource file, but not\r\n * the language-specific resource file. Default is 'en'.\r\n * \r\n * Set mxDefaultLanguage prior to loading the mxClient library as follows to override\r\n * this setting:\r\n *\r\n * (code)\r\n * <script type=\"text/javascript\">\r\n * \t\tmxDefaultLanguage = 'de';\r\n * </script>\r\n * <script type=\"text/javascript\" src=\"js/mxClient.js\"></script>\r\n * (end)\r\n */\r\nif (typeof(mxDefaultLanguage) != 'undefined' && mxDefaultLanguage != null)\r\n{\r\n\tmxClient.defaultLanguage = mxDefaultLanguage;\r\n}\r\nelse\r\n{\r\n\tmxClient.defaultLanguage = 'en';\r\n}\r\n\r\n// Adds all required stylesheets and namespaces\r\nif (mxLoadStylesheets)\r\n{\r\n\tmxClient.link('stylesheet', mxClient.basePath + '/css/common.css');\r\n}\r\n\r\n/**\r\n * Variable: languages\r\n *\r\n * Defines the optional array of all supported language extensions. The default\r\n * language does not have to be part of this list. See\r\n * <mxResources.isLanguageSupported>.\r\n *\r\n * (code)\r\n * <script type=\"text/javascript\">\r\n * \t\tmxLanguages = ['de', 'it', 'fr'];\r\n * </script>\r\n * <script type=\"text/javascript\" src=\"js/mxClient.js\"></script>\r\n * (end)\r\n * \r\n * This is used to avoid unnecessary requests to language files, ie. if a 404\r\n * will be returned.\r\n */\r\nif (typeof(mxLanguages) != 'undefined' && mxLanguages != null)\r\n{\r\n\tmxClient.languages = mxLanguages;\r\n}\r\n\r\n// Adds required namespaces, stylesheets and memory handling for older IE browsers\r\nif (mxClient.IS_VML)\r\n{\r\n\tif (mxClient.IS_SVG)\r\n\t{\r\n\t\tmxClient.IS_VML = false;\r\n\t}\r\n\telse\r\n\t{\r\n\t\t// Enables support for IE8 standards mode. Note that this requires all attributes for VML\r\n\t\t// elements to be set using direct notation, ie. node.attr = value. The use of setAttribute\r\n\t\t// is not possible.\r\n\t\tif (document.documentMode == 8)\r\n\t\t{\r\n\t\t\tdocument.namespaces.add(mxClient.VML_PREFIX, 'urn:schemas-microsoft-com:vml', '#default#VML');\r\n\t\t\tdocument.namespaces.add(mxClient.OFFICE_PREFIX, 'urn:schemas-microsoft-com:office:office', '#default#VML');\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tdocument.namespaces.add(mxClient.VML_PREFIX, 'urn:schemas-microsoft-com:vml');\r\n\t\t\tdocument.namespaces.add(mxClient.OFFICE_PREFIX, 'urn:schemas-microsoft-com:office:office');\r\n\t\t}\r\n\r\n\t\t// Workaround for limited number of stylesheets in IE (does not work in standards mode)\r\n\t\tif (mxClient.IS_QUIRKS && document.styleSheets.length >= 30)\r\n\t\t{\r\n\t\t\t(function()\r\n\t\t\t{\r\n\t\t\t\tvar node = document.createElement('style');\r\n\t\t\t\tnode.type = 'text/css';\r\n\t\t\t\tnode.styleSheet.cssText = mxClient.VML_PREFIX + '\\\\:*{behavior:url(#default#VML)}' +\r\n\t\t        \tmxClient.OFFICE_PREFIX + '\\\\:*{behavior:url(#default#VML)}';\r\n\t\t        document.getElementsByTagName('head')[0].appendChild(node);\r\n\t\t\t})();\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tdocument.createStyleSheet().cssText = mxClient.VML_PREFIX + '\\\\:*{behavior:url(#default#VML)}' +\r\n\t\t    \tmxClient.OFFICE_PREFIX + '\\\\:*{behavior:url(#default#VML)}';\r\n\t\t}\r\n\t    \r\n\t    if (mxLoadStylesheets)\r\n\t    {\r\n\t    \tmxClient.link('stylesheet', mxClient.basePath + '/css/explorer.css');\r\n\t    }\r\n\t}\r\n}\r\n\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nvar mxLog =\r\n{\r\n\t/**\r\n\t * Class: mxLog\r\n\t * \r\n\t * A singleton class that implements a simple console.\r\n\t * \r\n\t * Variable: consoleName\r\n\t * \r\n\t * Specifies the name of the console window. Default is 'Console'.\r\n\t */\r\n\tconsoleName: 'Console',\r\n\t\r\n\t/**\r\n\t * Variable: TRACE\r\n\t * \r\n\t * Specified if the output for <enter> and <leave> should be visible in the\r\n\t * console. Default is false.\r\n\t */\r\n\tTRACE: false,\r\n\r\n\t/**\r\n\t * Variable: DEBUG\r\n\t * \r\n\t * Specifies if the output for <debug> should be visible in the console.\r\n\t * Default is true.\r\n\t */\r\n\tDEBUG: true,\r\n\r\n\t/**\r\n\t * Variable: WARN\r\n\t * \r\n\t * Specifies if the output for <warn> should be visible in the console.\r\n\t * Default is true.\r\n\t */\r\n\tWARN: true,\r\n\r\n\t/**\r\n\t * Variable: buffer\r\n\t * \r\n\t * Buffer for pre-initialized content.\r\n\t */\r\n\tbuffer: '',\r\n\t\r\n\t/**\r\n\t * Function: init\r\n\t *\r\n\t * Initializes the DOM node for the console. This requires document.body to\r\n\t * point to a non-null value. This is called from within <setVisible> if the\r\n\t * log has not yet been initialized.\r\n\t */\r\n\tinit: function()\r\n\t{\r\n\t\tif (mxLog.window == null && document.body != null)\r\n\t\t{\r\n\t\t\tvar title = mxLog.consoleName + ' - mxGraph ' + mxClient.VERSION;\r\n\r\n\t\t\t// Creates a table that maintains the layout\r\n\t\t\tvar table = document.createElement('table');\r\n\t\t\ttable.setAttribute('width', '100%');\r\n\t\t\ttable.setAttribute('height', '100%');\r\n\r\n\t\t\tvar tbody = document.createElement('tbody');\r\n\t\t\tvar tr = document.createElement('tr');\r\n\t\t\tvar td = document.createElement('td');\r\n\t\t\ttd.style.verticalAlign = 'top';\r\n\t\t\t\t\r\n\t\t\t// Adds the actual console as a textarea\r\n\t\t\tmxLog.textarea = document.createElement('textarea');\r\n\t\t\tmxLog.textarea.setAttribute('wrap', 'off');\r\n\t\t\tmxLog.textarea.setAttribute('readOnly', 'true');\r\n\t\t\tmxLog.textarea.style.height = '100%';\r\n\t\t\tmxLog.textarea.style.resize = 'none';\r\n\t\t\tmxLog.textarea.value = mxLog.buffer;\r\n\r\n\t\t\t// Workaround for wrong width in standards mode\r\n\t\t\tif (mxClient.IS_NS && document.compatMode != 'BackCompat')\r\n\t\t\t{\r\n\t\t\t\tmxLog.textarea.style.width = '99%';\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tmxLog.textarea.style.width = '100%';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\ttd.appendChild(mxLog.textarea);\r\n\t\t\ttr.appendChild(td);\r\n\t\t\ttbody.appendChild(tr);\r\n\r\n\t\t\t// Creates the container div\r\n\t\t\ttr = document.createElement('tr');\r\n\t\t\tmxLog.td = document.createElement('td');\r\n\t\t\tmxLog.td.style.verticalAlign = 'top';\r\n\t\t\tmxLog.td.setAttribute('height', '30px');\r\n\t\t\t\r\n\t\t\ttr.appendChild(mxLog.td);\r\n\t\t\ttbody.appendChild(tr);\r\n\t\t\ttable.appendChild(tbody);\r\n\r\n\t\t\t// Adds various debugging buttons\r\n\t\t\tmxLog.addButton('Info', function (evt)\r\n\t\t\t{\r\n\t\t\t\tmxLog.info();\r\n\t\t\t});\r\n\t\t\r\n\t\t\tmxLog.addButton('DOM', function (evt)\r\n\t\t\t{\r\n\t\t\t\tvar content = mxUtils.getInnerHtml(document.body);\r\n\t\t\t\tmxLog.debug(content);\r\n\t\t\t});\r\n\t\r\n\t\t\tmxLog.addButton('Trace', function (evt)\r\n\t\t\t{\r\n\t\t\t\tmxLog.TRACE = !mxLog.TRACE;\r\n\t\t\t\t\r\n\t\t\t\tif (mxLog.TRACE)\r\n\t\t\t\t{\r\n\t\t\t\t\tmxLog.debug('Tracing enabled');\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tmxLog.debug('Tracing disabled');\r\n\t\t\t\t}\r\n\t\t\t});\t\r\n\r\n\t\t\tmxLog.addButton('Copy', function (evt)\r\n\t\t\t{\r\n\t\t\t\ttry\r\n\t\t\t\t{\r\n\t\t\t\t\tmxUtils.copy(mxLog.textarea.value);\r\n\t\t\t\t}\r\n\t\t\t\tcatch (err)\r\n\t\t\t\t{\r\n\t\t\t\t\tmxUtils.alert(err);\r\n\t\t\t\t}\r\n\t\t\t});\t\t\t\r\n\r\n\t\t\tmxLog.addButton('Show', function (evt)\r\n\t\t\t{\r\n\t\t\t\ttry\r\n\t\t\t\t{\r\n\t\t\t\t\tmxUtils.popup(mxLog.textarea.value);\r\n\t\t\t\t}\r\n\t\t\t\tcatch (err)\r\n\t\t\t\t{\r\n\t\t\t\t\tmxUtils.alert(err);\r\n\t\t\t\t}\r\n\t\t\t});\t\r\n\t\t\t\r\n\t\t\tmxLog.addButton('Clear', function (evt)\r\n\t\t\t{\r\n\t\t\t\tmxLog.textarea.value = '';\r\n\t\t\t});\r\n\r\n\t\t\t// Cross-browser code to get window size\r\n\t\t\tvar h = 0;\r\n\t\t\tvar w = 0;\r\n\t\t\t\r\n\t\t\tif (typeof(window.innerWidth) === 'number')\r\n\t\t\t{\r\n\t\t\t\th = window.innerHeight;\r\n\t\t\t\tw = window.innerWidth;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\th = (document.documentElement.clientHeight || document.body.clientHeight);\r\n\t\t\t\tw = document.body.clientWidth;\r\n\t\t\t}\r\n\r\n\t\t\tmxLog.window = new mxWindow(title, table, Math.max(0, w - 320), Math.max(0, h - 210), 300, 160);\r\n\t\t\tmxLog.window.setMaximizable(true);\r\n\t\t\tmxLog.window.setScrollable(false);\r\n\t\t\tmxLog.window.setResizable(true);\r\n\t\t\tmxLog.window.setClosable(true);\r\n\t\t\tmxLog.window.destroyOnClose = false;\r\n\t\t\t\r\n\t\t\t// Workaround for ignored textarea height in various setups\r\n\t\t\tif (((mxClient.IS_NS || mxClient.IS_IE) && !mxClient.IS_GC &&\r\n\t\t\t\t!mxClient.IS_SF && document.compatMode != 'BackCompat') ||\r\n\t\t\t\tdocument.documentMode == 11)\r\n\t\t\t{\r\n\t\t\t\tvar elt = mxLog.window.getElement();\r\n\t\t\t\t\r\n\t\t\t\tvar resizeHandler = function(sender, evt)\r\n\t\t\t\t{\r\n\t\t\t\t\tmxLog.textarea.style.height = Math.max(0, elt.offsetHeight - 70) + 'px';\r\n\t\t\t\t}; \r\n\t\t\t\t\r\n\t\t\t\tmxLog.window.addListener(mxEvent.RESIZE_END, resizeHandler);\r\n\t\t\t\tmxLog.window.addListener(mxEvent.MAXIMIZE, resizeHandler);\r\n\t\t\t\tmxLog.window.addListener(mxEvent.NORMALIZE, resizeHandler);\r\n\r\n\t\t\t\tmxLog.textarea.style.height = '92px';\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: info\r\n\t * \r\n\t * Writes the current navigator information to the console.\r\n\t */\r\n\tinfo: function()\r\n\t{\r\n\t\tmxLog.writeln(mxUtils.toString(navigator));\r\n\t},\r\n\t\t\t\r\n\t/**\r\n\t * Function: addButton\r\n\t * \r\n\t * Adds a button to the console using the given label and function.\r\n\t */\r\n\taddButton: function(lab, funct)\r\n\t{\r\n\t\tvar button = document.createElement('button');\r\n\t\tmxUtils.write(button, lab);\r\n\t\tmxEvent.addListener(button, 'click', funct);\r\n\t\tmxLog.td.appendChild(button);\r\n\t},\r\n\t\t\t\t\r\n\t/**\r\n\t * Function: isVisible\r\n\t * \r\n\t * Returns true if the console is visible.\r\n\t */\r\n\tisVisible: function()\r\n\t{\r\n\t\tif (mxLog.window != null)\r\n\t\t{\r\n\t\t\treturn mxLog.window.isVisible();\r\n\t\t}\r\n\t\t\r\n\t\treturn false;\r\n\t},\r\n\t\r\n\r\n\t/**\r\n\t * Function: show\r\n\t * \r\n\t * Shows the console.\r\n\t */\r\n\tshow: function()\r\n\t{\r\n\t\tmxLog.setVisible(true);\r\n\t},\r\n\r\n\t/**\r\n\t * Function: setVisible\r\n\t * \r\n\t * Shows or hides the console.\r\n\t */\r\n\tsetVisible: function(visible)\r\n\t{\r\n\t\tif (mxLog.window == null)\r\n\t\t{\r\n\t\t\tmxLog.init();\r\n\t\t}\r\n\r\n\t\tif (mxLog.window != null)\r\n\t\t{\r\n\t\t\tmxLog.window.setVisible(visible);\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Function: enter\r\n\t * \r\n\t * Writes the specified string to the console\r\n\t * if <TRACE> is true and returns the current \r\n\t * time in milliseconds.\r\n\t *\r\n\t * Example:\r\n\t * \r\n\t * (code)\r\n\t * mxLog.show();\r\n\t * var t0 = mxLog.enter('Hello');\r\n\t * // Do something\r\n\t * mxLog.leave('World!', t0);\r\n\t * (end)\r\n\t */\r\n\tenter: function(string)\r\n\t{\r\n\t\tif (mxLog.TRACE)\r\n\t\t{\r\n\t\t\tmxLog.writeln('Entering '+string);\r\n\t\t\t\r\n\t\t\treturn new Date().getTime();\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Function: leave\r\n\t * \r\n\t * Writes the specified string to the console\r\n\t * if <TRACE> is true and computes the difference\r\n\t * between the current time and t0 in milliseconds.\r\n\t * See <enter> for an example.\r\n\t */\r\n\tleave: function(string, t0)\r\n\t{\r\n\t\tif (mxLog.TRACE)\r\n\t\t{\r\n\t\t\tvar dt = (t0 != 0) ? ' ('+(new Date().getTime() - t0)+' ms)' : '';\r\n\t\t\tmxLog.writeln('Leaving '+string+dt);\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: debug\r\n\t * \r\n\t * Adds all arguments to the console if <DEBUG> is enabled.\r\n\t *\r\n\t * Example:\r\n\t * \r\n\t * (code)\r\n\t * mxLog.show();\r\n\t * mxLog.debug('Hello, World!');\r\n\t * (end)\r\n\t */\r\n\tdebug: function()\r\n\t{\r\n\t\tif (mxLog.DEBUG)\r\n\t\t{\r\n\t\t\tmxLog.writeln.apply(this, arguments);\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: warn\r\n\t * \r\n\t * Adds all arguments to the console if <WARN> is enabled.\r\n\t *\r\n\t * Example:\r\n\t * \r\n\t * (code)\r\n\t * mxLog.show();\r\n\t * mxLog.warn('Hello, World!');\r\n\t * (end)\r\n\t */\r\n\twarn: function()\r\n\t{\r\n\t\tif (mxLog.WARN)\r\n\t\t{\r\n\t\t\tmxLog.writeln.apply(this, arguments);\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Function: write\r\n\t * \r\n\t * Adds the specified strings to the console.\r\n\t */\r\n\twrite: function()\r\n\t{\r\n\t\tvar string = '';\r\n\t\t\r\n\t\tfor (var i = 0; i < arguments.length; i++)\r\n\t\t{\r\n\t\t\tstring += arguments[i];\r\n\t\t\t\r\n\t\t\tif (i < arguments.length - 1)\r\n\t\t\t{\r\n\t\t\t\tstring += ' ';\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (mxLog.textarea != null)\r\n\t\t{\r\n\t\t\tmxLog.textarea.value = mxLog.textarea.value + string;\r\n\r\n\t\t\t// Workaround for no update in Presto 2.5.22 (Opera 10.5)\r\n\t\t\tif (navigator.userAgent.indexOf('Presto/2.5') >= 0)\r\n\t\t\t{\r\n\t\t\t\tmxLog.textarea.style.visibility = 'hidden';\r\n\t\t\t\tmxLog.textarea.style.visibility = 'visible';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tmxLog.textarea.scrollTop = mxLog.textarea.scrollHeight;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tmxLog.buffer += string;\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: writeln\r\n\t * \r\n\t * Adds the specified strings to the console, appending a linefeed at the\r\n\t * end of each string.\r\n\t */\r\n\twriteln: function()\r\n\t{\r\n\t\tvar string = '';\r\n\t\t\r\n\t\tfor (var i = 0; i < arguments.length; i++)\r\n\t\t{\r\n\t\t\tstring += arguments[i];\r\n\t\t\t\r\n\t\t\tif (i < arguments.length - 1)\r\n\t\t\t{\r\n\t\t\t\tstring += ' ';\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tmxLog.write(string + '\\n');\r\n\t}\r\n\t\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nvar mxObjectIdentity =\r\n{\r\n\t/**\r\n\t * Class: mxObjectIdentity\r\n\t * \r\n\t * Identity for JavaScript objects and functions. This is implemented using\r\n\t * a simple incrementing counter which is stored in each object under\r\n\t * <FIELD_NAME>.\r\n\t * \r\n\t * The identity for an object does not change during its lifecycle.\r\n\t * \r\n\t * Variable: FIELD_NAME\r\n\t * \r\n\t * Name of the field to be used to store the object ID. Default is\r\n\t * <code>mxObjectId</code>.\r\n\t */\r\n\tFIELD_NAME: 'mxObjectId',\r\n\r\n\t/**\r\n\t * Variable: counter\r\n\t * \r\n\t * Current counter.\r\n\t */\r\n\tcounter: 0,\r\n\r\n\t/**\r\n\t * Function: get\r\n\t * \r\n\t * Returns the ID for the given object or function or null if no object\r\n\t * is specified.\r\n\t */\r\n\tget: function(obj)\r\n\t{\r\n\t\tif (obj != null)\r\n\t\t{\r\n\t\t\tif (obj[mxObjectIdentity.FIELD_NAME] == null)\r\n\t\t\t{\r\n\t\t\t\tif (typeof obj === 'object')\r\n\t\t\t\t{\r\n\t\t\t\t\tvar ctor = mxUtils.getFunctionName(obj.constructor);\r\n\t\t\t\t\tobj[mxObjectIdentity.FIELD_NAME] = ctor + '#' + mxObjectIdentity.counter++;\r\n\t\t\t\t}\r\n\t\t\t\telse if (typeof obj === 'function')\r\n\t\t\t\t{\r\n\t\t\t\t\tobj[mxObjectIdentity.FIELD_NAME] = 'Function#' + mxObjectIdentity.counter++;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\treturn obj[mxObjectIdentity.FIELD_NAME];\r\n\t\t}\r\n\t\t\r\n\t\treturn null;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: clear\r\n\t * \r\n\t * Deletes the ID from the given object or function.\r\n\t */\r\n\tclear: function(obj)\r\n\t{\r\n\t\tif (typeof(obj) === 'object' || typeof obj === 'function')\r\n\t\t{\r\n\t\t\tdelete obj[mxObjectIdentity.FIELD_NAME];\r\n\t\t}\r\n\t}\r\n\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxDictionary\r\n *\r\n * A wrapper class for an associative array with object keys. Note: This\r\n * implementation uses <mxObjectIdentitiy> to turn object keys into strings.\r\n * \r\n * Constructor: mxEventSource\r\n *\r\n * Constructs a new dictionary which allows object to be used as keys.\r\n */\r\nfunction mxDictionary()\r\n{\r\n\tthis.clear();\r\n};\r\n\r\n/**\r\n * Function: map\r\n *\r\n * Stores the (key, value) pairs in this dictionary.\r\n */\r\nmxDictionary.prototype.map = null;\r\n\r\n/**\r\n * Function: clear\r\n *\r\n * Clears the dictionary.\r\n */\r\nmxDictionary.prototype.clear = function()\r\n{\r\n\tthis.map = {};\r\n};\r\n\r\n/**\r\n * Function: get\r\n *\r\n * Returns the value for the given key.\r\n */\r\nmxDictionary.prototype.get = function(key)\r\n{\r\n\tvar id = mxObjectIdentity.get(key);\r\n\t\r\n\treturn this.map[id];\r\n};\r\n\r\n/**\r\n * Function: put\r\n *\r\n * Stores the value under the given key and returns the previous\r\n * value for that key.\r\n */\r\nmxDictionary.prototype.put = function(key, value)\r\n{\r\n\tvar id = mxObjectIdentity.get(key);\r\n\tvar previous = this.map[id];\r\n\tthis.map[id] = value;\r\n\t\r\n\treturn previous;\r\n};\r\n\r\n/**\r\n * Function: remove\r\n *\r\n * Removes the value for the given key and returns the value that\r\n * has been removed.\r\n */\r\nmxDictionary.prototype.remove = function(key)\r\n{\r\n\tvar id = mxObjectIdentity.get(key);\r\n\tvar previous = this.map[id];\r\n\tdelete this.map[id];\r\n\t\r\n\treturn previous;\r\n};\r\n\r\n/**\r\n * Function: getKeys\r\n *\r\n * Returns all keys as an array.\r\n */\r\nmxDictionary.prototype.getKeys = function()\r\n{\r\n\tvar result = [];\r\n\t\r\n\tfor (var key in this.map)\r\n\t{\r\n\t\tresult.push(key);\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: getValues\r\n *\r\n * Returns all values as an array.\r\n */\r\nmxDictionary.prototype.getValues = function()\r\n{\r\n\tvar result = [];\r\n\t\r\n\tfor (var key in this.map)\r\n\t{\r\n\t\tresult.push(this.map[key]);\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: visit\r\n *\r\n * Visits all entries in the dictionary using the given function with the\r\n * following signature: function(key, value) where key is a string and\r\n * value is an object.\r\n * \r\n * Parameters:\r\n * \r\n * visitor - A function that takes the key and value as arguments.\r\n */\r\nmxDictionary.prototype.visit = function(visitor)\r\n{\r\n\tfor (var key in this.map)\r\n\t{\r\n\t\tvisitor(key, this.map[key]);\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2016, JGraph Ltd\r\n * Copyright (c) 2006-2016, Gaudenz Alder\r\n */\r\nvar mxResources =\r\n{\r\n\t/**\r\n\t * Class: mxResources\r\n\t * \r\n\t * Implements internationalization. You can provide any number of \r\n\t * resource files on the server using the following format for the \r\n\t * filename: name[-en].properties. The en stands for any lowercase \r\n\t * 2-character language shortcut (eg. de for german, fr for french).\r\n\t *\r\n\t * If the optional language extension is omitted, then the file is used as a \r\n\t * default resource which is loaded in all cases. If a properties file for a \r\n\t * specific language exists, then it is used to override the settings in the \r\n\t * default resource. All entries in the file are of the form key=value. The\r\n\t * values may then be accessed in code via <get>. Lines without \r\n\t * equal signs in the properties files are ignored.\r\n\t *\r\n\t * Resource files may either be added programmatically using\r\n\t * <add> or via a resource tag in the UI section of the \r\n\t * editor configuration file, eg:\r\n\t * \r\n\t * (code)\r\n\t * <mxEditor>\r\n\t *   <ui>\r\n\t *     <resource basename=\"examples/resources/mxWorkflow\"/>\r\n\t * (end)\r\n\t * \r\n\t * The above element will load examples/resources/mxWorkflow.properties as well\r\n\t * as the language specific file for the current language, if it exists.\r\n\t * \r\n\t * Values may contain placeholders of the form {1}...{n} where each placeholder\r\n\t * is replaced with the value of the corresponding array element in the params\r\n\t * argument passed to <mxResources.get>. The placeholder {1} maps to the first\r\n\t * element in the array (at index 0).\r\n\t * \r\n\t * See <mxClient.language> for more information on specifying the default\r\n\t * language or disabling all loading of resources.\r\n\t * \r\n\t * Lines that start with a # sign will be ignored.\r\n\t * \r\n\t * Special characters\r\n\t * \r\n\t * To use unicode characters, use the standard notation (eg. \\u8fd1) or %u as a\r\n\t * prefix (eg. %u20AC will display a Euro sign). For normal hex encoded strings,\r\n\t * use % as a prefix, eg. %F6 will display a \"o umlaut\" (&ouml;).\r\n\t * \r\n\t * See <resourcesEncoded> to disable this. If you disable this, make sure that\r\n\t * your files are UTF-8 encoded.\r\n\t * \r\n\t * Asynchronous loading\r\n\t * \r\n\t * By default, the core adds two resource files synchronously at load time.\r\n\t * To load these files asynchronously, set <mxLoadResources> to false\r\n\t * before loading mxClient.js and use <mxResources.loadResources> instead.\r\n\t * \r\n\t * Variable: resources\r\n\t * \r\n\t * Object that maps from keys to values.\r\n\t */\r\n\tresources: {},\r\n\r\n\t/**\r\n\t * Variable: extension\r\n\t * \r\n\t * Specifies the extension used for language files. Default is <mxResourceExtension>.\r\n\t */\r\n\textension: mxResourceExtension,\r\n\r\n\t/**\r\n\t * Variable: resourcesEncoded\r\n\t * \r\n\t * Specifies whether or not values in resource files are encoded with \\u or\r\n\t * percentage. Default is false.\r\n\t */\r\n\tresourcesEncoded: false,\r\n\r\n\t/**\r\n\t * Variable: loadDefaultBundle\r\n\t * \r\n\t * Specifies if the default file for a given basename should be loaded.\r\n\t * Default is true.\r\n\t */\r\n\tloadDefaultBundle: true,\r\n\r\n\t/**\r\n\t * Variable: loadDefaultBundle\r\n\t * \r\n\t * Specifies if the specific language file file for a given basename should\r\n\t * be loaded. Default is true.\r\n\t */\r\n\tloadSpecialBundle: true,\r\n\r\n\t/**\r\n\t * Function: isLanguageSupported\r\n\t * \r\n\t * Hook for subclassers to disable support for a given language. This\r\n\t * implementation returns true if lan is in <mxClient.languages>.\r\n\t * \r\n\t * Parameters:\r\n\t *\r\n\t * lan - The current language.\r\n\t */\r\n\tisLanguageSupported: function(lan)\r\n\t{\r\n\t\tif (mxClient.languages != null)\r\n\t\t{\r\n\t\t\treturn mxUtils.indexOf(mxClient.languages, lan) >= 0;\r\n\t\t}\r\n\t\t\r\n\t\treturn true;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: getDefaultBundle\r\n\t * \r\n\t * Hook for subclassers to return the URL for the special bundle. This\r\n\t * implementation returns basename + <extension> or null if\r\n\t * <loadDefaultBundle> is false.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * basename - The basename for which the file should be loaded.\r\n\t * lan - The current language.\r\n\t */\r\n\tgetDefaultBundle: function(basename, lan)\r\n\t{\r\n\t\tif (mxResources.loadDefaultBundle || !mxResources.isLanguageSupported(lan))\r\n\t\t{\r\n\t\t\treturn basename + mxResources.extension;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn null;\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Function: getSpecialBundle\r\n\t * \r\n\t * Hook for subclassers to return the URL for the special bundle. This\r\n\t * implementation returns basename + '_' + lan + <extension> or null if\r\n\t * <loadSpecialBundle> is false or lan equals <mxClient.defaultLanguage>.\r\n\t * \r\n\t * If <mxResources.languages> is not null and <mxClient.language> contains\r\n\t * a dash, then this method checks if <isLanguageSupported> returns true\r\n\t * for the full language (including the dash). If that returns false the\r\n\t * first part of the language (up to the dash) will be tried as an extension.\r\n\t * \r\n\t * If <mxResources.language> is null then the first part of the language is\r\n\t * used to maintain backwards compatibility.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * basename - The basename for which the file should be loaded.\r\n\t * lan - The language for which the file should be loaded.\r\n\t */\r\n\tgetSpecialBundle: function(basename, lan)\r\n\t{\r\n\t\tif (mxClient.languages == null || !this.isLanguageSupported(lan))\r\n\t\t{\r\n\t\t\tvar dash = lan.indexOf('-');\r\n\t\t\t\r\n\t\t\tif (dash > 0)\r\n\t\t\t{\r\n\t\t\t\tlan = lan.substring(0, dash);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (mxResources.loadSpecialBundle && mxResources.isLanguageSupported(lan) && lan != mxClient.defaultLanguage)\r\n\t\t{\r\n\t\t\treturn basename + '_' + lan + mxResources.extension;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn null;\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Function: add\r\n\t * \r\n\t * Adds the default and current language properties file for the specified\r\n\t * basename. Existing keys are overridden as new files are added. If no\r\n\t * callback is used then the request is synchronous.\r\n\t *\r\n\t * Example:\r\n\t * \r\n\t * At application startup, additional resources may be \r\n\t * added using the following code:\r\n\t * \r\n\t * (code)\r\n\t * mxResources.add('resources/editor');\r\n\t * (end)\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * basename - The basename for which the file should be loaded.\r\n\t * lan - The language for which the file should be loaded.\r\n\t * callback - Optional callback for asynchronous loading.\r\n\t */\r\n\tadd: function(basename, lan, callback)\r\n\t{\r\n\t\tlan = (lan != null) ? lan : ((mxClient.language != null) ?\r\n\t\t\tmxClient.language.toLowerCase() : mxConstants.NONE);\r\n\t\t\r\n\t\tif (lan != mxConstants.NONE)\r\n\t\t{\r\n\t\t\tvar defaultBundle = mxResources.getDefaultBundle(basename, lan);\r\n\t\t\tvar specialBundle = mxResources.getSpecialBundle(basename, lan);\r\n\t\t\t\r\n\t\t\tvar loadSpecialBundle = function()\r\n\t\t\t{\r\n\t\t\t\tif (specialBundle != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (callback)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tmxUtils.get(specialBundle, function(req)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tmxResources.parse(req.getText());\r\n\t\t\t\t\t\t\tcallback();\r\n\t\t\t\t\t\t}, function()\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tcallback();\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ttry\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t   \t\tvar req = mxUtils.load(specialBundle);\r\n\t\t\t\t\t   \t\t\r\n\t\t\t\t\t   \t\tif (req.isReady())\r\n\t\t\t\t\t   \t\t{\r\n\t\t\t\t\t \t   \t\tmxResources.parse(req.getText());\r\n\t\t\t\t\t   \t\t}\r\n\t\t\t\t   \t\t}\r\n\t\t\t\t   \t\tcatch (e)\r\n\t\t\t\t   \t\t{\r\n\t\t\t\t   \t\t\t// ignore\r\n\t\t\t\t\t   \t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if (callback != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tcallback();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (defaultBundle != null)\r\n\t\t\t{\r\n\t\t\t\tif (callback)\r\n\t\t\t\t{\r\n\t\t\t\t\tmxUtils.get(defaultBundle, function(req)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tmxResources.parse(req.getText());\r\n\t\t\t\t\t\tloadSpecialBundle();\r\n\t\t\t\t\t}, function()\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tloadSpecialBundle();\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\ttry\r\n\t\t\t\t\t{\r\n\t\t\t\t   \t\tvar req = mxUtils.load(defaultBundle);\r\n\t\t\t\t   \t\t\r\n\t\t\t\t   \t\tif (req.isReady())\r\n\t\t\t\t   \t\t{\r\n\t\t\t\t \t   \t\tmxResources.parse(req.getText());\r\n\t\t\t\t   \t\t}\r\n\t\t\t\t   \t\t\r\n\t\t\t\t   \t\tloadSpecialBundle();\r\n\t\t\t\t  \t}\r\n\t\t\t\t  \tcatch (e)\r\n\t\t\t\t  \t{\r\n\t\t\t\t  \t\t// ignore\r\n\t\t\t\t  \t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\t// Overlays the language specific file (_lan-extension)\r\n\t\t\t\tloadSpecialBundle();\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Function: parse\r\n\t * \r\n\t * Parses the key, value pairs in the specified\r\n\t * text and stores them as local resources.\r\n\t */\r\n\tparse: function(text)\r\n\t{\r\n\t\tif (text != null)\r\n\t\t{\r\n\t\t\tvar lines = text.split('\\n');\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < lines.length; i++)\r\n\t\t\t{\r\n\t\t\t\tif (lines[i].charAt(0) != '#')\r\n\t\t\t\t{\r\n\t\t\t\t\tvar index = lines[i].indexOf('=');\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (index > 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar key = lines[i].substring(0, index);\r\n\t\t\t\t\t\tvar idx = lines[i].length;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (lines[i].charCodeAt(idx - 1) == 13)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tidx--;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tvar value = lines[i].substring(index + 1, idx);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (this.resourcesEncoded)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvalue = value.replace(/\\\\(?=u[a-fA-F\\d]{4})/g,\"%\");\r\n\t\t\t\t\t\t\tmxResources.resources[key] = unescape(value);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tmxResources.resources[key] = value;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Function: get\r\n\t * \r\n\t * Returns the value for the specified resource key.\r\n\t *\r\n\t * Example:\r\n\t * To read the value for 'welomeMessage', use the following:\r\n\t * (code)\r\n\t * var result = mxResources.get('welcomeMessage') || '';\r\n\t * (end)\r\n\t *\r\n\t * This would require an entry of the following form in\r\n\t * one of the English language resource files:\r\n\t * (code)\r\n\t * welcomeMessage=Welcome to mxGraph!\r\n\t * (end)\r\n\t * \r\n\t * The part behind the || is the string value to be used if the given\r\n\t * resource is not available.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * key - String that represents the key of the resource to be returned.\r\n\t * params - Array of the values for the placeholders of the form {1}...{n}\r\n\t * to be replaced with in the resulting string.\r\n\t * defaultValue - Optional string that specifies the default return value.\r\n\t */\r\n\tget: function(key, params, defaultValue)\r\n\t{\r\n\t\tvar value = mxResources.resources[key];\r\n\t\t\r\n\t\t// Applies the default value if no resource was found\r\n\t\tif (value == null)\r\n\t\t{\r\n\t\t\tvalue = defaultValue;\r\n\t\t}\r\n\t\t\r\n\t\t// Replaces the placeholders with the values in the array\r\n\t\tif (value != null && params != null)\r\n\t\t{\r\n\t\t\tvalue = mxResources.replacePlaceholders(value, params);\r\n\t\t}\r\n\t\t\r\n\t\treturn value;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: replacePlaceholders\r\n\t * \r\n\t * Replaces the given placeholders with the given parameters.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * value - String that contains the placeholders.\r\n\t * params - Array of the values for the placeholders of the form {1}...{n}\r\n\t * to be replaced with in the resulting string.\r\n\t */\r\n\treplacePlaceholders: function(value, params)\r\n\t{\r\n\t\tvar result = [];\r\n\t\tvar index = null;\r\n\t\t\r\n\t\tfor (var i = 0; i < value.length; i++)\r\n\t\t{\r\n\t\t\tvar c = value.charAt(i);\r\n\r\n\t\t\tif (c == '{')\r\n\t\t\t{\r\n\t\t\t\tindex = '';\r\n\t\t\t}\r\n\t\t\telse if (index != null && \tc == '}')\r\n\t\t\t{\r\n\t\t\t\tindex = parseInt(index)-1;\r\n\t\t\t\t\r\n\t\t\t\tif (index >= 0 && index < params.length)\r\n\t\t\t\t{\r\n\t\t\t\t\tresult.push(params[index]);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tindex = null;\r\n\t\t\t}\r\n\t\t\telse if (index != null)\r\n\t\t\t{\r\n\t\t\t\tindex += c;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tresult.push(c);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn result.join('');\r\n\t},\r\n\r\n\t/**\r\n\t * Function: loadResources\r\n\t * \r\n\t * Loads all required resources asynchronously. Use this to load the graph and\r\n\t * editor resources if <mxLoadResources> is false.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * callback - Callback function for asynchronous loading.\r\n\t */\r\n\tloadResources: function(callback)\r\n\t{\r\n\t\tmxResources.add(mxClient.basePath+'/resources/editor', null, function()\r\n\t\t{\r\n\t\t\tmxResources.add(mxClient.basePath+'/resources/graph', null, callback);\r\n\t\t});\r\n\t}\r\n\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxPoint\r\n *\r\n * Implements a 2-dimensional vector with double precision coordinates.\r\n * \r\n * Constructor: mxPoint\r\n *\r\n * Constructs a new point for the optional x and y coordinates. If no\r\n * coordinates are given, then the default values for <x> and <y> are used.\r\n */\r\nfunction mxPoint(x, y)\r\n{\r\n\tthis.x = (x != null) ? x : 0;\r\n\tthis.y = (y != null) ? y : 0;\r\n};\r\n\r\n/**\r\n * Variable: x\r\n *\r\n * Holds the x-coordinate of the point. Default is 0.\r\n */\r\nmxPoint.prototype.x = null;\r\n\r\n/**\r\n * Variable: y\r\n *\r\n * Holds the y-coordinate of the point. Default is 0.\r\n */\r\nmxPoint.prototype.y = null;\r\n\r\n/**\r\n * Function: equals\r\n * \r\n * Returns true if the given object equals this point.\r\n */\r\nmxPoint.prototype.equals = function(obj)\r\n{\r\n\treturn obj != null && obj.x == this.x && obj.y == this.y;\r\n};\r\n\r\n/**\r\n * Function: clone\r\n *\r\n * Returns a clone of this <mxPoint>.\r\n */\r\nmxPoint.prototype.clone = function()\r\n{\r\n\t// Handles subclasses as well\r\n\treturn mxUtils.clone(this);\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxRectangle\r\n *\r\n * Extends <mxPoint> to implement a 2-dimensional rectangle with double\r\n * precision coordinates.\r\n * \r\n * Constructor: mxRectangle\r\n *\r\n * Constructs a new rectangle for the optional parameters. If no parameters\r\n * are given then the respective default values are used.\r\n */\r\nfunction mxRectangle(x, y, width, height)\r\n{\r\n\tmxPoint.call(this, x, y);\r\n\r\n\tthis.width = (width != null) ? width : 0;\r\n\tthis.height = (height != null) ? height : 0;\r\n};\r\n\r\n/**\r\n * Extends mxPoint.\r\n */\r\nmxRectangle.prototype = new mxPoint();\r\nmxRectangle.prototype.constructor = mxRectangle;\r\n\r\n/**\r\n * Variable: width\r\n *\r\n * Holds the width of the rectangle. Default is 0.\r\n */\r\nmxRectangle.prototype.width = null;\r\n\r\n/**\r\n * Variable: height\r\n *\r\n * Holds the height of the rectangle. Default is 0.\r\n */\r\nmxRectangle.prototype.height = null;\r\n\r\n/**\r\n * Function: setRect\r\n * \r\n * Sets this rectangle to the specified values\r\n */\r\nmxRectangle.prototype.setRect = function(x, y, w, h)\r\n{\r\n    this.x = x;\r\n    this.y = y;\r\n    this.width = w;\r\n    this.height = h;\r\n};\r\n\r\n/**\r\n * Function: getCenterX\r\n * \r\n * Returns the x-coordinate of the center point.\r\n */\r\nmxRectangle.prototype.getCenterX = function ()\r\n{\r\n\treturn this.x + this.width/2;\r\n};\r\n\r\n/**\r\n * Function: getCenterY\r\n * \r\n * Returns the y-coordinate of the center point.\r\n */\r\nmxRectangle.prototype.getCenterY = function ()\r\n{\r\n\treturn this.y + this.height/2;\r\n};\r\n\r\n/**\r\n * Function: add\r\n *\r\n * Adds the given rectangle to this rectangle.\r\n */\r\nmxRectangle.prototype.add = function(rect)\r\n{\r\n\tif (rect != null)\r\n\t{\r\n\t\tvar minX = Math.min(this.x, rect.x);\r\n\t\tvar minY = Math.min(this.y, rect.y);\r\n\t\tvar maxX = Math.max(this.x + this.width, rect.x + rect.width);\r\n\t\tvar maxY = Math.max(this.y + this.height, rect.y + rect.height);\r\n\t\t\r\n\t\tthis.x = minX;\r\n\t\tthis.y = minY;\r\n\t\tthis.width = maxX - minX;\r\n\t\tthis.height = maxY - minY;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: intersect\r\n * \r\n * Changes this rectangle to where it overlaps with the given rectangle.\r\n */\r\nmxRectangle.prototype.intersect = function(rect)\r\n{\r\n\tif (rect != null)\r\n\t{\r\n\t\tvar r1 = this.x + this.width;\r\n\t\tvar r2 = rect.x + rect.width;\r\n\t\t\r\n\t\tvar b1 = this.y + this.height;\r\n\t\tvar b2 = rect.y + rect.height;\r\n\t\t\r\n\t\tthis.x = Math.max(this.x, rect.x);\r\n\t\tthis.y = Math.max(this.y, rect.y);\r\n\t\tthis.width = Math.min(r1, r2) - this.x;\r\n\t\tthis.height = Math.min(b1, b2) - this.y;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: grow\r\n *\r\n * Grows the rectangle by the given amount, that is, this method subtracts\r\n * the given amount from the x- and y-coordinates and adds twice the amount\r\n * to the width and height.\r\n */\r\nmxRectangle.prototype.grow = function(amount)\r\n{\r\n\tthis.x -= amount;\r\n\tthis.y -= amount;\r\n\tthis.width += 2 * amount;\r\n\tthis.height += 2 * amount;\r\n};\r\n\r\n/**\r\n * Function: getPoint\r\n * \r\n * Returns the top, left corner as a new <mxPoint>.\r\n */\r\nmxRectangle.prototype.getPoint = function()\r\n{\r\n\treturn new mxPoint(this.x, this.y);\r\n};\r\n\r\n/**\r\n * Function: rotate90\r\n * \r\n * Rotates this rectangle by 90 degree around its center point.\r\n */\r\nmxRectangle.prototype.rotate90 = function()\r\n{\r\n\tvar t = (this.width - this.height) / 2;\r\n\tthis.x += t;\r\n\tthis.y -= t;\r\n\tvar tmp = this.width;\r\n\tthis.width = this.height;\r\n\tthis.height = tmp;\r\n};\r\n\r\n/**\r\n * Function: equals\r\n * \r\n * Returns true if the given object equals this rectangle.\r\n */\r\nmxRectangle.prototype.equals = function(obj)\r\n{\r\n\treturn obj != null && obj.x == this.x && obj.y == this.y &&\r\n\t\tobj.width == this.width && obj.height == this.height;\r\n};\r\n\r\n/**\r\n * Function: fromRectangle\r\n * \r\n * Returns a new <mxRectangle> which is a copy of the given rectangle.\r\n */\r\nmxRectangle.fromRectangle = function(rect)\r\n{\r\n\treturn new mxRectangle(rect.x, rect.y, rect.width, rect.height);\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nvar mxEffects =\r\n{\r\n\r\n\t/**\r\n\t * Class: mxEffects\r\n\t * \r\n\t * Provides animation effects.\r\n\t */\r\n\r\n\t/**\r\n\t * Function: animateChanges\r\n\t * \r\n\t * Asynchronous animated move operation. See also: <mxMorphing>.\r\n\t * \r\n\t * Example:\r\n\t * \r\n\t * (code)\r\n\t * graph.model.addListener(mxEvent.CHANGE, function(sender, evt)\r\n\t * {\r\n\t *   var changes = evt.getProperty('edit').changes;\r\n\t * \r\n\t *   if (changes.length < 10)\r\n\t *   {\r\n\t *     mxEffects.animateChanges(graph, changes);\r\n\t *   }\r\n\t * });\r\n\t * (end)\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * graph - <mxGraph> that received the changes.\r\n\t * changes - Array of changes to be animated.\r\n\t * done - Optional function argument that is invoked after the\r\n\t * last step of the animation.\r\n\t */\r\n\tanimateChanges: function(graph, changes, done)\r\n\t{\r\n\t\tvar maxStep = 10;\r\n\t\tvar step = 0;\r\n\r\n\t\tvar animate = function() \r\n\t\t{\r\n\t\t\tvar isRequired = false;\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < changes.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar change = changes[i];\r\n\t\t\t\t\r\n\t\t\t\tif (change instanceof mxGeometryChange ||\r\n\t\t\t\t\tchange instanceof mxTerminalChange ||\r\n\t\t\t\t\tchange instanceof mxValueChange ||\r\n\t\t\t\t\tchange instanceof mxChildChange ||\r\n\t\t\t\t\tchange instanceof mxStyleChange)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar state = graph.getView().getState(change.cell || change.child, false);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (state != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tisRequired = true;\r\n\t\t\t\t\t\r\n\t\t\t\t\t\tif (change.constructor != mxGeometryChange || graph.model.isEdge(change.cell))\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tmxUtils.setOpacity(state.shape.node, 100 * step / maxStep);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar scale = graph.getView().scale;\t\t\t\t\t\r\n\r\n\t\t\t\t\t\t\tvar dx = (change.geometry.x - change.previous.x) * scale;\r\n\t\t\t\t\t\t\tvar dy = (change.geometry.y - change.previous.y) * scale;\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tvar sx = (change.geometry.width - change.previous.width) * scale;\r\n\t\t\t\t\t\t\tvar sy = (change.geometry.height - change.previous.height) * scale;\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tif (step == 0)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tstate.x -= dx;\r\n\t\t\t\t\t\t\t\tstate.y -= dy;\r\n\t\t\t\t\t\t\t\tstate.width -= sx;\r\n\t\t\t\t\t\t\t\tstate.height -= sy;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tstate.x += dx / maxStep;\r\n\t\t\t\t\t\t\t\tstate.y += dy / maxStep;\r\n\t\t\t\t\t\t\t\tstate.width += sx / maxStep;\r\n\t\t\t\t\t\t\t\tstate.height += sy / maxStep;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tgraph.cellRenderer.redraw(state);\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t// Fades all connected edges and children\r\n\t\t\t\t\t\t\tmxEffects.cascadeOpacity(graph, change.cell, 100 * step / maxStep);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (step < maxStep && isRequired)\r\n\t\t\t{\r\n\t\t\t\tstep++;\r\n\t\t\t\twindow.setTimeout(animate, delay);\r\n\t\t\t}\r\n\t\t\telse if (done != null)\r\n\t\t\t{\r\n\t\t\t\tdone();\r\n\t\t\t}\r\n\t\t};\r\n\t\t\r\n\t\tvar delay = 30;\r\n\t\tanimate();\r\n\t},\r\n    \r\n\t/**\r\n\t * Function: cascadeOpacity\r\n\t * \r\n\t * Sets the opacity on the given cell and its descendants.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * graph - <mxGraph> that contains the cells.\r\n\t * cell - <mxCell> to set the opacity for.\r\n\t * opacity - New value for the opacity in %.\r\n\t */\r\n    cascadeOpacity: function(graph, cell, opacity)\r\n\t{\r\n\t\t// Fades all children\r\n\t\tvar childCount = graph.model.getChildCount(cell);\r\n\t\t\r\n\t\tfor (var i=0; i<childCount; i++)\r\n\t\t{\r\n\t\t\tvar child = graph.model.getChildAt(cell, i);\r\n\t\t\tvar childState = graph.getView().getState(child);\r\n\t\t\t\r\n\t\t\tif (childState != null)\r\n\t\t\t{\r\n\t\t\t\tmxUtils.setOpacity(childState.shape.node, opacity);\r\n\t\t\t\tmxEffects.cascadeOpacity(graph, child, opacity);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Fades all connected edges\r\n\t\tvar edges = graph.model.getEdges(cell);\r\n\t\t\r\n\t\tif (edges != null)\r\n\t\t{\r\n\t\t\tfor (var i=0; i<edges.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar edgeState = graph.getView().getState(edges[i]);\r\n\t\t\t\t\r\n\t\t\t\tif (edgeState != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tmxUtils.setOpacity(edgeState.shape.node, opacity);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Function: fadeOut\r\n\t * \r\n\t * Asynchronous fade-out operation.\r\n\t */\r\n\tfadeOut: function(node, from, remove, step, delay, isEnabled)\r\n\t{\r\n\t\tstep = step || 40;\r\n\t\tdelay = delay || 30;\r\n\t\t\r\n\t\tvar opacity = from || 100;\r\n\t\t\r\n\t\tmxUtils.setOpacity(node, opacity);\r\n\t\t\r\n\t\tif (isEnabled || isEnabled == null)\r\n\t\t{\r\n\t\t\tvar f = function()\r\n\t\t\t{\r\n\t\t\t    opacity = Math.max(opacity-step, 0);\r\n\t\t\t\tmxUtils.setOpacity(node, opacity);\r\n\t\t\t\t\r\n\t\t\t\tif (opacity > 0)\r\n\t\t\t\t{\r\n\t\t\t\t\twindow.setTimeout(f, delay);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tnode.style.visibility = 'hidden';\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (remove && node.parentNode)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tnode.parentNode.removeChild(node);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t};\r\n\t\t\twindow.setTimeout(f, delay);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tnode.style.visibility = 'hidden';\r\n\t\t\t\r\n\t\t\tif (remove && node.parentNode)\r\n\t\t\t{\r\n\t\t\t\tnode.parentNode.removeChild(node);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nvar mxUtils =\r\n{\r\n\t/**\r\n\t * Class: mxUtils\r\n\t * \r\n\t * A singleton class that provides cross-browser helper methods.\r\n\t * This is a global functionality. To access the functions in this\r\n\t * class, use the global classname appended by the functionname.\r\n\t * You may have to load chrome://global/content/contentAreaUtils.js\r\n\t * to disable certain security restrictions in Mozilla for the <open>,\r\n\t * <save>, <saveAs> and <copy> function.\r\n\t * \r\n\t * For example, the following code displays an error message:\r\n\t * \r\n\t * (code)\r\n\t * mxUtils.error('Browser is not supported!', 200, false);\r\n\t * (end)\r\n\t * \r\n\t * Variable: errorResource\r\n\t * \r\n\t * Specifies the resource key for the title of the error window. If the\r\n\t * resource for this key does not exist then the value is used as\r\n\t * the title. Default is 'error'.\r\n\t */\r\n\terrorResource: (mxClient.language != 'none') ? 'error' : '',\r\n\t\r\n\t/**\r\n\t * Variable: closeResource\r\n\t * \r\n\t * Specifies the resource key for the label of the close button. If the\r\n\t * resource for this key does not exist then the value is used as\r\n\t * the label. Default is 'close'.\r\n\t */\r\n\tcloseResource: (mxClient.language != 'none') ? 'close' : '',\r\n\r\n\t/**\r\n\t * Variable: errorImage\r\n\t * \r\n\t * Defines the image used for error dialogs.\r\n\t */\r\n\terrorImage: mxClient.imageBasePath + '/error.gif',\r\n\t\r\n\t/**\r\n\t * Function: removeCursors\r\n\t * \r\n\t * Removes the cursors from the style of the given DOM node and its\r\n\t * descendants.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * element - DOM node to remove the cursor style from.\r\n\t */\r\n\tremoveCursors: function(element)\r\n\t{\r\n\t\tif (element.style != null)\r\n\t\t{\r\n\t\t\telement.style.cursor = '';\r\n\t\t}\r\n\t\t\r\n\t\tvar children = element.childNodes;\r\n\t\t\r\n\t\tif (children != null)\r\n\t\t{\r\n\t        var childCount = children.length;\r\n\t        \r\n\t        for (var i = 0; i < childCount; i += 1)\r\n\t        {\r\n\t            mxUtils.removeCursors(children[i]);\r\n\t        }\r\n\t    }\r\n\t},\r\n\r\n\t/**\r\n\t * Function: getCurrentStyle\r\n\t * \r\n\t * Returns the current style of the specified element.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * element - DOM node whose current style should be returned.\r\n\t */\r\n\tgetCurrentStyle: function()\r\n\t{\r\n\t\tif (mxClient.IS_IE && (document.documentMode == null || document.documentMode < 9))\r\n\t\t{\r\n\t\t\treturn function(element)\r\n\t\t\t{\r\n\t\t\t\treturn (element != null) ? element.currentStyle : null;\r\n\t\t\t};\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn function(element)\r\n\t\t\t{\r\n\t\t\t\treturn (element != null) ?\r\n\t\t\t\t\twindow.getComputedStyle(element, '') :\r\n\t\t\t\t\tnull;\r\n\t\t\t};\r\n\t\t}\r\n\t}(),\r\n\t\r\n\t/**\r\n\t * Function: parseCssNumber\r\n\t * \r\n\t * Parses the given CSS numeric value adding handling for the values thin,\r\n\t * medium and thick (2, 4 and 6).\r\n\t */\r\n\tparseCssNumber: function(value)\r\n\t{\r\n\t\tif (value == 'thin')\r\n\t\t{\r\n\t\t\tvalue = '2';\r\n\t\t}\r\n\t\telse if (value == 'medium')\r\n\t\t{\r\n\t\t\tvalue = '4';\r\n\t\t}\r\n\t\telse if (value == 'thick')\r\n\t\t{\r\n\t\t\tvalue = '6';\r\n\t\t}\r\n\t\t\r\n\t\tvalue = parseFloat(value);\r\n\t\t\r\n\t\tif (isNaN(value))\r\n\t\t{\r\n\t\t\tvalue = 0;\r\n\t\t}\r\n\t\t\r\n\t\treturn value;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: setPrefixedStyle\r\n\t * \r\n\t * Adds the given style with the standard name and an optional vendor prefix for the current\r\n\t * browser.\r\n\t * \r\n\t * (code)\r\n\t * mxUtils.setPrefixedStyle(node.style, 'transformOrigin', '0% 0%');\r\n\t * (end)\r\n\t */\r\n\tsetPrefixedStyle: function()\r\n\t{\r\n\t\tvar prefix = null;\r\n\t\t\r\n\t\tif (mxClient.IS_OT)\r\n\t\t{\r\n\t\t\tprefix = 'O';\r\n\t\t}\r\n\t\telse if (mxClient.IS_SF || mxClient.IS_GC)\r\n\t\t{\r\n\t\t\tprefix = 'Webkit';\r\n\t\t}\r\n\t\telse if (mxClient.IS_MT)\r\n\t\t{\r\n\t\t\tprefix = 'Moz';\r\n\t\t}\r\n\t\telse if (mxClient.IS_IE && document.documentMode >= 9 && document.documentMode < 10)\r\n\t\t{\r\n\t\t\tprefix = 'ms';\r\n\t\t}\r\n\r\n\t\treturn function(style, name, value)\r\n\t\t{\r\n\t\t\tstyle[name] = value;\r\n\t\t\t\r\n\t\t\tif (prefix != null && name.length > 0)\r\n\t\t\t{\r\n\t\t\t\tname = prefix + name.substring(0, 1).toUpperCase() + name.substring(1);\r\n\t\t\t\tstyle[name] = value;\r\n\t\t\t}\r\n\t\t};\r\n\t}(),\r\n\t\r\n\t/**\r\n\t * Function: hasScrollbars\r\n\t * \r\n\t * Returns true if the overflow CSS property of the given node is either\r\n\t * scroll or auto.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * node - DOM node whose style should be checked for scrollbars.\r\n\t */\r\n\thasScrollbars: function(node)\r\n\t{\r\n\t\tvar style = mxUtils.getCurrentStyle(node);\r\n\r\n\t\treturn style != null && (style.overflow == 'scroll' || style.overflow == 'auto');\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: bind\r\n\t * \r\n\t * Returns a wrapper function that locks the execution scope of the given\r\n\t * function to the specified scope. Inside funct, the \"this\" keyword\r\n\t * becomes a reference to that scope.\r\n\t */\r\n\tbind: function(scope, funct)\r\n\t{\r\n\t\treturn function()\r\n\t\t{\r\n\t\t\treturn funct.apply(scope, arguments);\r\n\t\t};\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: eval\r\n\t * \r\n\t * Evaluates the given expression using eval and returns the JavaScript\r\n\t * object that represents the expression result. Supports evaluation of\r\n\t * expressions that define functions and returns the function object for\r\n\t * these expressions.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * expr - A string that represents a JavaScript expression.\r\n\t */\r\n\teval: function(expr)\r\n\t{\r\n\t\tvar result = null;\r\n\r\n\t\tif (expr.indexOf('function') >= 0)\r\n\t\t{\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\teval('var _mxJavaScriptExpression='+expr);\r\n\t\t\t\tresult = _mxJavaScriptExpression;\r\n\t\t\t\t// TODO: Use delete here?\r\n\t\t\t\t_mxJavaScriptExpression = null;\r\n\t\t\t}\r\n\t\t\tcatch (e)\r\n\t\t\t{\r\n\t\t\t\tmxLog.warn(e.message + ' while evaluating ' + expr);\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tresult = eval(expr);\r\n\t\t\t}\r\n\t\t\tcatch (e)\r\n\t\t\t{\r\n\t\t\t\tmxLog.warn(e.message + ' while evaluating ' + expr);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn result;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: findNode\r\n\t * \r\n\t * Returns the first node where attr equals value.\r\n\t * This implementation does not use XPath.\r\n\t */\r\n\tfindNode: function(node, attr, value)\r\n\t{\r\n\t\tif (node.nodeType == mxConstants.NODETYPE_ELEMENT)\r\n\t\t{\r\n\t\t\tvar tmp = node.getAttribute(attr);\r\n\t\r\n\t\t\tif (tmp != null && tmp == value)\r\n\t\t\t{\r\n\t\t\t\treturn node;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tnode = node.firstChild;\r\n\t\t\r\n\t\twhile (node != null)\r\n\t\t{\r\n\t\t\tvar result = mxUtils.findNode(node, attr, value);\r\n\t\t\t\r\n\t\t\tif (result != null)\r\n\t\t\t{\r\n\t\t\t\treturn result;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tnode = node.nextSibling;\r\n\t\t}\r\n\t\t\r\n\t\treturn null;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: getFunctionName\r\n\t * \r\n\t * Returns the name for the given function.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * f - JavaScript object that represents a function.\r\n\t */\r\n\tgetFunctionName: function(f)\r\n\t{\r\n\t\tvar str = null;\r\n\r\n\t\tif (f != null)\r\n\t\t{\r\n\t\t\tif (f.name != null)\r\n\t\t\t{\r\n\t\t\t\tstr = f.name;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tstr = mxUtils.trim(f.toString());\r\n\t\t\t\t\r\n\t\t\t\tif (/^function\\s/.test(str))\r\n\t\t\t\t{\r\n\t\t\t\t\tstr = mxUtils.ltrim(str.substring(9));\r\n\t\t\t\t\tvar idx2 = str.indexOf('(');\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (idx2 > 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tstr = str.substring(0, idx2);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn str;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: indexOf\r\n\t * \r\n\t * Returns the index of obj in array or -1 if the array does not contain\r\n\t * the given object.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * array - Array to check for the given obj.\r\n\t * obj - Object to find in the given array.\r\n\t */\r\n\tindexOf: function(array, obj)\r\n\t{\r\n\t\tif (array != null && obj != null)\r\n\t\t{\r\n\t\t\tfor (var i = 0; i < array.length; i++)\r\n\t\t\t{\r\n\t\t\t\tif (array[i] == obj)\r\n\t\t\t\t{\r\n\t\t\t\t\treturn i;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn -1;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: forEach\r\n\t * \r\n\t * Calls the given function for each element of the given array and returns\r\n\t * the array.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * array - Array that contains the elements.\r\n\t * fn - Function to be called for each object.\r\n\t */\r\n\tforEach: function(array, fn)\r\n\t{\r\n\t\tif (array != null && fn != null)\r\n\t\t{\r\n\t\t\tfor (var i = 0; i < array.length; i++)\r\n\t\t\t{\r\n\t\t\t\tfn(array[i]);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn array;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: remove\r\n\t * \r\n\t * Removes all occurrences of the given object in the given array or\r\n\t * object. If there are multiple occurrences of the object, be they\r\n\t * associative or as an array entry, all occurrences are removed from\r\n\t * the array or deleted from the object. By removing the object from\r\n\t * the array, all elements following the removed element are shifted\r\n\t * by one step towards the beginning of the array.\r\n\t * \r\n\t * The length of arrays is not modified inside this function.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * obj - Object to find in the given array.\r\n\t * array - Array to check for the given obj.\r\n\t */\r\n\tremove: function(obj, array)\r\n\t{\r\n\t\tvar result = null;\r\n\t\t\r\n\t\tif (typeof(array) == 'object')\r\n\t\t{\r\n\t\t\tvar index = mxUtils.indexOf(array, obj);\r\n\t\t\t\r\n\t\t\twhile (index >= 0)\r\n\t\t\t{\r\n\t\t\t\tarray.splice(index, 1);\r\n\t\t\t\tresult = obj;\r\n\t\t\t\tindex = mxUtils.indexOf(array, obj);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tfor (var key in array)\r\n\t\t{\r\n\t\t\tif (array[key] == obj)\r\n\t\t\t{\r\n\t\t\t\tdelete array[key];\r\n\t\t\t\tresult = obj;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn result;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: isNode\r\n\t * \r\n\t * Returns true if the given value is an XML node with the node name\r\n\t * and if the optional attribute has the specified value.\r\n\t * \r\n\t * This implementation assumes that the given value is a DOM node if the\r\n\t * nodeType property is numeric, that is, if isNaN returns false for\r\n\t * value.nodeType.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * value - Object that should be examined as a node.\r\n\t * nodeName - String that specifies the node name.\r\n\t * attributeName - Optional attribute name to check.\r\n\t * attributeValue - Optional attribute value to check.\r\n\t */\r\n\t isNode: function(value, nodeName, attributeName, attributeValue)\r\n\t {\r\n\t \tif (value != null && !isNaN(value.nodeType) && (nodeName == null ||\r\n\t \t\tvalue.nodeName.toLowerCase() == nodeName.toLowerCase()))\r\n \t\t{\r\n \t\t\treturn attributeName == null ||\r\n \t\t\t\tvalue.getAttribute(attributeName) == attributeValue;\r\n \t\t}\r\n\t \t\r\n\t \treturn false;\r\n\t },\r\n\t\r\n\t/**\r\n\t * Function: isAncestorNode\r\n\t * \r\n\t * Returns true if the given ancestor is an ancestor of the\r\n\t * given DOM node in the DOM. This also returns true if the\r\n\t * child is the ancestor.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * ancestor - DOM node that represents the ancestor.\r\n\t * child - DOM node that represents the child.\r\n\t */\r\n\t isAncestorNode: function(ancestor, child)\r\n\t {\r\n\t \tvar parent = child;\r\n\t \t\r\n\t \twhile (parent != null)\r\n\t \t{\r\n\t \t\tif (parent == ancestor)\r\n\t \t\t{\r\n\t \t\t\treturn true;\r\n\t \t\t}\r\n\r\n\t \t\tparent = parent.parentNode;\r\n\t \t}\r\n\t \t\r\n\t \treturn false;\r\n\t },\r\n\r\n\t/**\r\n\t * Function: getChildNodes\r\n\t * \r\n\t * Returns an array of child nodes that are of the given node type.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * node - Parent DOM node to return the children from.\r\n\t * nodeType - Optional node type to return. Default is\r\n\t * <mxConstants.NODETYPE_ELEMENT>.\r\n\t */\r\n\tgetChildNodes: function(node, nodeType)\r\n\t{\r\n\t\tnodeType = nodeType || mxConstants.NODETYPE_ELEMENT;\r\n\t\t\r\n\t\tvar children = [];\r\n\t\tvar tmp = node.firstChild;\r\n\t\t\r\n\t\twhile (tmp != null)\r\n\t\t{\r\n\t\t\tif (tmp.nodeType == nodeType)\r\n\t\t\t{\r\n\t\t\t\tchildren.push(tmp);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\ttmp = tmp.nextSibling;\r\n\t\t}\r\n\t\t\r\n\t\treturn children;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: importNode\r\n\t * \r\n\t * Cross browser implementation for document.importNode. Uses document.importNode\r\n\t * in all browsers but IE, where the node is cloned by creating a new node and\r\n\t * copying all attributes and children into it using importNode, recursively.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * doc - Document to import the node into.\r\n\t * node - Node to be imported.\r\n\t * allChildren - If all children should be imported.\r\n\t */\r\n\timportNode: function(doc, node, allChildren)\r\n\t{\r\n\t\tif (mxClient.IS_IE && (document.documentMode == null || document.documentMode < 10))\r\n\t\t{\r\n\t\t\tswitch (node.nodeType)\r\n\t\t\t{\r\n\t\t\t\tcase 1: /* element */\r\n\t\t\t\t{\r\n\t\t\t\t\tvar newNode = doc.createElement(node.nodeName);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (node.attributes && node.attributes.length > 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tfor (var i = 0; i < node.attributes.length; i++)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tnewNode.setAttribute(node.attributes[i].nodeName,\r\n\t\t\t\t\t\t\t\tnode.getAttribute(node.attributes[i].nodeName));\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (allChildren && node.childNodes && node.childNodes.length > 0)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tfor (var i = 0; i < node.childNodes.length; i++)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tnewNode.appendChild(mxUtils.importNode(doc, node.childNodes[i], allChildren));\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\treturn newNode;\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t\tcase 3: /* text */\r\n\t\t\t    case 4: /* cdata-section */\r\n\t\t\t    case 8: /* comment */\r\n\t\t\t    {\r\n\t\t\t      return doc.createTextNode(node.value);\r\n\t\t\t      break;\r\n\t\t\t    }\r\n\t\t\t};\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn doc.importNode(node, allChildren);\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Function: createXmlDocument\r\n\t * \r\n\t * Returns a new, empty XML document.\r\n\t */\r\n\tcreateXmlDocument: function()\r\n\t{\r\n\t\tvar doc = null;\r\n\t\t\r\n\t\tif (document.implementation && document.implementation.createDocument)\r\n\t\t{\r\n\t\t\tdoc = document.implementation.createDocument('', '', null);\r\n\t\t}\r\n\t\telse if (window.ActiveXObject)\r\n\t\t{\r\n\t\t\tdoc = new ActiveXObject('Microsoft.XMLDOM');\r\n\t \t}\r\n\t \t\r\n\t \treturn doc;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: parseXml\r\n\t * \r\n\t * Parses the specified XML string into a new XML document and returns the\r\n\t * new document.\r\n\t * \r\n\t * Example:\r\n\t * \r\n\t * (code)\r\n\t * var doc = mxUtils.parseXml(\r\n\t *   '<mxGraphModel><root><MyDiagram id=\"0\"><mxCell/></MyDiagram>'+\r\n\t *   '<MyLayer id=\"1\"><mxCell parent=\"0\" /></MyLayer><MyObject id=\"2\">'+\r\n\t *   '<mxCell style=\"strokeColor=blue;fillColor=red\" parent=\"1\" vertex=\"1\">'+\r\n\t *   '<mxGeometry x=\"10\" y=\"10\" width=\"80\" height=\"30\" as=\"geometry\"/>'+\r\n\t *   '</mxCell></MyObject></root></mxGraphModel>');\r\n\t * (end)\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * xml - String that contains the XML data.\r\n\t */\r\n\tparseXml: function()\r\n\t{\r\n\t\tif (window.DOMParser)\r\n\t\t{\r\n\t\t\treturn function(xml)\r\n\t\t\t{\r\n\t\t\t\tvar parser = new DOMParser();\r\n\t\t\t\t\r\n\t\t\t\treturn parser.parseFromString(xml, 'text/xml');\r\n\t\t\t};\r\n\t\t}\r\n\t\telse // IE<=9\r\n\t\t{\r\n\t\t\treturn function(xml)\r\n\t\t\t{\r\n\t\t\t\tvar result = mxUtils.createXmlDocument();\r\n\t\t\t\tresult.async = false;\r\n\t\t\t\t// Workaround for parsing errors with SVG DTD\r\n\t\t\t\tresult.validateOnParse = false;\r\n\t\t\t\tresult.resolveExternals = false;\r\n\t\t\t\tresult.loadXML(xml);\r\n\t\t\t\t\r\n\t\t\t\treturn result;\r\n\t\t\t};\r\n\t\t}\r\n\t}(),\r\n\r\n\t/**\r\n\t * Function: clearSelection\r\n\t * \r\n\t * Clears the current selection in the page.\r\n\t */\r\n\tclearSelection: function()\r\n\t{\r\n\t\tif (document.selection)\r\n\t\t{\r\n\t\t\treturn function()\r\n\t\t\t{\r\n\t\t\t\tdocument.selection.empty();\r\n\t\t\t};\r\n\t\t}\r\n\t\telse if (window.getSelection)\r\n\t\t{\r\n\t\t\treturn function()\r\n\t\t\t{\r\n\t\t\t\tif (window.getSelection().empty)\r\n\t\t\t\t{\r\n\t\t\t\t\twindow.getSelection().empty();\r\n\t\t\t\t}\r\n\t\t\t\telse if (window.getSelection().removeAllRanges)\r\n\t\t\t\t{\r\n\t\t\t\t\twindow.getSelection().removeAllRanges();\r\n\t\t\t\t}\r\n\t\t\t};\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn function() { };\r\n\t\t}\r\n\t}(),\r\n\r\n\t/**\r\n\t * Function: getPrettyXML\r\n\t * \r\n\t * Returns a pretty printed string that represents the XML tree for the\r\n\t * given node. This method should only be used to print XML for reading,\r\n\t * use <getXml> instead to obtain a string for processing.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * node - DOM node to return the XML for.\r\n\t * tab - Optional string that specifies the indentation for one level.\r\n\t * Default is two spaces.\r\n\t * indent - Optional string that represents the current indentation.\r\n\t * Default is an empty string.\r\n\t */\r\n\tgetPrettyXml: function(node, tab, indent)\r\n\t{\r\n\t\tvar result = [];\r\n\t\t\r\n\t\tif (node != null)\r\n\t\t{\r\n\t\t\ttab = tab || '  ';\r\n\t\t\tindent = indent || '';\r\n\t\t\t\r\n\t\t\tif (node.nodeType == mxConstants.NODETYPE_TEXT)\r\n\t\t\t{\r\n\t\t\t\tvar value =  mxUtils.trim(mxUtils.getTextContent(node));\r\n\t\t\t\t\r\n\t\t\t\tif (value.length > 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tresult.push(indent + mxUtils.htmlEntities(value) + '\\n');\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tresult.push(indent + '<' + node.nodeName);\r\n\t\t\t\t\r\n\t\t\t\t// Creates the string with the node attributes\r\n\t\t\t\t// and converts all HTML entities in the values\r\n\t\t\t\tvar attrs = node.attributes;\r\n\t\t\t\t\r\n\t\t\t\tif (attrs != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tfor (var i = 0; i < attrs.length; i++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar val = mxUtils.htmlEntities(attrs[i].value);\r\n\t\t\t\t\t\tresult.push(' ' + attrs[i].nodeName + '=\"' + val + '\"');\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Recursively creates the XML string for each\r\n\t\t\t\t// child nodes and appends it here with an\r\n\t\t\t\t// indentation\r\n\t\t\t\tvar tmp = node.firstChild;\r\n\t\t\t\t\r\n\t\t\t\tif (tmp != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tresult.push('>\\n');\r\n\t\t\t\t\t\r\n\t\t\t\t\twhile (tmp != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tresult.push(mxUtils.getPrettyXml(tmp, tab, indent + tab));\r\n\t\t\t\t\t\ttmp = tmp.nextSibling;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tresult.push(indent + '</'+node.nodeName + '>\\n');\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tresult.push('/>\\n');\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn result.join('');\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: removeWhitespace\r\n\t * \r\n\t * Removes the sibling text nodes for the given node that only consists\r\n\t * of tabs, newlines and spaces.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * node - DOM node whose siblings should be removed.\r\n\t * before - Optional boolean that specifies the direction of the traversal.\r\n\t */\r\n\tremoveWhitespace: function(node, before)\r\n\t{\r\n\t\tvar tmp = (before) ? node.previousSibling : node.nextSibling;\r\n\t\t\r\n\t\twhile (tmp != null && tmp.nodeType == mxConstants.NODETYPE_TEXT)\r\n\t\t{\r\n\t\t\tvar next = (before) ? tmp.previousSibling : tmp.nextSibling;\r\n\t\t\tvar text = mxUtils.getTextContent(tmp);\r\n\t\t\t\r\n\t\t\tif (mxUtils.trim(text).length == 0)\r\n\t\t\t{\r\n\t\t\t\ttmp.parentNode.removeChild(tmp);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\ttmp = next;\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: htmlEntities\r\n\t * \r\n\t * Replaces characters (less than, greater than, newlines and quotes) with\r\n\t * their HTML entities in the given string and returns the result.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * s - String that contains the characters to be converted.\r\n\t * newline - If newlines should be replaced. Default is true.\r\n\t */\r\n\thtmlEntities: function(s, newline)\r\n\t{\r\n\t\ts = String(s || '');\r\n\t\t\r\n\t\ts = s.replace(/&/g,'&amp;'); // 38 26\r\n\t\ts = s.replace(/\"/g,'&quot;'); // 34 22\r\n\t\ts = s.replace(/\\'/g,'&#39;'); // 39 27\r\n\t\ts = s.replace(/</g,'&lt;'); // 60 3C\r\n\t\ts = s.replace(/>/g,'&gt;'); // 62 3E\r\n\r\n\t\tif (newline == null || newline)\r\n\t\t{\r\n\t\t\ts = s.replace(/\\n/g, '&#xa;');\r\n\t\t}\r\n\t\t\r\n\t\treturn s;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: isVml\r\n\t * \r\n\t * Returns true if the given node is in the VML namespace.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * node - DOM node whose tag urn should be checked.\r\n\t */\r\n\tisVml: function(node)\r\n\t{\r\n\t\treturn node != null && node.tagUrn == 'urn:schemas-microsoft-com:vml';\r\n\t},\r\n\r\n\t/**\r\n\t * Function: getXml\r\n\t * \r\n\t * Returns the XML content of the specified node. For Internet Explorer,\r\n\t * all \\r\\n\\t[\\t]* are removed from the XML string and the remaining \\r\\n\r\n\t * are replaced by \\n. All \\n are then replaced with linefeed, or &#xa; if\r\n\t * no linefeed is defined.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * node - DOM node to return the XML for.\r\n\t * linefeed - Optional string that linefeeds are converted into. Default is\r\n\t * &#xa;\r\n\t */\r\n\tgetXml: function(node, linefeed)\r\n\t{\r\n\t\tvar xml = '';\r\n\r\n\t\tif (window.XMLSerializer != null)\r\n\t\t{\r\n\t\t\tvar xmlSerializer = new XMLSerializer();\r\n\t\t\txml = xmlSerializer.serializeToString(node);     \r\n\t\t}\r\n\t\telse if (node.xml != null)\r\n\t\t{\r\n\t\t\txml = node.xml.replace(/\\r\\n\\t[\\t]*/g, '').\r\n\t\t\t\treplace(/>\\r\\n/g, '>').\r\n\t\t\t\treplace(/\\r\\n/g, '\\n');\r\n\t\t}\r\n\r\n\t\t// Replaces linefeeds with HTML Entities.\r\n\t\tlinefeed = linefeed || '&#xa;';\r\n\t\txml = xml.replace(/\\n/g, linefeed);\r\n\t\t  \r\n\t\treturn xml;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: extractTextWithWhitespace\r\n\t * \r\n\t * Returns the text content of the specified node.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * elems - DOM nodes to return the text for.\r\n\t */\r\n\textractTextWithWhitespace: function(elems)\r\n\t{\r\n\t    // Known block elements for handling linefeeds (list is not complete)\r\n\t\tvar blocks = ['BLOCKQUOTE', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'OL', 'P', 'PRE', 'TABLE', 'UL'];\r\n\t\tvar ret = [];\r\n\t\t\r\n\t\tfunction doExtract(elts)\r\n\t\t{\r\n\t\t\t// Single break should be ignored\r\n\t\t\tif (elts.length == 1 && (elts[0].nodeName == 'BR' ||\r\n\t\t\t\telts[0].innerHTML == '\\n'))\r\n\t\t\t{\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t    for (var i = 0; i < elts.length; i++)\r\n\t\t    {\r\n\t\t        var elem = elts[i];\r\n\r\n\t\t\t\t// DIV with a br or linefeed forces a linefeed\r\n\t\t\t\tif (elem.nodeName == 'BR' || elem.innerHTML == '\\n' ||\r\n\t\t\t\t\t((elts.length == 1 || i == 0) && (elem.nodeName == 'DIV' &&\r\n\t\t\t\t\telem.innerHTML.toLowerCase() == '<br>')))\r\n\t\t    \t{\r\n\t    \t\t\tret.push('\\n');\r\n\t\t    \t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t        if (elem.nodeType === 3 || elem.nodeType === 4)\r\n\t\t\t        {\r\n\t\t\t        \tif (elem.nodeValue.length > 0)\r\n\t\t\t        \t{\r\n\t\t\t        \t\tret.push(elem.nodeValue);\r\n\t\t\t        \t}\r\n\t\t\t        }\r\n\t\t\t        else if (elem.nodeType !== 8 && elem.childNodes.length > 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tdoExtract(elem.childNodes);\r\n\t\t\t\t\t}\r\n\t\t\t        \r\n\t        \t\tif (i < elts.length - 1 && mxUtils.indexOf(blocks, elts[i + 1].nodeName) >= 0)\r\n\t        \t\t{\r\n\t        \t\t\tret.push('\\n');\t\t\r\n\t        \t\t}\r\n\t\t\t\t}\r\n\t\t    }\r\n\t\t};\r\n\t\t\r\n\t\tdoExtract(elems);\r\n\t    \r\n\t    return ret.join('');\r\n\t},\r\n\r\n\t/**\r\n\t * Function: replaceTrailingNewlines\r\n\t * \r\n\t * Replaces each trailing newline with the given pattern.\r\n\t */\r\n\treplaceTrailingNewlines: function(str, pattern)\r\n\t{\r\n\t\t// LATER: Check is this can be done with a regular expression\r\n\t\tvar postfix = '';\r\n\t\t\r\n\t\twhile (str.length > 0 && str.charAt(str.length - 1) == '\\n')\r\n\t\t{\r\n\t\t\tstr = str.substring(0, str.length - 1);\r\n\t\t\tpostfix += pattern;\r\n\t\t}\r\n\t\t\r\n\t\treturn str + postfix;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: getTextContent\r\n\t * \r\n\t * Returns the text content of the specified node.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * node - DOM node to return the text content for.\r\n\t */\r\n\tgetTextContent: function(node)\r\n\t{\r\n\t\t// Only IE10-\r\n\t\tif (mxClient.IS_IE && node.innerText !== undefined)\r\n\t\t{\r\n\t\t\treturn node.innerText;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn (node != null) ? node[(node.textContent === undefined) ? 'text' : 'textContent'] : '';\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: setTextContent\r\n\t * \r\n\t * Sets the text content of the specified node.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * node - DOM node to set the text content for.\r\n\t * text - String that represents the text content.\r\n\t */\r\n\tsetTextContent: function(node, text)\r\n\t{\r\n\t\tif (node.innerText !== undefined)\r\n\t\t{\r\n\t\t\tnode.innerText = text;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tnode[(node.textContent === undefined) ? 'text' : 'textContent'] = text;\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: getInnerHtml\r\n\t * \r\n\t * Returns the inner HTML for the given node as a string or an empty string\r\n\t * if no node was specified. The inner HTML is the text representing all\r\n\t * children of the node, but not the node itself.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * node - DOM node to return the inner HTML for.\r\n\t */\r\n\tgetInnerHtml: function()\r\n\t{\r\n\t\tif (mxClient.IS_IE)\r\n\t\t{\r\n\t\t\treturn function(node)\r\n\t\t\t{\r\n\t\t\t\tif (node != null)\r\n\t\t\t\t{\r\n\t\t\t\t\treturn node.innerHTML;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\treturn '';\r\n\t\t\t};\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn function(node)\r\n\t\t\t{\r\n\t\t\t\tif (node != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar serializer = new XMLSerializer();\r\n\t\t\t\t\treturn serializer.serializeToString(node);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\treturn '';\r\n\t\t\t};\r\n\t\t}\r\n\t}(),\r\n\r\n\t/**\r\n\t * Function: getOuterHtml\r\n\t * \r\n\t * Returns the outer HTML for the given node as a string or an empty\r\n\t * string if no node was specified. The outer HTML is the text representing\r\n\t * all children of the node including the node itself.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * node - DOM node to return the outer HTML for.\r\n\t */\r\n\tgetOuterHtml: function()\r\n\t{\r\n\t\tif (mxClient.IS_IE)\r\n\t\t{\r\n\t\t\treturn function(node)\r\n\t\t\t{\r\n\t\t\t\tif (node != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (node.outerHTML != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn node.outerHTML;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar tmp = [];\r\n\t\t\t\t\t\ttmp.push('<'+node.nodeName);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tvar attrs = node.attributes;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (attrs != null)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tfor (var i = 0; i < attrs.length; i++)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tvar value = attrs[i].value;\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\tif (value != null && value.length > 0)\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\ttmp.push(' ');\r\n\t\t\t\t\t\t\t\t\ttmp.push(attrs[i].nodeName);\r\n\t\t\t\t\t\t\t\t\ttmp.push('=\"');\r\n\t\t\t\t\t\t\t\t\ttmp.push(value);\r\n\t\t\t\t\t\t\t\t\ttmp.push('\"');\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (node.innerHTML.length == 0)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\ttmp.push('/>');\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\ttmp.push('>');\r\n\t\t\t\t\t\t\ttmp.push(node.innerHTML);\r\n\t\t\t\t\t\t\ttmp.push('</'+node.nodeName+'>');\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\treturn tmp.join('');\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\treturn '';\r\n\t\t\t};\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn function(node)\r\n\t\t\t{\r\n\t\t\t\tif (node != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar serializer = new XMLSerializer();\r\n\t\t\t\t\treturn serializer.serializeToString(node);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\treturn '';\r\n\t\t\t};\r\n\t\t}\r\n\t}(),\r\n\t\r\n\t/**\r\n\t * Function: write\r\n\t * \r\n\t * Creates a text node for the given string and appends it to the given\r\n\t * parent. Returns the text node.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * parent - DOM node to append the text node to.\r\n\t * text - String representing the text to be added.\r\n\t */\r\n\twrite: function(parent, text)\r\n\t{\r\n\t\tvar doc = parent.ownerDocument;\r\n\t\tvar node = doc.createTextNode(text);\r\n\t\t\r\n\t\tif (parent != null)\r\n\t\t{\r\n\t\t\tparent.appendChild(node);\r\n\t\t}\r\n\t\t\r\n\t\treturn node;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: writeln\r\n\t * \r\n\t * Creates a text node for the given string and appends it to the given\r\n\t * parent with an additional linefeed. Returns the text node.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * parent - DOM node to append the text node to.\r\n\t * text - String representing the text to be added.\r\n\t */\r\n\twriteln: function(parent, text)\r\n\t{\r\n\t\tvar doc = parent.ownerDocument;\r\n\t\tvar node = doc.createTextNode(text);\r\n\t\t\r\n\t\tif (parent != null)\r\n\t\t{\r\n\t\t\tparent.appendChild(node);\r\n\t\t\tparent.appendChild(document.createElement('br'));\r\n\t\t}\r\n\t\t\r\n\t\treturn node;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: br\r\n\t * \r\n\t * Appends a linebreak to the given parent and returns the linebreak.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * parent - DOM node to append the linebreak to.\r\n\t */\r\n\tbr: function(parent, count)\r\n\t{\r\n\t\tcount = count || 1;\r\n\t\tvar br = null;\r\n\t\t\r\n\t\tfor (var i = 0; i < count; i++)\r\n\t\t{\r\n\t\t\tif (parent != null)\r\n\t\t\t{\r\n\t\t\t\tbr = parent.ownerDocument.createElement('br');\r\n\t\t\t\tparent.appendChild(br);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn br;\r\n\t},\r\n\t\t\r\n\t/**\r\n\t * Function: button\r\n\t * \r\n\t * Returns a new button with the given level and function as an onclick\r\n\t * event handler.\r\n\t * \r\n\t * (code)\r\n\t * document.body.appendChild(mxUtils.button('Test', function(evt)\r\n\t * {\r\n\t *   alert('Hello, World!');\r\n\t * }));\r\n\t * (end)\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * label - String that represents the label of the button.\r\n\t * funct - Function to be called if the button is pressed.\r\n\t * doc - Optional document to be used for creating the button. Default is the\r\n\t * current document.\r\n\t */\r\n\tbutton: function(label, funct, doc)\r\n\t{\r\n\t\tdoc = (doc != null) ? doc : document;\r\n\t\t\r\n\t\tvar button = doc.createElement('button');\r\n\t\tmxUtils.write(button, label);\r\n\r\n\t\tmxEvent.addListener(button, 'click', function(evt)\r\n\t\t{\r\n\t\t\tfunct(evt);\r\n\t\t});\r\n\t\t\r\n\t\treturn button;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: para\r\n\t * \r\n\t * Appends a new paragraph with the given text to the specified parent and\r\n\t * returns the paragraph.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * parent - DOM node to append the text node to.\r\n\t * text - String representing the text for the new paragraph.\r\n\t */\r\n\tpara: function(parent, text)\r\n\t{\r\n\t\tvar p = document.createElement('p');\r\n\t\tmxUtils.write(p, text);\r\n\r\n\t\tif (parent != null)\r\n\t\t{\r\n\t\t\tparent.appendChild(p);\r\n\t\t}\r\n\t\t\r\n\t\treturn p;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: addTransparentBackgroundFilter\r\n\t * \r\n\t * Adds a transparent background to the filter of the given node. This\r\n\t * background can be used in IE8 standards mode (native IE8 only) to pass\r\n\t * events through the node.\r\n\t */\r\n\taddTransparentBackgroundFilter: function(node)\r\n\t{\r\n\t\tnode.style.filter += 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\\'' +\r\n\t\t\tmxClient.imageBasePath + '/transparent.gif\\', sizingMethod=\\'scale\\')';\r\n\t},\r\n\r\n\t/**\r\n\t * Function: linkAction\r\n\t * \r\n\t * Adds a hyperlink to the specified parent that invokes action on the\r\n\t * specified editor.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * parent - DOM node to contain the new link.\r\n\t * text - String that is used as the link label.\r\n\t * editor - <mxEditor> that will execute the action.\r\n\t * action - String that defines the name of the action to be executed.\r\n\t * pad - Optional left-padding for the link. Default is 0.\r\n\t */\r\n\tlinkAction: function(parent, text, editor, action, pad)\r\n\t{\r\n\t\treturn mxUtils.link(parent, text, function()\r\n\t\t{\r\n\t\t\teditor.execute(action);\r\n\t\t}, pad);\r\n\t},\r\n\r\n\t/**\r\n\t * Function: linkInvoke\r\n\t * \r\n\t * Adds a hyperlink to the specified parent that invokes the specified\r\n\t * function on the editor passing along the specified argument. The\r\n\t * function name is the name of a function of the editor instance,\r\n\t * not an action name.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * parent - DOM node to contain the new link.\r\n\t * text - String that is used as the link label.\r\n\t * editor - <mxEditor> instance to execute the function on.\r\n\t * functName - String that represents the name of the function.\r\n\t * arg - Object that represents the argument to the function.\r\n\t * pad - Optional left-padding for the link. Default is 0.\r\n\t */\r\n\tlinkInvoke: function(parent, text, editor, functName, arg, pad)\r\n\t{\r\n\t\treturn mxUtils.link(parent, text, function()\r\n\t\t{\r\n\t\t\teditor[functName](arg);\r\n\t\t}, pad);\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: link\r\n\t * \r\n\t * Adds a hyperlink to the specified parent and invokes the given function\r\n\t * when the link is clicked.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * parent - DOM node to contain the new link.\r\n\t * text - String that is used as the link label.\r\n\t * funct - Function to execute when the link is clicked.\r\n\t * pad - Optional left-padding for the link. Default is 0.\r\n\t */\r\n\tlink: function(parent, text, funct, pad)\r\n\t{\r\n\t\tvar a = document.createElement('span');\r\n\t\t\r\n\t\ta.style.color = 'blue';\r\n\t\ta.style.textDecoration = 'underline';\r\n\t\ta.style.cursor = 'pointer';\r\n\t\t\r\n\t\tif (pad != null)\r\n\t\t{\r\n\t\t\ta.style.paddingLeft = pad+'px';\r\n\t\t}\r\n\t\t\r\n\t\tmxEvent.addListener(a, 'click', funct);\r\n\t\tmxUtils.write(a, text);\r\n\t\t\r\n\t\tif (parent != null)\r\n\t\t{\r\n\t\t\tparent.appendChild(a);\r\n\t\t}\r\n\t\t\r\n\t\treturn a;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: getDocumentSize\r\n\t * \r\n\t * Returns the client size for the current document as an <mxRectangle>.\r\n\t */\r\n\tgetDocumentSize: function()\r\n\t{\r\n\t\tvar b = document.body;\r\n\t\tvar d = document.documentElement;\r\n\t\t\r\n\t\ttry\r\n\t\t{\r\n\t\t\treturn new mxRectangle(0, 0, b.clientWidth || d.clientWidth, Math.max(b.clientHeight || 0, d.clientHeight));\r\n\t\t}\r\n\t\tcatch (e)\r\n\t\t{\r\n\t\t\treturn new mxRectangle();\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: fit\r\n\t * \r\n\t * Makes sure the given node is inside the visible area of the window. This\r\n\t * is done by setting the left and top in the style. \r\n\t */\r\n\tfit: function(node)\r\n\t{\r\n\t\tvar ds = mxUtils.getDocumentSize();\r\n\t\tvar left = parseInt(node.offsetLeft);\r\n\t\tvar width = parseInt(node.offsetWidth);\r\n\t\t\t\r\n\t\tvar offset = mxUtils.getDocumentScrollOrigin(node.ownerDocument);\r\n\t\tvar sl = offset.x;\r\n\t\tvar st = offset.y;\r\n\r\n\t\tvar b = document.body;\r\n\t\tvar d = document.documentElement;\r\n\t\tvar right = (sl) + ds.width;\r\n\t\t\r\n\t\tif (left + width > right)\r\n\t\t{\r\n\t\t\tnode.style.left = Math.max(sl, right - width) + 'px';\r\n\t\t}\r\n\t\t\r\n\t\tvar top = parseInt(node.offsetTop);\r\n\t\tvar height = parseInt(node.offsetHeight);\r\n\t\t\r\n\t\tvar bottom = st + ds.height;\r\n\t\t\r\n\t\tif (top + height > bottom)\r\n\t\t{\r\n\t\t\tnode.style.top = Math.max(st, bottom - height) + 'px';\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Function: load\r\n\t * \r\n\t * Loads the specified URL *synchronously* and returns the <mxXmlRequest>.\r\n\t * Throws an exception if the file cannot be loaded. See <mxUtils.get> for\r\n\t * an asynchronous implementation.\r\n\t *\r\n\t * Example:\r\n\t * \r\n\t * (code)\r\n\t * try\r\n\t * {\r\n\t *   var req = mxUtils.load(filename);\r\n\t *   var root = req.getDocumentElement();\r\n\t *   // Process XML DOM...\r\n\t * }\r\n\t * catch (ex)\r\n\t * {\r\n\t *   mxUtils.alert('Cannot load '+filename+': '+ex);\r\n\t * }\r\n\t * (end)\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * url - URL to get the data from.\r\n\t */\r\n\tload: function(url)\r\n\t{\r\n\t\tvar req = new mxXmlRequest(url, null, 'GET', false);\r\n\t\treq.send();\r\n\t\t\r\n\t\treturn req;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: get\r\n\t * \r\n\t * Loads the specified URL *asynchronously* and invokes the given functions\r\n\t * depending on the request status. Returns the <mxXmlRequest> in use. Both\r\n\t * functions take the <mxXmlRequest> as the only parameter. See\r\n\t * <mxUtils.load> for a synchronous implementation.\r\n\t *\r\n\t * Example:\r\n\t * \r\n\t * (code)\r\n\t * mxUtils.get(url, function(req)\r\n\t * {\r\n\t *    var node = req.getDocumentElement();\r\n\t *    // Process XML DOM...\r\n\t * });\r\n\t * (end)\r\n\t * \r\n\t * So for example, to load a diagram into an existing graph model, the\r\n\t * following code is used.\r\n\t * \r\n\t * (code)\r\n\t * mxUtils.get(url, function(req)\r\n\t * {\r\n\t *   var node = req.getDocumentElement();\r\n\t *   var dec = new mxCodec(node.ownerDocument);\r\n\t *   dec.decode(node, graph.getModel());\r\n\t * });\r\n\t * (end)\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * url - URL to get the data from.\r\n\t * onload - Optional function to execute for a successful response.\r\n\t * onerror - Optional function to execute on error.\r\n\t * binary - Optional boolean parameter that specifies if the request is\r\n\t * binary.\r\n\t * timeout - Optional timeout in ms before calling ontimeout.\r\n\t * ontimeout - Optional function to execute on timeout.\r\n\t */\r\n\tget: function(url, onload, onerror, binary, timeout, ontimeout)\r\n\t{\r\n\t\tvar req = new mxXmlRequest(url, null, 'GET');\r\n\t\t\r\n\t\tif (binary != null)\r\n\t\t{\r\n\t\t\treq.setBinary(binary);\r\n\t\t}\r\n\t\t\r\n\t\treq.send(onload, onerror, timeout, ontimeout);\r\n\t\t\r\n\t\treturn req;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: getAll\r\n\t * \r\n\t * Loads the URLs in the given array *asynchronously* and invokes the given function\r\n\t * if all requests returned with a valid 2xx status. The error handler is invoked\r\n\t * once on the first error or invalid response.\r\n\t *\r\n\t * Parameters:\r\n\t * \r\n\t * urls - Array of URLs to be loaded.\r\n\t * onload - Callback with array of <mxXmlRequests>.\r\n\t * onerror - Optional function to execute on error.\r\n\t */\r\n\tgetAll: function(urls, onload, onerror)\r\n\t{\r\n\t\tvar remain = urls.length;\r\n\t\tvar result = [];\r\n\t\tvar errors = 0;\r\n\t\tvar err = function()\r\n\t\t{\r\n\t\t\tif (errors == 0 && onerror != null)\r\n\t\t\t{\r\n\t\t\t\tonerror();\r\n\t\t\t}\r\n\r\n\t\t\terrors++;\r\n\t\t};\r\n\t\t\r\n\t\tfor (var i = 0; i < urls.length; i++)\r\n\t\t{\r\n\t\t\t(function(url, index)\r\n\t\t\t{\r\n\t\t\t\tmxUtils.get(url, function(req)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar status = req.getStatus();\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (status < 200 || status > 299)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\terr();\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tresult[index] = req;\r\n\t\t\t\t\t\tremain--;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (remain == 0)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tonload(result);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}, err);\r\n\t\t\t})(urls[i], i);\r\n\t\t}\r\n\t\t\r\n\t\tif (remain == 0)\r\n\t\t{\r\n\t\t\tonload(result);\t\t\t\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: post\r\n\t * \r\n\t * Posts the specified params to the given URL *asynchronously* and invokes\r\n\t * the given functions depending on the request status. Returns the\r\n\t * <mxXmlRequest> in use. Both functions take the <mxXmlRequest> as the\r\n\t * only parameter. Make sure to use encodeURIComponent for the parameter\r\n\t * values.\r\n\t *\r\n\t * Example:\r\n\t * \r\n\t * (code)\r\n\t * mxUtils.post(url, 'key=value', function(req)\r\n\t * {\r\n\t * \tmxUtils.alert('Ready: '+req.isReady()+' Status: '+req.getStatus());\r\n\t *  // Process req.getDocumentElement() using DOM API if OK...\r\n\t * });\r\n\t * (end)\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * url - URL to get the data from.\r\n\t * params - Parameters for the post request.\r\n\t * onload - Optional function to execute for a successful response.\r\n\t * onerror - Optional function to execute on error.\r\n\t */\r\n\tpost: function(url, params, onload, onerror)\r\n\t{\r\n\t\treturn new mxXmlRequest(url, params).send(onload, onerror);\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: submit\r\n\t * \r\n\t * Submits the given parameters to the specified URL using\r\n\t * <mxXmlRequest.simulate> and returns the <mxXmlRequest>.\r\n\t * Make sure to use encodeURIComponent for the parameter\r\n\t * values.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * url - URL to get the data from.\r\n\t * params - Parameters for the form.\r\n\t * doc - Document to create the form in.\r\n\t * target - Target to send the form result to.\r\n\t */\r\n\tsubmit: function(url, params, doc, target)\r\n\t{\r\n\t\treturn new mxXmlRequest(url, params).simulate(doc, target);\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: loadInto\r\n\t * \r\n\t * Loads the specified URL *asynchronously* into the specified document,\r\n\t * invoking onload after the document has been loaded. This implementation\r\n\t * does not use <mxXmlRequest>, but the document.load method.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * url - URL to get the data from.\r\n\t * doc - The document to load the URL into.\r\n\t * onload - Function to execute when the URL has been loaded.\r\n\t */\r\n\tloadInto: function(url, doc, onload)\r\n\t{\r\n\t\tif (mxClient.IS_IE)\r\n\t\t{\r\n\t\t\tdoc.onreadystatechange = function ()\r\n\t\t\t{\r\n\t\t\t\tif (doc.readyState == 4)\r\n\t\t\t\t{\r\n\t\t\t\t\tonload();\r\n\t\t\t\t}\r\n\t\t\t};\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tdoc.addEventListener('load', onload, false);\r\n\t\t}\r\n\t\t\r\n\t\tdoc.load(url);\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: getValue\r\n\t * \r\n\t * Returns the value for the given key in the given associative array or\r\n\t * the given default value if the value is null.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * array - Associative array that contains the value for the key.\r\n\t * key - Key whose value should be returned.\r\n\t * defaultValue - Value to be returned if the value for the given\r\n\t * key is null.\r\n\t */\r\n\tgetValue: function(array, key, defaultValue)\r\n\t{\r\n\t\tvar value = (array != null) ? array[key] : null;\r\n\r\n\t\tif (value == null)\r\n\t\t{\r\n\t\t\tvalue = defaultValue;\t\t\t\r\n\t\t}\r\n\t\t\r\n\t\treturn value;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: getNumber\r\n\t * \r\n\t * Returns the numeric value for the given key in the given associative\r\n\t * array or the given default value (or 0) if the value is null. The value\r\n\t * is converted to a numeric value using the Number function.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * array - Associative array that contains the value for the key.\r\n\t * key - Key whose value should be returned.\r\n\t * defaultValue - Value to be returned if the value for the given\r\n\t * key is null. Default is 0.\r\n\t */\r\n\tgetNumber: function(array, key, defaultValue)\r\n\t{\r\n\t\tvar value = (array != null) ? array[key] : null;\r\n\r\n\t\tif (value == null)\r\n\t\t{\r\n\t\t\tvalue = defaultValue || 0;\t\t\t\r\n\t\t}\r\n\t\t\r\n\t\treturn Number(value);\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: getColor\r\n\t * \r\n\t * Returns the color value for the given key in the given associative\r\n\t * array or the given default value if the value is null. If the value\r\n\t * is <mxConstants.NONE> then null is returned.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * array - Associative array that contains the value for the key.\r\n\t * key - Key whose value should be returned.\r\n\t * defaultValue - Value to be returned if the value for the given\r\n\t * key is null. Default is null.\r\n\t */\r\n\tgetColor: function(array, key, defaultValue)\r\n\t{\r\n\t\tvar value = (array != null) ? array[key] : null;\r\n\r\n\t\tif (value == null)\r\n\t\t{\r\n\t\t\tvalue = defaultValue;\r\n\t\t}\r\n\t\telse if (value == mxConstants.NONE)\r\n\t\t{\r\n\t\t\tvalue = null;\r\n\t\t}\r\n\t\t\r\n\t\treturn value;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: clone\r\n\t * \r\n\t * Recursively clones the specified object ignoring all fieldnames in the\r\n\t * given array of transient fields. <mxObjectIdentity.FIELD_NAME> is always\r\n\t * ignored by this function.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * obj - Object to be cloned.\r\n\t * transients - Optional array of strings representing the fieldname to be\r\n\t * ignored.\r\n\t * shallow - Optional boolean argument to specify if a shallow clone should\r\n\t * be created, that is, one where all object references are not cloned or,\r\n\t * in other words, one where only atomic (strings, numbers) values are\r\n\t * cloned. Default is false.\r\n\t */\r\n\tclone: function(obj, transients, shallow)\r\n\t{\r\n\t\tshallow = (shallow != null) ? shallow : false;\r\n\t\tvar clone = null;\r\n\t\t\r\n\t\tif (obj != null && typeof(obj.constructor) == 'function')\r\n\t\t{\r\n\t\t\tclone = new obj.constructor();\r\n\t\t\t\r\n\t\t    for (var i in obj)\r\n\t\t    {\r\n\t\t    \tif (i != mxObjectIdentity.FIELD_NAME && (transients == null ||\r\n\t\t    \t\tmxUtils.indexOf(transients, i) < 0))\r\n\t\t    \t{\r\n\t\t\t    \tif (!shallow && typeof(obj[i]) == 'object')\r\n\t\t\t    \t{\r\n\t\t\t            clone[i] = mxUtils.clone(obj[i]);\r\n\t\t\t        }\r\n\t\t\t        else\r\n\t\t\t        {\r\n\t\t\t            clone[i] = obj[i];\r\n\t\t\t        }\r\n\t\t\t\t}\r\n\t\t    }\r\n\t\t}\r\n\t\t\r\n\t    return clone;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: equalPoints\r\n\t * \r\n\t * Compares all mxPoints in the given lists.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * a - Array of <mxPoints> to be compared.\r\n\t * b - Array of <mxPoints> to be compared.\r\n\t */\r\n\tequalPoints: function(a, b)\r\n\t{\r\n\t\tif ((a == null && b != null) || (a != null && b == null) ||\r\n\t\t\t(a != null && b != null && a.length != b.length))\r\n\t\t{\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\telse if (a != null && b != null)\r\n\t\t{\r\n\t\t\tfor (var i = 0; i < a.length; i++)\r\n\t\t\t{\r\n\t\t\t\tif (a[i] == b[i] || (a[i] != null && !a[i].equals(b[i])))\r\n\t\t\t\t{\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn true;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: equalEntries\r\n\t * \r\n\t * Returns true if all properties of the given objects are equal. Values\r\n\t * with NaN are equal to NaN and unequal to any other value.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * a - First object to be compared.\r\n\t * b - Second object to be compared.\r\n\t */\r\n\tequalEntries: function(a, b)\r\n\t{\r\n\t\tif ((a == null && b != null) || (a != null && b == null) ||\r\n\t\t\t(a != null && b != null && a.length != b.length))\r\n\t\t{\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\telse if (a != null && b != null)\r\n\t\t{\r\n\t\t\t// Counts keys in b to check if all values have been compared\r\n\t\t\tvar count = 0;\r\n\t\t\t\r\n\t\t\tfor (var key in b)\r\n\t\t\t{\r\n\t\t\t\tcount++;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tfor (var key in a)\r\n\t\t\t{\r\n\t\t\t\tcount--\r\n\t\t\t\t\r\n\t\t\t\tif ((!mxUtils.isNaN(a[key]) || !mxUtils.isNaN(b[key])) && a[key] != b[key])\r\n\t\t\t\t{\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn count == 0;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: removeDuplicates\r\n\t * \r\n\t * Removes all duplicates from the given array.\r\n\t */\r\n\tremoveDuplicates: function(arr)\r\n\t{\r\n\t\tvar dict = new mxDictionary();\r\n\t\tvar result = [];\r\n\t\t\r\n\t\tfor (var i = 0; i < arr.length; i++)\r\n\t\t{\r\n\t\t\tif (!dict.get(arr[i]))\r\n\t\t\t{\r\n\t\t\t\tresult.push(arr[i]);\r\n\t\t\t\tdict.put(arr[i], true);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn result;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: isNaN\r\n\t *\r\n\t * Returns true if the given value is of type number and isNaN returns true.\r\n\t */\r\n\tisNaN: function(value)\r\n\t{\r\n\t\treturn typeof(value) == 'number' && isNaN(value);\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: extend\r\n\t *\r\n\t * Assigns a copy of the superclass prototype to the subclass prototype.\r\n\t * Note that this does not call the constructor of the superclass at this\r\n\t * point, the superclass constructor should be called explicitely in the\r\n\t * subclass constructor. Below is an example.\r\n\t * \r\n\t * (code)\r\n\t * MyGraph = function(container, model, renderHint, stylesheet)\r\n\t * {\r\n\t *   mxGraph.call(this, container, model, renderHint, stylesheet);\r\n\t * }\r\n\t * \r\n\t * mxUtils.extend(MyGraph, mxGraph);\r\n\t * (end)\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * ctor - Constructor of the subclass.\r\n\t * superCtor - Constructor of the superclass.\r\n\t */\r\n\textend: function(ctor, superCtor)\r\n\t{\r\n\t\tvar f = function() {};\r\n\t\tf.prototype = superCtor.prototype;\r\n\t\t\r\n\t\tctor.prototype = new f();\r\n\t\tctor.prototype.constructor = ctor;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: toString\r\n\t * \r\n\t * Returns a textual representation of the specified object.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * obj - Object to return the string representation for.\r\n\t */\r\n\ttoString: function(obj)\r\n\t{\r\n\t    var output = '';\r\n\t    \r\n\t    for (var i in obj)\r\n\t    {\r\n\t    \ttry\r\n\t    \t{\r\n\t\t\t    if (obj[i] == null)\r\n\t\t\t    {\r\n\t\t            output += i + ' = [null]\\n';\r\n\t\t\t    }\r\n\t\t\t    else if (typeof(obj[i]) == 'function')\r\n\t\t\t    {\r\n\t\t            output += i + ' => [Function]\\n';\r\n\t\t        }\r\n\t\t        else if (typeof(obj[i]) == 'object')\r\n\t\t        {\r\n\t\t        \tvar ctor = mxUtils.getFunctionName(obj[i].constructor); \r\n\t\t            output += i + ' => [' + ctor + ']\\n';\r\n\t\t        }\r\n\t\t        else\r\n\t\t        {\r\n\t\t            output += i + ' = ' + obj[i] + '\\n';\r\n\t\t        }\r\n\t    \t}\r\n\t    \tcatch (e)\r\n\t    \t{\r\n\t    \t\toutput += i + '=' + e.message;\r\n\t    \t}\r\n\t    }\r\n\t    \r\n\t    return output;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: toRadians\r\n\t * \r\n\t * Converts the given degree to radians.\r\n\t */\r\n\ttoRadians: function(deg)\r\n\t{\r\n\t\treturn Math.PI * deg / 180;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: toDegree\r\n\t * \r\n\t * Converts the given radians to degree.\r\n\t */\r\n\ttoDegree: function(rad)\r\n\t{\r\n\t\treturn rad * 180 / Math.PI;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: arcToCurves\r\n\t * \r\n\t * Converts the given arc to a series of curves.\r\n\t */\r\n\tarcToCurves: function(x0, y0, r1, r2, angle, largeArcFlag, sweepFlag, x, y)\r\n\t{\r\n\t\tx -= x0;\r\n\t\ty -= y0;\r\n\t\t\r\n        if (r1 === 0 || r2 === 0) \r\n        {\r\n        \treturn result;\r\n        }\r\n        \r\n        var fS = sweepFlag;\r\n        var psai = angle;\r\n        r1 = Math.abs(r1);\r\n        r2 = Math.abs(r2);\r\n        var ctx = -x / 2;\r\n        var cty = -y / 2;\r\n        var cpsi = Math.cos(psai * Math.PI / 180);\r\n        var spsi = Math.sin(psai * Math.PI / 180);\r\n        var rxd = cpsi * ctx + spsi * cty;\r\n        var ryd = -1 * spsi * ctx + cpsi * cty;\r\n        var rxdd = rxd * rxd;\r\n        var rydd = ryd * ryd;\r\n        var r1x = r1 * r1;\r\n        var r2y = r2 * r2;\r\n        var lamda = rxdd / r1x + rydd / r2y;\r\n        var sds;\r\n        \r\n        if (lamda > 1) \r\n        {\r\n        \tr1 = Math.sqrt(lamda) * r1;\r\n        \tr2 = Math.sqrt(lamda) * r2;\r\n        \tsds = 0;\r\n        }  \r\n        else\r\n        {\r\n        \tvar seif = 1;\r\n            \r\n        \tif (largeArcFlag === fS) \r\n        \t{\r\n        \t\tseif = -1;\r\n        \t}\r\n            \r\n        \tsds = seif * Math.sqrt((r1x * r2y - r1x * rydd - r2y * rxdd) / (r1x * rydd + r2y * rxdd));\r\n        }\r\n        \r\n        var txd = sds * r1 * ryd / r2;\r\n        var tyd = -1 * sds * r2 * rxd / r1;\r\n        var tx = cpsi * txd - spsi * tyd + x / 2;\r\n        var ty = spsi * txd + cpsi * tyd + y / 2;\r\n        var rad = Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1) - Math.atan2(0, 1);\r\n        var s1 = (rad >= 0) ? rad : 2 * Math.PI + rad;\r\n        rad = Math.atan2((-ryd - tyd) / r2, (-rxd - txd) / r1) - Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1);\r\n        var dr = (rad >= 0) ? rad : 2 * Math.PI + rad;\r\n        \r\n        if (fS == 0 && dr > 0) \r\n        {\r\n        \tdr -= 2 * Math.PI;\r\n        }\r\n        else if (fS != 0 && dr < 0) \r\n        {\r\n        \tdr += 2 * Math.PI;\r\n        }\r\n        \r\n        var sse = dr * 2 / Math.PI;\r\n        var seg = Math.ceil(sse < 0 ? -1 * sse : sse);\r\n        var segr = dr / seg;\r\n        var t = 8/3 * Math.sin(segr / 4) * Math.sin(segr / 4) / Math.sin(segr / 2);\r\n        var cpsir1 = cpsi * r1;\r\n        var cpsir2 = cpsi * r2;\r\n        var spsir1 = spsi * r1;\r\n        var spsir2 = spsi * r2;\r\n        var mc = Math.cos(s1);\r\n        var ms = Math.sin(s1);\r\n        var x2 = -t * (cpsir1 * ms + spsir2 * mc);\r\n        var y2 = -t * (spsir1 * ms - cpsir2 * mc);\r\n        var x3 = 0;\r\n        var y3 = 0;\r\n\r\n\t\tvar result = [];\r\n        \r\n        for (var n = 0; n < seg; ++n) \r\n        {\r\n            s1 += segr;\r\n            mc = Math.cos(s1);\r\n            ms = Math.sin(s1);\r\n            \r\n            x3 = cpsir1 * mc - spsir2 * ms + tx;\r\n            y3 = spsir1 * mc + cpsir2 * ms + ty;\r\n            var dx = -t * (cpsir1 * ms + spsir2 * mc);\r\n            var dy = -t * (spsir1 * ms - cpsir2 * mc);\r\n            \r\n            // CurveTo updates x0, y0 so need to restore it\r\n            var index = n * 6;\r\n            result[index] = Number(x2 + x0);\r\n            result[index + 1] = Number(y2 + y0);\r\n            result[index + 2] = Number(x3 - dx + x0);\r\n            result[index + 3] = Number(y3 - dy + y0);\r\n            result[index + 4] = Number(x3 + x0);\r\n            result[index + 5] = Number(y3 + y0);\r\n            \r\n\t\t\tx2 = x3 + dx;\r\n            y2 = y3 + dy;\r\n        }\r\n        \r\n        return result;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: getBoundingBox\r\n\t * \r\n\t * Returns the bounding box for the rotated rectangle.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * rect - <mxRectangle> to be rotated.\r\n\t * angle - Number that represents the angle (in degrees).\r\n\t * cx - Optional <mxPoint> that represents the rotation center. If no\r\n\t * rotation center is given then the center of rect is used.\r\n\t */\r\n\tgetBoundingBox: function(rect, rotation, cx)\r\n\t{\r\n        var result = null;\r\n\r\n        if (rect != null && rotation != null && rotation != 0)\r\n        {\r\n            var rad = mxUtils.toRadians(rotation);\r\n            var cos = Math.cos(rad);\r\n            var sin = Math.sin(rad);\r\n\r\n            cx = (cx != null) ? cx : new mxPoint(rect.x + rect.width / 2, rect.y  + rect.height / 2);\r\n\r\n            var p1 = new mxPoint(rect.x, rect.y);\r\n            var p2 = new mxPoint(rect.x + rect.width, rect.y);\r\n            var p3 = new mxPoint(p2.x, rect.y + rect.height);\r\n            var p4 = new mxPoint(rect.x, p3.y);\r\n\r\n            p1 = mxUtils.getRotatedPoint(p1, cos, sin, cx);\r\n            p2 = mxUtils.getRotatedPoint(p2, cos, sin, cx);\r\n            p3 = mxUtils.getRotatedPoint(p3, cos, sin, cx);\r\n            p4 = mxUtils.getRotatedPoint(p4, cos, sin, cx);\r\n\r\n            result = new mxRectangle(p1.x, p1.y, 0, 0);\r\n            result.add(new mxRectangle(p2.x, p2.y, 0, 0));\r\n            result.add(new mxRectangle(p3.x, p3.y, 0, 0));\r\n            result.add(new mxRectangle(p4.x, p4.y, 0, 0));\r\n        }\r\n\r\n        return result;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: getRotatedPoint\r\n\t * \r\n\t * Rotates the given point by the given cos and sin.\r\n\t */\r\n\tgetRotatedPoint: function(pt, cos, sin, c)\r\n\t{\r\n\t\tc = (c != null) ? c : new mxPoint();\r\n\t\tvar x = pt.x - c.x;\r\n\t\tvar y = pt.y - c.y;\r\n\r\n\t\tvar x1 = x * cos - y * sin;\r\n\t\tvar y1 = y * cos + x * sin;\r\n\r\n\t\treturn new mxPoint(x1 + c.x, y1 + c.y);\r\n\t},\r\n\t\r\n\t/**\r\n\t * Returns an integer mask of the port constraints of the given map\r\n\t * @param dict the style map to determine the port constraints for\r\n\t * @param defaultValue Default value to return if the key is undefined.\r\n\t * @return the mask of port constraint directions\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * terminal - <mxCelState> that represents the terminal.\r\n\t * edge - <mxCellState> that represents the edge.\r\n\t * source - Boolean that specifies if the terminal is the source terminal.\r\n\t * defaultValue - Default value to be returned.\r\n\t */\r\n\tgetPortConstraints: function(terminal, edge, source, defaultValue)\r\n\t{\r\n\t\tvar value = mxUtils.getValue(terminal.style, mxConstants.STYLE_PORT_CONSTRAINT,\r\n\t\t\tmxUtils.getValue(edge.style, (source) ? mxConstants.STYLE_SOURCE_PORT_CONSTRAINT :\r\n\t\t\t\tmxConstants.STYLE_TARGET_PORT_CONSTRAINT, null));\r\n\t\t\r\n\t\tif (value == null)\r\n\t\t{\r\n\t\t\treturn defaultValue;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar directions = value.toString();\r\n\t\t\tvar returnValue = mxConstants.DIRECTION_MASK_NONE;\r\n\t\t\tvar constraintRotationEnabled = mxUtils.getValue(terminal.style, mxConstants.STYLE_PORT_CONSTRAINT_ROTATION, 0);\r\n\t\t\tvar rotation = 0;\r\n\t\t\t\r\n\t\t\tif (constraintRotationEnabled == 1)\r\n\t\t\t{\r\n\t\t\t\trotation = mxUtils.getValue(terminal.style, mxConstants.STYLE_ROTATION, 0);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar quad = 0;\r\n\r\n\t\t\tif (rotation > 45)\r\n\t\t\t{\r\n\t\t\t\tquad = 1;\r\n\t\t\t\t\r\n\t\t\t\tif (rotation >= 135)\r\n\t\t\t\t{\r\n\t\t\t\t\tquad = 2;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if (rotation < -45)\r\n\t\t\t{\r\n\t\t\t\tquad = 3;\r\n\t\t\t\t\r\n\t\t\t\tif (rotation <= -135)\r\n\t\t\t\t{\r\n\t\t\t\t\tquad = 2;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (directions.indexOf(mxConstants.DIRECTION_NORTH) >= 0)\r\n\t\t\t{\r\n\t\t\t\tswitch (quad)\r\n\t\t\t\t{\r\n\t\t\t\t\tcase 0:\r\n\t\t\t\t\t\treturnValue |= mxConstants.DIRECTION_MASK_NORTH;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 1:\r\n\t\t\t\t\t\treturnValue |= mxConstants.DIRECTION_MASK_EAST;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 2:\r\n\t\t\t\t\t\treturnValue |= mxConstants.DIRECTION_MASK_SOUTH;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 3:\r\n\t\t\t\t\t\treturnValue |= mxConstants.DIRECTION_MASK_WEST;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (directions.indexOf(mxConstants.DIRECTION_WEST) >= 0)\r\n\t\t\t{\r\n\t\t\t\tswitch (quad)\r\n\t\t\t\t{\r\n\t\t\t\t\tcase 0:\r\n\t\t\t\t\t\treturnValue |= mxConstants.DIRECTION_MASK_WEST;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 1:\r\n\t\t\t\t\t\treturnValue |= mxConstants.DIRECTION_MASK_NORTH;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 2:\r\n\t\t\t\t\t\treturnValue |= mxConstants.DIRECTION_MASK_EAST;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 3:\r\n\t\t\t\t\t\treturnValue |= mxConstants.DIRECTION_MASK_SOUTH;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (directions.indexOf(mxConstants.DIRECTION_SOUTH) >= 0)\r\n\t\t\t{\r\n\t\t\t\tswitch (quad)\r\n\t\t\t\t{\r\n\t\t\t\t\tcase 0:\r\n\t\t\t\t\t\treturnValue |= mxConstants.DIRECTION_MASK_SOUTH;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 1:\r\n\t\t\t\t\t\treturnValue |= mxConstants.DIRECTION_MASK_WEST;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 2:\r\n\t\t\t\t\t\treturnValue |= mxConstants.DIRECTION_MASK_NORTH;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 3:\r\n\t\t\t\t\t\treturnValue |= mxConstants.DIRECTION_MASK_EAST;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (directions.indexOf(mxConstants.DIRECTION_EAST) >= 0)\r\n\t\t\t{\r\n\t\t\t\tswitch (quad)\r\n\t\t\t\t{\r\n\t\t\t\t\tcase 0:\r\n\t\t\t\t\t\treturnValue |= mxConstants.DIRECTION_MASK_EAST;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 1:\r\n\t\t\t\t\t\treturnValue |= mxConstants.DIRECTION_MASK_SOUTH;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 2:\r\n\t\t\t\t\t\treturnValue |= mxConstants.DIRECTION_MASK_WEST;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 3:\r\n\t\t\t\t\t\treturnValue |= mxConstants.DIRECTION_MASK_NORTH;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\treturn returnValue;\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: reversePortConstraints\r\n\t * \r\n\t * Reverse the port constraint bitmask. For example, north | east\r\n\t * becomes south | west\r\n\t */\r\n\treversePortConstraints: function(constraint)\r\n\t{\r\n\t\tvar result = 0;\r\n\t\t\r\n\t\tresult = (constraint & mxConstants.DIRECTION_MASK_WEST) << 3;\r\n\t\tresult |= (constraint & mxConstants.DIRECTION_MASK_NORTH) << 1;\r\n\t\tresult |= (constraint & mxConstants.DIRECTION_MASK_SOUTH) >> 1;\r\n\t\tresult |= (constraint & mxConstants.DIRECTION_MASK_EAST) >> 3;\r\n\t\t\r\n\t\treturn result;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: findNearestSegment\r\n\t * \r\n\t * Finds the index of the nearest segment on the given cell state for\r\n\t * the specified coordinate pair.\r\n\t */\r\n\tfindNearestSegment: function(state, x, y)\r\n\t{\r\n\t\tvar index = -1;\r\n\t\t\r\n\t\tif (state.absolutePoints.length > 0)\r\n\t\t{\r\n\t\t\tvar last = state.absolutePoints[0];\r\n\t\t\tvar min = null;\r\n\t\t\t\r\n\t\t\tfor (var i = 1; i < state.absolutePoints.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar current = state.absolutePoints[i];\r\n\t\t\t\tvar dist = mxUtils.ptSegDistSq(last.x, last.y,\r\n\t\t\t\t\tcurrent.x, current.y, x, y);\r\n\t\t\t\t\r\n\t\t\t\tif (min == null || dist < min)\r\n\t\t\t\t{\r\n\t\t\t\t\tmin = dist;\r\n\t\t\t\t\tindex = i - 1;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tlast = current;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn index;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: getDirectedBounds\r\n\t * \r\n\t * Adds the given margins to the given rectangle and rotates and flips the\r\n\t * rectangle according to the respective styles in style.\r\n\t */\r\n\tgetDirectedBounds: function (rect, m, style, flipH, flipV)\r\n\t{\r\n\t\tvar d = mxUtils.getValue(style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST);\r\n\t\tflipH = (flipH != null) ? flipH : mxUtils.getValue(style, mxConstants.STYLE_FLIPH, false);\r\n\t\tflipV = (flipV != null) ? flipV : mxUtils.getValue(style, mxConstants.STYLE_FLIPV, false);\r\n\r\n\t\tm.x = Math.round(Math.max(0, Math.min(rect.width, m.x)));\r\n\t\tm.y = Math.round(Math.max(0, Math.min(rect.height, m.y)));\r\n\t\tm.width = Math.round(Math.max(0, Math.min(rect.width, m.width)));\r\n\t\tm.height = Math.round(Math.max(0, Math.min(rect.height, m.height)));\r\n\t\t\r\n\t\tif ((flipV && (d == mxConstants.DIRECTION_SOUTH || d == mxConstants.DIRECTION_NORTH)) ||\r\n\t\t\t(flipH && (d == mxConstants.DIRECTION_EAST || d == mxConstants.DIRECTION_WEST)))\r\n\t\t{\r\n\t\t\tvar tmp = m.x;\r\n\t\t\tm.x = m.width;\r\n\t\t\tm.width = tmp;\r\n\t\t}\r\n\t\t\t\r\n\t\tif ((flipH && (d == mxConstants.DIRECTION_SOUTH || d == mxConstants.DIRECTION_NORTH)) ||\r\n\t\t\t(flipV && (d == mxConstants.DIRECTION_EAST || d == mxConstants.DIRECTION_WEST)))\r\n\t\t{\r\n\t\t\tvar tmp = m.y;\r\n\t\t\tm.y = m.height;\r\n\t\t\tm.height = tmp;\r\n\t\t}\r\n\t\t\r\n\t\tvar m2 = mxRectangle.fromRectangle(m);\r\n\t\t\r\n\t\tif (d == mxConstants.DIRECTION_SOUTH)\r\n\t\t{\r\n\t\t\tm2.y = m.x;\r\n\t\t\tm2.x = m.height;\r\n\t\t\tm2.width = m.y;\r\n\t\t\tm2.height = m.width;\r\n\t\t}\r\n\t\telse if (d == mxConstants.DIRECTION_WEST)\r\n\t\t{\r\n\t\t\tm2.y = m.height;\r\n\t\t\tm2.x = m.width;\r\n\t\t\tm2.width = m.x;\r\n\t\t\tm2.height = m.y;\r\n\t\t}\r\n\t\telse if (d == mxConstants.DIRECTION_NORTH)\r\n\t\t{\r\n\t\t\tm2.y = m.width;\r\n\t\t\tm2.x = m.y;\r\n\t\t\tm2.width = m.height;\r\n\t\t\tm2.height = m.x;\r\n\t\t}\r\n\t\t\r\n\t\treturn new mxRectangle(rect.x + m2.x, rect.y + m2.y, rect.width - m2.width - m2.x, rect.height - m2.height - m2.y);\r\n\t},\r\n\r\n\t/**\r\n\t * Function: getPerimeterPoint\r\n\t * \r\n\t * Returns the intersection between the polygon defined by the array of\r\n\t * points and the line between center and point.\r\n\t */\r\n\tgetPerimeterPoint: function (pts, center, point)\r\n\t{\r\n\t\tvar min = null;\r\n\t\t\r\n\t\tfor (var i = 0; i < pts.length - 1; i++)\r\n\t\t{\r\n\t\t\tvar pt = mxUtils.intersection(pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y,\r\n\t\t\t\tcenter.x, center.y, point.x, point.y);\r\n\t\t\t\r\n\t\t\tif (pt != null)\r\n\t\t\t{\r\n\t\t\t\tvar dx = point.x - pt.x;\r\n\t\t\t\tvar dy = point.y - pt.y;\r\n\t\t\t\tvar ip = {p: pt, distSq: dy * dy + dx * dx};\r\n\t\t\t\t\r\n\t\t\t\tif (ip != null && (min == null || min.distSq > ip.distSq))\r\n\t\t\t\t{\r\n\t\t\t\t\tmin = ip;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn (min != null) ? min.p : null;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: rectangleIntersectsSegment\r\n\t * \r\n\t * Returns true if the given rectangle intersects the given segment.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * bounds - <mxRectangle> that represents the rectangle.\r\n\t * p1 - <mxPoint> that represents the first point of the segment.\r\n\t * p2 - <mxPoint> that represents the second point of the segment.\r\n\t */\r\n\trectangleIntersectsSegment: function(bounds, p1, p2)\r\n\t{\r\n\t\tvar top = bounds.y;\r\n\t\tvar left = bounds.x;\r\n\t\tvar bottom = top + bounds.height;\r\n\t\tvar right = left + bounds.width;\r\n\t\t\t\r\n\t\t// Find min and max X for the segment\r\n\t\tvar minX = p1.x;\r\n\t\tvar maxX = p2.x;\r\n\t\t\r\n\t\tif (p1.x > p2.x)\r\n\t\t{\r\n\t\t  minX = p2.x;\r\n\t\t  maxX = p1.x;\r\n\t\t}\r\n\t\t\r\n\t\t// Find the intersection of the segment's and rectangle's x-projections\r\n\t\tif (maxX > right)\r\n\t\t{\r\n\t\t  maxX = right;\r\n\t\t}\r\n\t\t\r\n\t\tif (minX < left)\r\n\t\t{\r\n\t\t  minX = left;\r\n\t\t}\r\n\t\t\r\n\t\tif (minX > maxX) // If their projections do not intersect return false\r\n\t\t{\r\n\t\t  return false;\r\n\t\t}\r\n\t\t\r\n\t\t// Find corresponding min and max Y for min and max X we found before\r\n\t\tvar minY = p1.y;\r\n\t\tvar maxY = p2.y;\r\n\t\tvar dx = p2.x - p1.x;\r\n\t\t\r\n\t\tif (Math.abs(dx) > 0.0000001)\r\n\t\t{\r\n\t\t  var a = (p2.y - p1.y) / dx;\r\n\t\t  var b = p1.y - a * p1.x;\r\n\t\t  minY = a * minX + b;\r\n\t\t  maxY = a * maxX + b;\r\n\t\t}\r\n\t\t\r\n\t\tif (minY > maxY)\r\n\t\t{\r\n\t\t  var tmp = maxY;\r\n\t\t  maxY = minY;\r\n\t\t  minY = tmp;\r\n\t\t}\r\n\t\t\r\n\t\t// Find the intersection of the segment's and rectangle's y-projections\r\n\t\tif (maxY > bottom)\r\n\t\t{\r\n\t\t  maxY = bottom;\r\n\t\t}\r\n\t\t\r\n\t\tif (minY < top)\r\n\t\t{\r\n\t\t  minY = top;\r\n\t\t}\r\n\t\t\r\n\t\tif (minY > maxY) // If Y-projections do not intersect return false\r\n\t\t{\r\n\t\t  return false;\r\n\t\t}\r\n\t\t\r\n\t\treturn true;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: contains\r\n\t * \r\n\t * Returns true if the specified point (x, y) is contained in the given rectangle.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * bounds - <mxRectangle> that represents the area.\r\n\t * x - X-coordinate of the point.\r\n\t * y - Y-coordinate of the point.\r\n\t */\r\n\tcontains: function(bounds, x, y)\r\n\t{\r\n\t\treturn (bounds.x <= x && bounds.x + bounds.width >= x &&\r\n\t\t\t\tbounds.y <= y && bounds.y + bounds.height >= y);\r\n\t},\r\n\r\n\t/**\r\n\t * Function: intersects\r\n\t * \r\n\t * Returns true if the two rectangles intersect.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * a - <mxRectangle> to be checked for intersection.\r\n\t * b - <mxRectangle> to be checked for intersection.\r\n\t */\r\n\tintersects: function(a, b)\r\n\t{\r\n\t\tvar tw = a.width;\r\n\t\tvar th = a.height;\r\n\t\tvar rw = b.width;\r\n\t\tvar rh = b.height;\r\n\t\t\r\n\t\tif (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0)\r\n\t\t{\r\n\t\t    return false;\r\n\t\t}\r\n\t\t\r\n\t\tvar tx = a.x;\r\n\t\tvar ty = a.y;\r\n\t\tvar rx = b.x;\r\n\t\tvar ry = b.y;\r\n\t\t\r\n\t\trw += rx;\r\n\t\trh += ry;\r\n\t\ttw += tx;\r\n\t\tth += ty;\r\n\r\n\t\treturn ((rw < rx || rw > tx) &&\r\n\t\t\t(rh < ry || rh > ty) &&\r\n\t\t\t(tw < tx || tw > rx) &&\r\n\t\t\t(th < ty || th > ry));\r\n\t},\r\n\r\n\t/**\r\n\t * Function: intersects\r\n\t * \r\n\t * Returns true if the two rectangles intersect.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * a - <mxRectangle> to be checked for intersection.\r\n\t * b - <mxRectangle> to be checked for intersection.\r\n\t */\r\n\tintersectsHotspot: function(state, x, y, hotspot, min, max)\r\n\t{\r\n\t\thotspot = (hotspot != null) ? hotspot : 1;\r\n\t\tmin = (min != null) ? min : 0;\r\n\t\tmax = (max != null) ? max : 0;\r\n\t\t\r\n\t\tif (hotspot > 0)\r\n\t\t{\r\n\t\t\tvar cx = state.getCenterX();\r\n\t\t\tvar cy = state.getCenterY();\r\n\t\t\tvar w = state.width;\r\n\t\t\tvar h = state.height;\r\n\t\t\t\r\n\t\t\tvar start = mxUtils.getValue(state.style, mxConstants.STYLE_STARTSIZE) * state.view.scale;\r\n\r\n\t\t\tif (start > 0)\r\n\t\t\t{\r\n\t\t\t\tif (mxUtils.getValue(state.style, mxConstants.STYLE_HORIZONTAL, true))\r\n\t\t\t\t{\r\n\t\t\t\t\tcy = state.y + start / 2;\r\n\t\t\t\t\th = start;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tcx = state.x + start / 2;\r\n\t\t\t\t\tw = start;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tw = Math.max(min, w * hotspot);\r\n\t\t\th = Math.max(min, h * hotspot);\r\n\t\t\t\r\n\t\t\tif (max > 0)\r\n\t\t\t{\r\n\t\t\t\tw = Math.min(w, max);\r\n\t\t\t\th = Math.min(h, max);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar rect = new mxRectangle(cx - w / 2, cy - h / 2, w, h);\r\n\t\t\tvar alpha = mxUtils.toRadians(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0);\r\n\t\t\t\r\n\t\t\tif (alpha != 0)\r\n\t\t\t{\r\n\t\t\t\tvar cos = Math.cos(-alpha);\r\n\t\t\t\tvar sin = Math.sin(-alpha);\r\n\t\t\t\tvar cx = new mxPoint(state.getCenterX(), state.getCenterY());\r\n\t\t\t\tvar pt = mxUtils.getRotatedPoint(new mxPoint(x, y), cos, sin, cx);\r\n\t\t\t\tx = pt.x;\r\n\t\t\t\ty = pt.y;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\treturn mxUtils.contains(rect, x, y);\t\t\t\r\n\t\t}\r\n\t\t\r\n\t\treturn true;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: getOffset\r\n\t * \r\n\t * Returns the offset for the specified container as an <mxPoint>. The\r\n\t * offset is the distance from the top left corner of the container to the\r\n\t * top left corner of the document.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * container - DOM node to return the offset for.\r\n\t * scollOffset - Optional boolean to add the scroll offset of the document.\r\n\t * Default is false.\r\n\t */\r\n\tgetOffset: function(container, scrollOffset)\r\n\t{\r\n\t\tvar offsetLeft = 0;\r\n\t\tvar offsetTop = 0;\r\n\t\t\r\n\t\t// Ignores document scroll origin for fixed elements\r\n\t\tvar fixed = false;\r\n\t\tvar node = container;\r\n\t\tvar b = document.body;\r\n\t\tvar d = document.documentElement;\r\n\r\n\t\twhile (node != null && node != b && node != d && !fixed)\r\n\t\t{\r\n\t\t\tvar style = mxUtils.getCurrentStyle(node);\r\n\t\t\t\r\n\t\t\tif (style != null)\r\n\t\t\t{\r\n\t\t\t\tfixed = fixed || style.position == 'fixed';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tnode = node.parentNode;\r\n\t\t}\r\n\t\t\r\n\t\tif (!scrollOffset && !fixed)\r\n\t\t{\r\n\t\t\tvar offset = mxUtils.getDocumentScrollOrigin(container.ownerDocument);\r\n\t\t\toffsetLeft += offset.x;\r\n\t\t\toffsetTop += offset.y;\r\n\t\t}\r\n\t\t\r\n\t\tvar r = container.getBoundingClientRect();\r\n\t\t\r\n\t\tif (r != null)\r\n\t\t{\r\n\t\t\toffsetLeft += r.left;\r\n\t\t\toffsetTop += r.top;\r\n\t\t}\r\n\t\t\r\n\t\treturn new mxPoint(offsetLeft, offsetTop);\r\n\t},\r\n\r\n\t/**\r\n\t * Function: getDocumentScrollOrigin\r\n\t * \r\n\t * Returns the scroll origin of the given document or the current document\r\n\t * if no document is given.\r\n\t */\r\n\tgetDocumentScrollOrigin: function(doc)\r\n\t{\r\n\t\tif (mxClient.IS_QUIRKS)\r\n\t\t{\r\n\t\t\treturn new mxPoint(doc.body.scrollLeft, doc.body.scrollTop);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar wnd = doc.defaultView || doc.parentWindow;\r\n\t\t\t\r\n\t\t\tvar x = (wnd != null && window.pageXOffset !== undefined) ? window.pageXOffset : (document.documentElement || document.body.parentNode || document.body).scrollLeft;\r\n\t\t\tvar y = (wnd != null && window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;\r\n\t\t\t\r\n\t\t\treturn new mxPoint(x, y);\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: getScrollOrigin\r\n\t * \r\n\t * Returns the top, left corner of the viewrect as an <mxPoint>.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * node - DOM node whose scroll origin should be returned.\r\n\t * includeAncestors - Whether the scroll origin of the ancestors should be\r\n\t * included. Default is false.\r\n\t * includeDocument - Whether the scroll origin of the document should be\r\n\t * included. Default is true.\r\n\t */\r\n\tgetScrollOrigin: function(node, includeAncestors, includeDocument)\r\n\t{\r\n\t\tincludeAncestors = (includeAncestors != null) ? includeAncestors : false;\r\n\t\tincludeDocument = (includeDocument != null) ? includeDocument : true;\r\n\t\t\r\n\t\tvar doc = (node != null) ? node.ownerDocument : document;\r\n\t\tvar b = doc.body;\r\n\t\tvar d = doc.documentElement;\r\n\t\tvar result = new mxPoint();\r\n\t\tvar fixed = false;\r\n\r\n\t\twhile (node != null && node != b && node != d)\r\n\t\t{\r\n\t\t\tif (!isNaN(node.scrollLeft) && !isNaN(node.scrollTop))\r\n\t\t\t{\r\n\t\t\t\tresult.x += node.scrollLeft;\r\n\t\t\t\tresult.y += node.scrollTop;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar style = mxUtils.getCurrentStyle(node);\r\n\t\t\t\r\n\t\t\tif (style != null)\r\n\t\t\t{\r\n\t\t\t\tfixed = fixed || style.position == 'fixed';\r\n\t\t\t}\r\n\r\n\t\t\tnode = (includeAncestors) ? node.parentNode : null;\r\n\t\t}\r\n\r\n\t\tif (!fixed && includeDocument)\r\n\t\t{\r\n\t\t\tvar origin = mxUtils.getDocumentScrollOrigin(doc);\r\n\r\n\t\t\tresult.x += origin.x;\r\n\t\t\tresult.y += origin.y;\r\n\t\t}\r\n\t\t\r\n\t\treturn result;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: convertPoint\r\n\t * \r\n\t * Converts the specified point (x, y) using the offset of the specified\r\n\t * container and returns a new <mxPoint> with the result.\r\n\t * \r\n\t * (code)\r\n\t * var pt = mxUtils.convertPoint(graph.container,\r\n\t *   mxEvent.getClientX(evt), mxEvent.getClientY(evt));\r\n\t * (end)\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * container - DOM node to use for the offset.\r\n\t * x - X-coordinate of the point to be converted.\r\n\t * y - Y-coordinate of the point to be converted.\r\n\t */\r\n\tconvertPoint: function(container, x, y)\r\n\t{\r\n\t\tvar origin = mxUtils.getScrollOrigin(container, false);\r\n\t\tvar offset = mxUtils.getOffset(container);\r\n\r\n\t\toffset.x -= origin.x;\r\n\t\toffset.y -= origin.y;\r\n\t\t\r\n\t\treturn new mxPoint(x - offset.x, y - offset.y);\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: ltrim\r\n\t * \r\n\t * Strips all whitespaces from the beginning of the string. Without the\r\n\t * second parameter, this will trim these characters:\r\n\t * \r\n\t * - \" \" (ASCII 32 (0x20)), an ordinary space\r\n\t * - \"\\t\" (ASCII 9 (0x09)), a tab\r\n\t * - \"\\n\" (ASCII 10 (0x0A)), a new line (line feed)\r\n\t * - \"\\r\" (ASCII 13 (0x0D)), a carriage return\r\n\t * - \"\\0\" (ASCII 0 (0x00)), the NUL-byte\r\n\t * - \"\\x0B\" (ASCII 11 (0x0B)), a vertical tab\r\n\t */\r\n\tltrim: function(str, chars)\r\n\t{\r\n\t\tchars = chars || \"\\\\s\";\r\n\t\t\r\n\t\treturn (str != null) ? str.replace(new RegExp(\"^[\" + chars + \"]+\", \"g\"), \"\") : null;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: rtrim\r\n\t * \r\n\t * Strips all whitespaces from the end of the string. Without the second\r\n\t * parameter, this will trim these characters:\r\n\t * \r\n\t * - \" \" (ASCII 32 (0x20)), an ordinary space\r\n\t * - \"\\t\" (ASCII 9 (0x09)), a tab\r\n\t * - \"\\n\" (ASCII 10 (0x0A)), a new line (line feed)\r\n\t * - \"\\r\" (ASCII 13 (0x0D)), a carriage return\r\n\t * - \"\\0\" (ASCII 0 (0x00)), the NUL-byte\r\n\t * - \"\\x0B\" (ASCII 11 (0x0B)), a vertical tab\r\n\t */\r\n\trtrim: function(str, chars)\r\n\t{\r\n\t\tchars = chars || \"\\\\s\";\r\n\t\t\r\n\t\treturn (str != null) ? str.replace(new RegExp(\"[\" + chars + \"]+$\", \"g\"), \"\") : null;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: trim\r\n\t * \r\n\t * Strips all whitespaces from both end of the string.\r\n\t * Without the second parameter, Javascript function will trim these\r\n\t * characters:\r\n\t * \r\n\t * - \" \" (ASCII 32 (0x20)), an ordinary space\r\n\t * - \"\\t\" (ASCII 9 (0x09)), a tab\r\n\t * - \"\\n\" (ASCII 10 (0x0A)), a new line (line feed)\r\n\t * - \"\\r\" (ASCII 13 (0x0D)), a carriage return\r\n\t * - \"\\0\" (ASCII 0 (0x00)), the NUL-byte\r\n\t * - \"\\x0B\" (ASCII 11 (0x0B)), a vertical tab\r\n\t */\r\n\ttrim: function(str, chars)\r\n\t{\r\n\t\treturn mxUtils.ltrim(mxUtils.rtrim(str, chars), chars);\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: isNumeric\r\n\t * \r\n\t * Returns true if the specified value is numeric, that is, if it is not\r\n\t * null, not an empty string, not a HEX number and isNaN returns false.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * n - String representing the possibly numeric value.\r\n\t */\r\n\tisNumeric: function(n)\r\n\t{\r\n\t\treturn !isNaN(parseFloat(n)) && isFinite(n) && (typeof(n) != 'string' || n.toLowerCase().indexOf('0x') < 0);\r\n\t},\r\n\r\n\t/**\r\n\t * Function: isInteger\r\n\t * \r\n\t * Returns true if the given value is an valid integer number.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * n - String representing the possibly numeric value.\r\n\t */\r\n\tisInteger: function(n)\r\n\t{\r\n\t\treturn String(parseInt(n)) === String(n);\r\n\t},\r\n\r\n\t/**\r\n\t * Function: mod\r\n\t * \r\n\t * Returns the remainder of division of n by m. You should use this instead\r\n\t * of the built-in operation as the built-in operation does not properly\r\n\t * handle negative numbers.\r\n\t */\r\n\tmod: function(n, m)\r\n\t{\r\n\t\treturn ((n % m) + m) % m;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: intersection\r\n\t * \r\n\t * Returns the intersection of two lines as an <mxPoint>.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * x0 - X-coordinate of the first line's startpoint.\r\n\t * y0 - X-coordinate of the first line's startpoint.\r\n\t * x1 - X-coordinate of the first line's endpoint.\r\n\t * y1 - Y-coordinate of the first line's endpoint.\r\n\t * x2 - X-coordinate of the second line's startpoint.\r\n\t * y2 - Y-coordinate of the second line's startpoint.\r\n\t * x3 - X-coordinate of the second line's endpoint.\r\n\t * y3 - Y-coordinate of the second line's endpoint.\r\n\t */\r\n\tintersection: function (x0, y0, x1, y1, x2, y2, x3, y3)\r\n\t{\r\n\t\tvar denom = ((y3 - y2) * (x1 - x0)) - ((x3 - x2) * (y1 - y0));\r\n\t\tvar nume_a = ((x3 - x2) * (y0 - y2)) - ((y3 - y2) * (x0 - x2));\r\n\t\tvar nume_b = ((x1 - x0) * (y0 - y2)) - ((y1 - y0) * (x0 - x2));\r\n\r\n\t\tvar ua = nume_a / denom;\r\n\t\tvar ub = nume_b / denom;\r\n\t\t\r\n\t\tif(ua >= 0.0 && ua <= 1.0 && ub >= 0.0 && ub <= 1.0)\r\n\t\t{\r\n\t\t\t// Get the intersection point\r\n\t\t\tvar x = x0 + ua * (x1 - x0);\r\n\t\t\tvar y = y0 + ua * (y1 - y0);\r\n\t\t\t\r\n\t\t\treturn new mxPoint(x, y);\r\n\t\t}\r\n\t\t\r\n\t\t// No intersection\r\n\t\treturn null;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: ptSegDistSq\r\n\t * \r\n\t * Returns the square distance between a segment and a point. To get the\r\n\t * distance between a point and a line (with infinite length) use\r\n\t * <mxUtils.ptLineDist>.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * x1 - X-coordinate of the startpoint of the segment.\r\n\t * y1 - Y-coordinate of the startpoint of the segment.\r\n\t * x2 - X-coordinate of the endpoint of the segment.\r\n\t * y2 - Y-coordinate of the endpoint of the segment.\r\n\t * px - X-coordinate of the point.\r\n\t * py - Y-coordinate of the point.\r\n\t */\r\n\tptSegDistSq: function(x1, y1, x2, y2, px, py)\r\n    {\r\n\t\tx2 -= x1;\r\n\t\ty2 -= y1;\r\n\r\n\t\tpx -= x1;\r\n\t\tpy -= y1;\r\n\r\n\t\tvar dotprod = px * x2 + py * y2;\r\n\t\tvar projlenSq;\r\n\r\n\t\tif (dotprod <= 0.0)\r\n\t\t{\r\n\t\t    projlenSq = 0.0;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t    px = x2 - px;\r\n\t\t    py = y2 - py;\r\n\t\t    dotprod = px * x2 + py * y2;\r\n\r\n\t\t    if (dotprod <= 0.0)\r\n\t\t    {\r\n\t\t\t\tprojlenSq = 0.0;\r\n\t\t    }\r\n\t\t    else\r\n\t\t    {\r\n\t\t\t\tprojlenSq = dotprod * dotprod / (x2 * x2 + y2 * y2);\r\n\t\t    }\r\n\t\t}\r\n\r\n\t\tvar lenSq = px * px + py * py - projlenSq;\r\n\t\t\r\n\t\tif (lenSq < 0)\r\n\t\t{\r\n\t\t    lenSq = 0;\r\n\t\t}\r\n\t\t\r\n\t\treturn lenSq;\r\n    },\r\n\t\r\n\t/**\r\n\t * Function: ptLineDist\r\n\t * \r\n\t * Returns the distance between a line defined by two points and a point.\r\n\t * To get the distance between a point and a segment (with a specific\r\n\t * length) use <mxUtils.ptSeqDistSq>.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * x1 - X-coordinate of point 1 of the line.\r\n\t * y1 - Y-coordinate of point 1 of the line.\r\n\t * x2 - X-coordinate of point 1 of the line.\r\n\t * y2 - Y-coordinate of point 1 of the line.\r\n\t * px - X-coordinate of the point.\r\n\t * py - Y-coordinate of the point.\r\n\t */\r\n    ptLineDist: function(x1, y1, x2, y2, px, py)\r\n    {\r\n\t\treturn Math.abs((y2 - y1) * px - (x2 - x1) * py + x2 * y1 - y2 * x1) /\r\n\t\t\tMath.sqrt((y2 - y1) * (y2 - y1) + (x2 - x1) * (x2 - x1));\r\n    },\r\n    \t\r\n\t/**\r\n\t * Function: relativeCcw\r\n\t * \r\n\t * Returns 1 if the given point on the right side of the segment, 0 if its\r\n\t * on the segment, and -1 if the point is on the left side of the segment.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * x1 - X-coordinate of the startpoint of the segment.\r\n\t * y1 - Y-coordinate of the startpoint of the segment.\r\n\t * x2 - X-coordinate of the endpoint of the segment.\r\n\t * y2 - Y-coordinate of the endpoint of the segment.\r\n\t * px - X-coordinate of the point.\r\n\t * py - Y-coordinate of the point.\r\n\t */\r\n\trelativeCcw: function(x1, y1, x2, y2, px, py)\r\n    {\r\n\t\tx2 -= x1;\r\n\t\ty2 -= y1;\r\n\t\tpx -= x1;\r\n\t\tpy -= y1;\r\n\t\tvar ccw = px * y2 - py * x2;\r\n\t\t\r\n\t\tif (ccw == 0.0)\r\n\t\t{\r\n\t\t    ccw = px * x2 + py * y2;\r\n\t\t    \r\n\t\t    if (ccw > 0.0)\r\n\t\t    {\r\n\t\t\t\tpx -= x2;\r\n\t\t\t\tpy -= y2;\r\n\t\t\t\tccw = px * x2 + py * y2;\r\n\t\t\t\t\r\n\t\t\t\tif (ccw < 0.0)\r\n\t\t\t\t{\r\n\t\t\t\t    ccw = 0.0;\r\n\t\t\t\t}\r\n\t\t    }\r\n\t\t}\r\n\t\t\r\n\t\treturn (ccw < 0.0) ? -1 : ((ccw > 0.0) ? 1 : 0);\r\n    },\r\n    \r\n\t/**\r\n\t * Function: animateChanges\r\n\t * \r\n\t * See <mxEffects.animateChanges>. This is for backwards compatibility and\r\n\t * will be removed later.\r\n\t */\r\n\tanimateChanges: function(graph, changes)\r\n\t{\r\n\t\t// LATER: Deprecated, remove this function\r\n    \tmxEffects.animateChanges.apply(this, arguments);\r\n\t},\r\n    \r\n\t/**\r\n\t * Function: cascadeOpacity\r\n\t * \r\n\t * See <mxEffects.cascadeOpacity>. This is for backwards compatibility and\r\n\t * will be removed later.\r\n\t */\r\n    cascadeOpacity: function(graph, cell, opacity)\r\n\t{\r\n\t\tmxEffects.cascadeOpacity.apply(this, arguments);\r\n\t},\r\n\r\n\t/**\r\n\t * Function: fadeOut\r\n\t * \r\n\t * See <mxEffects.fadeOut>. This is for backwards compatibility and\r\n\t * will be removed later.\r\n\t */\r\n\tfadeOut: function(node, from, remove, step, delay, isEnabled)\r\n\t{\r\n\t\tmxEffects.fadeOut.apply(this, arguments);\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: setOpacity\r\n\t * \r\n\t * Sets the opacity of the specified DOM node to the given value in %.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * node - DOM node to set the opacity for.\r\n\t * value - Opacity in %. Possible values are between 0 and 100.\r\n\t */\r\n\tsetOpacity: function(node, value)\r\n\t{\r\n\t\tif (mxUtils.isVml(node))\r\n\t\t{\r\n\t    \tif (value >= 100)\r\n\t    \t{\r\n\t    \t\tnode.style.filter = '';\r\n\t    \t}\r\n\t    \telse\r\n\t    \t{\r\n\t    \t\t// TODO: Why is the division by 5 needed in VML?\r\n\t\t\t    node.style.filter = 'alpha(opacity=' + (value/5) + ')';\r\n\t    \t}\r\n\t\t}\r\n\t\telse if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' || document.documentMode < 9))\r\n\t    {\r\n\t    \tif (value >= 100)\r\n\t    \t{\r\n\t    \t\tnode.style.filter = '';\r\n\t    \t}\r\n\t    \telse\r\n\t    \t{\r\n\t\t\t    node.style.filter = 'alpha(opacity=' + value + ')';\r\n\t    \t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t    node.style.opacity = (value / 100);\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Function: createImage\r\n\t * \r\n\t * Creates and returns an image (IMG node) or VML image (v:image) in IE6 in\r\n\t * quirks mode.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * src - URL that points to the image to be displayed.\r\n\t */\r\n\tcreateImage: function(src)\r\n\t{\r\n        var imageNode = null;\r\n        \r\n\t\tif (mxClient.IS_IE6 && document.compatMode != 'CSS1Compat')\r\n\t\t{\r\n        \timageNode = document.createElement(mxClient.VML_PREFIX + ':image');\r\n        \timageNode.setAttribute('src', src);\r\n        \timageNode.style.borderStyle = 'none';\r\n        }\r\n\t\telse\r\n\t\t{\r\n\t\t\timageNode = document.createElement('img');\r\n\t\t\timageNode.setAttribute('src', src);\r\n\t\t\timageNode.setAttribute('border', '0');\r\n\t\t}\r\n\t\t\r\n\t\treturn imageNode;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: sortCells\r\n\t * \r\n\t * Sorts the given cells according to the order in the cell hierarchy.\r\n\t * Ascending is optional and defaults to true.\r\n\t */\r\n\tsortCells: function(cells, ascending)\r\n\t{\r\n\t\tascending = (ascending != null) ? ascending : true;\r\n\t\tvar lookup = new mxDictionary();\r\n\t\tcells.sort(function(o1, o2)\r\n\t\t{\r\n\t\t\tvar p1 = lookup.get(o1);\r\n\t\t\t\r\n\t\t\tif (p1 == null)\r\n\t\t\t{\r\n\t\t\t\tp1 = mxCellPath.create(o1).split(mxCellPath.PATH_SEPARATOR);\r\n\t\t\t\tlookup.put(o1, p1);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar p2 = lookup.get(o2);\r\n\t\t\t\r\n\t\t\tif (p2 == null)\r\n\t\t\t{\r\n\t\t\t\tp2 = mxCellPath.create(o2).split(mxCellPath.PATH_SEPARATOR);\r\n\t\t\t\tlookup.put(o2, p2);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar comp = mxCellPath.compare(p1, p2);\r\n\t\t\t\r\n\t\t\treturn (comp == 0) ? 0 : (((comp > 0) == ascending) ? 1 : -1);\r\n\t\t});\r\n\t\t\r\n\t\treturn cells;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: getStylename\r\n\t * \r\n\t * Returns the stylename in a style of the form [(stylename|key=value);] or\r\n\t * an empty string if the given style does not contain a stylename.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * style - String of the form [(stylename|key=value);].\r\n\t */\r\n\tgetStylename: function(style)\r\n\t{\r\n\t\tif (style != null)\r\n\t\t{\r\n\t\t\tvar pairs = style.split(';');\r\n\t\t\tvar stylename = pairs[0];\r\n\t\t\t\r\n\t\t\tif (stylename.indexOf('=') < 0)\r\n\t\t\t{\r\n\t\t\t\treturn stylename;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\t\t\r\n\t\treturn '';\r\n\t},\r\n\r\n\t/**\r\n\t * Function: getStylenames\r\n\t * \r\n\t * Returns the stylenames in a style of the form [(stylename|key=value);]\r\n\t * or an empty array if the given style does not contain any stylenames.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * style - String of the form [(stylename|key=value);].\r\n\t */\r\n\tgetStylenames: function(style)\r\n\t{\r\n\t\tvar result = [];\r\n\t\t\r\n\t\tif (style != null)\r\n\t\t{\r\n\t\t\tvar pairs = style.split(';');\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < pairs.length; i++)\r\n\t\t\t{\r\n\t\t\t\tif (pairs[i].indexOf('=') < 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tresult.push(pairs[i]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\t\t\r\n\t\treturn result;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: indexOfStylename\r\n\t * \r\n\t * Returns the index of the given stylename in the given style. This\r\n\t * returns -1 if the given stylename does not occur (as a stylename) in the\r\n\t * given style, otherwise it returns the index of the first character.\r\n\t */\r\n\tindexOfStylename: function(style, stylename)\r\n\t{\r\n\t\tif (style != null && stylename != null)\r\n\t\t{\r\n\t\t\tvar tokens = style.split(';');\r\n\t\t\tvar pos = 0;\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < tokens.length; i++)\r\n\t\t\t{\r\n\t\t\t\tif (tokens[i] == stylename)\r\n\t\t\t\t{\r\n\t\t\t\t\treturn pos;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tpos += tokens[i].length + 1;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn -1;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: addStylename\r\n\t * \r\n\t * Adds the specified stylename to the given style if it does not already\r\n\t * contain the stylename.\r\n\t */\r\n\taddStylename: function(style, stylename)\r\n\t{\r\n\t\tif (mxUtils.indexOfStylename(style, stylename) < 0)\r\n\t\t{\r\n\t\t\tif (style == null)\r\n\t\t\t{\r\n\t\t\t\tstyle = '';\r\n\t\t\t}\r\n\t\t\telse if (style.length > 0 && style.charAt(style.length - 1) != ';')\r\n\t\t\t{\r\n\t\t\t\tstyle += ';';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tstyle += stylename;\r\n\t\t}\r\n\t\t\r\n\t\treturn style;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: removeStylename\r\n\t * \r\n\t * Removes all occurrences of the specified stylename in the given style\r\n\t * and returns the updated style. Trailing semicolons are not preserved.\r\n\t */\r\n\tremoveStylename: function(style, stylename)\r\n\t{\r\n\t\tvar result = [];\r\n\t\t\r\n\t\tif (style != null)\r\n\t\t{\r\n\t\t\tvar tokens = style.split(';');\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < tokens.length; i++)\r\n\t\t\t{\r\n\t\t\t\tif (tokens[i] != stylename)\r\n\t\t\t\t{\r\n\t\t\t\t\tresult.push(tokens[i]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn result.join(';');\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: removeAllStylenames\r\n\t * \r\n\t * Removes all stylenames from the given style and returns the updated\r\n\t * style.\r\n\t */\r\n\tremoveAllStylenames: function(style)\r\n\t{\r\n\t\tvar result = [];\r\n\t\t\r\n\t\tif (style != null)\r\n\t\t{\r\n\t\t\tvar tokens = style.split(';');\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < tokens.length; i++)\r\n\t\t\t{\r\n\t\t\t\t// Keeps the key, value assignments\r\n\t\t\t\tif (tokens[i].indexOf('=') >= 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tresult.push(tokens[i]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn result.join(';');\r\n\t},\r\n\r\n\t/**\r\n\t * Function: setCellStyles\r\n\t * \r\n\t * Assigns the value for the given key in the styles of the given cells, or\r\n\t * removes the key from the styles if the value is null.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * model - <mxGraphModel> to execute the transaction in.\r\n\t * cells - Array of <mxCells> to be updated.\r\n\t * key - Key of the style to be changed.\r\n\t * value - New value for the given key.\r\n\t */\r\n\tsetCellStyles: function(model, cells, key, value)\r\n\t{\r\n\t\tif (cells != null && cells.length > 0)\r\n\t\t{\r\n\t\t\tmodel.beginUpdate();\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (cells[i] != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar style = mxUtils.setStyle(model.getStyle(cells[i]), key, value);\r\n\t\t\t\t\t\tmodel.setStyle(cells[i], style);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tfinally\r\n\t\t\t{\r\n\t\t\t\tmodel.endUpdate();\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: setStyle\r\n\t * \r\n\t * Adds or removes the given key, value pair to the style and returns the\r\n\t * new style. If value is null or zero length then the key is removed from\r\n\t * the style. This is for cell styles, not for CSS styles.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * style - String of the form [(stylename|key=value);].\r\n\t * key - Key of the style to be changed.\r\n\t * value - New value for the given key.\r\n\t */\r\n\tsetStyle: function(style, key, value)\r\n\t{\r\n\t\tvar isValue = value != null && (typeof(value.length) == 'undefined' || value.length > 0);\r\n\t\t\r\n\t\tif (style == null || style.length == 0)\r\n\t\t{\r\n\t\t\tif (isValue)\r\n\t\t\t{\r\n\t\t\t\tstyle = key + '=' + value + ';';\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tif (style.substring(0, key.length + 1) == key + '=')\r\n\t\t\t{\r\n\t\t\t\tvar next = style.indexOf(';');\r\n\t\t\t\t\r\n\t\t\t\tif (isValue)\r\n\t\t\t\t{\r\n\t\t\t\t\tstyle = key + '=' + value + ((next < 0) ? ';' : style.substring(next));\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tstyle = (next < 0 || next == style.length - 1) ? '' : style.substring(next + 1);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tvar index = style.indexOf(';' + key + '=');\r\n\t\t\t\t\r\n\t\t\t\tif (index < 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (isValue)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar sep = (style.charAt(style.length - 1) == ';') ? '' : ';';\r\n\t\t\t\t\t\tstyle = style + sep + key + '=' + value + ';';\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tvar next = style.indexOf(';', index + 1);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (isValue)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tstyle = style.substring(0, index + 1) + key + '=' + value + ((next < 0) ? ';' : style.substring(next));\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tstyle = style.substring(0, index) + ((next < 0) ? ';' : style.substring(next));\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn style;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: setCellStyleFlags\r\n\t * \r\n\t * Sets or toggles the flag bit for the given key in the cell's styles.\r\n\t * If value is null then the flag is toggled.\r\n\t * \r\n\t * Example:\r\n\t * \r\n\t * (code)\r\n\t * var cells = graph.getSelectionCells();\r\n\t * mxUtils.setCellStyleFlags(graph.model,\r\n\t * \t\t\tcells,\r\n\t * \t\t\tmxConstants.STYLE_FONTSTYLE,\r\n\t * \t\t\tmxConstants.FONT_BOLD);\r\n\t * (end)\r\n\t * \r\n\t * Toggles the bold font style.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * model - <mxGraphModel> that contains the cells.\r\n\t * cells - Array of <mxCells> to change the style for.\r\n\t * key - Key of the style to be changed.\r\n\t * flag - Integer for the bit to be changed.\r\n\t * value - Optional boolean value for the flag.\r\n\t */\r\n\tsetCellStyleFlags: function(model, cells, key, flag, value)\r\n\t{\r\n\t\tif (cells != null && cells.length > 0)\r\n\t\t{\r\n\t\t\tmodel.beginUpdate();\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (cells[i] != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar style = mxUtils.setStyleFlag(\r\n\t\t\t\t\t\t\tmodel.getStyle(cells[i]),\r\n\t\t\t\t\t\t\tkey, flag, value);\r\n\t\t\t\t\t\tmodel.setStyle(cells[i], style);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tfinally\r\n\t\t\t{\r\n\t\t\t\tmodel.endUpdate();\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: setStyleFlag\r\n\t * \r\n\t * Sets or removes the given key from the specified style and returns the\r\n\t * new style. If value is null then the flag is toggled.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * style - String of the form [(stylename|key=value);].\r\n\t * key - Key of the style to be changed.\r\n\t * flag - Integer for the bit to be changed.\r\n\t * value - Optional boolean value for the given flag.\r\n\t */\r\n\tsetStyleFlag: function(style, key, flag, value)\r\n\t{\r\n\t\tif (style == null || style.length == 0)\r\n\t\t{\r\n\t\t\tif (value || value == null)\r\n\t\t\t{\r\n\t\t\t\tstyle = key+'='+flag;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tstyle = key+'=0';\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar index = style.indexOf(key+'=');\r\n\t\t\t\r\n\t\t\tif (index < 0)\r\n\t\t\t{\r\n\t\t\t\tvar sep = (style.charAt(style.length-1) == ';') ? '' : ';';\r\n\r\n\t\t\t\tif (value || value == null)\r\n\t\t\t\t{\r\n\t\t\t\t\tstyle = style + sep + key + '=' + flag;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tstyle = style + sep + key + '=0';\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tvar cont = style.indexOf(';', index);\r\n\t\t\t\tvar tmp = '';\r\n\t\t\t\t\r\n\t\t\t\tif (cont < 0)\r\n\t\t\t\t{\r\n\t\t\t\t\ttmp  = style.substring(index+key.length+1);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\ttmp = style.substring(index+key.length+1, cont);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (value == null)\r\n\t\t\t\t{\r\n\t\t\t\t\ttmp = parseInt(tmp) ^ flag;\r\n\t\t\t\t}\r\n\t\t\t\telse if (value)\r\n\t\t\t\t{\r\n\t\t\t\t\ttmp = parseInt(tmp) | flag;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\ttmp = parseInt(tmp) & ~flag;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tstyle = style.substring(0, index) + key + '=' + tmp +\r\n\t\t\t\t\t((cont >= 0) ? style.substring(cont) : '');\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn style;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: getAlignmentAsPoint\r\n\t * \r\n\t * Returns an <mxPoint> that represents the horizontal and vertical alignment\r\n\t * for numeric computations. X is -0.5 for center, -1 for right and 0 for\r\n\t * left alignment. Y is -0.5 for middle, -1 for bottom and 0 for top\r\n\t * alignment. Default values for missing arguments is top, left.\r\n\t */\r\n\tgetAlignmentAsPoint: function(align, valign)\r\n\t{\r\n\t\tvar dx = 0;\r\n\t\tvar dy = 0;\r\n\t\t\r\n\t\t// Horizontal alignment\r\n\t\tif (align == mxConstants.ALIGN_CENTER)\r\n\t\t{\r\n\t\t\tdx = -0.5;\r\n\t\t}\r\n\t\telse if (align == mxConstants.ALIGN_RIGHT)\r\n\t\t{\r\n\t\t\tdx = -1;\r\n\t\t}\r\n\r\n\t\t// Vertical alignment\r\n\t\tif (valign == mxConstants.ALIGN_MIDDLE)\r\n\t\t{\r\n\t\t\tdy = -0.5;\r\n\t\t}\r\n\t\telse if (valign == mxConstants.ALIGN_BOTTOM)\r\n\t\t{\r\n\t\t\tdy = -1;\r\n\t\t}\r\n\t\t\r\n\t\treturn new mxPoint(dx, dy);\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: getSizeForString\r\n\t * \r\n\t * Returns an <mxRectangle> with the size (width and height in pixels) of\r\n\t * the given string. The string may contain HTML markup. Newlines should be\r\n\t * converted to <br> before calling this method. The caller is responsible\r\n\t * for sanitizing the HTML markup.\r\n\t * \r\n\t * Example:\r\n\t * \r\n\t * (code)\r\n\t * var label = graph.getLabel(cell).replace(/\\n/g, \"<br>\");\r\n\t * var size = graph.getSizeForString(label);\r\n\t * (end)\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * text - String whose size should be returned.\r\n\t * fontSize - Integer that specifies the font size in pixels. Default is\r\n\t * <mxConstants.DEFAULT_FONTSIZE>.\r\n\t * fontFamily - String that specifies the name of the font family. Default\r\n\t * is <mxConstants.DEFAULT_FONTFAMILY>.\r\n\t * textWidth - Optional width for text wrapping.\r\n\t */\r\n\tgetSizeForString: function(text, fontSize, fontFamily, textWidth)\r\n\t{\r\n\t\tfontSize = (fontSize != null) ? fontSize : mxConstants.DEFAULT_FONTSIZE;\r\n\t\tfontFamily = (fontFamily != null) ? fontFamily : mxConstants.DEFAULT_FONTFAMILY;\r\n\t\tvar div = document.createElement('div');\r\n\t\t\r\n\t\t// Sets the font size and family\r\n\t\tdiv.style.fontFamily = fontFamily;\r\n\t\tdiv.style.fontSize = Math.round(fontSize) + 'px';\r\n\t\tdiv.style.lineHeight = Math.round(fontSize * mxConstants.LINE_HEIGHT) + 'px';\r\n\t\t\r\n\t\t// Disables block layout and outside wrapping and hides the div\r\n\t\tdiv.style.position = 'absolute';\r\n\t\tdiv.style.visibility = 'hidden';\r\n\t\tdiv.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';\r\n\t\tdiv.style.zoom = '1';\r\n\t\t\r\n\t\tif (textWidth != null)\r\n\t\t{\r\n\t\t\tdiv.style.width = textWidth + 'px';\r\n\t\t\tdiv.style.whiteSpace = 'normal';\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tdiv.style.whiteSpace = 'nowrap';\r\n\t\t}\r\n\t\t\r\n\t\t// Adds the text and inserts into DOM for updating of size\r\n\t\tdiv.innerHTML = text;\r\n\t\tdocument.body.appendChild(div);\r\n\t\t\r\n\t\t// Gets the size and removes from DOM\r\n\t\tvar size = new mxRectangle(0, 0, div.offsetWidth, div.offsetHeight);\r\n\t\tdocument.body.removeChild(div);\r\n\t\t\r\n\t\treturn size;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: getViewXml\r\n\t */\r\n\tgetViewXml: function(graph, scale, cells, x0, y0)\r\n\t{\r\n\t\tx0 = (x0 != null) ? x0 : 0;\r\n\t\ty0 = (y0 != null) ? y0 : 0;\r\n\t\tscale = (scale != null) ? scale : 1;\r\n\r\n\t\tif (cells == null)\r\n\t\t{\r\n\t\t\tvar model = graph.getModel();\r\n\t\t\tcells = [model.getRoot()];\r\n\t\t}\r\n\t\t\r\n\t\tvar view = graph.getView();\r\n\t\tvar result = null;\r\n\r\n\t\t// Disables events on the view\r\n\t\tvar eventsEnabled = view.isEventsEnabled();\r\n\t\tview.setEventsEnabled(false);\r\n\r\n\t\t// Workaround for label bounds not taken into account for image export.\r\n\t\t// Creates a temporary draw pane which is used for rendering the text.\r\n\t\t// Text rendering is required for finding the bounds of the labels.\r\n\t\tvar drawPane = view.drawPane;\r\n\t\tvar overlayPane = view.overlayPane;\r\n\r\n\t\tif (graph.dialect == mxConstants.DIALECT_SVG)\r\n\t\t{\r\n\t\t\tview.drawPane = document.createElementNS(mxConstants.NS_SVG, 'g');\r\n\t\t\tview.canvas.appendChild(view.drawPane);\r\n\r\n\t\t\t// Redirects cell overlays into temporary container\r\n\t\t\tview.overlayPane = document.createElementNS(mxConstants.NS_SVG, 'g');\r\n\t\t\tview.canvas.appendChild(view.overlayPane);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tview.drawPane = view.drawPane.cloneNode(false);\r\n\t\t\tview.canvas.appendChild(view.drawPane);\r\n\t\t\t\r\n\t\t\t// Redirects cell overlays into temporary container\r\n\t\t\tview.overlayPane = view.overlayPane.cloneNode(false);\r\n\t\t\tview.canvas.appendChild(view.overlayPane);\r\n\t\t}\r\n\r\n\t\t// Resets the translation\r\n\t\tvar translate = view.getTranslate();\r\n\t\tview.translate = new mxPoint(x0, y0);\r\n\r\n\t\t// Creates the temporary cell states in the view\r\n\t\tvar temp = new mxTemporaryCellStates(graph.getView(), scale, cells);\r\n\r\n\t\ttry\r\n\t\t{\r\n\t\t\tvar enc = new mxCodec();\r\n\t\t\tresult = enc.encode(graph.getView());\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\ttemp.destroy();\r\n\t\t\tview.translate = translate;\r\n\t\t\tview.canvas.removeChild(view.drawPane);\r\n\t\t\tview.canvas.removeChild(view.overlayPane);\r\n\t\t\tview.drawPane = drawPane;\r\n\t\t\tview.overlayPane = overlayPane;\r\n\t\t\tview.setEventsEnabled(eventsEnabled);\r\n\t\t}\r\n\r\n\t\treturn result;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: getScaleForPageCount\r\n\t * \r\n\t * Returns the scale to be used for printing the graph with the given\r\n\t * bounds across the specifies number of pages with the given format. The\r\n\t * scale is always computed such that it given the given amount or fewer\r\n\t * pages in the print output. See <mxPrintPreview> for an example.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * pageCount - Specifies the number of pages in the print output.\r\n\t * graph - <mxGraph> that should be printed.\r\n\t * pageFormat - Optional <mxRectangle> that specifies the page format.\r\n\t * Default is <mxConstants.PAGE_FORMAT_A4_PORTRAIT>.\r\n\t * border - The border along each side of every page.\r\n\t */\r\n\tgetScaleForPageCount: function(pageCount, graph, pageFormat, border)\r\n\t{\r\n\t\tif (pageCount < 1)\r\n\t\t{\r\n\t\t\t// We can't work with less than 1 page, return no scale\r\n\t\t\t// change\r\n\t\t\treturn 1;\r\n\t\t}\r\n\t\t\r\n\t\tpageFormat = (pageFormat != null) ? pageFormat : mxConstants.PAGE_FORMAT_A4_PORTRAIT;\r\n\t\tborder = (border != null) ? border : 0;\r\n\t\t\r\n\t\tvar availablePageWidth = pageFormat.width - (border * 2);\r\n\t\tvar availablePageHeight = pageFormat.height - (border * 2);\r\n\r\n\t\t// Work out the number of pages required if the\r\n\t\t// graph is not scaled.\r\n\t\tvar graphBounds = graph.getGraphBounds().clone();\r\n\t\tvar sc = graph.getView().getScale();\r\n\t\tgraphBounds.width /= sc;\r\n\t\tgraphBounds.height /= sc;\r\n\t\tvar graphWidth = graphBounds.width;\r\n\t\tvar graphHeight = graphBounds.height;\r\n\r\n\t\tvar scale = 1;\r\n\t\t\r\n\t\t// The ratio of the width/height for each printer page\r\n\t\tvar pageFormatAspectRatio = availablePageWidth / availablePageHeight;\r\n\t\t// The ratio of the width/height for the graph to be printer\r\n\t\tvar graphAspectRatio = graphWidth / graphHeight;\r\n\t\t\r\n\t\t// The ratio of horizontal pages / vertical pages for this \r\n\t\t// graph to maintain its aspect ratio on this page format\r\n\t\tvar pagesAspectRatio = graphAspectRatio / pageFormatAspectRatio;\r\n\t\t\r\n\t\t// Factor the square root of the page count up and down \r\n\t\t// by the pages aspect ratio to obtain a horizontal and \r\n\t\t// vertical page count that adds up to the page count\r\n\t\t// and has the correct aspect ratio\r\n\t\tvar pageRoot = Math.sqrt(pageCount);\r\n\t\tvar pagesAspectRatioSqrt = Math.sqrt(pagesAspectRatio);\r\n\t\tvar numRowPages = pageRoot * pagesAspectRatioSqrt;\r\n\t\tvar numColumnPages = pageRoot / pagesAspectRatioSqrt;\r\n\r\n\t\t// These value are rarely more than 2 rounding downs away from\r\n\t\t// a total that meets the page count. In cases of one being less \r\n\t\t// than 1 page, the other value can be too high and take more iterations \r\n\t\t// In this case, just change that value to be the page count, since \r\n\t\t// we know the other value is 1\r\n\t\tif (numRowPages < 1 && numColumnPages > pageCount)\r\n\t\t{\r\n\t\t\tvar scaleChange = numColumnPages / pageCount;\r\n\t\t\tnumColumnPages = pageCount;\r\n\t\t\tnumRowPages /= scaleChange;\r\n\t\t}\r\n\t\t\r\n\t\tif (numColumnPages < 1 && numRowPages > pageCount)\r\n\t\t{\r\n\t\t\tvar scaleChange = numRowPages / pageCount;\r\n\t\t\tnumRowPages = pageCount;\r\n\t\t\tnumColumnPages /= scaleChange;\r\n\t\t}\t\t\r\n\r\n\t\tvar currentTotalPages = Math.ceil(numRowPages) * Math.ceil(numColumnPages);\r\n\r\n\t\tvar numLoops = 0;\r\n\t\t\r\n\t\t// Iterate through while the rounded up number of pages comes to\r\n\t\t// a total greater than the required number\r\n\t\twhile (currentTotalPages > pageCount)\r\n\t\t{\r\n\t\t\t// Round down the page count (rows or columns) that is\r\n\t\t\t// closest to its next integer down in percentage terms.\r\n\t\t\t// i.e. Reduce the page total by reducing the total\r\n\t\t\t// page area by the least possible amount\r\n\r\n\t\t\tvar roundRowDownProportion = Math.floor(numRowPages) / numRowPages;\r\n\t\t\tvar roundColumnDownProportion = Math.floor(numColumnPages) / numColumnPages;\r\n\t\t\t\r\n\t\t\t// If the round down proportion is, work out the proportion to\r\n\t\t\t// round down to 1 page less\r\n\t\t\tif (roundRowDownProportion == 1)\r\n\t\t\t{\r\n\t\t\t\troundRowDownProportion = Math.floor(numRowPages-1) / numRowPages;\r\n\t\t\t}\r\n\t\t\tif (roundColumnDownProportion == 1)\r\n\t\t\t{\r\n\t\t\t\troundColumnDownProportion = Math.floor(numColumnPages-1) / numColumnPages;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Check which rounding down is smaller, but in the case of very small roundings\r\n\t\t\t// try the other dimension instead\r\n\t\t\tvar scaleChange = 1;\r\n\t\t\t\r\n\t\t\t// Use the higher of the two values\r\n\t\t\tif (roundRowDownProportion > roundColumnDownProportion)\r\n\t\t\t{\r\n\t\t\t\tscaleChange = roundRowDownProportion;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tscaleChange = roundColumnDownProportion;\r\n\t\t\t}\r\n\r\n\t\t\tnumRowPages = numRowPages * scaleChange;\r\n\t\t\tnumColumnPages = numColumnPages * scaleChange;\r\n\t\t\tcurrentTotalPages = Math.ceil(numRowPages) * Math.ceil(numColumnPages);\r\n\t\t\t\r\n\t\t\tnumLoops++;\r\n\t\t\t\r\n\t\t\tif (numLoops > 10)\r\n\t\t\t{\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Work out the scale from the number of row pages required\r\n\t\t// The column pages will give the same value\r\n\t\tvar posterWidth = availablePageWidth * numRowPages;\r\n\t\tscale = posterWidth / graphWidth;\r\n\t\t\r\n\t\t// Allow for rounding errors\r\n\t\treturn scale * 0.99999;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: show\r\n\t * \r\n\t * Copies the styles and the markup from the graph's container into the\r\n\t * given document and removes all cursor styles. The document is returned.\r\n\t * \r\n\t * This function should be called from within the document with the graph.\r\n\t * If you experience problems with missing stylesheets in IE then try adding\r\n\t * the domain to the trusted sites.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * graph - <mxGraph> to be copied.\r\n\t * doc - Document where the new graph is created.\r\n\t * x0 - X-coordinate of the graph view origin. Default is 0.\r\n\t * y0 - Y-coordinate of the graph view origin. Default is 0.\r\n\t * w - Optional width of the graph view.\r\n\t * h - Optional height of the graph view.\r\n\t */\r\n\tshow: function(graph, doc, x0, y0, w, h)\r\n\t{\r\n\t\tx0 = (x0 != null) ? x0 : 0;\r\n\t\ty0 = (y0 != null) ? y0 : 0;\r\n\t\t\r\n\t\tif (doc == null)\r\n\t\t{\r\n\t\t\tvar wnd = window.open();\r\n\t\t\tdoc = wnd.document;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tdoc.open();\r\n\t\t}\r\n\r\n\t\t// Workaround for missing print output in IE9 standards\r\n\t\tif (document.documentMode == 9)\r\n\t\t{\r\n\t\t\tdoc.writeln('<!--[if IE]><meta http-equiv=\"X-UA-Compatible\" content=\"IE=9\"><![endif]-->');\r\n\t\t}\r\n\t\t\r\n\t\tvar bounds = graph.getGraphBounds();\r\n\t\tvar dx = Math.ceil(x0 - bounds.x);\r\n\t\tvar dy = Math.ceil(y0 - bounds.y);\r\n\t\t\r\n\t\tif (w == null)\r\n\t\t{\r\n\t\t\tw = Math.ceil(bounds.width + x0) + Math.ceil(Math.ceil(bounds.x) - bounds.x);\r\n\t\t}\r\n\t\t\r\n\t\tif (h == null)\r\n\t\t{\r\n\t\t\th = Math.ceil(bounds.height + y0) + Math.ceil(Math.ceil(bounds.y) - bounds.y);\r\n\t\t}\r\n\t\t\r\n\t\t// Needs a special way of creating the page so that no click is required\r\n\t\t// to refresh the contents after the external CSS styles have been loaded.\r\n\t\t// To avoid a click or programmatic refresh, the styleSheets[].cssText\r\n\t\t// property is copied over from the original document.\r\n\t\tif (mxClient.IS_IE || document.documentMode == 11)\r\n\t\t{\r\n\t\t\tvar html = '<html><head>';\r\n\r\n\t\t\tvar base = document.getElementsByTagName('base');\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < base.length; i++)\r\n\t\t\t{\r\n\t\t\t\thtml += base[i].outerHTML;\r\n\t\t\t}\r\n\r\n\t\t\thtml += '<style>';\r\n\r\n\t\t\t// Copies the stylesheets without having to load them again\r\n\t\t\tfor (var i = 0; i < document.styleSheets.length; i++)\r\n\t\t\t{\r\n\t\t\t\ttry\r\n\t\t\t\t{\r\n\t\t\t\t\thtml += document.styleSheets[i].cssText;\r\n\t\t\t\t}\r\n\t\t\t\tcatch (e)\r\n\t\t\t\t{\r\n\t\t\t\t\t// ignore security exception\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\thtml += '</style></head><body style=\"margin:0px;\">';\r\n\t\t\t\r\n\t\t\t// Copies the contents of the graph container\r\n\t\t\thtml += '<div style=\"position:absolute;overflow:hidden;width:' + w + 'px;height:' + h + 'px;\"><div style=\"position:relative;left:' + dx + 'px;top:' + dy + 'px;\">';\r\n\t\t\thtml += graph.container.innerHTML;\r\n\t\t\thtml += '</div></div></body><html>';\r\n\r\n\t\t\tdoc.writeln(html);\r\n\t\t\tdoc.close();\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tdoc.writeln('<html><head>');\r\n\t\t\t\r\n\t\t\tvar base = document.getElementsByTagName('base');\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < base.length; i++)\r\n\t\t\t{\r\n\t\t\t\tdoc.writeln(mxUtils.getOuterHtml(base[i]));\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar links = document.getElementsByTagName('link');\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < links.length; i++)\r\n\t\t\t{\r\n\t\t\t\tdoc.writeln(mxUtils.getOuterHtml(links[i]));\r\n\t\t\t}\r\n\t\r\n\t\t\tvar styles = document.getElementsByTagName('style');\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < styles.length; i++)\r\n\t\t\t{\r\n\t\t\t\tdoc.writeln(mxUtils.getOuterHtml(styles[i]));\r\n\t\t\t}\r\n\r\n\t\t\tdoc.writeln('</head><body style=\"margin:0px;\"></body></html>');\r\n\t\t\tdoc.close();\r\n\r\n\t\t\tvar outer = doc.createElement('div');\r\n\t\t\touter.position = 'absolute';\r\n\t\t\touter.overflow = 'hidden';\r\n\t\t\touter.style.width = w + 'px';\r\n\t\t\touter.style.height = h + 'px';\r\n\r\n\t\t\t// Required for HTML labels if foreignObjects are disabled\r\n\t\t\tvar div = doc.createElement('div');\r\n\t\t\tdiv.style.position = 'absolute';\r\n\t\t\tdiv.style.left = dx + 'px';\r\n\t\t\tdiv.style.top = dy + 'px';\r\n\r\n\t\t\tvar node = graph.container.firstChild;\r\n\t\t\tvar svg = null;\r\n\t\t\t\r\n\t\t\twhile (node != null)\r\n\t\t\t{\r\n\t\t\t\tvar clone = node.cloneNode(true);\r\n\t\t\t\t\r\n\t\t\t\tif (node == graph.view.drawPane.ownerSVGElement)\r\n\t\t\t\t{\r\n\t\t\t\t\touter.appendChild(clone);\r\n\t\t\t\t\tsvg = clone;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tdiv.appendChild(clone);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tnode = node.nextSibling;\r\n\t\t\t}\r\n\r\n\t\t\tdoc.body.appendChild(outer);\r\n\t\t\t\r\n\t\t\tif (div.firstChild != null)\r\n\t\t\t{\r\n\t\t\t\tdoc.body.appendChild(div);\r\n\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\tif (svg != null)\r\n\t\t\t{\r\n\t\t\t\tsvg.style.minWidth = '';\r\n\t\t\t\tsvg.style.minHeight = '';\r\n\t\t\t\tsvg.firstChild.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tmxUtils.removeCursors(doc.body);\r\n\t\r\n\t\treturn doc;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: printScreen\r\n\t * \r\n\t * Prints the specified graph using a new window and the built-in print\r\n\t * dialog.\r\n\t * \r\n\t * This function should be called from within the document with the graph.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * graph - <mxGraph> to be printed.\r\n\t */\r\n\tprintScreen: function(graph)\r\n\t{\r\n\t\tvar wnd = window.open();\r\n\t\tvar bounds = graph.getGraphBounds();\r\n\t\tmxUtils.show(graph, wnd.document);\r\n\t\t\r\n\t\tvar print = function()\r\n\t\t{\r\n\t\t\twnd.focus();\r\n\t\t\twnd.print();\r\n\t\t\twnd.close();\r\n\t\t};\r\n\t\t\r\n\t\t// Workaround for Google Chrome which needs a bit of a\r\n\t\t// delay in order to render the SVG contents\r\n\t\tif (mxClient.IS_GC)\r\n\t\t{\r\n\t\t\twnd.setTimeout(print, 500);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tprint();\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: popup\r\n\t * \r\n\t * Shows the specified text content in a new <mxWindow> or a new browser\r\n\t * window if isInternalWindow is false.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * content - String that specifies the text to be displayed.\r\n\t * isInternalWindow - Optional boolean indicating if an mxWindow should be\r\n\t * used instead of a new browser window. Default is false.\r\n\t */\r\n\tpopup: function(content, isInternalWindow)\r\n\t{\r\n\t   \tif (isInternalWindow)\r\n\t   \t{\r\n\t\t\tvar div = document.createElement('div');\r\n\t\t\t\r\n\t\t\tdiv.style.overflow = 'scroll';\r\n\t\t\tdiv.style.width = '636px';\r\n\t\t\tdiv.style.height = '460px';\r\n\t\t\t\r\n\t\t\tvar pre = document.createElement('pre');\r\n\t\t    pre.innerHTML = mxUtils.htmlEntities(content, false).\r\n\t\t    \treplace(/\\n/g,'<br>').replace(/ /g, '&nbsp;');\r\n\t\t\t\r\n\t\t\tdiv.appendChild(pre);\r\n\t\t\t\r\n\t\t\tvar w = document.body.clientWidth;\r\n\t\t\tvar h = Math.max(document.body.clientHeight || 0, document.documentElement.clientHeight)\r\n\t\t\tvar wnd = new mxWindow('Popup Window', div,\r\n\t\t\t\tw/2-320, h/2-240, 640, 480, false, true);\r\n\r\n\t\t\twnd.setClosable(true);\r\n\t\t\twnd.setVisible(true);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Wraps up the XML content in a textarea\r\n\t\t\tif (mxClient.IS_NS)\r\n\t\t\t{\r\n\t\t\t    var wnd = window.open();\r\n\t\t\t\twnd.document.writeln('<pre>'+mxUtils.htmlEntities(content)+'</pre');\r\n\t\t\t   \twnd.document.close();\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t    var wnd = window.open();\r\n\t\t\t    var pre = wnd.document.createElement('pre');\r\n\t\t\t    pre.innerHTML = mxUtils.htmlEntities(content, false).\r\n\t\t\t    \treplace(/\\n/g,'<br>').replace(/ /g, '&nbsp;');\r\n\t\t\t   \twnd.document.body.appendChild(pre);\r\n\t\t\t}\r\n\t   \t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: alert\r\n\t * \r\n\t * Displayss the given alert in a new dialog. This implementation uses the\r\n\t * built-in alert function. This is used to display validation errors when\r\n\t * connections cannot be changed or created.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * message - String specifying the message to be displayed.\r\n\t */\r\n\talert: function(message)\r\n\t{\r\n\t\talert(message);\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: prompt\r\n\t * \r\n\t * Displays the given message in a prompt dialog. This implementation uses\r\n\t * the built-in prompt function.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * message - String specifying the message to be displayed.\r\n\t * defaultValue - Optional string specifying the default value.\r\n\t */\r\n\tprompt: function(message, defaultValue)\r\n\t{\r\n\t\treturn prompt(message, (defaultValue != null) ? defaultValue : '');\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: confirm\r\n\t * \r\n\t * Displays the given message in a confirm dialog. This implementation uses\r\n\t * the built-in confirm function.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * message - String specifying the message to be displayed.\r\n\t */\r\n\tconfirm: function(message)\r\n\t{\r\n\t\treturn confirm(message);\r\n\t},\r\n\r\n\t/**\r\n\t * Function: error\r\n\t * \r\n\t * Displays the given error message in a new <mxWindow> of the given width.\r\n\t * If close is true then an additional close button is added to the window.\r\n\t * The optional icon specifies the icon to be used for the window. Default\r\n\t * is <mxUtils.errorImage>.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * message - String specifying the message to be displayed.\r\n\t * width - Integer specifying the width of the window.\r\n\t * close - Optional boolean indicating whether to add a close button.\r\n\t * icon - Optional icon for the window decoration.\r\n\t */\r\n\terror: function(message, width, close, icon)\r\n\t{\r\n\t\tvar div = document.createElement('div');\r\n\t\tdiv.style.padding = '20px';\r\n\r\n\t\tvar img = document.createElement('img');\r\n\t\timg.setAttribute('src', icon || mxUtils.errorImage);\r\n\t\timg.setAttribute('valign', 'bottom');\r\n\t\timg.style.verticalAlign = 'middle';\r\n\t\tdiv.appendChild(img);\r\n\r\n\t\tdiv.appendChild(document.createTextNode('\\u00a0')); // &nbsp;\r\n\t\tdiv.appendChild(document.createTextNode('\\u00a0')); // &nbsp;\r\n\t\tdiv.appendChild(document.createTextNode('\\u00a0')); // &nbsp;\r\n\t\tmxUtils.write(div, message);\r\n\r\n\t\tvar w = document.body.clientWidth;\r\n\t\tvar h = (document.body.clientHeight || document.documentElement.clientHeight);\r\n\t\tvar warn = new mxWindow(mxResources.get(mxUtils.errorResource) ||\r\n\t\t\tmxUtils.errorResource, div, (w-width)/2, h/4, width, null,\r\n\t\t\tfalse, true);\r\n\r\n\t\tif (close)\r\n\t\t{\r\n\t\t\tmxUtils.br(div);\r\n\t\t\t\r\n\t\t\tvar tmp = document.createElement('p');\r\n\t\t\tvar button = document.createElement('button');\r\n\r\n\t\t\tif (mxClient.IS_IE)\r\n\t\t\t{\r\n\t\t\t\tbutton.style.cssText = 'float:right';\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tbutton.setAttribute('style', 'float:right');\r\n\t\t\t}\r\n\r\n\t\t\tmxEvent.addListener(button, 'click', function(evt)\r\n\t\t\t{\r\n\t\t\t\twarn.destroy();\r\n\t\t\t});\r\n\r\n\t\t\tmxUtils.write(button, mxResources.get(mxUtils.closeResource) ||\r\n\t\t\t\tmxUtils.closeResource);\r\n\t\t\t\r\n\t\t\ttmp.appendChild(button);\r\n\t\t\tdiv.appendChild(tmp);\r\n\t\t\t\r\n\t\t\tmxUtils.br(div);\r\n\t\t\t\r\n\t\t\twarn.setClosable(true);\r\n\t\t}\r\n\t\t\r\n\t\twarn.setVisible(true);\r\n\t\t\r\n\t\treturn warn;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: makeDraggable\r\n\t * \r\n\t * Configures the given DOM element to act as a drag source for the\r\n\t * specified graph. Returns a a new <mxDragSource>. If\r\n\t * <mxDragSource.guideEnabled> is enabled then the x and y arguments must\r\n\t * be used in funct to match the preview location.\r\n\t * \r\n\t * Example:\r\n\t * \r\n\t * (code)\r\n\t * var funct = function(graph, evt, cell, x, y)\r\n\t * {\r\n\t *   if (graph.canImportCell(cell))\r\n\t *   {\r\n\t *     var parent = graph.getDefaultParent();\r\n\t *     var vertex = null;\r\n\t *     \r\n\t *     graph.getModel().beginUpdate();\r\n\t *     try\r\n\t *     {\r\n\t * \t     vertex = graph.insertVertex(parent, null, 'Hello', x, y, 80, 30);\r\n\t *     }\r\n\t *     finally\r\n\t *     {\r\n\t *       graph.getModel().endUpdate();\r\n\t *     }\r\n\t *\r\n\t *     graph.setSelectionCell(vertex);\r\n\t *   }\r\n\t * }\r\n\t * \r\n\t * var img = document.createElement('img');\r\n\t * img.setAttribute('src', 'editors/images/rectangle.gif');\r\n\t * img.style.position = 'absolute';\r\n\t * img.style.left = '0px';\r\n\t * img.style.top = '0px';\r\n\t * img.style.width = '16px';\r\n\t * img.style.height = '16px';\r\n\t * \r\n\t * var dragImage = img.cloneNode(true);\r\n\t * dragImage.style.width = '32px';\r\n\t * dragImage.style.height = '32px';\r\n\t * mxUtils.makeDraggable(img, graph, funct, dragImage);\r\n\t * document.body.appendChild(img);\r\n\t * (end)\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * element - DOM element to make draggable.\r\n\t * graphF - <mxGraph> that acts as the drop target or a function that takes a\r\n\t * mouse event and returns the current <mxGraph>.\r\n\t * funct - Function to execute on a successful drop.\r\n\t * dragElement - Optional DOM node to be used for the drag preview.\r\n\t * dx - Optional horizontal offset between the cursor and the drag\r\n\t * preview.\r\n\t * dy - Optional vertical offset between the cursor and the drag\r\n\t * preview.\r\n\t * autoscroll - Optional boolean that specifies if autoscroll should be\r\n\t * used. Default is mxGraph.autoscroll.\r\n\t * scalePreview - Optional boolean that specifies if the preview element\r\n\t * should be scaled according to the graph scale. If this is true, then\r\n\t * the offsets will also be scaled. Default is false.\r\n\t * highlightDropTargets - Optional boolean that specifies if dropTargets\r\n\t * should be highlighted. Default is true.\r\n\t * getDropTarget - Optional function to return the drop target for a given\r\n\t * location (x, y). Default is mxGraph.getCellAt.\r\n\t */\r\n\tmakeDraggable: function(element, graphF, funct, dragElement, dx, dy, autoscroll,\r\n\t\t\tscalePreview, highlightDropTargets, getDropTarget)\r\n\t{\r\n\t\tvar dragSource = new mxDragSource(element, funct);\r\n\t\tdragSource.dragOffset = new mxPoint((dx != null) ? dx : 0,\r\n\t\t\t(dy != null) ? dy : mxConstants.TOOLTIP_VERTICAL_OFFSET);\r\n\t\tdragSource.autoscroll = autoscroll;\r\n\t\t\r\n\t\t// Cannot enable this by default. This needs to be enabled in the caller\r\n\t\t// if the funct argument uses the new x- and y-arguments.\r\n\t\tdragSource.setGuidesEnabled(false);\r\n\t\t\r\n\t\tif (highlightDropTargets != null)\r\n\t\t{\r\n\t\t\tdragSource.highlightDropTargets = highlightDropTargets;\r\n\t\t}\r\n\t\t\r\n\t\t// Overrides function to find drop target cell\r\n\t\tif (getDropTarget != null)\r\n\t\t{\r\n\t\t\tdragSource.getDropTarget = getDropTarget;\r\n\t\t}\r\n\t\t\r\n\t\t// Overrides function to get current graph\r\n\t\tdragSource.getGraphForEvent = function(evt)\r\n\t\t{\r\n\t\t\treturn (typeof(graphF) == 'function') ? graphF(evt) : graphF;\r\n\t\t};\r\n\t\t\r\n\t\t// Translates switches into dragSource customizations\r\n\t\tif (dragElement != null)\r\n\t\t{\r\n\t\t\tdragSource.createDragElement = function()\r\n\t\t\t{\r\n\t\t\t\treturn dragElement.cloneNode(true);\r\n\t\t\t};\r\n\t\t\t\r\n\t\t\tif (scalePreview)\r\n\t\t\t{\r\n\t\t\t\tdragSource.createPreviewElement = function(graph)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar elt = dragElement.cloneNode(true);\r\n\r\n\t\t\t\t\tvar w = parseInt(elt.style.width);\r\n\t\t\t\t\tvar h = parseInt(elt.style.height);\r\n\t\t\t\t\telt.style.width = Math.round(w * graph.view.scale) + 'px';\r\n\t\t\t\t\telt.style.height = Math.round(h * graph.view.scale) + 'px';\r\n\t\t\t\t\t\r\n\t\t\t\t\treturn elt;\r\n\t\t\t\t};\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn dragSource;\r\n\t}\r\n\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n var mxConstants =\r\n {\r\n\t/**\r\n\t * Class: mxConstants\r\n\t * \r\n\t * Defines various global constants.\r\n\t * \r\n\t * Variable: DEFAULT_HOTSPOT\r\n\t * \r\n\t * Defines the portion of the cell which is to be used as a connectable\r\n\t * region. Default is 0.3. Possible values are 0 < x <= 1. \r\n\t */\r\n\tDEFAULT_HOTSPOT: 0.3,\r\n\r\n\t/**\r\n\t * Variable: MIN_HOTSPOT_SIZE\r\n\t * \r\n\t * Defines the minimum size in pixels of the portion of the cell which is\r\n\t * to be used as a connectable region. Default is 8.\r\n\t */\r\n\tMIN_HOTSPOT_SIZE: 8,\r\n\r\n\t/**\r\n\t * Variable: MAX_HOTSPOT_SIZE\r\n\t * \r\n\t * Defines the maximum size in pixels of the portion of the cell which is\r\n\t * to be used as a connectable region. Use 0 for no maximum. Default is 0.\r\n\t */\r\n\tMAX_HOTSPOT_SIZE: 0,\r\n\r\n\t/**\r\n\t * Variable: RENDERING_HINT_EXACT\r\n\t * \r\n\t * Defines the exact rendering hint.\r\n\t */\r\n\tRENDERING_HINT_EXACT: 'exact',\r\n\r\n\t/**\r\n\t * Variable: RENDERING_HINT_FASTER\r\n\t * \r\n\t * Defines the faster rendering hint.\r\n\t */\r\n\tRENDERING_HINT_FASTER: 'faster',\r\n\r\n\t/**\r\n\t * Variable: RENDERING_HINT_FASTEST\r\n\t * \r\n\t * Defines the fastest rendering hint.\r\n\t */\r\n\tRENDERING_HINT_FASTEST: 'fastest',\r\n\r\n\t/**\r\n\t * Variable: DIALECT_SVG\r\n\t * \r\n\t * Defines the SVG display dialect name.\r\n\t */\r\n\tDIALECT_SVG: 'svg',\r\n\r\n\t/**\r\n\t * Variable: DIALECT_VML\r\n\t * \r\n\t * Defines the VML display dialect name.\r\n\t */\r\n\tDIALECT_VML: 'vml',\r\n\r\n\t/**\r\n\t * Variable: DIALECT_MIXEDHTML\r\n\t * \r\n\t * Defines the mixed HTML display dialect name.\r\n\t */\r\n\tDIALECT_MIXEDHTML: 'mixedHtml',\r\n\r\n\t/**\r\n\t * Variable: DIALECT_PREFERHTML\r\n\t * \r\n\t * Defines the preferred HTML display dialect name.\r\n\t */\r\n\tDIALECT_PREFERHTML: 'preferHtml',\r\n\r\n\t/**\r\n\t * Variable: DIALECT_STRICTHTML\r\n\t * \r\n\t * Defines the strict HTML display dialect.\r\n\t */\r\n\tDIALECT_STRICTHTML: 'strictHtml',\r\n\r\n\t/**\r\n\t * Variable: NS_SVG\r\n\t * \r\n\t * Defines the SVG namespace.\r\n\t */\r\n\tNS_SVG: 'http://www.w3.org/2000/svg',\r\n\r\n\t/**\r\n\t * Variable: NS_XHTML\r\n\t * \r\n\t * Defines the XHTML namespace.\r\n\t */\r\n\tNS_XHTML: 'http://www.w3.org/1999/xhtml',\r\n\r\n\t/**\r\n\t * Variable: NS_XLINK\r\n\t * \r\n\t * Defines the XLink namespace.\r\n\t */\r\n\tNS_XLINK: 'http://www.w3.org/1999/xlink',\r\n\r\n\t/**\r\n\t * Variable: SHADOWCOLOR\r\n\t * \r\n\t * Defines the color to be used to draw shadows in shapes and windows.\r\n\t * Default is gray.\r\n\t */\r\n\tSHADOWCOLOR: 'gray',\r\n\r\n\t/**\r\n\t * Variable: VML_SHADOWCOLOR\r\n\t * \r\n\t * Used for shadow color in filters where transparency is not supported\r\n\t * (Microsoft Internet Explorer). Default is gray.\r\n\t */\r\n\tVML_SHADOWCOLOR: 'gray',\r\n\r\n\t/**\r\n\t * Variable: SHADOW_OFFSET_X\r\n\t * \r\n\t * Specifies the x-offset of the shadow. Default is 2.\r\n\t */\r\n\tSHADOW_OFFSET_X: 2,\r\n\r\n\t/**\r\n\t * Variable: SHADOW_OFFSET_Y\r\n\t * \r\n\t * Specifies the y-offset of the shadow. Default is 3.\r\n\t */\r\n\tSHADOW_OFFSET_Y: 3,\r\n\t\r\n\t/**\r\n\t * Variable: SHADOW_OPACITY\r\n\t * \r\n\t * Defines the opacity for shadows. Default is 1.\r\n\t */\r\n\tSHADOW_OPACITY: 1,\r\n \r\n\t/**\r\n\t * Variable: NODETYPE_ELEMENT\r\n\t * \r\n\t * DOM node of type ELEMENT.\r\n\t */\r\n\tNODETYPE_ELEMENT: 1,\r\n\r\n\t/**\r\n\t * Variable: NODETYPE_ATTRIBUTE\r\n\t * \r\n\t * DOM node of type ATTRIBUTE.\r\n\t */\r\n\tNODETYPE_ATTRIBUTE: 2,\r\n\r\n\t/**\r\n\t * Variable: NODETYPE_TEXT\r\n\t * \r\n\t * DOM node of type TEXT.\r\n\t */\r\n\tNODETYPE_TEXT: 3,\r\n\r\n\t/**\r\n\t * Variable: NODETYPE_CDATA\r\n\t * \r\n\t * DOM node of type CDATA.\r\n\t */\r\n\tNODETYPE_CDATA: 4,\r\n\t\r\n\t/**\r\n\t * Variable: NODETYPE_ENTITY_REFERENCE\r\n\t * \r\n\t * DOM node of type ENTITY_REFERENCE.\r\n\t */\r\n\tNODETYPE_ENTITY_REFERENCE: 5,\r\n\r\n\t/**\r\n\t * Variable: NODETYPE_ENTITY\r\n\t * \r\n\t * DOM node of type ENTITY.\r\n\t */\r\n\tNODETYPE_ENTITY: 6,\r\n\r\n\t/**\r\n\t * Variable: NODETYPE_PROCESSING_INSTRUCTION\r\n\t * \r\n\t * DOM node of type PROCESSING_INSTRUCTION.\r\n\t */\r\n\tNODETYPE_PROCESSING_INSTRUCTION: 7,\r\n\r\n\t/**\r\n\t * Variable: NODETYPE_COMMENT\r\n\t * \r\n\t * DOM node of type COMMENT.\r\n\t */\r\n\tNODETYPE_COMMENT: 8,\r\n\t\t\r\n\t/**\r\n\t * Variable: NODETYPE_DOCUMENT\r\n\t * \r\n\t * DOM node of type DOCUMENT.\r\n\t */\r\n\tNODETYPE_DOCUMENT: 9,\r\n\r\n\t/**\r\n\t * Variable: NODETYPE_DOCUMENTTYPE\r\n\t * \r\n\t * DOM node of type DOCUMENTTYPE.\r\n\t */\r\n\tNODETYPE_DOCUMENTTYPE: 10,\r\n\r\n\t/**\r\n\t * Variable: NODETYPE_DOCUMENT_FRAGMENT\r\n\t * \r\n\t * DOM node of type DOCUMENT_FRAGMENT.\r\n\t */\r\n\tNODETYPE_DOCUMENT_FRAGMENT: 11,\r\n\r\n\t/**\r\n\t * Variable: NODETYPE_NOTATION\r\n\t * \r\n\t * DOM node of type NOTATION.\r\n\t */\r\n\tNODETYPE_NOTATION: 12,\r\n\t\r\n\t/**\r\n\t * Variable: TOOLTIP_VERTICAL_OFFSET\r\n\t * \r\n\t * Defines the vertical offset for the tooltip.\r\n\t * Default is 16.\r\n\t */\r\n\tTOOLTIP_VERTICAL_OFFSET: 16,\r\n\r\n\t/**\r\n\t * Variable: DEFAULT_VALID_COLOR\r\n\t * \r\n\t * Specifies the default valid color. Default is #0000FF.\r\n\t */\r\n\tDEFAULT_VALID_COLOR: '#00FF00',\r\n\r\n\t/**\r\n\t * Variable: DEFAULT_INVALID_COLOR\r\n\t * \r\n\t * Specifies the default invalid color. Default is #FF0000.\r\n\t */\r\n\tDEFAULT_INVALID_COLOR: '#FF0000',\r\n\r\n\t/**\r\n\t * Variable: OUTLINE_HIGHLIGHT_COLOR\r\n\t * \r\n\t * Specifies the default highlight color for shape outlines.\r\n\t * Default is #0000FF. This is used in <mxEdgeHandler>.\r\n\t */\r\n\tOUTLINE_HIGHLIGHT_COLOR: '#00FF00',\r\n\r\n\t/**\r\n\t * Variable: OUTLINE_HIGHLIGHT_COLOR\r\n\t * \r\n\t * Defines the strokewidth to be used for shape outlines.\r\n\t * Default is 5. This is used in <mxEdgeHandler>.\r\n\t */\r\n\tOUTLINE_HIGHLIGHT_STROKEWIDTH: 5,\r\n\r\n\t/**\r\n\t * Variable: HIGHLIGHT_STROKEWIDTH\r\n\t * \r\n\t * Defines the strokewidth to be used for the highlights.\r\n\t * Default is 3.\r\n\t */\r\n\tHIGHLIGHT_STROKEWIDTH: 3,\r\n\r\n\t/**\r\n\t * Variable: CONSTRAINT_HIGHLIGHT_SIZE\r\n\t * \r\n\t * Size of the constraint highlight (in px). Default is 2.\r\n\t */\r\n\tHIGHLIGHT_SIZE: 2,\r\n\t\r\n\t/**\r\n\t * Variable: HIGHLIGHT_OPACITY\r\n\t * \r\n\t * Opacity (in %) used for the highlights (including outline).\r\n\t * Default is 100.\r\n\t */\r\n\tHIGHLIGHT_OPACITY: 100,\r\n\t\r\n\t/**\r\n\t * Variable: CURSOR_MOVABLE_VERTEX\r\n\t * \r\n\t * Defines the cursor for a movable vertex. Default is 'move'.\r\n\t */\r\n\tCURSOR_MOVABLE_VERTEX: 'move',\r\n\t\r\n\t/**\r\n\t * Variable: CURSOR_MOVABLE_EDGE\r\n\t * \r\n\t * Defines the cursor for a movable edge. Default is 'move'.\r\n\t */\r\n\tCURSOR_MOVABLE_EDGE: 'move',\r\n\t\r\n\t/**\r\n\t * Variable: CURSOR_LABEL_HANDLE\r\n\t * \r\n\t * Defines the cursor for a movable label. Default is 'default'.\r\n\t */\r\n\tCURSOR_LABEL_HANDLE: 'default',\r\n\t\r\n\t/**\r\n\t * Variable: CURSOR_TERMINAL_HANDLE\r\n\t * \r\n\t * Defines the cursor for a terminal handle. Default is 'pointer'.\r\n\t */\r\n\tCURSOR_TERMINAL_HANDLE: 'pointer',\r\n\t\r\n\t/**\r\n\t * Variable: CURSOR_BEND_HANDLE\r\n\t * \r\n\t * Defines the cursor for a movable bend. Default is 'crosshair'.\r\n\t */\r\n\tCURSOR_BEND_HANDLE: 'crosshair',\r\n\r\n\t/**\r\n\t * Variable: CURSOR_VIRTUAL_BEND_HANDLE\r\n\t * \r\n\t * Defines the cursor for a movable bend. Default is 'crosshair'.\r\n\t */\r\n\tCURSOR_VIRTUAL_BEND_HANDLE: 'crosshair',\r\n\t\r\n\t/**\r\n\t * Variable: CURSOR_CONNECT\r\n\t * \r\n\t * Defines the cursor for a connectable state. Default is 'pointer'.\r\n\t */\r\n\tCURSOR_CONNECT: 'pointer',\r\n\r\n\t/**\r\n\t * Variable: HIGHLIGHT_COLOR\r\n\t * \r\n\t * Defines the color to be used for the cell highlighting.\r\n\t * Use 'none' for no color. Default is #00FF00.\r\n\t */\r\n\tHIGHLIGHT_COLOR: '#00FF00',\r\n\r\n\t/**\r\n\t * Variable: TARGET_HIGHLIGHT_COLOR\r\n\t * \r\n\t * Defines the color to be used for highlighting a target cell for a new\r\n\t * or changed connection. Note that this may be either a source or\r\n\t * target terminal in the graph. Use 'none' for no color.\r\n\t * Default is #0000FF.\r\n\t */\r\n\tCONNECT_TARGET_COLOR: '#0000FF',\r\n\r\n\t/**\r\n\t * Variable: INVALID_CONNECT_TARGET_COLOR\r\n\t * \r\n\t * Defines the color to be used for highlighting a invalid target cells\r\n\t * for a new or changed connections. Note that this may be either a source\r\n\t * or target terminal in the graph. Use 'none' for no color. Default is\r\n\t * #FF0000.\r\n\t */\r\n\tINVALID_CONNECT_TARGET_COLOR: '#FF0000',\r\n\r\n\t/**\r\n\t * Variable: DROP_TARGET_COLOR\r\n\t * \r\n\t * Defines the color to be used for the highlighting target parent cells\r\n\t * (for drag and drop). Use 'none' for no color. Default is #0000FF.\r\n\t */\r\n\tDROP_TARGET_COLOR: '#0000FF',\r\n\r\n\t/**\r\n\t * Variable: VALID_COLOR\r\n\t * \r\n\t * Defines the color to be used for the coloring valid connection\r\n\t * previews. Use 'none' for no color. Default is #FF0000.\r\n\t */\r\n\tVALID_COLOR: '#00FF00',\r\n\r\n\t/**\r\n\t * Variable: INVALID_COLOR\r\n\t * \r\n\t * Defines the color to be used for the coloring invalid connection\r\n\t * previews. Use 'none' for no color. Default is #FF0000.\r\n\t */\r\n\tINVALID_COLOR: '#FF0000',\r\n\r\n\t/**\r\n\t * Variable: EDGE_SELECTION_COLOR\r\n\t * \r\n\t * Defines the color to be used for the selection border of edges. Use\r\n\t * 'none' for no color. Default is #00FF00.\r\n\t */\r\n\tEDGE_SELECTION_COLOR: '#00FF00',\r\n\r\n\t/**\r\n\t * Variable: VERTEX_SELECTION_COLOR\r\n\t * \r\n\t * Defines the color to be used for the selection border of vertices. Use\r\n\t * 'none' for no color. Default is #00FF00.\r\n\t */\r\n\tVERTEX_SELECTION_COLOR: '#00FF00',\r\n\r\n\t/**\r\n\t * Variable: VERTEX_SELECTION_STROKEWIDTH\r\n\t * \r\n\t * Defines the strokewidth to be used for vertex selections.\r\n\t * Default is 1.\r\n\t */\r\n\tVERTEX_SELECTION_STROKEWIDTH: 1,\r\n\r\n\t/**\r\n\t * Variable: EDGE_SELECTION_STROKEWIDTH\r\n\t * \r\n\t * Defines the strokewidth to be used for edge selections.\r\n\t * Default is 1.\r\n\t */\r\n\tEDGE_SELECTION_STROKEWIDTH: 1,\r\n\r\n\t/**\r\n\t * Variable: SELECTION_DASHED\r\n\t * \r\n\t * Defines the dashed state to be used for the vertex selection\r\n\t * border. Default is true.\r\n\t */\r\n\tVERTEX_SELECTION_DASHED: true,\r\n\r\n\t/**\r\n\t * Variable: SELECTION_DASHED\r\n\t * \r\n\t * Defines the dashed state to be used for the edge selection\r\n\t * border. Default is true.\r\n\t */\r\n\tEDGE_SELECTION_DASHED: true,\r\n\r\n\t/**\r\n\t * Variable: GUIDE_COLOR\r\n\t * \r\n\t * Defines the color to be used for the guidelines in mxGraphHandler.\r\n\t * Default is #FF0000.\r\n\t */\r\n\tGUIDE_COLOR: '#FF0000',\r\n\r\n\t/**\r\n\t * Variable: GUIDE_STROKEWIDTH\r\n\t * \r\n\t * Defines the strokewidth to be used for the guidelines in mxGraphHandler.\r\n\t * Default is 1.\r\n\t */\r\n\tGUIDE_STROKEWIDTH: 1,\r\n\r\n\t/**\r\n\t * Variable: OUTLINE_COLOR\r\n\t * \r\n\t * Defines the color to be used for the outline rectangle\r\n\t * border.  Use 'none' for no color. Default is #0099FF.\r\n\t */\r\n\tOUTLINE_COLOR: '#0099FF',\r\n\r\n\t/**\r\n\t * Variable: OUTLINE_STROKEWIDTH\r\n\t * \r\n\t * Defines the strokewidth to be used for the outline rectangle\r\n\t * stroke width. Default is 3.\r\n\t */\r\n\tOUTLINE_STROKEWIDTH: (mxClient.IS_IE) ? 2 : 3,\r\n\r\n\t/**\r\n\t * Variable: HANDLE_SIZE\r\n\t * \r\n\t * Defines the default size for handles. Default is 6.\r\n\t */\r\n\tHANDLE_SIZE: 6,\r\n\r\n\t/**\r\n\t * Variable: LABEL_HANDLE_SIZE\r\n\t * \r\n\t * Defines the default size for label handles. Default is 4.\r\n\t */\r\n\tLABEL_HANDLE_SIZE: 4,\r\n\r\n\t/**\r\n\t * Variable: HANDLE_FILLCOLOR\r\n\t * \r\n\t * Defines the color to be used for the handle fill color. Use 'none' for\r\n\t * no color. Default is #00FF00 (green).\r\n\t */\r\n\tHANDLE_FILLCOLOR: '#00FF00',\r\n\r\n\t/**\r\n\t * Variable: HANDLE_STROKECOLOR\r\n\t * \r\n\t * Defines the color to be used for the handle stroke color. Use 'none' for\r\n\t * no color. Default is black.\r\n\t */\r\n\tHANDLE_STROKECOLOR: 'black',\r\n\r\n\t/**\r\n\t * Variable: LABEL_HANDLE_FILLCOLOR\r\n\t * \r\n\t * Defines the color to be used for the label handle fill color. Use 'none'\r\n\t * for no color. Default is yellow.\r\n\t */\r\n\tLABEL_HANDLE_FILLCOLOR: 'yellow',\r\n\r\n\t/**\r\n\t * Variable: CONNECT_HANDLE_FILLCOLOR\r\n\t * \r\n\t * Defines the color to be used for the connect handle fill color. Use\r\n\t * 'none' for no color. Default is #0000FF (blue).\r\n\t */\r\n\tCONNECT_HANDLE_FILLCOLOR: '#0000FF',\r\n\r\n\t/**\r\n\t * Variable: LOCKED_HANDLE_FILLCOLOR\r\n\t * \r\n\t * Defines the color to be used for the locked handle fill color. Use\r\n\t * 'none' for no color. Default is #FF0000 (red).\r\n\t */\r\n\tLOCKED_HANDLE_FILLCOLOR: '#FF0000',\r\n\r\n\t/**\r\n\t * Variable: OUTLINE_HANDLE_FILLCOLOR\r\n\t * \r\n\t * Defines the color to be used for the outline sizer fill color. Use\r\n\t * 'none' for no color. Default is #00FFFF.\r\n\t */\r\n\tOUTLINE_HANDLE_FILLCOLOR: '#00FFFF',\r\n\r\n\t/**\r\n\t * Variable: OUTLINE_HANDLE_STROKECOLOR\r\n\t * \r\n\t * Defines the color to be used for the outline sizer stroke color. Use\r\n\t * 'none' for no color. Default is #0033FF.\r\n\t */\r\n\tOUTLINE_HANDLE_STROKECOLOR: '#0033FF',\r\n\r\n\t/**\r\n\t * Variable: DEFAULT_FONTFAMILY\r\n\t * \r\n\t * Defines the default family for all fonts. Default is Arial,Helvetica.\r\n\t */\r\n\tDEFAULT_FONTFAMILY: 'Arial,Helvetica',\r\n\r\n\t/**\r\n\t * Variable: DEFAULT_FONTSIZE\r\n\t * \r\n\t * Defines the default size (in px). Default is 11.\r\n\t */\r\n\tDEFAULT_FONTSIZE: 11,\r\n\r\n\t/**\r\n\t * Variable: DEFAULT_TEXT_DIRECTION\r\n\t * \r\n\t * Defines the default value for the <STYLE_TEXT_DIRECTION> if no value is\r\n\t * defined for it in the style. Default value is an empty string which means\r\n\t * the default system setting is used and no direction is set.\r\n\t */\r\n\tDEFAULT_TEXT_DIRECTION: '',\r\n\r\n\t/**\r\n\t * Variable: LINE_HEIGHT\r\n\t * \r\n\t * Defines the default line height for text labels. Default is 1.2.\r\n\t */\r\n\tLINE_HEIGHT: 1.2,\r\n\r\n\t/**\r\n\t * Variable: WORD_WRAP\r\n\t * \r\n\t * Defines the CSS value for the word-wrap property. Default is \"normal\".\r\n\t * Change this to \"break-word\" to allow long words to be able to be broken\r\n\t * and wrap onto the next line.\r\n\t */\r\n\tWORD_WRAP: 'normal',\r\n\r\n\t/**\r\n\t * Variable: ABSOLUTE_LINE_HEIGHT\r\n\t * \r\n\t * Specifies if absolute line heights should be used (px) in CSS. Default\r\n\t * is false. Set this to true for backwards compatibility.\r\n\t */\r\n\tABSOLUTE_LINE_HEIGHT: false,\r\n\r\n\t/**\r\n\t * Variable: DEFAULT_FONTSTYLE\r\n\t * \r\n\t * Defines the default style for all fonts. Default is 0. This can be set\r\n\t * to any combination of font styles as follows.\r\n\t * \r\n\t * (code)\r\n\t * mxConstants.DEFAULT_FONTSTYLE = mxConstants.FONT_BOLD | mxConstants.FONT_ITALIC;\r\n\t * (end)\r\n\t */\r\n\tDEFAULT_FONTSTYLE: 0,\r\n\r\n\t/**\r\n\t * Variable: DEFAULT_STARTSIZE\r\n\t * \r\n\t * Defines the default start size for swimlanes. Default is 40.\r\n\t */\r\n\tDEFAULT_STARTSIZE: 40,\r\n\r\n\t/**\r\n\t * Variable: DEFAULT_MARKERSIZE\r\n\t * \r\n\t * Defines the default size for all markers. Default is 6.\r\n\t */\r\n\tDEFAULT_MARKERSIZE: 6,\r\n\r\n\t/**\r\n\t * Variable: DEFAULT_IMAGESIZE\r\n\t * \r\n\t * Defines the default width and height for images used in the\r\n\t * label shape. Default is 24.\r\n\t */\r\n\tDEFAULT_IMAGESIZE: 24,\r\n\r\n\t/**\r\n\t * Variable: ENTITY_SEGMENT\r\n\t * \r\n\t * Defines the length of the horizontal segment of an Entity Relation.\r\n\t * This can be overridden using <mxConstants.STYLE_SEGMENT> style.\r\n\t * Default is 30.\r\n\t */\r\n\tENTITY_SEGMENT: 30,\r\n\r\n\t/**\r\n\t * Variable: RECTANGLE_ROUNDING_FACTOR\r\n\t * \r\n\t * Defines the rounding factor for rounded rectangles in percent between\r\n\t * 0 and 1. Values should be smaller than 0.5. Default is 0.15.\r\n\t */\r\n\tRECTANGLE_ROUNDING_FACTOR: 0.15,\r\n\r\n\t/**\r\n\t * Variable: LINE_ARCSIZE\r\n\t * \r\n\t * Defines the size of the arcs for rounded edges. Default is 20.\r\n\t */\r\n\tLINE_ARCSIZE: 20,\r\n\r\n\t/**\r\n\t * Variable: ARROW_SPACING\r\n\t * \r\n\t * Defines the spacing between the arrow shape and its terminals. Default is 0.\r\n\t */\r\n\tARROW_SPACING: 0,\r\n\r\n\t/**\r\n\t * Variable: ARROW_WIDTH\r\n\t * \r\n\t * Defines the width of the arrow shape. Default is 30.\r\n\t */\r\n\tARROW_WIDTH: 30,\r\n\r\n\t/**\r\n\t * Variable: ARROW_SIZE\r\n\t * \r\n\t * Defines the size of the arrowhead in the arrow shape. Default is 30.\r\n\t */\r\n\tARROW_SIZE: 30,\r\n\r\n\t/**\r\n\t * Variable: PAGE_FORMAT_A4_PORTRAIT\r\n\t * \r\n\t * Defines the rectangle for the A4 portrait page format. The dimensions\r\n\t * of this page format are 826x1169 pixels.\r\n\t */\r\n\tPAGE_FORMAT_A4_PORTRAIT: new mxRectangle(0, 0, 827, 1169),\r\n\r\n\t/**\r\n\t * Variable: PAGE_FORMAT_A4_PORTRAIT\r\n\t * \r\n\t * Defines the rectangle for the A4 portrait page format. The dimensions\r\n\t * of this page format are 826x1169 pixels.\r\n\t */\r\n\tPAGE_FORMAT_A4_LANDSCAPE: new mxRectangle(0, 0, 1169, 827),\r\n\r\n\t/**\r\n\t * Variable: PAGE_FORMAT_LETTER_PORTRAIT\r\n\t * \r\n\t * Defines the rectangle for the Letter portrait page format. The\r\n\t * dimensions of this page format are 850x1100 pixels.\r\n\t */\r\n\tPAGE_FORMAT_LETTER_PORTRAIT: new mxRectangle(0, 0, 850, 1100),\r\n\r\n\t/**\r\n\t * Variable: PAGE_FORMAT_LETTER_PORTRAIT\r\n\t * \r\n\t * Defines the rectangle for the Letter portrait page format. The dimensions\r\n\t * of this page format are 850x1100 pixels.\r\n\t */\r\n\tPAGE_FORMAT_LETTER_LANDSCAPE: new mxRectangle(0, 0, 1100, 850),\r\n\r\n\t/**\r\n\t * Variable: NONE\r\n\t * \r\n\t * Defines the value for none. Default is \"none\".\r\n\t */\r\n\tNONE: 'none',\r\n\r\n\t/**\r\n\t * Variable: STYLE_PERIMETER\r\n\t * \r\n\t * Defines the key for the perimeter style. This is a function that defines\r\n\t * the perimeter around a particular shape. Possible values are the\r\n\t * functions defined in <mxPerimeter>. Alternatively, the constants in this\r\n\t * class that start with \"PERIMETER_\" may be used to access\r\n\t * perimeter styles in <mxStyleRegistry>. Value is \"perimeter\".\r\n\t */\r\n\tSTYLE_PERIMETER: 'perimeter',\r\n\t\r\n\t/**\r\n\t * Variable: STYLE_SOURCE_PORT\r\n\t * \r\n\t * Defines the ID of the cell that should be used for computing the\r\n\t * perimeter point of the source for an edge. This allows for graphically\r\n\t * connecting to a cell while keeping the actual terminal of the edge.\r\n\t * Value is \"sourcePort\".\r\n\t */\r\n\tSTYLE_SOURCE_PORT: 'sourcePort',\r\n\t\r\n\t/**\r\n\t * Variable: STYLE_TARGET_PORT\r\n\t * \r\n\t * Defines the ID of the cell that should be used for computing the\r\n\t * perimeter point of the target for an edge. This allows for graphically\r\n\t * connecting to a cell while keeping the actual terminal of the edge.\r\n\t * Value is \"targetPort\".\r\n\t */\r\n\tSTYLE_TARGET_PORT: 'targetPort',\r\n\r\n\t/**\r\n\t * Variable: STYLE_PORT_CONSTRAINT\r\n\t * \r\n\t * Defines the direction(s) that edges are allowed to connect to cells in.\r\n\t * Possible values are \"DIRECTION_NORTH, DIRECTION_SOUTH, \r\n\t * DIRECTION_EAST\" and \"DIRECTION_WEST\". Value is\r\n\t * \"portConstraint\".\r\n\t */\r\n\tSTYLE_PORT_CONSTRAINT: 'portConstraint',\r\n\r\n\t/**\r\n\t * Variable: STYLE_PORT_CONSTRAINT_ROTATION\r\n\t * \r\n\t * Define whether port constraint directions are rotated with vertex\r\n\t * rotation. 0 (default) causes port constraints to remain absolute, \r\n\t * relative to the graph, 1 causes the constraints to rotate with\r\n\t * the vertex. Value is \"portConstraintRotation\".\r\n\t */\r\n\tSTYLE_PORT_CONSTRAINT_ROTATION: 'portConstraintRotation',\r\n\r\n\t/**\r\n\t * Variable: STYLE_SOURCE_PORT_CONSTRAINT\r\n\t * \r\n\t * Defines the direction(s) that edges are allowed to connect to sources in.\r\n\t * Possible values are \"DIRECTION_NORTH, DIRECTION_SOUTH, DIRECTION_EAST\"\r\n\t * and \"DIRECTION_WEST\". Value is \"sourcePortConstraint\".\r\n\t */\r\n\tSTYLE_SOURCE_PORT_CONSTRAINT: 'sourcePortConstraint',\r\n\r\n\t/**\r\n\t * Variable: STYLE_TARGET_PORT_CONSTRAINT\r\n\t * \r\n\t * Defines the direction(s) that edges are allowed to connect to targets in.\r\n\t * Possible values are \"DIRECTION_NORTH, DIRECTION_SOUTH, DIRECTION_EAST\"\r\n\t * and \"DIRECTION_WEST\". Value is \"targetPortConstraint\".\r\n\t */\r\n\tSTYLE_TARGET_PORT_CONSTRAINT: 'targetPortConstraint',\r\n\r\n\t/**\r\n\t * Variable: STYLE_OPACITY\r\n\t * \r\n\t * Defines the key for the opacity style. The type of the value is \r\n\t * numeric and the possible range is 0-100. Value is \"opacity\".\r\n\t */\r\n\tSTYLE_OPACITY: 'opacity',\r\n\r\n\t/**\r\n\t * Variable: STYLE_FILL_OPACITY\r\n\t * \r\n\t * Defines the key for the fill opacity style. The type of the value is \r\n\t * numeric and the possible range is 0-100. Value is \"fillOpacity\".\r\n\t */\r\n\tSTYLE_FILL_OPACITY: 'fillOpacity',\r\n\r\n\t/**\r\n\t * Variable: STYLE_STROKE_OPACITY\r\n\t * \r\n\t * Defines the key for the stroke opacity style. The type of the value is \r\n\t * numeric and the possible range is 0-100. Value is \"strokeOpacity\".\r\n\t */\r\n\tSTYLE_STROKE_OPACITY: 'strokeOpacity',\r\n\r\n\t/**\r\n\t * Variable: STYLE_TEXT_OPACITY\r\n\t * \r\n\t * Defines the key for the text opacity style. The type of the value is \r\n\t * numeric and the possible range is 0-100. Value is \"textOpacity\".\r\n\t */\r\n\tSTYLE_TEXT_OPACITY: 'textOpacity',\r\n\r\n\t/**\r\n\t * Variable: STYLE_TEXT_DIRECTION\r\n\t * \r\n\t * Defines the key for the text direction style. Possible values are\r\n\t * \"TEXT_DIRECTION_DEFAULT, TEXT_DIRECTION_AUTO, TEXT_DIRECTION_LTR\"\r\n\t * and \"TEXT_DIRECTION_RTL\". Value is \"textDirection\".\r\n\t * The default value for the style is defined in <DEFAULT_TEXT_DIRECTION>.\r\n\t * It is used is no value is defined for this key in a given style. This is\r\n\t * an experimental style that is currently ignored in the backends.\r\n\t */\r\n\tSTYLE_TEXT_DIRECTION: 'textDirection',\r\n\r\n\t/**\r\n\t * Variable: STYLE_OVERFLOW\r\n\t * \r\n\t * Defines the key for the overflow style. Possible values are 'visible',\r\n\t * 'hidden', 'fill' and 'width'. The default value is 'visible'. This value\r\n\t * specifies how overlapping vertex labels are handled. A value of\r\n\t * 'visible' will show the complete label. A value of 'hidden' will clip\r\n\t * the label so that it does not overlap the vertex bounds. A value of\r\n\t * 'fill' will use the vertex bounds and a value of 'width' will use the\r\n\t * the vertex width for the label. See <mxGraph.isLabelClipped>. Note that\r\n\t * the vertical alignment is ignored for overflow fill and for horizontal\r\n\t * alignment, left should be used to avoid pixel offsets in Internet Explorer\r\n\t * 11 and earlier or if foreignObjects are disabled. Value is \"overflow\".\r\n\t */\r\n\tSTYLE_OVERFLOW: 'overflow',\r\n\r\n\t/**\r\n\t * Variable: STYLE_ORTHOGONAL\r\n\t * \r\n\t * Defines if the connection points on either end of the edge should be\r\n\t * computed so that the edge is vertical or horizontal if possible and\r\n\t * if the point is not at a fixed location. Default is false. This is\r\n\t * used in <mxGraph.isOrthogonal>, which also returns true if the edgeStyle\r\n\t * of the edge is an elbow or entity. Value is \"orthogonal\".\r\n\t */\r\n\tSTYLE_ORTHOGONAL: 'orthogonal',\r\n\r\n\t/**\r\n\t * Variable: STYLE_EXIT_X\r\n\t * \r\n\t * Defines the key for the horizontal relative coordinate connection point\r\n\t * of an edge with its source terminal. Value is \"exitX\".\r\n\t */\r\n\tSTYLE_EXIT_X: 'exitX',\r\n\r\n\t/**\r\n\t * Variable: STYLE_EXIT_Y\r\n\t * \r\n\t * Defines the key for the vertical relative coordinate connection point\r\n\t * of an edge with its source terminal. Value is \"exitY\".\r\n\t */\r\n\tSTYLE_EXIT_Y: 'exitY',\r\n\r\n\t\r\n\t/**\r\n\t* Variable: STYLE_EXIT_DX\r\n\t* \r\n\t* Defines the key for the horizontal offset of the connection point\r\n\t* of an edge with its source terminal. Value is \"exitDx\".\r\n\t*/\r\n\tSTYLE_EXIT_DX: 'exitDx',\r\n\r\n\t/**\r\n\t* Variable: STYLE_EXIT_DY\r\n\t* \r\n\t* Defines the key for the vertical offset of the connection point\r\n\t* of an edge with its source terminal. Value is \"exitDy\".\r\n\t*/\r\n\tSTYLE_EXIT_DY: 'exitDy',\r\n\t\r\n\t/**\r\n\t * Variable: STYLE_EXIT_PERIMETER\r\n\t * \r\n\t * Defines if the perimeter should be used to find the exact entry point\r\n\t * along the perimeter of the source. Possible values are 0 (false) and\r\n\t * 1 (true). Default is 1 (true). Value is \"exitPerimeter\".\r\n\t */\r\n\tSTYLE_EXIT_PERIMETER: 'exitPerimeter',\r\n\r\n\t/**\r\n\t * Variable: STYLE_ENTRY_X\r\n\t * \r\n\t * Defines the key for the horizontal relative coordinate connection point\r\n\t * of an edge with its target terminal. Value is \"entryX\".\r\n\t */\r\n\tSTYLE_ENTRY_X: 'entryX',\r\n\r\n\t/**\r\n\t * Variable: STYLE_ENTRY_Y\r\n\t * \r\n\t * Defines the key for the vertical relative coordinate connection point\r\n\t * of an edge with its target terminal. Value is \"entryY\".\r\n\t */\r\n\tSTYLE_ENTRY_Y: 'entryY',\r\n\r\n\t/**\r\n\t * Variable: STYLE_ENTRY_DX\r\n\t * \r\n\t* Defines the key for the horizontal offset of the connection point\r\n\t* of an edge with its target terminal. Value is \"entryDx\".\r\n\t*/\r\n\tSTYLE_ENTRY_DX: 'entryDx',\r\n\r\n\t/**\r\n\t * Variable: STYLE_ENTRY_DY\r\n\t * \r\n\t* Defines the key for the vertical offset of the connection point\r\n\t* of an edge with its target terminal. Value is \"entryDy\".\r\n\t*/\r\n\tSTYLE_ENTRY_DY: 'entryDy',\r\n\r\n\t/**\r\n\t * Variable: STYLE_ENTRY_PERIMETER\r\n\t * \r\n\t * Defines if the perimeter should be used to find the exact entry point\r\n\t * along the perimeter of the target. Possible values are 0 (false) and\r\n\t * 1 (true). Default is 1 (true). Value is \"entryPerimeter\".\r\n\t */\r\n\tSTYLE_ENTRY_PERIMETER: 'entryPerimeter',\r\n\r\n\t/**\r\n\t * Variable: STYLE_WHITE_SPACE\r\n\t * \r\n\t * Defines the key for the white-space style. Possible values are 'nowrap'\r\n\t * and 'wrap'. The default value is 'nowrap'. This value specifies how\r\n\t * white-space inside a HTML vertex label should be handled. A value of\r\n\t * 'nowrap' means the text will never wrap to the next line until a\r\n\t * linefeed is encountered. A value of 'wrap' means text will wrap when\r\n\t * necessary. This style is only used for HTML labels.\r\n\t * See <mxGraph.isWrapping>. Value is \"whiteSpace\".\r\n\t */\r\n\tSTYLE_WHITE_SPACE: 'whiteSpace',\r\n\r\n\t/**\r\n\t * Variable: STYLE_ROTATION\r\n\t * \r\n\t * Defines the key for the rotation style. The type of the value is \r\n\t * numeric and the possible range is 0-360. Value is \"rotation\".\r\n\t */\r\n\tSTYLE_ROTATION: 'rotation',\r\n\r\n\t/**\r\n\t * Variable: STYLE_FILLCOLOR\r\n\t * \r\n\t * Defines the key for the fill color. Possible values are all HTML color\r\n\t * names or HEX codes, as well as special keywords such as 'swimlane,\r\n\t * 'inherit' or 'indicated' to use the color code of a related cell or the\r\n\t * indicator shape. Value is \"fillColor\".\r\n\t */\r\n\tSTYLE_FILLCOLOR: 'fillColor',\r\n\r\n\t/**\r\n\t * Variable: STYLE_POINTER_EVENTS\r\n\t * \r\n\t * Specifies if pointer events should be fired on transparent backgrounds.\r\n\t * This style is currently only supported in <mxRectangleShape>. Default\r\n\t * is true. Value is \"pointerEvents\". This is typically set to\r\n\t * false in groups where the transparent part should allow any underlying\r\n\t * cells to be clickable.\r\n\t */\r\n\tSTYLE_POINTER_EVENTS: 'pointerEvents',\r\n\r\n\t/**\r\n\t * Variable: STYLE_SWIMLANE_FILLCOLOR\r\n\t * \r\n\t * Defines the key for the fill color of the swimlane background. Possible\r\n\t * values are all HTML color names or HEX codes. Default is no background.\r\n\t * Value is \"swimlaneFillColor\".\r\n\t */\r\n\tSTYLE_SWIMLANE_FILLCOLOR: 'swimlaneFillColor',\r\n\r\n\t/**\r\n\t * Variable: STYLE_MARGIN\r\n\t * \r\n\t * Defines the key for the margin between the ellipses in the double ellipse shape.\r\n\t * Possible values are all positive numbers. Value is \"margin\".\r\n\t */\r\n\tSTYLE_MARGIN: 'margin',\r\n\r\n\t/**\r\n\t * Variable: STYLE_GRADIENTCOLOR\r\n\t * \r\n\t * Defines the key for the gradient color. Possible values are all HTML color\r\n\t * names or HEX codes, as well as special keywords such as 'swimlane,\r\n\t * 'inherit' or 'indicated' to use the color code of a related cell or the\r\n\t * indicator shape. This is ignored if no fill color is defined. Value is\r\n\t * \"gradientColor\".\r\n\t */\r\n\tSTYLE_GRADIENTCOLOR: 'gradientColor',\r\n\r\n\t/**\r\n\t * Variable: STYLE_GRADIENT_DIRECTION\r\n\t * \r\n\t * Defines the key for the gradient direction. Possible values are\r\n\t * <DIRECTION_EAST>, <DIRECTION_WEST>, <DIRECTION_NORTH> and\r\n\t * <DIRECTION_SOUTH>. Default is <DIRECTION_SOUTH>. Generally, and by\r\n\t * default in mxGraph, gradient painting is done from the value of\r\n\t * <STYLE_FILLCOLOR> to the value of <STYLE_GRADIENTCOLOR>. Taking the\r\n\t * example of <DIRECTION_NORTH>, this means <STYLE_FILLCOLOR> color at the \r\n\t * bottom of paint pattern and <STYLE_GRADIENTCOLOR> at top, with a\r\n\t * gradient in-between. Value is \"gradientDirection\".\r\n\t */\r\n\tSTYLE_GRADIENT_DIRECTION: 'gradientDirection',\r\n\r\n\t/**\r\n\t * Variable: STYLE_STROKECOLOR\r\n\t * \r\n\t * Defines the key for the strokeColor style. Possible values are all HTML\r\n\t * color names or HEX codes, as well as special keywords such as 'swimlane,\r\n\t * 'inherit', 'indicated' to use the color code of a related cell or the\r\n\t * indicator shape or 'none' for no color. Value is \"strokeColor\".\r\n\t */\r\n\tSTYLE_STROKECOLOR: 'strokeColor',\r\n\r\n\t/**\r\n\t * Variable: STYLE_SEPARATORCOLOR\r\n\t * \r\n\t * Defines the key for the separatorColor style. Possible values are all\r\n\t * HTML color names or HEX codes. This style is only used for\r\n\t * <SHAPE_SWIMLANE> shapes. Value is \"separatorColor\".\r\n\t */\r\n\tSTYLE_SEPARATORCOLOR: 'separatorColor',\r\n\r\n\t/**\r\n\t * Variable: STYLE_STROKEWIDTH\r\n\t * \r\n\t * Defines the key for the strokeWidth style. The type of the value is \r\n\t * numeric and the possible range is any non-negative value larger or equal\r\n\t * to 1. The value defines the stroke width in pixels. Note: To hide a\r\n\t * stroke use strokeColor none. Value is \"strokeWidth\".\r\n\t */\r\n\tSTYLE_STROKEWIDTH: 'strokeWidth',\r\n\r\n\t/**\r\n\t * Variable: STYLE_ALIGN\r\n\t * \r\n\t * Defines the key for the align style. Possible values are <ALIGN_LEFT>,\r\n\t * <ALIGN_CENTER> and <ALIGN_RIGHT>. This value defines how the lines of\r\n\t * the label are horizontally aligned. <ALIGN_LEFT> mean label text lines\r\n\t * are aligned to left of the label bounds, <ALIGN_RIGHT> to the right of\r\n\t * the label bounds and <ALIGN_CENTER> means the center of the text lines\r\n\t * are aligned in the center of the label bounds. Note this value doesn't\r\n\t * affect the positioning of the overall label bounds relative to the\r\n\t * vertex, to move the label bounds horizontally, use\r\n\t * <STYLE_LABEL_POSITION>. Value is \"align\".\r\n\t */\r\n\tSTYLE_ALIGN: 'align',\r\n\r\n\t/**\r\n\t * Variable: STYLE_VERTICAL_ALIGN\r\n\t * \r\n\t * Defines the key for the verticalAlign style. Possible values are\r\n\t * <ALIGN_TOP>, <ALIGN_MIDDLE> and <ALIGN_BOTTOM>. This value defines how\r\n\t * the lines of the label are vertically aligned. <ALIGN_TOP> means the\r\n\t * topmost label text line is aligned against the top of the label bounds,\r\n\t * <ALIGN_BOTTOM> means the bottom-most label text line is aligned against\r\n\t * the bottom of the label bounds and <ALIGN_MIDDLE> means there is equal\r\n\t * spacing between the topmost text label line and the top of the label\r\n\t * bounds and the bottom-most text label line and the bottom of the label\r\n\t * bounds. Note this value doesn't affect the positioning of the overall\r\n\t * label bounds relative to the vertex, to move the label bounds\r\n\t * vertically, use <STYLE_VERTICAL_LABEL_POSITION>. Value is \"verticalAlign\".\r\n\t */\r\n\tSTYLE_VERTICAL_ALIGN: 'verticalAlign',\r\n\r\n\t/**\r\n\t * Variable: STYLE_LABEL_WIDTH\r\n\t * \r\n\t * Defines the key for the width of the label if the label position is not\r\n\t * center. Value is \"labelWidth\".\r\n\t */\r\n\tSTYLE_LABEL_WIDTH: 'labelWidth',\r\n\r\n\t/**\r\n\t * Variable: STYLE_LABEL_POSITION\r\n\t * \r\n\t * Defines the key for the horizontal label position of vertices. Possible\r\n\t * values are <ALIGN_LEFT>, <ALIGN_CENTER> and <ALIGN_RIGHT>. Default is\r\n\t * <ALIGN_CENTER>. The label align defines the position of the label\r\n\t * relative to the cell. <ALIGN_LEFT> means the entire label bounds is\r\n\t * placed completely just to the left of the vertex, <ALIGN_RIGHT> means\r\n\t * adjust to the right and <ALIGN_CENTER> means the label bounds are\r\n\t * vertically aligned with the bounds of the vertex. Note this value\r\n\t * doesn't affect the positioning of label within the label bounds, to move\r\n\t * the label horizontally within the label bounds, use <STYLE_ALIGN>.\r\n\t * Value is \"labelPosition\".\r\n\t */\r\n\tSTYLE_LABEL_POSITION: 'labelPosition',\r\n\r\n\t/**\r\n\t * Variable: STYLE_VERTICAL_LABEL_POSITION\r\n\t * \r\n\t * Defines the key for the vertical label position of vertices. Possible\r\n\t * values are <ALIGN_TOP>, <ALIGN_BOTTOM> and <ALIGN_MIDDLE>. Default is\r\n\t * <ALIGN_MIDDLE>. The label align defines the position of the label\r\n\t * relative to the cell. <ALIGN_TOP> means the entire label bounds is\r\n\t * placed completely just on the top of the vertex, <ALIGN_BOTTOM> means\r\n\t * adjust on the bottom and <ALIGN_MIDDLE> means the label bounds are\r\n\t * horizontally aligned with the bounds of the vertex. Note this value\r\n\t * doesn't affect the positioning of label within the label bounds, to move\r\n\t * the label vertically within the label bounds, use\r\n\t * <STYLE_VERTICAL_ALIGN>. Value is \"verticalLabelPosition\".\r\n\t */\r\n\tSTYLE_VERTICAL_LABEL_POSITION: 'verticalLabelPosition',\r\n\t\r\n\t/**\r\n\t * Variable: STYLE_IMAGE_ASPECT\r\n\t * \r\n\t * Defines the key for the image aspect style. Possible values are 0 (do\r\n\t * not preserve aspect) or 1 (keep aspect). This is only used in\r\n\t * <mxImageShape>. Default is 1. Value is \"imageAspect\".\r\n\t */\r\n\tSTYLE_IMAGE_ASPECT: 'imageAspect',\r\n\r\n\t/**\r\n\t * Variable: STYLE_IMAGE_ALIGN\r\n\t * \r\n\t * Defines the key for the align style. Possible values are <ALIGN_LEFT>,\r\n\t * <ALIGN_CENTER> and <ALIGN_RIGHT>. The value defines how any image in the\r\n\t * vertex label is aligned horizontally within the label bounds of a\r\n\t * <SHAPE_LABEL> shape. Value is \"imageAlign\".\r\n\t */\r\n\tSTYLE_IMAGE_ALIGN: 'imageAlign',\r\n\r\n\t/**\r\n\t * Variable: STYLE_IMAGE_VERTICAL_ALIGN\r\n\t * \r\n\t * Defines the key for the verticalAlign style. Possible values are\r\n\t * <ALIGN_TOP>, <ALIGN_MIDDLE> and <ALIGN_BOTTOM>. The value defines how\r\n\t * any image in the vertex label is aligned vertically within the label\r\n\t * bounds of a <SHAPE_LABEL> shape. Value is \"imageVerticalAlign\".\r\n\t */\r\n\tSTYLE_IMAGE_VERTICAL_ALIGN: 'imageVerticalAlign',\r\n\r\n\t/**\r\n\t * Variable: STYLE_GLASS\r\n\t * \r\n\t * Defines the key for the glass style. Possible values are 0 (disabled) and\r\n\t * 1(enabled). The default value is 0. This is used in <mxLabel>. Value is\r\n\t * \"glass\".\r\n\t */\r\n\tSTYLE_GLASS: 'glass',\r\n\r\n\t/**\r\n\t * Variable: STYLE_IMAGE\r\n\t * \r\n\t * Defines the key for the image style. Possible values are any image URL,\r\n\t * the type of the value is String. This is the path to the image that is\r\n\t * to be displayed within the label of a vertex. Data URLs should use the\r\n\t * following format: data:image/png,xyz where xyz is the base64 encoded\r\n\t * data (without the \"base64\"-prefix). Note that Data URLs are only\r\n\t * supported in modern browsers. Value is \"image\".\r\n\t */\r\n\tSTYLE_IMAGE: 'image',\r\n\r\n\t/**\r\n\t * Variable: STYLE_IMAGE_WIDTH\r\n\t * \r\n\t * Defines the key for the imageWidth style. The type of this value is\r\n\t * int, the value is the image width in pixels and must be greater than 0.\r\n\t * Value is \"imageWidth\".\r\n\t */\r\n\tSTYLE_IMAGE_WIDTH: 'imageWidth',\r\n\r\n\t/**\r\n\t * Variable: STYLE_IMAGE_HEIGHT\r\n\t * \r\n\t * Defines the key for the imageHeight style. The type of this value is\r\n\t * int, the value is the image height in pixels and must be greater than 0.\r\n\t * Value is \"imageHeight\".\r\n\t */\r\n\tSTYLE_IMAGE_HEIGHT: 'imageHeight',\r\n\r\n\t/**\r\n\t * Variable: STYLE_IMAGE_BACKGROUND\r\n\t * \r\n\t * Defines the key for the image background color. This style is only used\r\n\t * in <mxImageShape>. Possible values are all HTML color names or HEX\r\n\t * codes. Value is \"imageBackground\".\r\n\t */\r\n\tSTYLE_IMAGE_BACKGROUND: 'imageBackground',\r\n\r\n\t/**\r\n\t * Variable: STYLE_IMAGE_BORDER\r\n\t * \r\n\t * Defines the key for the image border color. This style is only used in\r\n\t * <mxImageShape>. Possible values are all HTML color names or HEX codes.\r\n\t * Value is \"imageBorder\".\r\n\t */\r\n\tSTYLE_IMAGE_BORDER: 'imageBorder',\r\n\r\n\t/**\r\n\t * Variable: STYLE_FLIPH\r\n\t * \r\n\t * Defines the key for the horizontal image flip. This style is only used\r\n\t * in <mxImageShape>. Possible values are 0 and 1. Default is 0. Value is\r\n\t * \"flipH\".\r\n\t */\r\n\tSTYLE_FLIPH: 'flipH',\r\n\r\n\t/**\r\n\t * Variable: STYLE_FLIPV\r\n\t * \r\n\t * Defines the key for the vertical flip. Possible values are 0 and 1.\r\n\t * Default is 0. Value is \"flipV\".\r\n\t */\r\n\tSTYLE_FLIPV: 'flipV',\r\n\r\n\t/**\r\n\t * Variable: STYLE_NOLABEL\r\n\t * \r\n\t * Defines the key for the noLabel style. If this is true then no label is\r\n\t * visible for a given cell. Possible values are true or false (1 or 0).\r\n\t * Default is false. Value is \"noLabel\".\r\n\t */\r\n\tSTYLE_NOLABEL: 'noLabel',\r\n\r\n\t/**\r\n\t * Variable: STYLE_NOEDGESTYLE\r\n\t * \r\n\t * Defines the key for the noEdgeStyle style. If this is true then no edge\r\n\t * style is applied for a given edge. Possible values are true or false\r\n\t * (1 or 0). Default is false. Value is \"noEdgeStyle\".\r\n\t */\r\n\tSTYLE_NOEDGESTYLE: 'noEdgeStyle',\r\n\r\n\t/**\r\n\t * Variable: STYLE_LABEL_BACKGROUNDCOLOR\r\n\t * \r\n\t * Defines the key for the label background color. Possible values are all\r\n\t * HTML color names or HEX codes. Value is \"labelBackgroundColor\".\r\n\t */\r\n\tSTYLE_LABEL_BACKGROUNDCOLOR: 'labelBackgroundColor',\r\n\r\n\t/**\r\n\t * Variable: STYLE_LABEL_BORDERCOLOR\r\n\t * \r\n\t * Defines the key for the label border color. Possible values are all\r\n\t * HTML color names or HEX codes. Value is \"labelBorderColor\".\r\n\t */\r\n\tSTYLE_LABEL_BORDERCOLOR: 'labelBorderColor',\r\n\r\n\t/**\r\n\t * Variable: STYLE_LABEL_PADDING\r\n\t * \r\n\t * Defines the key for the label padding, ie. the space between the label\r\n\t * border and the label. Value is \"labelPadding\".\r\n\t */\r\n\tSTYLE_LABEL_PADDING: 'labelPadding',\r\n\r\n\t/**\r\n\t * Variable: STYLE_INDICATOR_SHAPE\r\n\t * \r\n\t * Defines the key for the indicator shape used within an <mxLabel>.\r\n\t * Possible values are all SHAPE_* constants or the names of any new\r\n\t * shapes. The indicatorShape has precedence over the indicatorImage.\r\n\t * Value is \"indicatorShape\".\r\n\t */\r\n\tSTYLE_INDICATOR_SHAPE: 'indicatorShape',\r\n\r\n\t/**\r\n\t * Variable: STYLE_INDICATOR_IMAGE\r\n\t * \r\n\t * Defines the key for the indicator image used within an <mxLabel>.\r\n\t * Possible values are all image URLs. The indicatorShape has\r\n\t * precedence over the indicatorImage. Value is \"indicatorImage\".\r\n\t */\r\n\tSTYLE_INDICATOR_IMAGE: 'indicatorImage',\r\n\r\n\t/**\r\n\t * Variable: STYLE_INDICATOR_COLOR\r\n\t * \r\n\t * Defines the key for the indicatorColor style. Possible values are all\r\n\t * HTML color names or HEX codes, as well as the special 'swimlane' keyword\r\n\t * to refer to the color of the parent swimlane if one exists. Value is\r\n\t * \"indicatorColor\".\r\n\t */\r\n\tSTYLE_INDICATOR_COLOR: 'indicatorColor',\r\n\r\n\t/**\r\n\t * Variable: STYLE_INDICATOR_STROKECOLOR\r\n\t * \r\n\t * Defines the key for the indicator stroke color in <mxLabel>.\r\n\t * Possible values are all color codes. Value is \"indicatorStrokeColor\".\r\n\t */\r\n\tSTYLE_INDICATOR_STROKECOLOR: 'indicatorStrokeColor',\r\n\r\n\t/**\r\n\t * Variable: STYLE_INDICATOR_GRADIENTCOLOR\r\n\t * \r\n\t * Defines the key for the indicatorGradientColor style. Possible values\r\n\t * are all HTML color names or HEX codes. This style is only supported in\r\n\t * <SHAPE_LABEL> shapes. Value is \"indicatorGradientColor\".\r\n\t */\r\n\tSTYLE_INDICATOR_GRADIENTCOLOR: 'indicatorGradientColor',\r\n\r\n\t/**\r\n\t * Variable: STYLE_INDICATOR_SPACING\r\n\t * \r\n\t * The defines the key for the spacing between the label and the\r\n\t * indicator in <mxLabel>. Possible values are in pixels. Value is\r\n\t * \"indicatorSpacing\".\r\n\t */\r\n\tSTYLE_INDICATOR_SPACING: 'indicatorSpacing',\r\n\r\n\t/**\r\n\t * Variable: STYLE_INDICATOR_WIDTH\r\n\t * \r\n\t * Defines the key for the indicator width. Possible values start at 0 (in\r\n\t * pixels). Value is \"indicatorWidth\".\r\n\t */\r\n\tSTYLE_INDICATOR_WIDTH: 'indicatorWidth',\r\n\r\n\t/**\r\n\t * Variable: STYLE_INDICATOR_HEIGHT\r\n\t * \r\n\t * Defines the key for the indicator height. Possible values start at 0 (in\r\n\t * pixels). Value is \"indicatorHeight\".\r\n\t */\r\n\tSTYLE_INDICATOR_HEIGHT: 'indicatorHeight',\r\n\r\n\t/**\r\n\t * Variable: STYLE_INDICATOR_DIRECTION\r\n\t * \r\n\t * Defines the key for the indicatorDirection style. The direction style is\r\n\t * used to specify the direction of certain shapes (eg. <mxTriangle>).\r\n\t * Possible values are <DIRECTION_EAST> (default), <DIRECTION_WEST>,\r\n\t * <DIRECTION_NORTH> and <DIRECTION_SOUTH>. Value is \"indicatorDirection\".\r\n\t */\r\n\tSTYLE_INDICATOR_DIRECTION: 'indicatorDirection',\r\n\r\n\t/**\r\n\t * Variable: STYLE_SHADOW\r\n\t * \r\n\t * Defines the key for the shadow style. The type of the value is Boolean.\r\n\t * Value is \"shadow\".\r\n\t */\r\n\tSTYLE_SHADOW: 'shadow',\r\n\t\r\n\t/**\r\n\t * Variable: STYLE_SEGMENT\r\n\t * \r\n\t * Defines the key for the segment style. The type of this value is float\r\n\t * and the value represents the size of the horizontal segment of the\r\n\t * entity relation style. Default is ENTITY_SEGMENT. Value is \"segment\".\r\n\t */\r\n\tSTYLE_SEGMENT: 'segment',\r\n\t\r\n\t/**\r\n\t * Variable: STYLE_ENDARROW\r\n\t *\r\n\t * Defines the key for the end arrow marker. Possible values are all\r\n\t * constants with an ARROW-prefix. This is only used in <mxConnector>.\r\n\t * Value is \"endArrow\".\r\n\t *\r\n\t * Example:\r\n\t * (code)\r\n\t * style[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC;\r\n\t * (end)\r\n\t */\r\n\tSTYLE_ENDARROW: 'endArrow',\r\n\r\n\t/**\r\n\t * Variable: STYLE_STARTARROW\r\n\t * \r\n\t * Defines the key for the start arrow marker. Possible values are all\r\n\t * constants with an ARROW-prefix. This is only used in <mxConnector>.\r\n\t * See <STYLE_ENDARROW>. Value is \"startArrow\".\r\n\t */\r\n\tSTYLE_STARTARROW: 'startArrow',\r\n\r\n\t/**\r\n\t * Variable: STYLE_ENDSIZE\r\n\t * \r\n\t * Defines the key for the endSize style. The type of this value is numeric\r\n\t * and the value represents the size of the end marker in pixels. Value is\r\n\t * \"endSize\".\r\n\t */\r\n\tSTYLE_ENDSIZE: 'endSize',\r\n\r\n\t/**\r\n\t * Variable: STYLE_STARTSIZE\r\n\t * \r\n\t * Defines the key for the startSize style. The type of this value is\r\n\t * numeric and the value represents the size of the start marker or the\r\n\t * size of the swimlane title region depending on the shape it is used for.\r\n\t * Value is \"startSize\".\r\n\t */\r\n\tSTYLE_STARTSIZE: 'startSize',\r\n\r\n\t/**\r\n\t * Variable: STYLE_SWIMLANE_LINE\r\n\t * \r\n\t * Defines the key for the swimlaneLine style. This style specifies whether\r\n\t * the line between the title regio of a swimlane should be visible. Use 0\r\n\t * for hidden or 1 (default) for visible. Value is \"swimlaneLine\".\r\n\t */\r\n\tSTYLE_SWIMLANE_LINE: 'swimlaneLine',\r\n\r\n\t/**\r\n\t * Variable: STYLE_ENDFILL\r\n\t * \r\n\t * Defines the key for the endFill style. Use 0 for no fill or 1 (default)\r\n\t * for fill. (This style is only exported via <mxImageExport>.) Value is\r\n\t * \"endFill\".\r\n\t */\r\n\tSTYLE_ENDFILL: 'endFill',\r\n\r\n\t/**\r\n\t * Variable: STYLE_STARTFILL\r\n\t * \r\n\t * Defines the key for the startFill style. Use 0 for no fill or 1 (default)\r\n\t * for fill. (This style is only exported via <mxImageExport>.) Value is\r\n\t * \"startFill\".\r\n\t */\r\n\tSTYLE_STARTFILL: 'startFill',\r\n\r\n\t/**\r\n\t * Variable: STYLE_DASHED\r\n\t * \r\n\t * Defines the key for the dashed style. Use 0 (default) for non-dashed or 1\r\n\t * for dashed. Value is \"dashed\".\r\n\t */\r\n\tSTYLE_DASHED: 'dashed',\r\n\r\n\t/**\r\n\t * Defines the key for the dashed pattern style in SVG and image exports.\r\n\t * The type of this value is a space separated list of numbers that specify\r\n\t * a custom-defined dash pattern. Dash styles are defined in terms of the\r\n\t * length of the dash (the drawn part of the stroke) and the length of the\r\n\t * space between the dashes. The lengths are relative to the line width: a\r\n\t * length of \"1\" is equal to the line width. VML ignores this style and\r\n\t * uses dashStyle instead as defined in the VML specification. This style\r\n\t * is only used in the <mxConnector> shape. Value is \"dashPattern\".\r\n\t */\r\n\tSTYLE_DASH_PATTERN: 'dashPattern',\r\n\r\n\t/**\r\n\t * Variable: STYLE_FIX_DASH\r\n\t * \r\n\t * Defines the key for the fixDash style. Use 0 (default) for dash patterns\r\n\t * that depend on the linewidth and 1 for dash patterns that ignore the\r\n\t * line width. Value is \"fixDash\".\r\n\t */\r\n\tSTYLE_FIX_DASH: 'fixDash',\r\n\r\n\t/**\r\n\t * Variable: STYLE_ROUNDED\r\n\t * \r\n\t * Defines the key for the rounded style. The type of this value is\r\n\t * Boolean. For edges this determines whether or not joins between edges\r\n\t * segments are smoothed to a rounded finish. For vertices that have the\r\n\t * rectangle shape, this determines whether or not the rectangle is\r\n\t * rounded. Use 0 (default) for non-rounded or 1 for rounded. Value is\r\n\t * \"rounded\".\r\n\t */\r\n\tSTYLE_ROUNDED: 'rounded',\r\n\r\n\t/**\r\n\t * Variable: STYLE_CURVED\r\n\t * \r\n\t * Defines the key for the curved style. The type of this value is\r\n\t * Boolean. It is only applicable for connector shapes. Use 0 (default)\r\n\t * for non-curved or 1 for curved. Value is \"curved\".\r\n\t */\r\n\tSTYLE_CURVED: 'curved',\r\n\r\n\t/**\r\n\t * Variable: STYLE_ARCSIZE\r\n\t * \r\n\t * Defines the rounding factor for a rounded rectangle in percent (without\r\n\t * the percent sign). Possible values are between 0 and 100. If this value\r\n\t * is not specified then RECTANGLE_ROUNDING_FACTOR * 100 is used. For\r\n\t * edges, this defines the absolute size of rounded corners in pixels. If\r\n\t * this values is not specified then LINE_ARCSIZE is used.\r\n\t * (This style is only exported via <mxImageExport>.) Value is \"arcSize\".\r\n\t */\r\n\tSTYLE_ARCSIZE: 'arcSize',\r\n\r\n\t/**\r\n\t * Variable: STYLE_ABSOLUTE_ARCSIZE\r\n\t * \r\n\t * Defines the key for the absolute arc size style. This specifies if\r\n\t * arcSize for rectangles is abolute or relative. Possible values are 1\r\n\t * and 0 (default). Value is \"absoluteArcSize\".\r\n\t */\r\n\tSTYLE_ABSOLUTE_ARCSIZE: 'absoluteArcSize',\r\n\r\n\t/**\r\n\t * Variable: STYLE_SOURCE_PERIMETER_SPACING\r\n\t * \r\n\t * Defines the key for the source perimeter spacing. The type of this value\r\n\t * is numeric. This is the distance between the source connection point of\r\n\t * an edge and the perimeter of the source vertex in pixels. This style\r\n\t * only applies to edges. Value is \"sourcePerimeterSpacing\".\r\n\t */\r\n\tSTYLE_SOURCE_PERIMETER_SPACING: 'sourcePerimeterSpacing',\r\n\r\n\t/**\r\n\t * Variable: STYLE_TARGET_PERIMETER_SPACING\r\n\t * \r\n\t * Defines the key for the target perimeter spacing. The type of this value\r\n\t * is numeric. This is the distance between the target connection point of\r\n\t * an edge and the perimeter of the target vertex in pixels. This style\r\n\t * only applies to edges. Value is \"targetPerimeterSpacing\".\r\n\t */\r\n\tSTYLE_TARGET_PERIMETER_SPACING: 'targetPerimeterSpacing',\r\n\r\n\t/**\r\n\t * Variable: STYLE_PERIMETER_SPACING\r\n\t * \r\n\t * Defines the key for the perimeter spacing. This is the distance between\r\n\t * the connection point and the perimeter in pixels. When used in a vertex\r\n\t * style, this applies to all incoming edges to floating ports (edges that\r\n\t * terminate on the perimeter of the vertex). When used in an edge style,\r\n\t * this spacing applies to the source and target separately, if they\r\n\t * terminate in floating ports (on the perimeter of the vertex). Value is\r\n\t * \"perimeterSpacing\".\r\n\t */\r\n\tSTYLE_PERIMETER_SPACING: 'perimeterSpacing',\r\n\r\n\t/**\r\n\t * Variable: STYLE_SPACING\r\n\t * \r\n\t * Defines the key for the spacing. The value represents the spacing, in\r\n\t * pixels, added to each side of a label in a vertex (style applies to\r\n\t * vertices only). Value is \"spacing\".\r\n\t */\r\n\tSTYLE_SPACING: 'spacing',\r\n\r\n\t/**\r\n\t * Variable: STYLE_SPACING_TOP\r\n\t * \r\n\t * Defines the key for the spacingTop style. The value represents the\r\n\t * spacing, in pixels, added to the top side of a label in a vertex (style\r\n\t * applies to vertices only). Value is \"spacingTop\".\r\n\t */\r\n\tSTYLE_SPACING_TOP: 'spacingTop',\r\n\r\n\t/**\r\n\t * Variable: STYLE_SPACING_LEFT\r\n\t * \r\n\t * Defines the key for the spacingLeft style. The value represents the\r\n\t * spacing, in pixels, added to the left side of a label in a vertex (style\r\n\t * applies to vertices only). Value is \"spacingLeft\".\r\n\t */\r\n\tSTYLE_SPACING_LEFT: 'spacingLeft',\r\n\r\n\t/**\r\n\t * Variable: STYLE_SPACING_BOTTOM\r\n\t * \r\n\t * Defines the key for the spacingBottom style The value represents the\r\n\t * spacing, in pixels, added to the bottom side of a label in a vertex\r\n\t * (style applies to vertices only). Value is \"spacingBottom\".\r\n\t */\r\n\tSTYLE_SPACING_BOTTOM: 'spacingBottom',\r\n\r\n\t/**\r\n\t * Variable: STYLE_SPACING_RIGHT\r\n\t * \r\n\t * Defines the key for the spacingRight style The value represents the\r\n\t * spacing, in pixels, added to the right side of a label in a vertex (style\r\n\t * applies to vertices only). Value is \"spacingRight\".\r\n\t */\r\n\tSTYLE_SPACING_RIGHT: 'spacingRight',\r\n\r\n\t/**\r\n\t * Variable: STYLE_HORIZONTAL\r\n\t * \r\n\t * Defines the key for the horizontal style. Possible values are\r\n\t * true or false. This value only applies to vertices. If the <STYLE_SHAPE>\r\n\t * is \"SHAPE_SWIMLANE\" a value of false indicates that the\r\n\t * swimlane should be drawn vertically, true indicates to draw it\r\n\t * horizontally. If the shape style does not indicate that this vertex is a\r\n\t * swimlane, this value affects only whether the label is drawn\r\n\t * horizontally or vertically. Value is \"horizontal\".\r\n\t */\r\n\tSTYLE_HORIZONTAL: 'horizontal',\r\n\r\n\t/**\r\n\t * Variable: STYLE_DIRECTION\r\n\t * \r\n\t * Defines the key for the direction style. The direction style is used\r\n\t * to specify the direction of certain shapes (eg. <mxTriangle>).\r\n\t * Possible values are <DIRECTION_EAST> (default), <DIRECTION_WEST>,\r\n\t * <DIRECTION_NORTH> and <DIRECTION_SOUTH>. Value is \"direction\".\r\n\t */\r\n\tSTYLE_DIRECTION: 'direction',\r\n\r\n\t/**\r\n\t * Variable: STYLE_ANCHOR_POINT_DIRECTION\r\n\t * \r\n\t * Defines the key for the anchorPointDirection style. The defines if the\r\n\t * direction style should be taken into account when computing the fixed\r\n\t * point location for connected edges. Default is 1 (yes). Set this to 0\r\n\t * to ignore the direction style for fixed connection points. Value is\r\n\t * \"anchorPointDirection\".\r\n\t */\r\n\tSTYLE_ANCHOR_POINT_DIRECTION: 'anchorPointDirection',\r\n\r\n\t/**\r\n\t * Variable: STYLE_ELBOW\r\n\t * \r\n\t * Defines the key for the elbow style. Possible values are\r\n\t * <ELBOW_HORIZONTAL> and <ELBOW_VERTICAL>. Default is <ELBOW_HORIZONTAL>.\r\n\t * This defines how the three segment orthogonal edge style leaves its\r\n\t * terminal vertices. The vertical style leaves the terminal vertices at\r\n\t * the top and bottom sides. Value is \"elbow\".\r\n\t */\r\n\tSTYLE_ELBOW: 'elbow',\r\n\r\n\t/**\r\n\t * Variable: STYLE_FONTCOLOR\r\n\t * \r\n\t * Defines the key for the fontColor style. Possible values are all HTML\r\n\t * color names or HEX codes. Value is \"fontColor\".\r\n\t */\r\n\tSTYLE_FONTCOLOR: 'fontColor',\r\n\r\n\t/**\r\n\t * Variable: STYLE_FONTFAMILY\r\n\t * \r\n\t * Defines the key for the fontFamily style. Possible values are names such\r\n\t * as Arial; Dialog; Verdana; Times New Roman. The value is of type String.\r\n\t * Value is fontFamily.\r\n\t */\r\n\tSTYLE_FONTFAMILY: 'fontFamily',\r\n\r\n\t/**\r\n\t * Variable: STYLE_FONTSIZE\r\n\t * \r\n\t * Defines the key for the fontSize style (in px). The type of the value\r\n\t * is int. Value is \"fontSize\".\r\n\t */\r\n\tSTYLE_FONTSIZE: 'fontSize',\r\n\r\n\t/**\r\n\t * Variable: STYLE_FONTSTYLE\r\n\t * \r\n\t * Defines the key for the fontStyle style. Values may be any logical AND\r\n\t * (sum) of <FONT_BOLD>, <FONT_ITALIC> and <FONT_UNDERLINE>.\r\n\t * The type of the value is int. Value is \"fontStyle\".\r\n\t */\r\n\tSTYLE_FONTSTYLE: 'fontStyle',\r\n\t\r\n\t/**\r\n\t * Variable: STYLE_ASPECT\r\n\t * \r\n\t * Defines the key for the aspect style. Possible values are empty or fixed.\r\n\t * If fixed is used then the aspect ratio of the cell will be maintained\r\n\t * when resizing. Default is empty. Value is \"aspect\".\r\n\t */\r\n\tSTYLE_ASPECT: 'aspect',\r\n\r\n\t/**\r\n\t * Variable: STYLE_AUTOSIZE\r\n\t * \r\n\t * Defines the key for the autosize style. This specifies if a cell should be\r\n\t * resized automatically if the value has changed. Possible values are 0 or 1.\r\n\t * Default is 0. See <mxGraph.isAutoSizeCell>. This is normally combined with\r\n\t * <STYLE_RESIZABLE> to disable manual sizing. Value is \"autosize\".\r\n\t */\r\n\tSTYLE_AUTOSIZE: 'autosize',\r\n\r\n\t/**\r\n\t * Variable: STYLE_FOLDABLE\r\n\t * \r\n\t * Defines the key for the foldable style. This specifies if a cell is foldable\r\n\t * using a folding icon. Possible values are 0 or 1. Default is 1. See\r\n\t * <mxGraph.isCellFoldable>. Value is \"foldable\".\r\n\t */\r\n\tSTYLE_FOLDABLE: 'foldable',\r\n\r\n\t/**\r\n\t * Variable: STYLE_EDITABLE\r\n\t * \r\n\t * Defines the key for the editable style. This specifies if the value of\r\n\t * a cell can be edited using the in-place editor. Possible values are 0 or\r\n\t * 1. Default is 1. See <mxGraph.isCellEditable>. Value is \"editable\".\r\n\t */\r\n\tSTYLE_EDITABLE: 'editable',\r\n\r\n\t/**\r\n\t * Variable: STYLE_BACKGROUND_OUTLINE\r\n\t * \r\n\t * Defines the key for the backgroundOutline style. This specifies if a\r\n\t * only the background of a cell should be painted when it is highlighted.\r\n\t * Possible values are 0 or 1. Default is 0. Value is \"backgroundOutline\".\r\n\t */\r\n\tSTYLE_BACKGROUND_OUTLINE: 'backgroundOutline',\r\n\r\n\t/**\r\n\t * Variable: STYLE_BENDABLE\r\n\t * \r\n\t * Defines the key for the bendable style. This specifies if the control\r\n\t * points of an edge can be moved. Possible values are 0 or 1. Default is\r\n\t * 1. See <mxGraph.isCellBendable>. Value is \"bendable\".\r\n\t */\r\n\tSTYLE_BENDABLE: 'bendable',\r\n\r\n\t/**\r\n\t * Variable: STYLE_MOVABLE\r\n\t * \r\n\t * Defines the key for the movable style. This specifies if a cell can\r\n\t * be moved. Possible values are 0 or 1. Default is 1. See\r\n\t * <mxGraph.isCellMovable>. Value is \"movable\".\r\n\t */\r\n\tSTYLE_MOVABLE: 'movable',\r\n\r\n\t/**\r\n\t * Variable: STYLE_RESIZABLE\r\n\t * \r\n\t * Defines the key for the resizable style. This specifies if a cell can\r\n\t * be resized. Possible values are 0 or 1. Default is 1. See\r\n\t * <mxGraph.isCellResizable>. Value is \"resizable\".\r\n\t */\r\n\tSTYLE_RESIZABLE: 'resizable',\r\n\r\n\t/**\r\n\t * Variable: STYLE_RESIZE_WIDTH\r\n\t * \r\n\t * Defines the key for the resizeWidth style. This specifies if a cell's\r\n\t * width is resized if the parent is resized. If this is 1 then the width\r\n\t * will be resized even if the cell's geometry is relative. If this is 0\r\n\t * then the cell's width will not be resized. Default is not defined. Value\r\n\t * is \"resizeWidth\".\r\n\t */\r\n\tSTYLE_RESIZE_WIDTH: 'resizeWidth',\r\n\r\n\t/**\r\n\t * Variable: STYLE_RESIZE_WIDTH\r\n\t * \r\n\t * Defines the key for the resizeHeight style. This specifies if a cell's\r\n\t * height if resize if the parent is resized. If this is 1 then the height\r\n\t * will be resized even if the cell's geometry is relative. If this is 0\r\n\t * then the cell's height will not be resized. Default is not defined. Value\r\n\t * is \"resizeHeight\".\r\n\t */\r\n\tSTYLE_RESIZE_HEIGHT: 'resizeHeight',\r\n\r\n\t/**\r\n\t * Variable: STYLE_ROTATABLE\r\n\t * \r\n\t * Defines the key for the rotatable style. This specifies if a cell can\r\n\t * be rotated. Possible values are 0 or 1. Default is 1. See\r\n\t * <mxGraph.isCellRotatable>. Value is \"rotatable\".\r\n\t */\r\n\tSTYLE_ROTATABLE: 'rotatable',\r\n\r\n\t/**\r\n\t * Variable: STYLE_CLONEABLE\r\n\t * \r\n\t * Defines the key for the cloneable style. This specifies if a cell can\r\n\t * be cloned. Possible values are 0 or 1. Default is 1. See\r\n\t * <mxGraph.isCellCloneable>. Value is \"cloneable\".\r\n\t */\r\n\tSTYLE_CLONEABLE: 'cloneable',\r\n\r\n\t/**\r\n\t * Variable: STYLE_DELETABLE\r\n\t * \r\n\t * Defines the key for the deletable style. This specifies if a cell can be\r\n\t * deleted. Possible values are 0 or 1. Default is 1. See\r\n\t * <mxGraph.isCellDeletable>. Value is \"deletable\".\r\n\t */\r\n\tSTYLE_DELETABLE: 'deletable',\r\n\r\n\t/**\r\n\t * Variable: STYLE_SHAPE\r\n\t * \r\n\t * Defines the key for the shape. Possible values are all constants with\r\n\t * a SHAPE-prefix or any newly defined shape names. Value is \"shape\".\r\n\t */\r\n\tSTYLE_SHAPE: 'shape',\r\n\r\n\t/**\r\n\t * Variable: STYLE_EDGE\r\n\t * \r\n\t * Defines the key for the edge style. Possible values are the functions\r\n\t * defined in <mxEdgeStyle>. Value is \"edgeStyle\".\r\n\t */\r\n\tSTYLE_EDGE: 'edgeStyle',\r\n\r\n\t/**\r\n\t * Variable: STYLE_JETTY_SIZE\r\n\t * \r\n\t * Defines the key for the jetty size in <mxEdgeStyle.OrthConnector>.\r\n\t * Default is 10. Possible values are all numeric values or \"auto\".\r\n\t * Jetty size is the minimum length of the orthogonal segment before\r\n\t * it attaches to a shape.\r\n\t * Value is \"jettySize\".\r\n\t */\r\n\tSTYLE_JETTY_SIZE: 'jettySize',\r\n\r\n\t/**\r\n\t * Variable: STYLE_SOURCE_JETTY_SIZE\r\n\t * \r\n\t * Defines the key for the jetty size in <mxEdgeStyle.OrthConnector>.\r\n\t * Default is 10. Possible values are numeric values or \"auto\". This has\r\n\t * precedence over <STYLE_JETTY_SIZE>. Value is \"sourceJettySize\".\r\n\t */\r\n\tSTYLE_SOURCE_JETTY_SIZE: 'sourceJettySize',\r\n\r\n\t/**\r\n\t * Variable: targetJettySize\r\n\t * \r\n\t * Defines the key for the jetty size in <mxEdgeStyle.OrthConnector>.\r\n\t * Default is 10. Possible values are numeric values or \"auto\". This has\r\n\t * precedence over <STYLE_JETTY_SIZE>. Value is \"targetJettySize\".\r\n\t */\r\n\tSTYLE_TARGET_JETTY_SIZE: 'targetJettySize',\r\n\r\n\t/**\r\n\t * Variable: STYLE_LOOP\r\n\t * \r\n\t * Defines the key for the loop style. Possible values are the functions\r\n\t * defined in <mxEdgeStyle>. Value is \"loopStyle\". Default is\r\n\t * <mxGraph.defaultLoopStylean>.\r\n\t */\r\n\tSTYLE_LOOP: 'loopStyle',\r\n\r\n\t/**\r\n\t * Variable: STYLE_ORTHOGONAL_LOOP\r\n\t * \r\n\t * Defines the key for the orthogonal loop style. Possible values are 0 and\r\n\t * 1. Default is 0. Value is \"orthogonalLoop\". Use this style to specify\r\n\t * if loops with no waypoints and defined anchor points should be routed\r\n\t * using <STYLE_LOOP> or not routed.\r\n\t */\r\n\tSTYLE_ORTHOGONAL_LOOP: 'orthogonalLoop',\r\n\r\n\t/**\r\n\t * Variable: STYLE_ROUTING_CENTER_X\r\n\t * \r\n\t * Defines the key for the horizontal routing center. Possible values are\r\n\t * between -0.5 and 0.5. This is the relative offset from the center used\r\n\t * for connecting edges. The type of this value is numeric. Value is\r\n\t * \"routingCenterX\".\r\n\t */\r\n\tSTYLE_ROUTING_CENTER_X: 'routingCenterX',\r\n\r\n\t/**\r\n\t * Variable: STYLE_ROUTING_CENTER_Y\r\n\t * \r\n\t * Defines the key for the vertical routing center. Possible values are\r\n\t * between -0.5 and 0.5. This is the relative offset from the center used\r\n\t * for connecting edges. The type of this value is numeric. Value is\r\n\t * \"routingCenterY\".\r\n\t */\r\n\tSTYLE_ROUTING_CENTER_Y: 'routingCenterY',\r\n\r\n\t/**\r\n\t * Variable: FONT_BOLD\r\n\t * \r\n\t * Constant for bold fonts. Default is 1.\r\n\t */\r\n\tFONT_BOLD: 1,\r\n\r\n\t/**\r\n\t * Variable: FONT_ITALIC\r\n\t * \r\n\t * Constant for italic fonts. Default is 2.\r\n\t */\r\n\tFONT_ITALIC: 2,\r\n\r\n\t/**\r\n\t * Variable: FONT_UNDERLINE\r\n\t * \r\n\t * Constant for underlined fonts. Default is 4.\r\n\t */\r\n\tFONT_UNDERLINE: 4,\r\n\r\n\t/**\r\n\t * Variable: SHAPE_RECTANGLE\r\n\t * \r\n\t * Name under which <mxRectangleShape> is registered in <mxCellRenderer>.\r\n\t * Default is rectangle.\r\n\t */\r\n\tSHAPE_RECTANGLE: 'rectangle',\r\n\r\n\t/**\r\n\t * Variable: SHAPE_ELLIPSE\r\n\t * \r\n\t * Name under which <mxEllipse> is registered in <mxCellRenderer>.\r\n\t * Default is ellipse.\r\n\t */\r\n\tSHAPE_ELLIPSE: 'ellipse',\r\n\r\n\t/**\r\n\t * Variable: SHAPE_DOUBLE_ELLIPSE\r\n\t * \r\n\t * Name under which <mxDoubleEllipse> is registered in <mxCellRenderer>.\r\n\t * Default is doubleEllipse.\r\n\t */\r\n\tSHAPE_DOUBLE_ELLIPSE: 'doubleEllipse',\r\n\r\n\t/**\r\n\t * Variable: SHAPE_RHOMBUS\r\n\t * \r\n\t * Name under which <mxRhombus> is registered in <mxCellRenderer>.\r\n\t * Default is rhombus.\r\n\t */\r\n\tSHAPE_RHOMBUS: 'rhombus',\r\n\r\n\t/**\r\n\t * Variable: SHAPE_LINE\r\n\t * \r\n\t * Name under which <mxLine> is registered in <mxCellRenderer>.\r\n\t * Default is line.\r\n\t */\r\n\tSHAPE_LINE: 'line',\r\n\r\n\t/**\r\n\t * Variable: SHAPE_IMAGE\r\n\t * \r\n\t * Name under which <mxImageShape> is registered in <mxCellRenderer>.\r\n\t * Default is image.\r\n\t */\r\n\tSHAPE_IMAGE: 'image',\r\n\t\r\n\t/**\r\n\t * Variable: SHAPE_ARROW\r\n\t * \r\n\t * Name under which <mxArrow> is registered in <mxCellRenderer>.\r\n\t * Default is arrow.\r\n\t */\r\n\tSHAPE_ARROW: 'arrow',\r\n\t\r\n\t/**\r\n\t * Variable: SHAPE_ARROW_CONNECTOR\r\n\t * \r\n\t * Name under which <mxArrowConnector> is registered in <mxCellRenderer>.\r\n\t * Default is arrowConnector.\r\n\t */\r\n\tSHAPE_ARROW_CONNECTOR: 'arrowConnector',\r\n\t\r\n\t/**\r\n\t * Variable: SHAPE_LABEL\r\n\t * \r\n\t * Name under which <mxLabel> is registered in <mxCellRenderer>.\r\n\t * Default is label.\r\n\t */\r\n\tSHAPE_LABEL: 'label',\r\n\t\r\n\t/**\r\n\t * Variable: SHAPE_CYLINDER\r\n\t * \r\n\t * Name under which <mxCylinder> is registered in <mxCellRenderer>.\r\n\t * Default is cylinder.\r\n\t */\r\n\tSHAPE_CYLINDER: 'cylinder',\r\n\t\r\n\t/**\r\n\t * Variable: SHAPE_SWIMLANE\r\n\t * \r\n\t * Name under which <mxSwimlane> is registered in <mxCellRenderer>.\r\n\t * Default is swimlane.\r\n\t */\r\n\tSHAPE_SWIMLANE: 'swimlane',\r\n\t\t\r\n\t/**\r\n\t * Variable: SHAPE_CONNECTOR\r\n\t * \r\n\t * Name under which <mxConnector> is registered in <mxCellRenderer>.\r\n\t * Default is connector.\r\n\t */\r\n\tSHAPE_CONNECTOR: 'connector',\r\n\r\n\t/**\r\n\t * Variable: SHAPE_ACTOR\r\n\t * \r\n\t * Name under which <mxActor> is registered in <mxCellRenderer>.\r\n\t * Default is actor.\r\n\t */\r\n\tSHAPE_ACTOR: 'actor',\r\n\t\t\r\n\t/**\r\n\t * Variable: SHAPE_CLOUD\r\n\t * \r\n\t * Name under which <mxCloud> is registered in <mxCellRenderer>.\r\n\t * Default is cloud.\r\n\t */\r\n\tSHAPE_CLOUD: 'cloud',\r\n\t\t\r\n\t/**\r\n\t * Variable: SHAPE_TRIANGLE\r\n\t * \r\n\t * Name under which <mxTriangle> is registered in <mxCellRenderer>.\r\n\t * Default is triangle.\r\n\t */\r\n\tSHAPE_TRIANGLE: 'triangle',\r\n\t\t\r\n\t/**\r\n\t * Variable: SHAPE_HEXAGON\r\n\t * \r\n\t * Name under which <mxHexagon> is registered in <mxCellRenderer>.\r\n\t * Default is hexagon.\r\n\t */\r\n\tSHAPE_HEXAGON: 'hexagon',\r\n\r\n\t/**\r\n\t * Variable: ARROW_CLASSIC\r\n\t * \r\n\t * Constant for classic arrow markers.\r\n\t */\r\n\tARROW_CLASSIC: 'classic',\r\n\r\n\t/**\r\n\t * Variable: ARROW_CLASSIC_THIN\r\n\t * \r\n\t * Constant for thin classic arrow markers.\r\n\t */\r\n\tARROW_CLASSIC_THIN: 'classicThin',\r\n\r\n\t/**\r\n\t * Variable: ARROW_BLOCK\r\n\t * \r\n\t * Constant for block arrow markers.\r\n\t */\r\n\tARROW_BLOCK: 'block',\r\n\r\n\t/**\r\n\t * Variable: ARROW_BLOCK_THIN\r\n\t * \r\n\t * Constant for thin block arrow markers.\r\n\t */\r\n\tARROW_BLOCK_THIN: 'blockThin',\r\n\r\n\t/**\r\n\t * Variable: ARROW_OPEN\r\n\t * \r\n\t * Constant for open arrow markers.\r\n\t */\r\n\tARROW_OPEN: 'open',\r\n\r\n\t/**\r\n\t * Variable: ARROW_OPEN_THIN\r\n\t * \r\n\t * Constant for thin open arrow markers.\r\n\t */\r\n\tARROW_OPEN_THIN: 'openThin',\r\n\r\n\t/**\r\n\t * Variable: ARROW_OVAL\r\n\t * \r\n\t * Constant for oval arrow markers.\r\n\t */\r\n\tARROW_OVAL: 'oval',\r\n\r\n\t/**\r\n\t * Variable: ARROW_DIAMOND\r\n\t * \r\n\t * Constant for diamond arrow markers.\r\n\t */\r\n\tARROW_DIAMOND: 'diamond',\r\n\r\n\t/**\r\n\t * Variable: ARROW_DIAMOND_THIN\r\n\t * \r\n\t * Constant for thin diamond arrow markers.\r\n\t */\r\n\tARROW_DIAMOND_THIN: 'diamondThin',\r\n\r\n\t/**\r\n\t * Variable: ALIGN_LEFT\r\n\t * \r\n\t * Constant for left horizontal alignment. Default is left.\r\n\t */\r\n\tALIGN_LEFT: 'left',\r\n\r\n\t/**\r\n\t * Variable: ALIGN_CENTER\r\n\t * \r\n\t * Constant for center horizontal alignment. Default is center.\r\n\t */\r\n\tALIGN_CENTER: 'center',\r\n\r\n\t/**\r\n\t * Variable: ALIGN_RIGHT\r\n\t * \r\n\t * Constant for right horizontal alignment. Default is right.\r\n\t */\r\n\tALIGN_RIGHT: 'right',\r\n\r\n\t/**\r\n\t * Variable: ALIGN_TOP\r\n\t * \r\n\t * Constant for top vertical alignment. Default is top.\r\n\t */\r\n\tALIGN_TOP: 'top',\r\n\r\n\t/**\r\n\t * Variable: ALIGN_MIDDLE\r\n\t * \r\n\t * Constant for middle vertical alignment. Default is middle.\r\n\t */\r\n\tALIGN_MIDDLE: 'middle',\r\n\r\n\t/**\r\n\t * Variable: ALIGN_BOTTOM\r\n\t * \r\n\t * Constant for bottom vertical alignment. Default is bottom.\r\n\t */\r\n\tALIGN_BOTTOM: 'bottom',\r\n\r\n\t/**\r\n\t * Variable: DIRECTION_NORTH\r\n\t * \r\n\t * Constant for direction north. Default is north.\r\n\t */\r\n\tDIRECTION_NORTH: 'north',\r\n\r\n\t/**\r\n\t * Variable: DIRECTION_SOUTH\r\n\t * \r\n\t * Constant for direction south. Default is south.\r\n\t */\r\n\tDIRECTION_SOUTH: 'south',\r\n\r\n\t/**\r\n\t * Variable: DIRECTION_EAST\r\n\t * \r\n\t * Constant for direction east. Default is east.\r\n\t */\r\n\tDIRECTION_EAST: 'east',\r\n\r\n\t/**\r\n\t * Variable: DIRECTION_WEST\r\n\t * \r\n\t * Constant for direction west. Default is west.\r\n\t */\r\n\tDIRECTION_WEST: 'west',\r\n\r\n\t/**\r\n\t * Variable: TEXT_DIRECTION_DEFAULT\r\n\t * \r\n\t * Constant for text direction default. Default is an empty string. Use\r\n\t * this value to use the default text direction of the operating system. \r\n\t */\r\n\tTEXT_DIRECTION_DEFAULT: '',\r\n\r\n\t/**\r\n\t * Variable: TEXT_DIRECTION_AUTO\r\n\t * \r\n\t * Constant for text direction automatic. Default is auto. Use this value\r\n\t * to find the direction for a given text with <mxText.getAutoDirection>. \r\n\t */\r\n\tTEXT_DIRECTION_AUTO: 'auto',\r\n\r\n\t/**\r\n\t * Variable: TEXT_DIRECTION_LTR\r\n\t * \r\n\t * Constant for text direction left to right. Default is ltr. Use this\r\n\t * value for left to right text direction.\r\n\t */\r\n\tTEXT_DIRECTION_LTR: 'ltr',\r\n\r\n\t/**\r\n\t * Variable: TEXT_DIRECTION_RTL\r\n\t * \r\n\t * Constant for text direction right to left. Default is rtl. Use this\r\n\t * value for right to left text direction.\r\n\t */\r\n\tTEXT_DIRECTION_RTL: 'rtl',\r\n\r\n\t/**\r\n\t * Variable: DIRECTION_MASK_NONE\r\n\t * \r\n\t * Constant for no direction.\r\n\t */\r\n\tDIRECTION_MASK_NONE: 0,\r\n\r\n\t/**\r\n\t * Variable: DIRECTION_MASK_WEST\r\n\t * \r\n\t * Bitwise mask for west direction.\r\n\t */\r\n\tDIRECTION_MASK_WEST: 1,\r\n\t\r\n\t/**\r\n\t * Variable: DIRECTION_MASK_NORTH\r\n\t * \r\n\t * Bitwise mask for north direction.\r\n\t */\r\n\tDIRECTION_MASK_NORTH: 2,\r\n\r\n\t/**\r\n\t * Variable: DIRECTION_MASK_SOUTH\r\n\t * \r\n\t * Bitwise mask for south direction.\r\n\t */\r\n\tDIRECTION_MASK_SOUTH: 4,\r\n\r\n\t/**\r\n\t * Variable: DIRECTION_MASK_EAST\r\n\t * \r\n\t * Bitwise mask for east direction.\r\n\t */\r\n\tDIRECTION_MASK_EAST: 8,\r\n\t\r\n\t/**\r\n\t * Variable: DIRECTION_MASK_ALL\r\n\t * \r\n\t * Bitwise mask for all directions.\r\n\t */\r\n\tDIRECTION_MASK_ALL: 15,\r\n\r\n\t/**\r\n\t * Variable: ELBOW_VERTICAL\r\n\t * \r\n\t * Constant for elbow vertical. Default is horizontal.\r\n\t */\r\n\tELBOW_VERTICAL: 'vertical',\r\n\r\n\t/**\r\n\t * Variable: ELBOW_HORIZONTAL\r\n\t * \r\n\t * Constant for elbow horizontal. Default is horizontal.\r\n\t */\r\n\tELBOW_HORIZONTAL: 'horizontal',\r\n\r\n\t/**\r\n\t * Variable: EDGESTYLE_ELBOW\r\n\t * \r\n\t * Name of the elbow edge style. Can be used as a string value\r\n\t * for the STYLE_EDGE style.\r\n\t */\r\n\tEDGESTYLE_ELBOW: 'elbowEdgeStyle',\r\n\r\n\t/**\r\n\t * Variable: EDGESTYLE_ENTITY_RELATION\r\n\t * \r\n\t * Name of the entity relation edge style. Can be used as a string value\r\n\t * for the STYLE_EDGE style.\r\n\t */\r\n\tEDGESTYLE_ENTITY_RELATION: 'entityRelationEdgeStyle',\r\n\r\n\t/**\r\n\t * Variable: EDGESTYLE_LOOP\r\n\t * \r\n\t * Name of the loop edge style. Can be used as a string value\r\n\t * for the STYLE_EDGE style.\r\n\t */\r\n\tEDGESTYLE_LOOP: 'loopEdgeStyle',\r\n\r\n\t/**\r\n\t * Variable: EDGESTYLE_SIDETOSIDE\r\n\t * \r\n\t * Name of the side to side edge style. Can be used as a string value\r\n\t * for the STYLE_EDGE style.\r\n\t */\r\n\tEDGESTYLE_SIDETOSIDE: 'sideToSideEdgeStyle',\r\n\r\n\t/**\r\n\t * Variable: EDGESTYLE_TOPTOBOTTOM\r\n\t * \r\n\t * Name of the top to bottom edge style. Can be used as a string value\r\n\t * for the STYLE_EDGE style.\r\n\t */\r\n\tEDGESTYLE_TOPTOBOTTOM: 'topToBottomEdgeStyle',\r\n\r\n\t/**\r\n\t * Variable: EDGESTYLE_ORTHOGONAL\r\n\t * \r\n\t * Name of the generic orthogonal edge style. Can be used as a string value\r\n\t * for the STYLE_EDGE style.\r\n\t */\r\n\tEDGESTYLE_ORTHOGONAL: 'orthogonalEdgeStyle',\r\n\r\n\t/**\r\n\t * Variable: EDGESTYLE_SEGMENT\r\n\t * \r\n\t * Name of the generic segment edge style. Can be used as a string value\r\n\t * for the STYLE_EDGE style.\r\n\t */\r\n\tEDGESTYLE_SEGMENT: 'segmentEdgeStyle',\r\n \r\n\t/**\r\n\t * Variable: PERIMETER_ELLIPSE\r\n\t * \r\n\t * Name of the ellipse perimeter. Can be used as a string value\r\n\t * for the STYLE_PERIMETER style.\r\n\t */\r\n\tPERIMETER_ELLIPSE: 'ellipsePerimeter',\r\n\r\n\t/**\r\n\t * Variable: PERIMETER_RECTANGLE\r\n\t *\r\n\t * Name of the rectangle perimeter. Can be used as a string value\r\n\t * for the STYLE_PERIMETER style.\r\n\t */\r\n\tPERIMETER_RECTANGLE: 'rectanglePerimeter',\r\n\r\n\t/**\r\n\t * Variable: PERIMETER_RHOMBUS\r\n\t * \r\n\t * Name of the rhombus perimeter. Can be used as a string value\r\n\t * for the STYLE_PERIMETER style.\r\n\t */\r\n\tPERIMETER_RHOMBUS: 'rhombusPerimeter',\r\n\r\n\t/**\r\n\t * Variable: PERIMETER_HEXAGON\r\n\t * \r\n\t * Name of the hexagon perimeter. Can be used as a string value \r\n\t * for the STYLE_PERIMETER style.\r\n\t */\r\n\tPERIMETER_HEXAGON: 'hexagonPerimeter',\r\n\r\n\t/**\r\n\t * Variable: PERIMETER_TRIANGLE\r\n\t * \r\n\t * Name of the triangle perimeter. Can be used as a string value\r\n\t * for the STYLE_PERIMETER style.\r\n\t */\r\n\tPERIMETER_TRIANGLE: 'trianglePerimeter'\r\n\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxEventObject\r\n * \r\n * The mxEventObject is a wrapper for all properties of a single event.\r\n * Additionally, it also offers functions to consume the event and check if it\r\n * was consumed as follows:\r\n * \r\n * (code)\r\n * evt.consume();\r\n * INV: evt.isConsumed() == true\r\n * (end)\r\n * \r\n * Constructor: mxEventObject\r\n *\r\n * Constructs a new event object with the specified name. An optional\r\n * sequence of key, value pairs can be appended to define properties.\r\n * \r\n * Example:\r\n *\r\n * (code)\r\n * new mxEventObject(\"eventName\", key1, val1, .., keyN, valN)\r\n * (end)\r\n */\r\nfunction mxEventObject(name)\r\n{\r\n\tthis.name = name;\r\n\tthis.properties = [];\r\n\t\r\n\tfor (var i = 1; i < arguments.length; i += 2)\r\n\t{\r\n\t\tif (arguments[i + 1] != null)\r\n\t\t{\r\n\t\t\tthis.properties[arguments[i]] = arguments[i + 1];\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Variable: name\r\n *\r\n * Holds the name.\r\n */\r\nmxEventObject.prototype.name = null;\r\n\r\n/**\r\n * Variable: properties\r\n *\r\n * Holds the properties as an associative array.\r\n */\r\nmxEventObject.prototype.properties = null;\r\n\r\n/**\r\n * Variable: consumed\r\n *\r\n * Holds the consumed state. Default is false.\r\n */\r\nmxEventObject.prototype.consumed = false;\r\n\r\n/**\r\n * Function: getName\r\n * \r\n * Returns <name>.\r\n */\r\nmxEventObject.prototype.getName = function()\r\n{\r\n\treturn this.name;\r\n};\r\n\r\n/**\r\n * Function: getProperties\r\n * \r\n * Returns <properties>.\r\n */\r\nmxEventObject.prototype.getProperties = function()\r\n{\r\n\treturn this.properties;\r\n};\r\n\r\n/**\r\n * Function: getProperty\r\n * \r\n * Returns the property for the given key.\r\n */\r\nmxEventObject.prototype.getProperty = function(key)\r\n{\r\n\treturn this.properties[key];\r\n};\r\n\r\n/**\r\n * Function: isConsumed\r\n *\r\n * Returns true if the event has been consumed.\r\n */\r\nmxEventObject.prototype.isConsumed = function()\r\n{\r\n\treturn this.consumed;\r\n};\r\n\r\n/**\r\n * Function: consume\r\n *\r\n * Consumes the event.\r\n */\r\nmxEventObject.prototype.consume = function()\r\n{\r\n\tthis.consumed = true;\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxMouseEvent\r\n * \r\n * Base class for all mouse events in mxGraph. A listener for this event should\r\n * implement the following methods:\r\n * \r\n * (code)\r\n * graph.addMouseListener(\r\n * {\r\n *   mouseDown: function(sender, evt)\r\n *   {\r\n *     mxLog.debug('mouseDown');\r\n *   },\r\n *   mouseMove: function(sender, evt)\r\n *   {\r\n *     mxLog.debug('mouseMove');\r\n *   },\r\n *   mouseUp: function(sender, evt)\r\n *   {\r\n *     mxLog.debug('mouseUp');\r\n *   }\r\n * });\r\n * (end)\r\n * \r\n * Constructor: mxMouseEvent\r\n *\r\n * Constructs a new event object for the given arguments.\r\n * \r\n * Parameters:\r\n * \r\n * evt - Native mouse event.\r\n * state - Optional <mxCellState> under the mouse.\r\n * \r\n */\r\nfunction mxMouseEvent(evt, state)\r\n{\r\n\tthis.evt = evt;\r\n\tthis.state = state;\r\n\tthis.sourceState = state;\r\n};\r\n\r\n/**\r\n * Variable: consumed\r\n *\r\n * Holds the consumed state of this event.\r\n */\r\nmxMouseEvent.prototype.consumed = false;\r\n\r\n/**\r\n * Variable: evt\r\n *\r\n * Holds the inner event object.\r\n */\r\nmxMouseEvent.prototype.evt = null;\r\n\r\n/**\r\n * Variable: graphX\r\n *\r\n * Holds the x-coordinate of the event in the graph. This value is set in\r\n * <mxGraph.fireMouseEvent>.\r\n */\r\nmxMouseEvent.prototype.graphX = null;\r\n\r\n/**\r\n * Variable: graphY\r\n *\r\n * Holds the y-coordinate of the event in the graph. This value is set in\r\n * <mxGraph.fireMouseEvent>.\r\n */\r\nmxMouseEvent.prototype.graphY = null;\r\n\r\n/**\r\n * Variable: state\r\n *\r\n * Holds the optional <mxCellState> associated with this event.\r\n */\r\nmxMouseEvent.prototype.state = null;\r\n\r\n/**\r\n * Variable: sourceState\r\n * \r\n * Holds the <mxCellState> that was passed to the constructor. This can be\r\n * different from <state> depending on the result of <mxGraph.getEventState>.\r\n */\r\nmxMouseEvent.prototype.sourceState = null;\r\n\r\n/**\r\n * Function: getEvent\r\n * \r\n * Returns <evt>.\r\n */\r\nmxMouseEvent.prototype.getEvent = function()\r\n{\r\n\treturn this.evt;\r\n};\r\n\r\n/**\r\n * Function: getSource\r\n * \r\n * Returns the target DOM element using <mxEvent.getSource> for <evt>.\r\n */\r\nmxMouseEvent.prototype.getSource = function()\r\n{\r\n\treturn mxEvent.getSource(this.evt);\r\n};\r\n\r\n/**\r\n * Function: isSource\r\n * \r\n * Returns true if the given <mxShape> is the source of <evt>.\r\n */\r\nmxMouseEvent.prototype.isSource = function(shape)\r\n{\r\n\tif (shape != null)\r\n\t{\r\n\t\treturn mxUtils.isAncestorNode(shape.node, this.getSource());\r\n\t}\r\n\t\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: getX\r\n * \r\n * Returns <evt.clientX>.\r\n */\r\nmxMouseEvent.prototype.getX = function()\r\n{\r\n\treturn mxEvent.getClientX(this.getEvent());\r\n};\r\n\r\n/**\r\n * Function: getY\r\n * \r\n * Returns <evt.clientY>.\r\n */\r\nmxMouseEvent.prototype.getY = function()\r\n{\r\n\treturn mxEvent.getClientY(this.getEvent());\r\n};\r\n\r\n/**\r\n * Function: getGraphX\r\n * \r\n * Returns <graphX>.\r\n */\r\nmxMouseEvent.prototype.getGraphX = function()\r\n{\r\n\treturn this.graphX;\r\n};\r\n\r\n/**\r\n * Function: getGraphY\r\n * \r\n * Returns <graphY>.\r\n */\r\nmxMouseEvent.prototype.getGraphY = function()\r\n{\r\n\treturn this.graphY;\r\n};\r\n\r\n/**\r\n * Function: getState\r\n * \r\n * Returns <state>.\r\n */\r\nmxMouseEvent.prototype.getState = function()\r\n{\r\n\treturn this.state;\r\n};\r\n\r\n/**\r\n * Function: getCell\r\n * \r\n * Returns the <mxCell> in <state> is not null.\r\n */\r\nmxMouseEvent.prototype.getCell = function()\r\n{\r\n\tvar state = this.getState();\r\n\t\r\n\tif (state != null)\r\n\t{\r\n\t\treturn state.cell;\r\n\t}\r\n\t\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: isPopupTrigger\r\n *\r\n * Returns true if the event is a popup trigger.\r\n */\r\nmxMouseEvent.prototype.isPopupTrigger = function()\r\n{\r\n\treturn mxEvent.isPopupTrigger(this.getEvent());\r\n};\r\n\r\n/**\r\n * Function: isConsumed\r\n *\r\n * Returns <consumed>.\r\n */\r\nmxMouseEvent.prototype.isConsumed = function()\r\n{\r\n\treturn this.consumed;\r\n};\r\n\r\n/**\r\n * Function: consume\r\n *\r\n * Sets <consumed> to true and invokes preventDefault on the native event\r\n * if such a method is defined. This is used mainly to avoid the cursor from\r\n * being changed to a text cursor in Webkit. You can use the preventDefault\r\n * flag to disable this functionality.\r\n * \r\n * Parameters:\r\n * \r\n * preventDefault - Specifies if the native event should be canceled. Default\r\n * is true.\r\n */\r\nmxMouseEvent.prototype.consume = function(preventDefault)\r\n{\r\n\tpreventDefault = (preventDefault != null) ? preventDefault : mxEvent.isMouseEvent(this.evt);\r\n\t\r\n\tif (preventDefault && this.evt.preventDefault)\r\n\t{\r\n\t\tthis.evt.preventDefault();\r\n\t}\r\n\r\n\t// Workaround for images being dragged in IE\r\n\t// Does not change returnValue in Opera\r\n\tif (mxClient.IS_IE)\r\n\t{\r\n\t\tthis.evt.returnValue = true;\r\n\t}\r\n\r\n\t// Sets local consumed state\r\n\tthis.consumed = true;\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxEventSource\r\n *\r\n * Base class for objects that dispatch named events. To create a subclass that\r\n * inherits from mxEventSource, the following code is used.\r\n *\r\n * (code)\r\n * function MyClass() { };\r\n *\r\n * MyClass.prototype = new mxEventSource();\r\n * MyClass.prototype.constructor = MyClass;\r\n * (end)\r\n *\r\n * Known Subclasses:\r\n *\r\n * <mxGraphModel>, <mxGraph>, <mxGraphView>, <mxEditor>, <mxCellOverlay>,\r\n * <mxToolbar>, <mxWindow>\r\n * \r\n * Constructor: mxEventSource\r\n *\r\n * Constructs a new event source.\r\n */\r\nfunction mxEventSource(eventSource)\r\n{\r\n\tthis.setEventSource(eventSource);\r\n};\r\n\r\n/**\r\n * Variable: eventListeners\r\n *\r\n * Holds the event names and associated listeners in an array. The array\r\n * contains the event name followed by the respective listener for each\r\n * registered listener.\r\n */\r\nmxEventSource.prototype.eventListeners = null;\r\n\r\n/**\r\n * Variable: eventsEnabled\r\n *\r\n * Specifies if events can be fired. Default is true.\r\n */\r\nmxEventSource.prototype.eventsEnabled = true;\r\n\r\n/**\r\n * Variable: eventSource\r\n *\r\n * Optional source for events. Default is null.\r\n */\r\nmxEventSource.prototype.eventSource = null;\r\n\r\n/**\r\n * Function: isEventsEnabled\r\n * \r\n * Returns <eventsEnabled>.\r\n */\r\nmxEventSource.prototype.isEventsEnabled = function()\r\n{\r\n\treturn this.eventsEnabled;\r\n};\r\n\r\n/**\r\n * Function: setEventsEnabled\r\n * \r\n * Sets <eventsEnabled>.\r\n */\r\nmxEventSource.prototype.setEventsEnabled = function(value)\r\n{\r\n\tthis.eventsEnabled = value;\r\n};\r\n\r\n/**\r\n * Function: getEventSource\r\n * \r\n * Returns <eventSource>.\r\n */\r\nmxEventSource.prototype.getEventSource = function()\r\n{\r\n\treturn this.eventSource;\r\n};\r\n\r\n/**\r\n * Function: setEventSource\r\n * \r\n * Sets <eventSource>.\r\n */\r\nmxEventSource.prototype.setEventSource = function(value)\r\n{\r\n\tthis.eventSource = value;\r\n};\r\n\r\n/**\r\n * Function: addListener\r\n *\r\n * Binds the specified function to the given event name. If no event name\r\n * is given, then the listener is registered for all events.\r\n * \r\n * The parameters of the listener are the sender and an <mxEventObject>.\r\n */\r\nmxEventSource.prototype.addListener = function(name, funct)\r\n{\r\n\tif (this.eventListeners == null)\r\n\t{\r\n\t\tthis.eventListeners = [];\r\n\t}\r\n\t\r\n\tthis.eventListeners.push(name);\r\n\tthis.eventListeners.push(funct);\r\n};\r\n\r\n/**\r\n * Function: removeListener\r\n *\r\n * Removes all occurrences of the given listener from <eventListeners>.\r\n */\r\nmxEventSource.prototype.removeListener = function(funct)\r\n{\r\n\tif (this.eventListeners != null)\r\n\t{\r\n\t\tvar i = 0;\r\n\t\t\r\n\t\twhile (i < this.eventListeners.length)\r\n\t\t{\r\n\t\t\tif (this.eventListeners[i+1] == funct)\r\n\t\t\t{\r\n\t\t\t\tthis.eventListeners.splice(i, 2);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\ti += 2;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: fireEvent\r\n *\r\n * Dispatches the given event to the listeners which are registered for\r\n * the event. The sender argument is optional. The current execution scope\r\n * (\"this\") is used for the listener invocation (see <mxUtils.bind>).\r\n *\r\n * Example:\r\n *\r\n * (code)\r\n * fireEvent(new mxEventObject(\"eventName\", key1, val1, .., keyN, valN))\r\n * (end)\r\n * \r\n * Parameters:\r\n *\r\n * evt - <mxEventObject> that represents the event.\r\n * sender - Optional sender to be passed to the listener. Default value is\r\n * the return value of <getEventSource>.\r\n */\r\nmxEventSource.prototype.fireEvent = function(evt, sender)\r\n{\r\n\tif (this.eventListeners != null && this.isEventsEnabled())\r\n\t{\r\n\t\tif (evt == null)\r\n\t\t{\r\n\t\t\tevt = new mxEventObject();\r\n\t\t}\r\n\t\t\r\n\t\tif (sender == null)\r\n\t\t{\r\n\t\t\tsender = this.getEventSource();\r\n\t\t}\r\n\r\n\t\tif (sender == null)\r\n\t\t{\r\n\t\t\tsender = this;\r\n\t\t}\r\n\r\n\t\tvar args = [sender, evt];\r\n\t\t\r\n\t\tfor (var i = 0; i < this.eventListeners.length; i += 2)\r\n\t\t{\r\n\t\t\tvar listen = this.eventListeners[i];\r\n\t\t\t\r\n\t\t\tif (listen == null || listen == evt.getName())\r\n\t\t\t{\r\n\t\t\t\tthis.eventListeners[i+1].apply(this, args);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nvar mxEvent =\r\n{\r\n\r\n\t/**\r\n\t * Class: mxEvent\r\n\t * \r\n\t * Cross-browser DOM event support. For internal event handling,\r\n\t * <mxEventSource> and the graph event dispatch loop in <mxGraph> are used.\r\n\t * \r\n\t * Memory Leaks:\r\n\t * \r\n\t * Use this class for adding and removing listeners to/from DOM nodes. The\r\n\t * <removeAllListeners> function is provided to remove all listeners that\r\n\t * have been added using <addListener>. The function should be invoked when\r\n\t * the last reference is removed in the JavaScript code, typically when the\r\n\t * referenced DOM node is removed from the DOM.\r\n\t *\r\n\t * Function: addListener\r\n\t * \r\n\t * Binds the function to the specified event on the given element. Use\r\n\t * <mxUtils.bind> in order to bind the \"this\" keyword inside the function\r\n\t * to a given execution scope.\r\n\t */\r\n\taddListener: function()\r\n\t{\r\n\t\tvar updateListenerList = function(element, eventName, funct)\r\n\t\t{\r\n\t\t\tif (element.mxListenerList == null)\r\n\t\t\t{\r\n\t\t\t\telement.mxListenerList = [];\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar entry = {name: eventName, f: funct};\r\n\t\t\telement.mxListenerList.push(entry);\r\n\t\t};\r\n\t\t\r\n\t\tif (window.addEventListener)\r\n\t\t{\r\n\t\t\treturn function(element, eventName, funct)\r\n\t\t\t{\r\n\t\t\t\telement.addEventListener(eventName, funct, false);\r\n\t\t\t\tupdateListenerList(element, eventName, funct);\r\n\t\t\t};\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn function(element, eventName, funct)\r\n\t\t\t{\r\n\t\t\t\telement.attachEvent('on' + eventName, funct);\r\n\t\t\t\tupdateListenerList(element, eventName, funct);\t\t\t\t\r\n\t\t\t};\r\n\t\t}\r\n\t}(),\r\n\r\n\t/**\r\n\t * Function: removeListener\r\n\t *\r\n\t * Removes the specified listener from the given element.\r\n\t */\r\n\tremoveListener: function()\r\n\t{\r\n\t\tvar updateListener = function(element, eventName, funct)\r\n\t\t{\r\n\t\t\tif (element.mxListenerList != null)\r\n\t\t\t{\r\n\t\t\t\tvar listenerCount = element.mxListenerList.length;\r\n\t\t\t\t\r\n\t\t\t\tfor (var i = 0; i < listenerCount; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar entry = element.mxListenerList[i];\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (entry.f == funct)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\telement.mxListenerList.splice(i, 1);\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (element.mxListenerList.length == 0)\r\n\t\t\t\t{\r\n\t\t\t\t\telement.mxListenerList = null;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t};\r\n\t\t\r\n\t\tif (window.removeEventListener)\r\n\t\t{\r\n\t\t\treturn function(element, eventName, funct)\r\n\t\t\t{\r\n\t\t\t\telement.removeEventListener(eventName, funct, false);\r\n\t\t\t\tupdateListener(element, eventName, funct);\r\n\t\t\t};\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn function(element, eventName, funct)\r\n\t\t\t{\r\n\t\t\t\telement.detachEvent('on' + eventName, funct);\r\n\t\t\t\tupdateListener(element, eventName, funct);\r\n\t\t\t};\r\n\t\t}\r\n\t}(),\r\n\r\n\t/**\r\n\t * Function: removeAllListeners\r\n\t * \r\n\t * Removes all listeners from the given element.\r\n\t */\r\n\tremoveAllListeners: function(element)\r\n\t{\r\n\t\tvar list = element.mxListenerList;\r\n\r\n\t\tif (list != null)\r\n\t\t{\r\n\t\t\twhile (list.length > 0)\r\n\t\t\t{\r\n\t\t\t\tvar entry = list[0];\r\n\t\t\t\tmxEvent.removeListener(element, entry.name, entry.f);\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: addGestureListeners\r\n\t * \r\n\t * Adds the given listeners for touch, mouse and/or pointer events. If\r\n\t * <mxClient.IS_POINTER> is true then pointer events will be registered,\r\n\t * else the respective mouse events will be registered. If <mxClient.IS_POINTER>\r\n\t * is false and <mxClient.IS_TOUCH> is true then the respective touch events\r\n\t * will be registered as well as the mouse events.\r\n\t */\r\n\taddGestureListeners: function(node, startListener, moveListener, endListener)\r\n\t{\r\n\t\tif (startListener != null)\r\n\t\t{\r\n\t\t\tmxEvent.addListener(node, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown', startListener);\r\n\t\t}\r\n\t\t\r\n\t\tif (moveListener != null)\r\n\t\t{\r\n\t\t\tmxEvent.addListener(node, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', moveListener);\r\n\t\t}\r\n\t\t\r\n\t\tif (endListener != null)\r\n\t\t{\r\n\t\t\tmxEvent.addListener(node, (mxClient.IS_POINTER) ? 'pointerup' : 'mouseup', endListener);\r\n\t\t}\r\n\t\t\r\n\t\tif (!mxClient.IS_POINTER && mxClient.IS_TOUCH)\r\n\t\t{\r\n\t\t\tif (startListener != null)\r\n\t\t\t{\r\n\t\t\t\tmxEvent.addListener(node, 'touchstart', startListener);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (moveListener != null)\r\n\t\t\t{\r\n\t\t\t\tmxEvent.addListener(node, 'touchmove', moveListener);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (endListener != null)\r\n\t\t\t{\r\n\t\t\t\tmxEvent.addListener(node, 'touchend', endListener);\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: removeGestureListeners\r\n\t * \r\n\t * Removes the given listeners from mousedown, mousemove, mouseup and the\r\n\t * respective touch events if <mxClient.IS_TOUCH> is true.\r\n\t */\r\n\tremoveGestureListeners: function(node, startListener, moveListener, endListener)\r\n\t{\r\n\t\tif (startListener != null)\r\n\t\t{\r\n\t\t\tmxEvent.removeListener(node, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown', startListener);\r\n\t\t}\r\n\t\t\r\n\t\tif (moveListener != null)\r\n\t\t{\r\n\t\t\tmxEvent.removeListener(node, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', moveListener);\r\n\t\t}\r\n\t\t\r\n\t\tif (endListener != null)\r\n\t\t{\r\n\t\t\tmxEvent.removeListener(node, (mxClient.IS_POINTER) ? 'pointerup' : 'mouseup', endListener);\r\n\t\t}\r\n\t\t\r\n\t\tif (!mxClient.IS_POINTER && mxClient.IS_TOUCH)\r\n\t\t{\r\n\t\t\tif (startListener != null)\r\n\t\t\t{\r\n\t\t\t\tmxEvent.removeListener(node, 'touchstart', startListener);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (moveListener != null)\r\n\t\t\t{\r\n\t\t\t\tmxEvent.removeListener(node, 'touchmove', moveListener);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (endListener != null)\r\n\t\t\t{\r\n\t\t\t\tmxEvent.removeListener(node, 'touchend', endListener);\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: redirectMouseEvents\r\n\t *\r\n\t * Redirects the mouse events from the given DOM node to the graph dispatch\r\n\t * loop using the event and given state as event arguments. State can\r\n\t * either be an instance of <mxCellState> or a function that returns an\r\n\t * <mxCellState>. The down, move, up and dblClick arguments are optional\r\n\t * functions that take the trigger event as arguments and replace the\r\n\t * default behaviour.\r\n\t */\r\n\tredirectMouseEvents: function(node, graph, state, down, move, up, dblClick)\r\n\t{\r\n\t\tvar getState = function(evt)\r\n\t\t{\r\n\t\t\treturn (typeof(state) == 'function') ? state(evt) : state;\r\n\t\t};\r\n\t\t\r\n\t\tmxEvent.addGestureListeners(node, function (evt)\r\n\t\t{\r\n\t\t\tif (down != null)\r\n\t\t\t{\r\n\t\t\t\tdown(evt);\r\n\t\t\t}\r\n\t\t\telse if (!mxEvent.isConsumed(evt))\r\n\t\t\t{\r\n\t\t\t\tgraph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, getState(evt)));\r\n\t\t\t}\r\n\t\t},\r\n\t\tfunction (evt)\r\n\t\t{\r\n\t\t\tif (move != null)\r\n\t\t\t{\r\n\t\t\t\tmove(evt);\r\n\t\t\t}\r\n\t\t\telse if (!mxEvent.isConsumed(evt))\r\n\t\t\t{\r\n\t\t\t\tgraph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));\r\n\t\t\t}\r\n\t\t},\r\n\t\tfunction (evt)\r\n\t\t{\r\n\t\t\tif (up != null)\r\n\t\t\t{\r\n\t\t\t\tup(evt);\r\n\t\t\t}\r\n\t\t\telse if (!mxEvent.isConsumed(evt))\r\n\t\t\t{\r\n\t\t\t\tgraph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, getState(evt)));\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tmxEvent.addListener(node, 'dblclick', function (evt)\r\n\t\t{\r\n\t\t\tif (dblClick != null)\r\n\t\t\t{\r\n\t\t\t\tdblClick(evt);\r\n\t\t\t}\r\n\t\t\telse if (!mxEvent.isConsumed(evt))\r\n\t\t\t{\r\n\t\t\t\tvar tmp = getState(evt);\r\n\t\t\t\tgraph.dblClick(evt, (tmp != null) ? tmp.cell : null);\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Function: release\r\n\t * \r\n\t * Removes the known listeners from the given DOM node and its descendants.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * element - DOM node to remove the listeners from.\r\n\t */\r\n\trelease: function(element)\r\n\t{\r\n\t\ttry\r\n\t\t{\r\n\t\t\tif (element != null)\r\n\t\t\t{\r\n\t\t\t\tmxEvent.removeAllListeners(element);\r\n\t\t\t\t\r\n\t\t\t\tvar children = element.childNodes;\r\n\t\t\t\t\r\n\t\t\t\tif (children != null)\r\n\t\t\t\t{\r\n\t\t\t        var childCount = children.length;\r\n\t\t\t        \r\n\t\t\t        for (var i = 0; i < childCount; i += 1)\r\n\t\t\t        {\r\n\t\t\t        \tmxEvent.release(children[i]);\r\n\t\t\t        }\r\n\t\t\t    }\r\n\t\t\t}\r\n\t\t}\r\n\t\tcatch (e)\r\n\t\t{\r\n\t\t\t// ignores errors as this is typically called in cleanup code\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Function: addMouseWheelListener\r\n\t * \r\n\t * Installs the given function as a handler for mouse wheel events. The\r\n\t * function has two arguments: the mouse event and a boolean that specifies\r\n\t * if the wheel was moved up or down.\r\n\t * \r\n\t * This has been tested with IE 6 and 7, Firefox (all versions), Opera and\r\n\t * Safari. It does currently not work on Safari for Mac.\r\n\t * \r\n\t * Example:\r\n\t * \r\n\t * (code)\r\n\t * mxEvent.addMouseWheelListener(function (evt, up)\r\n\t * {\r\n\t *   mxLog.show();\r\n\t *   mxLog.debug('mouseWheel: up='+up);\r\n\t * });\r\n\t *(end)\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * funct - Handler function that takes the event argument and a boolean up\r\n\t * argument for the mousewheel direction.\r\n\t * target - Target for installing the listener in Google Chrome. See \r\n\t * https://www.chromestatus.com/features/6662647093133312.\r\n\t */\r\n\taddMouseWheelListener: function(funct, target)\r\n\t{\r\n\t\tif (funct != null)\r\n\t\t{\r\n\t\t\tvar wheelHandler = function(evt)\r\n\t\t\t{\r\n\t\t\t\t// IE does not give an event object but the\r\n\t\t\t\t// global event object is the mousewheel event\r\n\t\t\t\t// at this point in time.\r\n\t\t\t\tif (evt == null)\r\n\t\t\t\t{\r\n\t\t\t\t\tevt = window.event;\r\n\t\t\t\t}\r\n\t\t\t\r\n\t\t\t\tvar delta = 0;\r\n\t\t\t\t\r\n\t\t\t\tif (mxClient.IS_FF)\r\n\t\t\t\t{\r\n\t\t\t\t\tdelta = -evt.detail / 2;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tdelta = evt.wheelDelta / 120;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Handles the event using the given function\r\n\t\t\t\tif (delta != 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tfunct(evt, delta > 0);\r\n\t\t\t\t}\r\n\t\t\t};\r\n\t\r\n\t\t\t// Webkit has NS event API, but IE event name and details \r\n\t\t\tif (mxClient.IS_NS && document.documentMode == null)\r\n\t\t\t{\r\n\t\t\t\tvar eventName = (mxClient.IS_SF || mxClient.IS_GC) ? 'mousewheel' : 'DOMMouseScroll';\r\n\t\t\t\tmxEvent.addListener((mxClient.IS_GC && target != null) ? target : window,\r\n\t\t\t\t\teventName, wheelHandler);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tmxEvent.addListener(document, 'mousewheel', wheelHandler);\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: disableContextMenu\r\n\t *\r\n\t * Disables the context menu for the given element.\r\n\t */\r\n\tdisableContextMenu: function(element)\r\n\t{\r\n\t\tmxEvent.addListener(element, 'contextmenu', function(evt)\r\n\t\t{\r\n\t\t\tif (evt.preventDefault)\r\n\t\t\t{\r\n\t\t\t\tevt.preventDefault();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\treturn false;\r\n\t\t});\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: getSource\r\n\t * \r\n\t * Returns the event's target or srcElement depending on the browser.\r\n\t */\r\n\tgetSource: function(evt)\r\n\t{\r\n\t\treturn (evt.srcElement != null) ? evt.srcElement : evt.target;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: isConsumed\r\n\t * \r\n\t * Returns true if the event has been consumed using <consume>.\r\n\t */\r\n\tisConsumed: function(evt)\r\n\t{\r\n\t\treturn evt.isConsumed != null && evt.isConsumed;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: isTouchEvent\r\n\t * \r\n\t * Returns true if the event was generated using a touch device (not a pen or mouse).\r\n\t */\r\n\tisTouchEvent: function(evt)\r\n\t{\r\n\t\treturn (evt.pointerType != null) ? (evt.pointerType == 'touch' || evt.pointerType ===\r\n\t\t\tevt.MSPOINTER_TYPE_TOUCH) : ((evt.mozInputSource != null) ?\r\n\t\t\t\t\tevt.mozInputSource == 5 : evt.type.indexOf('touch') == 0);\r\n\t},\r\n\r\n\t/**\r\n\t * Function: isPenEvent\r\n\t * \r\n\t * Returns true if the event was generated using a pen (not a touch device or mouse).\r\n\t */\r\n\tisPenEvent: function(evt)\r\n\t{\r\n\t\treturn (evt.pointerType != null) ? (evt.pointerType == 'pen' || evt.pointerType ===\r\n\t\t\tevt.MSPOINTER_TYPE_PEN) : ((evt.mozInputSource != null) ?\r\n\t\t\t\t\tevt.mozInputSource == 2 : evt.type.indexOf('pen') == 0);\r\n\t},\r\n\r\n\t/**\r\n\t * Function: isMultiTouchEvent\r\n\t * \r\n\t * Returns true if the event was generated using a touch device (not a pen or mouse).\r\n\t */\r\n\tisMultiTouchEvent: function(evt)\r\n\t{\r\n\t\treturn (evt.type != null && evt.type.indexOf('touch') == 0 && evt.touches != null && evt.touches.length > 1);\r\n\t},\r\n\r\n\t/**\r\n\t * Function: isMouseEvent\r\n\t * \r\n\t * Returns true if the event was generated using a mouse (not a pen or touch device).\r\n\t */\r\n\tisMouseEvent: function(evt)\r\n\t{\r\n\t\treturn (evt.pointerType != null) ? (evt.pointerType == 'mouse' || evt.pointerType ===\r\n\t\t\tevt.MSPOINTER_TYPE_MOUSE) : ((evt.mozInputSource != null) ?\r\n\t\t\t\tevt.mozInputSource == 1 : evt.type.indexOf('mouse') == 0);\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: isLeftMouseButton\r\n\t * \r\n\t * Returns true if the left mouse button is pressed for the given event.\r\n\t * To check if a button is pressed during a mouseMove you should use the\r\n\t * <mxGraph.isMouseDown> property. Note that this returns true in Firefox\r\n\t * for control+left-click on the Mac.\r\n\t */\r\n\tisLeftMouseButton: function(evt)\r\n\t{\r\n\t\t// Special case for mousemove and mousedown we check the buttons\r\n\t\t// if it exists because which is 0 even if no button is pressed\r\n\t\tif ('buttons' in evt && (evt.type == 'mousedown' || evt.type == 'mousemove'))\r\n\t\t{\r\n\t\t\treturn evt.buttons == 1;\r\n\t\t}\r\n\t\telse if ('which' in evt)\r\n\t\t{\r\n\t        return evt.which === 1;\r\n\t    }\r\n\t\telse\r\n\t\t{\r\n\t        return evt.button === 1;\r\n\t    }\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: isMiddleMouseButton\r\n\t * \r\n\t * Returns true if the middle mouse button is pressed for the given event.\r\n\t * To check if a button is pressed during a mouseMove you should use the\r\n\t * <mxGraph.isMouseDown> property.\r\n\t */\r\n\tisMiddleMouseButton: function(evt)\r\n\t{\r\n\t\tif ('which' in evt)\r\n\t\t{\r\n\t        return evt.which === 2;\r\n\t    }\r\n\t\telse\r\n\t\t{\r\n\t        return evt.button === 4;\r\n\t    }\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: isRightMouseButton\r\n\t * \r\n\t * Returns true if the right mouse button was pressed. Note that this\r\n\t * button might not be available on some systems. For handling a popup\r\n\t * trigger <isPopupTrigger> should be used.\r\n\t */\r\n\tisRightMouseButton: function(evt)\r\n\t{\r\n\t\tif ('which' in evt)\r\n\t\t{\r\n\t        return evt.which === 3;\r\n\t    }\r\n\t\telse\r\n\t\t{\r\n\t        return evt.button === 2;\r\n\t    }\r\n\t},\r\n\r\n\t/**\r\n\t * Function: isPopupTrigger\r\n\t * \r\n\t * Returns true if the event is a popup trigger. This implementation\r\n\t * returns true if the right button or the left button and control was\r\n\t * pressed on a Mac.\r\n\t */\r\n\tisPopupTrigger: function(evt)\r\n\t{\r\n\t\treturn mxEvent.isRightMouseButton(evt) || (mxClient.IS_MAC && mxEvent.isControlDown(evt) &&\r\n\t\t\t!mxEvent.isShiftDown(evt) && !mxEvent.isMetaDown(evt) && !mxEvent.isAltDown(evt));\r\n\t},\r\n\r\n\t/**\r\n\t * Function: isShiftDown\r\n\t * \r\n\t * Returns true if the shift key is pressed for the given event.\r\n\t */\r\n\tisShiftDown: function(evt)\r\n\t{\r\n\t\treturn (evt != null) ? evt.shiftKey : false;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: isAltDown\r\n\t * \r\n\t * Returns true if the alt key is pressed for the given event.\r\n\t */\r\n\tisAltDown: function(evt)\r\n\t{\r\n\t\treturn (evt != null) ? evt.altKey : false;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: isControlDown\r\n\t * \r\n\t * Returns true if the control key is pressed for the given event.\r\n\t */\r\n\tisControlDown: function(evt)\r\n\t{\r\n\t\treturn (evt != null) ? evt.ctrlKey : false;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: isMetaDown\r\n\t * \r\n\t * Returns true if the meta key is pressed for the given event.\r\n\t */\r\n\tisMetaDown: function(evt)\r\n\t{\r\n\t\treturn (evt != null) ? evt.metaKey : false;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: getMainEvent\r\n\t * \r\n\t * Returns the touch or mouse event that contains the mouse coordinates.\r\n\t */\r\n\tgetMainEvent: function(e)\r\n\t{\r\n\t\tif ((e.type == 'touchstart' || e.type == 'touchmove') && e.touches != null && e.touches[0] != null)\r\n\t\t{\r\n\t\t\te = e.touches[0];\r\n\t\t}\r\n\t\telse if (e.type == 'touchend' && e.changedTouches != null && e.changedTouches[0] != null)\r\n\t\t{\r\n\t\t\te = e.changedTouches[0];\r\n\t\t}\r\n\t\t\r\n\t\treturn e;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: getClientX\r\n\t * \r\n\t * Returns true if the meta key is pressed for the given event.\r\n\t */\r\n\tgetClientX: function(e)\r\n\t{\r\n\t\treturn mxEvent.getMainEvent(e).clientX;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: getClientY\r\n\t * \r\n\t * Returns true if the meta key is pressed for the given event.\r\n\t */\r\n\tgetClientY: function(e)\r\n\t{\r\n\t\treturn mxEvent.getMainEvent(e).clientY;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: consume\r\n\t * \r\n\t * Consumes the given event.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * evt - Native event to be consumed.\r\n\t * preventDefault - Optional boolean to prevent the default for the event.\r\n\t * Default is true.\r\n\t * stopPropagation - Option boolean to stop event propagation. Default is\r\n\t * true.\r\n\t */\r\n\tconsume: function(evt, preventDefault, stopPropagation)\r\n\t{\r\n\t\tpreventDefault = (preventDefault != null) ? preventDefault : true;\r\n\t\tstopPropagation = (stopPropagation != null) ? stopPropagation : true;\r\n\t\t\r\n\t\tif (preventDefault)\r\n\t\t{\r\n\t\t\tif (evt.preventDefault)\r\n\t\t\t{\r\n\t\t\t\tif (stopPropagation)\r\n\t\t\t\t{\r\n\t\t\t\t\tevt.stopPropagation();\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tevt.preventDefault();\r\n\t\t\t}\r\n\t\t\telse if (stopPropagation)\r\n\t\t\t{\r\n\t\t\t\tevt.cancelBubble = true;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Opera\r\n\t\tevt.isConsumed = true;\r\n\r\n\t\t// Other browsers\r\n\t\tif (!evt.preventDefault)\r\n\t\t{\r\n\t\t\tevt.returnValue = false;\r\n\t\t}\r\n\t},\r\n\t\r\n\t//\r\n\t// Special handles in mouse events\r\n\t//\r\n\t\r\n\t/**\r\n\t * Variable: LABEL_HANDLE\r\n\t * \r\n\t * Index for the label handle in an mxMouseEvent. This should be a negative\r\n\t * value that does not interfere with any possible handle indices. Default\r\n\t * is -1.\r\n\t */\r\n\tLABEL_HANDLE: -1,\r\n\t\r\n\t/**\r\n\t * Variable: ROTATION_HANDLE\r\n\t * \r\n\t * Index for the rotation handle in an mxMouseEvent. This should be a\r\n\t * negative value that does not interfere with any possible handle indices.\r\n\t * Default is -2.\r\n\t */\r\n\tROTATION_HANDLE: -2,\r\n\t\r\n\t/**\r\n\t * Variable: CUSTOM_HANDLE\r\n\t * \r\n\t * Start index for the custom handles in an mxMouseEvent. This should be a\r\n\t * negative value and is the start index which is decremented for each\r\n\t * custom handle. Default is -100.\r\n\t */\r\n\tCUSTOM_HANDLE: -100,\r\n\t\r\n\t/**\r\n\t * Variable: VIRTUAL_HANDLE\r\n\t * \r\n\t * Start index for the virtual handles in an mxMouseEvent. This should be a\r\n\t * negative value and is the start index which is decremented for each\r\n\t * virtual handle. Default is -100000. This assumes that there are no more\r\n\t * than VIRTUAL_HANDLE - CUSTOM_HANDLE custom handles.\r\n\t * \r\n\t */\r\n\tVIRTUAL_HANDLE: -100000,\r\n\t\r\n\t//\r\n\t// Event names\r\n\t//\r\n\t\r\n\t/**\r\n\t * Variable: MOUSE_DOWN\r\n\t *\r\n\t * Specifies the event name for mouseDown.\r\n\t */\r\n\tMOUSE_DOWN: 'mouseDown',\r\n\t\r\n\t/**\r\n\t * Variable: MOUSE_MOVE\r\n\t *\r\n\t * Specifies the event name for mouseMove. \r\n\t */\r\n\tMOUSE_MOVE: 'mouseMove',\r\n\t\r\n\t/**\r\n\t * Variable: MOUSE_UP\r\n\t *\r\n\t * Specifies the event name for mouseUp. \r\n\t */\r\n\tMOUSE_UP: 'mouseUp',\r\n\r\n\t/**\r\n\t * Variable: ACTIVATE\r\n\t *\r\n\t * Specifies the event name for activate.\r\n\t */\r\n\tACTIVATE: 'activate',\r\n\r\n\t/**\r\n\t * Variable: RESIZE_START\r\n\t *\r\n\t * Specifies the event name for resizeStart.\r\n\t */\r\n\tRESIZE_START: 'resizeStart',\r\n\r\n\t/**\r\n\t * Variable: RESIZE\r\n\t *\r\n\t * Specifies the event name for resize.\r\n\t */\r\n\tRESIZE: 'resize',\r\n\r\n\t/**\r\n\t * Variable: RESIZE_END\r\n\t *\r\n\t * Specifies the event name for resizeEnd.\r\n\t */\r\n\tRESIZE_END: 'resizeEnd',\r\n\r\n\t/**\r\n\t * Variable: MOVE_START\r\n\t *\r\n\t * Specifies the event name for moveStart.\r\n\t */\r\n\tMOVE_START: 'moveStart',\r\n\r\n\t/**\r\n\t * Variable: MOVE\r\n\t *\r\n\t * Specifies the event name for move.\r\n\t */\r\n\tMOVE: 'move',\r\n\r\n\t/**\r\n\t * Variable: MOVE_END\r\n\t *\r\n\t * Specifies the event name for moveEnd.\r\n\t */\r\n\tMOVE_END: 'moveEnd',\r\n\r\n\t/**\r\n\t * Variable: PAN_START\r\n\t *\r\n\t * Specifies the event name for panStart.\r\n\t */\r\n\tPAN_START: 'panStart',\r\n\r\n\t/**\r\n\t * Variable: PAN\r\n\t *\r\n\t * Specifies the event name for pan.\r\n\t */\r\n\tPAN: 'pan',\r\n\r\n\t/**\r\n\t * Variable: PAN_END\r\n\t *\r\n\t * Specifies the event name for panEnd.\r\n\t */\r\n\tPAN_END: 'panEnd',\r\n\r\n\t/**\r\n\t * Variable: MINIMIZE\r\n\t *\r\n\t * Specifies the event name for minimize.\r\n\t */\r\n\tMINIMIZE: 'minimize',\r\n\r\n\t/**\r\n\t * Variable: NORMALIZE\r\n\t *\r\n\t * Specifies the event name for normalize.\r\n\t */\r\n\tNORMALIZE: 'normalize',\r\n\r\n\t/**\r\n\t * Variable: MAXIMIZE\r\n\t *\r\n\t * Specifies the event name for maximize.\r\n\t */\r\n\tMAXIMIZE: 'maximize',\r\n\r\n\t/**\r\n\t * Variable: HIDE\r\n\t *\r\n\t * Specifies the event name for hide.\r\n\t */\r\n\tHIDE: 'hide',\r\n\r\n\t/**\r\n\t * Variable: SHOW\r\n\t *\r\n\t * Specifies the event name for show.\r\n\t */\r\n\tSHOW: 'show',\r\n\r\n\t/**\r\n\t * Variable: CLOSE\r\n\t *\r\n\t * Specifies the event name for close.\r\n\t */\r\n\tCLOSE: 'close',\r\n\r\n\t/**\r\n\t * Variable: DESTROY\r\n\t *\r\n\t * Specifies the event name for destroy.\r\n\t */\r\n\tDESTROY: 'destroy',\r\n\r\n\t/**\r\n\t * Variable: REFRESH\r\n\t *\r\n\t * Specifies the event name for refresh.\r\n\t */\r\n\tREFRESH: 'refresh',\r\n\r\n\t/**\r\n\t * Variable: SIZE\r\n\t *\r\n\t * Specifies the event name for size.\r\n\t */\r\n\tSIZE: 'size',\r\n\t\r\n\t/**\r\n\t * Variable: SELECT\r\n\t *\r\n\t * Specifies the event name for select.\r\n\t */\r\n\tSELECT: 'select',\r\n\r\n\t/**\r\n\t * Variable: FIRED\r\n\t *\r\n\t * Specifies the event name for fired.\r\n\t */\r\n\tFIRED: 'fired',\r\n\r\n\t/**\r\n\t * Variable: FIRE_MOUSE_EVENT\r\n\t *\r\n\t * Specifies the event name for fireMouseEvent.\r\n\t */\r\n\tFIRE_MOUSE_EVENT: 'fireMouseEvent',\r\n\r\n\t/**\r\n\t * Variable: GESTURE\r\n\t *\r\n\t * Specifies the event name for gesture.\r\n\t */\r\n\tGESTURE: 'gesture',\r\n\r\n\t/**\r\n\t * Variable: TAP_AND_HOLD\r\n\t *\r\n\t * Specifies the event name for tapAndHold.\r\n\t */\r\n\tTAP_AND_HOLD: 'tapAndHold',\r\n\r\n\t/**\r\n\t * Variable: GET\r\n\t *\r\n\t * Specifies the event name for get.\r\n\t */\r\n\tGET: 'get',\r\n\r\n\t/**\r\n\t * Variable: RECEIVE\r\n\t *\r\n\t * Specifies the event name for receive.\r\n\t */\r\n\tRECEIVE: 'receive',\r\n\r\n\t/**\r\n\t * Variable: CONNECT\r\n\t *\r\n\t * Specifies the event name for connect.\r\n\t */\r\n\tCONNECT: 'connect',\r\n\r\n\t/**\r\n\t * Variable: DISCONNECT\r\n\t *\r\n\t * Specifies the event name for disconnect.\r\n\t */\r\n\tDISCONNECT: 'disconnect',\r\n\r\n\t/**\r\n\t * Variable: SUSPEND\r\n\t *\r\n\t * Specifies the event name for suspend.\r\n\t */\r\n\tSUSPEND: 'suspend',\r\n\r\n\t/**\r\n\t * Variable: RESUME\r\n\t *\r\n\t * Specifies the event name for suspend.\r\n\t */\r\n\tRESUME: 'resume',\r\n\r\n\t/**\r\n\t * Variable: MARK\r\n\t *\r\n\t * Specifies the event name for mark.\r\n\t */\r\n\tMARK: 'mark',\r\n\r\n\t/**\r\n\t * Variable: ROOT\r\n\t *\r\n\t * Specifies the event name for root.\r\n\t */\r\n\tROOT: 'root',\r\n\r\n\t/**\r\n\t * Variable: POST\r\n\t *\r\n\t * Specifies the event name for post.\r\n\t */\r\n\tPOST: 'post',\r\n\r\n\t/**\r\n\t * Variable: OPEN\r\n\t *\r\n\t * Specifies the event name for open.\r\n\t */\r\n\tOPEN: 'open',\r\n\r\n\t/**\r\n\t * Variable: SAVE\r\n\t *\r\n\t * Specifies the event name for open.\r\n\t */\r\n\tSAVE: 'save',\r\n\r\n\t/**\r\n\t * Variable: BEFORE_ADD_VERTEX\r\n\t *\r\n\t * Specifies the event name for beforeAddVertex.\r\n\t */\r\n\tBEFORE_ADD_VERTEX: 'beforeAddVertex',\r\n\r\n\t/**\r\n\t * Variable: ADD_VERTEX\r\n\t *\r\n\t * Specifies the event name for addVertex.\r\n\t */\r\n\tADD_VERTEX: 'addVertex',\r\n\r\n\t/**\r\n\t * Variable: AFTER_ADD_VERTEX\r\n\t *\r\n\t * Specifies the event name for afterAddVertex.\r\n\t */\r\n\tAFTER_ADD_VERTEX: 'afterAddVertex',\r\n\r\n\t/**\r\n\t * Variable: DONE\r\n\t *\r\n\t * Specifies the event name for done.\r\n\t */\r\n\tDONE: 'done',\r\n\r\n\t/**\r\n\t * Variable: EXECUTE\r\n\t *\r\n\t * Specifies the event name for execute.\r\n\t */\r\n\tEXECUTE: 'execute',\r\n\r\n\t/**\r\n\t * Variable: EXECUTED\r\n\t *\r\n\t * Specifies the event name for executed.\r\n\t */\r\n\tEXECUTED: 'executed',\r\n\r\n\t/**\r\n\t * Variable: BEGIN_UPDATE\r\n\t *\r\n\t * Specifies the event name for beginUpdate.\r\n\t */\r\n\tBEGIN_UPDATE: 'beginUpdate',\r\n\r\n\t/**\r\n\t * Variable: START_EDIT\r\n\t *\r\n\t * Specifies the event name for startEdit.\r\n\t */\r\n\tSTART_EDIT: 'startEdit',\r\n\r\n\t/**\r\n\t * Variable: END_UPDATE\r\n\t *\r\n\t * Specifies the event name for endUpdate.\r\n\t */\r\n\tEND_UPDATE: 'endUpdate',\r\n\r\n\t/**\r\n\t * Variable: END_EDIT\r\n\t *\r\n\t * Specifies the event name for endEdit.\r\n\t */\r\n\tEND_EDIT: 'endEdit',\r\n\r\n\t/**\r\n\t * Variable: BEFORE_UNDO\r\n\t *\r\n\t * Specifies the event name for beforeUndo.\r\n\t */\r\n\tBEFORE_UNDO: 'beforeUndo',\r\n\r\n\t/**\r\n\t * Variable: UNDO\r\n\t *\r\n\t * Specifies the event name for undo.\r\n\t */\r\n\tUNDO: 'undo',\r\n\r\n\t/**\r\n\t * Variable: REDO\r\n\t *\r\n\t * Specifies the event name for redo.\r\n\t */\r\n\tREDO: 'redo',\r\n\r\n\t/**\r\n\t * Variable: CHANGE\r\n\t *\r\n\t * Specifies the event name for change.\r\n\t */\r\n\tCHANGE: 'change',\r\n\r\n\t/**\r\n\t * Variable: NOTIFY\r\n\t *\r\n\t * Specifies the event name for notify.\r\n\t */\r\n\tNOTIFY: 'notify',\r\n\r\n\t/**\r\n\t * Variable: LAYOUT_CELLS\r\n\t *\r\n\t * Specifies the event name for layoutCells.\r\n\t */\r\n\tLAYOUT_CELLS: 'layoutCells',\r\n\r\n\t/**\r\n\t * Variable: CLICK\r\n\t *\r\n\t * Specifies the event name for click.\r\n\t */\r\n\tCLICK: 'click',\r\n\r\n\t/**\r\n\t * Variable: SCALE\r\n\t *\r\n\t * Specifies the event name for scale.\r\n\t */\r\n\tSCALE: 'scale',\r\n\r\n\t/**\r\n\t * Variable: TRANSLATE\r\n\t *\r\n\t * Specifies the event name for translate.\r\n\t */\r\n\tTRANSLATE: 'translate',\r\n\r\n\t/**\r\n\t * Variable: SCALE_AND_TRANSLATE\r\n\t *\r\n\t * Specifies the event name for scaleAndTranslate.\r\n\t */\r\n\tSCALE_AND_TRANSLATE: 'scaleAndTranslate',\r\n\r\n\t/**\r\n\t * Variable: UP\r\n\t *\r\n\t * Specifies the event name for up.\r\n\t */\r\n\tUP: 'up',\r\n\r\n\t/**\r\n\t * Variable: DOWN\r\n\t *\r\n\t * Specifies the event name for down.\r\n\t */\r\n\tDOWN: 'down',\r\n\r\n\t/**\r\n\t * Variable: ADD\r\n\t *\r\n\t * Specifies the event name for add.\r\n\t */\r\n\tADD: 'add',\r\n\r\n\t/**\r\n\t * Variable: REMOVE\r\n\t *\r\n\t * Specifies the event name for remove.\r\n\t */\r\n\tREMOVE: 'remove',\r\n\t\r\n\t/**\r\n\t * Variable: CLEAR\r\n\t *\r\n\t * Specifies the event name for clear.\r\n\t */\r\n\tCLEAR: 'clear',\r\n\r\n\t/**\r\n\t * Variable: ADD_CELLS\r\n\t *\r\n\t * Specifies the event name for addCells.\r\n\t */\r\n\tADD_CELLS: 'addCells',\r\n\r\n\t/**\r\n\t * Variable: CELLS_ADDED\r\n\t *\r\n\t * Specifies the event name for cellsAdded.\r\n\t */\r\n\tCELLS_ADDED: 'cellsAdded',\r\n\r\n\t/**\r\n\t * Variable: MOVE_CELLS\r\n\t *\r\n\t * Specifies the event name for moveCells.\r\n\t */\r\n\tMOVE_CELLS: 'moveCells',\r\n\r\n\t/**\r\n\t * Variable: CELLS_MOVED\r\n\t *\r\n\t * Specifies the event name for cellsMoved.\r\n\t */\r\n\tCELLS_MOVED: 'cellsMoved',\r\n\r\n\t/**\r\n\t * Variable: RESIZE_CELLS\r\n\t *\r\n\t * Specifies the event name for resizeCells.\r\n\t */\r\n\tRESIZE_CELLS: 'resizeCells',\r\n\r\n\t/**\r\n\t * Variable: CELLS_RESIZED\r\n\t *\r\n\t * Specifies the event name for cellsResized.\r\n\t */\r\n\tCELLS_RESIZED: 'cellsResized',\r\n\r\n\t/**\r\n\t * Variable: TOGGLE_CELLS\r\n\t *\r\n\t * Specifies the event name for toggleCells.\r\n\t */\r\n\tTOGGLE_CELLS: 'toggleCells',\r\n\r\n\t/**\r\n\t * Variable: CELLS_TOGGLED\r\n\t *\r\n\t * Specifies the event name for cellsToggled.\r\n\t */\r\n\tCELLS_TOGGLED: 'cellsToggled',\r\n\r\n\t/**\r\n\t * Variable: ORDER_CELLS\r\n\t *\r\n\t * Specifies the event name for orderCells.\r\n\t */\r\n\tORDER_CELLS: 'orderCells',\r\n\r\n\t/**\r\n\t * Variable: CELLS_ORDERED\r\n\t *\r\n\t * Specifies the event name for cellsOrdered.\r\n\t */\r\n\tCELLS_ORDERED: 'cellsOrdered',\r\n\r\n\t/**\r\n\t * Variable: REMOVE_CELLS\r\n\t *\r\n\t * Specifies the event name for removeCells.\r\n\t */\r\n\tREMOVE_CELLS: 'removeCells',\r\n\r\n\t/**\r\n\t * Variable: CELLS_REMOVED\r\n\t *\r\n\t * Specifies the event name for cellsRemoved.\r\n\t */\r\n\tCELLS_REMOVED: 'cellsRemoved',\r\n\r\n\t/**\r\n\t * Variable: GROUP_CELLS\r\n\t *\r\n\t * Specifies the event name for groupCells.\r\n\t */\r\n\tGROUP_CELLS: 'groupCells',\r\n\r\n\t/**\r\n\t * Variable: UNGROUP_CELLS\r\n\t *\r\n\t * Specifies the event name for ungroupCells.\r\n\t */\r\n\tUNGROUP_CELLS: 'ungroupCells',\r\n\r\n\t/**\r\n\t * Variable: REMOVE_CELLS_FROM_PARENT\r\n\t *\r\n\t * Specifies the event name for removeCellsFromParent.\r\n\t */\r\n\tREMOVE_CELLS_FROM_PARENT: 'removeCellsFromParent',\r\n\r\n\t/**\r\n\t * Variable: FOLD_CELLS\r\n\t *\r\n\t * Specifies the event name for foldCells.\r\n\t */\r\n\tFOLD_CELLS: 'foldCells',\r\n\r\n\t/**\r\n\t * Variable: CELLS_FOLDED\r\n\t *\r\n\t * Specifies the event name for cellsFolded.\r\n\t */\r\n\tCELLS_FOLDED: 'cellsFolded',\r\n\r\n\t/**\r\n\t * Variable: ALIGN_CELLS\r\n\t *\r\n\t * Specifies the event name for alignCells.\r\n\t */\r\n\tALIGN_CELLS: 'alignCells',\r\n\r\n\t/**\r\n\t * Variable: LABEL_CHANGED\r\n\t *\r\n\t * Specifies the event name for labelChanged.\r\n\t */\r\n\tLABEL_CHANGED: 'labelChanged',\r\n\r\n\t/**\r\n\t * Variable: CONNECT_CELL\r\n\t *\r\n\t * Specifies the event name for connectCell.\r\n\t */\r\n\tCONNECT_CELL: 'connectCell',\r\n\r\n\t/**\r\n\t * Variable: CELL_CONNECTED\r\n\t *\r\n\t * Specifies the event name for cellConnected.\r\n\t */\r\n\tCELL_CONNECTED: 'cellConnected',\r\n\r\n\t/**\r\n\t * Variable: SPLIT_EDGE\r\n\t *\r\n\t * Specifies the event name for splitEdge.\r\n\t */\r\n\tSPLIT_EDGE: 'splitEdge',\r\n\r\n\t/**\r\n\t * Variable: FLIP_EDGE\r\n\t *\r\n\t * Specifies the event name for flipEdge.\r\n\t */\r\n\tFLIP_EDGE: 'flipEdge',\r\n\r\n\t/**\r\n\t * Variable: START_EDITING\r\n\t *\r\n\t * Specifies the event name for startEditing.\r\n\t */\r\n\tSTART_EDITING: 'startEditing',\r\n\r\n\t/**\r\n\t * Variable: EDITING_STARTED\r\n\t *\r\n\t * Specifies the event name for editingStarted.\r\n\t */\r\n\tEDITING_STARTED: 'editingStarted',\r\n\r\n\t/**\r\n\t * Variable: EDITING_STOPPED\r\n\t *\r\n\t * Specifies the event name for editingStopped.\r\n\t */\r\n\tEDITING_STOPPED: 'editingStopped',\r\n\r\n\t/**\r\n\t * Variable: ADD_OVERLAY\r\n\t *\r\n\t * Specifies the event name for addOverlay.\r\n\t */\r\n\tADD_OVERLAY: 'addOverlay',\r\n\r\n\t/**\r\n\t * Variable: REMOVE_OVERLAY\r\n\t *\r\n\t * Specifies the event name for removeOverlay.\r\n\t */\r\n\tREMOVE_OVERLAY: 'removeOverlay',\r\n\r\n\t/**\r\n\t * Variable: UPDATE_CELL_SIZE\r\n\t *\r\n\t * Specifies the event name for updateCellSize.\r\n\t */\r\n\tUPDATE_CELL_SIZE: 'updateCellSize',\r\n\r\n\t/**\r\n\t * Variable: ESCAPE\r\n\t *\r\n\t * Specifies the event name for escape.\r\n\t */\r\n\tESCAPE: 'escape',\r\n\r\n\t/**\r\n\t * Variable: DOUBLE_CLICK\r\n\t *\r\n\t * Specifies the event name for doubleClick.\r\n\t */\r\n\tDOUBLE_CLICK: 'doubleClick',\r\n\r\n\t/**\r\n\t * Variable: START\r\n\t *\r\n\t * Specifies the event name for start.\r\n\t */\r\n\tSTART: 'start',\r\n\r\n\t/**\r\n\t * Variable: RESET\r\n\t *\r\n\t * Specifies the event name for reset.\r\n\t */\r\n\tRESET: 'reset'\r\n\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxXmlRequest\r\n * \r\n * XML HTTP request wrapper. See also: <mxUtils.get>, <mxUtils.post> and\r\n * <mxUtils.load>. This class provides a cross-browser abstraction for Ajax\r\n * requests.\r\n * \r\n * Encoding:\r\n * \r\n * For encoding parameter values, the built-in encodeURIComponent JavaScript\r\n * method must be used. For automatic encoding of post data in <mxEditor> the\r\n * <mxEditor.escapePostData> switch can be set to true (default). The encoding\r\n * will be carried out using the conte type of the page. That is, the page\r\n * containting the editor should contain a meta tag in the header, eg.\r\n * <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * var onload = function(req)\r\n * {\r\n *   mxUtils.alert(req.getDocumentElement());\r\n * }\r\n * \r\n * var onerror = function(req)\r\n * {\r\n *   mxUtils.alert('Error');\r\n * }\r\n * new mxXmlRequest(url, 'key=value').send(onload, onerror);\r\n * (end)\r\n * \r\n * Sends an asynchronous POST request to the specified URL.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * var req = new mxXmlRequest(url, 'key=value', 'POST', false);\r\n * req.send();\r\n * mxUtils.alert(req.getDocumentElement());\r\n * (end)\r\n * \r\n * Sends a synchronous POST request to the specified URL.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * var encoder = new mxCodec();\r\n * var result = encoder.encode(graph.getModel());\r\n * var xml = encodeURIComponent(mxUtils.getXml(result));\r\n * new mxXmlRequest(url, 'xml='+xml).send();\r\n * (end)\r\n * \r\n * Sends an encoded graph model to the specified URL using xml as the\r\n * parameter name. The parameter can then be retrieved in C# as follows:\r\n * \r\n * (code)\r\n * string xml = HttpUtility.UrlDecode(context.Request.Params[\"xml\"]);\r\n * (end)\r\n * \r\n * Or in Java as follows:\r\n * \r\n * (code)\r\n * String xml = URLDecoder.decode(request.getParameter(\"xml\"), \"UTF-8\").replace(\"\\n\", \"&#xa;\");\r\n * (end)\r\n *\r\n * Note that the linefeeds should only be replaced if the XML is\r\n * processed in Java, for example when creating an image.\r\n * \r\n * Constructor: mxXmlRequest\r\n * \r\n * Constructs an XML HTTP request.\r\n * \r\n * Parameters:\r\n * \r\n * url - Target URL of the request.\r\n * params - Form encoded parameters to send with a POST request.\r\n * method - String that specifies the request method. Possible values are\r\n * POST and GET. Default is POST.\r\n * async - Boolean specifying if an asynchronous request should be used.\r\n * Default is true.\r\n * username - String specifying the username to be used for the request.\r\n * password - String specifying the password to be used for the request.\r\n */\r\nfunction mxXmlRequest(url, params, method, async, username, password)\r\n{\r\n\tthis.url = url;\r\n\tthis.params = params;\r\n\tthis.method = method || 'POST';\r\n\tthis.async = (async != null) ? async : true;\r\n\tthis.username = username;\r\n\tthis.password = password;\r\n};\r\n\r\n/**\r\n * Variable: url\r\n * \r\n * Holds the target URL of the request.\r\n */\r\nmxXmlRequest.prototype.url = null;\r\n\r\n/**\r\n * Variable: params\r\n * \r\n * Holds the form encoded data for the POST request.\r\n */\r\nmxXmlRequest.prototype.params = null;\r\n\r\n/**\r\n * Variable: method\r\n * \r\n * Specifies the request method. Possible values are POST and GET. Default\r\n * is POST.\r\n */\r\nmxXmlRequest.prototype.method = null;\r\n\r\n/**\r\n * Variable: async\r\n * \r\n * Boolean indicating if the request is asynchronous.\r\n */\r\nmxXmlRequest.prototype.async = null;\r\n\r\n/**\r\n * Variable: binary\r\n * \r\n * Boolean indicating if the request is binary. This option is ignored in IE.\r\n * In all other browsers the requested mime type is set to\r\n * text/plain; charset=x-user-defined. Default is false.\r\n */\r\nmxXmlRequest.prototype.binary = false;\r\n\r\n/**\r\n * Variable: withCredentials\r\n * \r\n * Specifies if withCredentials should be used in HTML5-compliant browsers. Default is\r\n * false.\r\n */\r\nmxXmlRequest.prototype.withCredentials = false;\r\n\r\n/**\r\n * Variable: username\r\n * \r\n * Specifies the username to be used for authentication.\r\n */\r\nmxXmlRequest.prototype.username = null;\r\n\r\n/**\r\n * Variable: password\r\n * \r\n * Specifies the password to be used for authentication.\r\n */\r\nmxXmlRequest.prototype.password = null;\r\n\r\n/**\r\n * Variable: request\r\n * \r\n * Holds the inner, browser-specific request object.\r\n */\r\nmxXmlRequest.prototype.request = null;\r\n\r\n/**\r\n * Variable: decodeSimulateValues\r\n * \r\n * Specifies if request values should be decoded as URIs before setting the\r\n * textarea value in <simulate>. Defaults to false for backwards compatibility,\r\n * to avoid another decode on the server this should be set to true.\r\n */\r\nmxXmlRequest.prototype.decodeSimulateValues = false;\r\n\r\n/**\r\n * Function: isBinary\r\n * \r\n * Returns <binary>.\r\n */\r\nmxXmlRequest.prototype.isBinary = function()\r\n{\r\n\treturn this.binary;\r\n};\r\n\r\n/**\r\n * Function: setBinary\r\n * \r\n * Sets <binary>.\r\n */\r\nmxXmlRequest.prototype.setBinary = function(value)\r\n{\r\n\tthis.binary = value;\r\n};\r\n\r\n/**\r\n * Function: getText\r\n * \r\n * Returns the response as a string.\r\n */\r\nmxXmlRequest.prototype.getText = function()\r\n{\r\n\treturn this.request.responseText;\r\n};\r\n\r\n/**\r\n * Function: isReady\r\n * \r\n * Returns true if the response is ready.\r\n */\r\nmxXmlRequest.prototype.isReady = function()\r\n{\r\n\treturn this.request.readyState == 4;\r\n};\r\n\r\n/**\r\n * Function: getDocumentElement\r\n * \r\n * Returns the document element of the response XML document.\r\n */\r\nmxXmlRequest.prototype.getDocumentElement = function()\r\n{\r\n\tvar doc = this.getXml();\r\n\t\r\n\tif (doc != null)\r\n\t{\r\n\t\treturn doc.documentElement;\r\n\t}\r\n\t\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: getXml\r\n * \r\n * Returns the response as an XML document. Use <getDocumentElement> to get\r\n * the document element of the XML document.\r\n */\r\nmxXmlRequest.prototype.getXml = function()\r\n{\r\n\tvar xml = this.request.responseXML;\r\n\t\r\n\t// Handles missing response headers in IE, the first condition handles\r\n\t// the case where responseXML is there, but using its nodes leads to\r\n\t// type errors in the mxCellCodec when putting the nodes into a new\r\n\t// document. This happens in IE9 standards mode and with XML user\r\n\t// objects only, as they are used directly as values in cells.\r\n\tif (document.documentMode >= 9 || xml == null || xml.documentElement == null)\r\n\t{\r\n\t\txml = mxUtils.parseXml(this.request.responseText);\r\n\t}\r\n\t\r\n\treturn xml;\r\n};\r\n\r\n/**\r\n * Function: getText\r\n * \r\n * Returns the response as a string.\r\n */\r\nmxXmlRequest.prototype.getText = function()\r\n{\r\n\treturn this.request.responseText;\r\n};\r\n\r\n/**\r\n * Function: getStatus\r\n * \r\n * Returns the status as a number, eg. 404 for \"Not found\" or 200 for \"OK\".\r\n * Note: The NS_ERROR_NOT_AVAILABLE for invalid responses cannot be cought.\r\n */\r\nmxXmlRequest.prototype.getStatus = function()\r\n{\r\n\treturn this.request.status;\r\n};\r\n\r\n/**\r\n * Function: create\r\n * \r\n * Creates and returns the inner <request> object.\r\n */\r\nmxXmlRequest.prototype.create = function()\r\n{\r\n\tif (window.XMLHttpRequest)\r\n\t{\r\n\t\treturn function()\r\n\t\t{\r\n\t\t\tvar req = new XMLHttpRequest();\r\n\t\t\t\r\n\t\t\t// TODO: Check for overrideMimeType required here?\r\n\t\t\tif (this.isBinary() && req.overrideMimeType)\r\n\t\t\t{\r\n\t\t\t\treq.overrideMimeType('text/plain; charset=x-user-defined');\r\n\t\t\t}\r\n\r\n\t\t\treturn req;\r\n\t\t};\r\n\t}\r\n\telse if (typeof(ActiveXObject) != 'undefined')\r\n\t{\r\n\t\treturn function()\r\n\t\t{\r\n\t\t\t// TODO: Implement binary option\r\n\t\t\treturn new ActiveXObject('Microsoft.XMLHTTP');\r\n\t\t};\r\n\t}\r\n}();\r\n\r\n/**\r\n * Function: send\r\n * \r\n * Send the <request> to the target URL using the specified functions to\r\n * process the response asychronously.\r\n * \r\n * Note: Due to technical limitations, onerror is currently ignored.\r\n * \r\n * Parameters:\r\n * \r\n * onload - Function to be invoked if a successful response was received.\r\n * onerror - Function to be called on any error.\r\n * timeout - Optional timeout in ms before calling ontimeout.\r\n * ontimeout - Optional function to execute on timeout.\r\n */\r\nmxXmlRequest.prototype.send = function(onload, onerror, timeout, ontimeout)\r\n{\r\n\tthis.request = this.create();\r\n\t\r\n\tif (this.request != null)\r\n\t{\r\n\t\tif (onload != null)\r\n\t\t{\r\n\t\t\tthis.request.onreadystatechange = mxUtils.bind(this, function()\r\n\t\t\t{\r\n\t\t\t\tif (this.isReady())\r\n\t\t\t\t{\r\n\t\t\t\t\tonload(this);\r\n\t\t\t\t\tthis.request.onreadystatechaange = null;\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\tthis.request.open(this.method, this.url, this.async,\r\n\t\t\tthis.username, this.password);\r\n\t\tthis.setRequestHeaders(this.request, this.params);\r\n\t\t\r\n\t\tif (window.XMLHttpRequest && this.withCredentials)\r\n\t\t{\r\n\t\t\tthis.request.withCredentials = 'true';\r\n\t\t}\r\n\t\t\r\n\t\tif (!mxClient.IS_QUIRKS && (document.documentMode == null || document.documentMode > 9) &&\r\n\t\t\twindow.XMLHttpRequest && timeout != null && ontimeout != null)\r\n\t\t{\r\n\t\t\tthis.request.timeout = timeout;\r\n\t\t\tthis.request.ontimeout = ontimeout;\r\n\t\t}\r\n\t\t\t\t\r\n\t\tthis.request.send(this.params);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setRequestHeaders\r\n * \r\n * Sets the headers for the given request and parameters. This sets the\r\n * content-type to application/x-www-form-urlencoded if any params exist.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * request.setRequestHeaders = function(request, params)\r\n * {\r\n *   if (params != null)\r\n *   {\r\n *     request.setRequestHeader('Content-Type',\r\n *             'multipart/form-data');\r\n *     request.setRequestHeader('Content-Length',\r\n *             params.length);\r\n *   }\r\n * };\r\n * (end)\r\n * \r\n * Use the code above before calling <send> if you require a\r\n * multipart/form-data request.   \r\n */\r\nmxXmlRequest.prototype.setRequestHeaders = function(request, params)\r\n{\r\n\tif (params != null)\r\n\t{\r\n\t\trequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: simulate\r\n * \r\n * Creates and posts a request to the given target URL using a dynamically\r\n * created form inside the given document.\r\n * \r\n * Parameters:\r\n * \r\n * docs - Document that contains the form element.\r\n * target - Target to send the form result to.\r\n */\r\nmxXmlRequest.prototype.simulate = function(doc, target)\r\n{\r\n\tdoc = doc || document;\r\n\tvar old = null;\r\n\r\n\tif (doc == document)\r\n\t{\r\n\t\told = window.onbeforeunload;\t\t\r\n\t\twindow.onbeforeunload = null;\r\n\t}\r\n\t\t\t\r\n\tvar form = doc.createElement('form');\r\n\tform.setAttribute('method', this.method);\r\n\tform.setAttribute('action', this.url);\r\n\r\n\tif (target != null)\r\n\t{\r\n\t\tform.setAttribute('target', target);\r\n\t}\r\n\r\n\tform.style.display = 'none';\r\n\tform.style.visibility = 'hidden';\r\n\t\r\n\tvar pars = (this.params.indexOf('&') > 0) ?\r\n\t\tthis.params.split('&') :\r\n\t\tthis.params.split();\r\n\r\n\t// Adds the parameters as textareas to the form\r\n\tfor (var i=0; i<pars.length; i++)\r\n\t{\r\n\t\tvar pos = pars[i].indexOf('=');\r\n\t\t\r\n\t\tif (pos > 0)\r\n\t\t{\r\n\t\t\tvar name = pars[i].substring(0, pos);\r\n\t\t\tvar value = pars[i].substring(pos+1);\r\n\t\t\t\r\n\t\t\tif (this.decodeSimulateValues)\r\n\t\t\t{\r\n\t\t\t\tvalue = decodeURIComponent(value);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar textarea = doc.createElement('textarea');\r\n\t\t\ttextarea.setAttribute('wrap', 'off');\r\n\t\t\ttextarea.setAttribute('name', name);\r\n\t\t\tmxUtils.write(textarea, value);\r\n\t\t\tform.appendChild(textarea);\r\n\t\t}\r\n\t}\r\n\t\r\n\tdoc.body.appendChild(form);\r\n\tform.submit();\r\n\t\r\n\tif (form.parentNode != null)\r\n\t{\r\n\t\tform.parentNode.removeChild(form);\r\n\t}\r\n\r\n\tif (old != null)\r\n\t{\t\t\r\n\t\twindow.onbeforeunload = old;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nvar mxClipboard =\r\n{\r\n\t/**\r\n\t * Class: mxClipboard\r\n\t * \r\n\t * Singleton that implements a clipboard for graph cells.\r\n\t *\r\n\t * Example:\r\n\t * \r\n\t * (code)\r\n\t * mxClipboard.copy(graph);\r\n\t * mxClipboard.paste(graph2);\r\n\t * (end)\r\n\t *\r\n\t * This copies the selection cells from the graph to the clipboard and\r\n\t * pastes them into graph2.\r\n\t * \r\n\t * For fine-grained control of the clipboard data the <mxGraph.canExportCell>\r\n\t * and <mxGraph.canImportCell> functions can be overridden.\r\n\t * \r\n\t * To restore previous parents for pasted cells, the implementation for\r\n\t * <copy> and <paste> can be changed as follows.\r\n\t * \r\n\t * (code)\r\n\t * mxClipboard.copy = function(graph, cells)\r\n\t * {\r\n\t *   cells = cells || graph.getSelectionCells();\r\n\t *   var result = graph.getExportableCells(cells);\r\n\t *   \r\n\t *   mxClipboard.parents = new Object();\r\n\t *   \r\n\t *   for (var i = 0; i < result.length; i++)\r\n\t *   {\r\n\t *     mxClipboard.parents[i] = graph.model.getParent(cells[i]);\r\n\t *   }\r\n\t *   \r\n\t *   mxClipboard.insertCount = 1;\r\n\t *   mxClipboard.setCells(graph.cloneCells(result));\r\n\t *   \r\n\t *   return result;\r\n\t * };\r\n\t * \r\n\t * mxClipboard.paste = function(graph)\r\n\t * {\r\n\t *   if (!mxClipboard.isEmpty())\r\n\t *   {\r\n\t *     var cells = graph.getImportableCells(mxClipboard.getCells());\r\n\t *     var delta = mxClipboard.insertCount * mxClipboard.STEPSIZE;\r\n\t *     var parent = graph.getDefaultParent();\r\n\t *     \r\n\t *     graph.model.beginUpdate();\r\n\t *     try\r\n\t *     {\r\n\t *       for (var i = 0; i < cells.length; i++)\r\n\t *       {\r\n\t *         var tmp = (mxClipboard.parents != null && graph.model.contains(mxClipboard.parents[i])) ?\r\n\t *              mxClipboard.parents[i] : parent;\r\n\t *         cells[i] = graph.importCells([cells[i]], delta, delta, tmp)[0];\r\n\t *       }\r\n\t *     }\r\n\t *     finally\r\n\t *     {\r\n\t *       graph.model.endUpdate();\r\n\t *     }\r\n\t *     \r\n\t *     // Increments the counter and selects the inserted cells\r\n\t *     mxClipboard.insertCount++;\r\n\t *     graph.setSelectionCells(cells);\r\n\t *   }\r\n\t * };\r\n\t * (end)\r\n\t * \r\n\t * Variable: STEPSIZE\r\n\t * \r\n\t * Defines the step size to offset the cells after each paste operation.\r\n\t * Default is 10.\r\n\t */\r\n\tSTEPSIZE: 10,\r\n\r\n\t/**\r\n\t * Variable: insertCount\r\n\t * \r\n\t * Counts the number of times the clipboard data has been inserted.\r\n\t */\r\n\tinsertCount: 1,\r\n\r\n\t/**\r\n\t * Variable: cells\r\n\t * \r\n\t * Holds the array of <mxCells> currently in the clipboard.\r\n\t */\r\n\tcells: null,\r\n\r\n\t/**\r\n\t * Function: setCells\r\n\t * \r\n\t * Sets the cells in the clipboard. Fires a <mxEvent.CHANGE> event.\r\n\t */\r\n\tsetCells: function(cells)\r\n\t{\r\n\t\tmxClipboard.cells = cells;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: getCells\r\n\t * \r\n\t * Returns  the cells in the clipboard.\r\n\t */\r\n\tgetCells: function()\r\n\t{\r\n\t\treturn mxClipboard.cells;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: isEmpty\r\n\t * \r\n\t * Returns true if the clipboard currently has not data stored.\r\n\t */\r\n\tisEmpty: function()\r\n\t{\r\n\t\treturn mxClipboard.getCells() == null;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: cut\r\n\t * \r\n\t * Cuts the given array of <mxCells> from the specified graph.\r\n\t * If cells is null then the selection cells of the graph will\r\n\t * be used. Returns the cells that have been cut from the graph.\r\n\t *\r\n\t * Parameters:\r\n\t * \r\n\t * graph - <mxGraph> that contains the cells to be cut.\r\n\t * cells - Optional array of <mxCells> to be cut.\r\n\t */\r\n\tcut: function(graph, cells)\r\n\t{\r\n\t\tcells = mxClipboard.copy(graph, cells);\r\n\t\tmxClipboard.insertCount = 0;\r\n\t\tmxClipboard.removeCells(graph, cells);\r\n\t\t\r\n\t\treturn cells;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: removeCells\r\n\t * \r\n\t * Hook to remove the given cells from the given graph after\r\n\t * a cut operation.\r\n\t *\r\n\t * Parameters:\r\n\t * \r\n\t * graph - <mxGraph> that contains the cells to be cut.\r\n\t * cells - Array of <mxCells> to be cut.\r\n\t */\r\n\tremoveCells: function(graph, cells)\r\n\t{\r\n\t\tgraph.removeCells(cells);\r\n\t},\r\n\r\n\t/**\r\n\t * Function: copy\r\n\t * \r\n\t * Copies the given array of <mxCells> from the specified\r\n\t * graph to <cells>. Returns the original array of cells that has\r\n\t * been cloned. Descendants of cells in the array are ignored.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * graph - <mxGraph> that contains the cells to be copied.\r\n\t * cells - Optional array of <mxCells> to be copied.\r\n\t */\r\n\tcopy: function(graph, cells)\r\n\t{\r\n\t\tcells = cells || graph.getSelectionCells();\r\n\t\tvar result = graph.getExportableCells(graph.model.getTopmostCells(cells));\r\n\t\tmxClipboard.insertCount = 1;\r\n\t\tmxClipboard.setCells(graph.cloneCells(result));\r\n\r\n\t\treturn result;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: paste\r\n\t * \r\n\t * Pastes the <cells> into the specified graph restoring\r\n\t * the relation to <parents>, if possible. If the parents\r\n\t * are no longer in the graph or invisible then the\r\n\t * cells are added to the graph's default or into the\r\n\t * swimlane under the cell's new location if one exists.\r\n\t * The cells are added to the graph using <mxGraph.importCells>\r\n\t * and returned.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * graph - <mxGraph> to paste the <cells> into.\r\n\t */\r\n\tpaste: function(graph)\r\n\t{\r\n\t\tvar cells = null;\r\n\t\t\r\n\t\tif (!mxClipboard.isEmpty())\r\n\t\t{\r\n\t\t\tcells = graph.getImportableCells(mxClipboard.getCells());\r\n\t\t\tvar delta = mxClipboard.insertCount * mxClipboard.STEPSIZE;\r\n\t\t\tvar parent = graph.getDefaultParent();\r\n\t\t\tcells = graph.importCells(cells, delta, delta, parent);\r\n\t\t\t\r\n\t\t\t// Increments the counter and selects the inserted cells\r\n\t\t\tmxClipboard.insertCount++;\r\n\t\t\tgraph.setSelectionCells(cells);\r\n\t\t}\r\n\t\t\r\n\t\treturn cells;\r\n\t}\r\n\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxWindow\r\n * \r\n * Basic window inside a document.\r\n * \r\n * Examples:\r\n * \r\n * Creating a simple window.\r\n *\r\n * (code)\r\n * var tb = document.createElement('div');\r\n * var wnd = new mxWindow('Title', tb, 100, 100, 200, 200, true, true);\r\n * wnd.setVisible(true); \r\n * (end)\r\n *\r\n * Creating a window that contains an iframe. \r\n * \r\n * (code)\r\n * var frame = document.createElement('iframe');\r\n * frame.setAttribute('width', '192px');\r\n * frame.setAttribute('height', '172px');\r\n * frame.setAttribute('src', 'http://www.example.com/');\r\n * frame.style.backgroundColor = 'white';\r\n * \r\n * var w = document.body.clientWidth;\r\n * var h = (document.body.clientHeight || document.documentElement.clientHeight);\r\n * var wnd = new mxWindow('Title', frame, (w-200)/2, (h-200)/3, 200, 200);\r\n * wnd.setVisible(true);\r\n * (end)\r\n * \r\n * To limit the movement of a window, eg. to keep it from being moved beyond\r\n * the top, left corner the following method can be overridden (recommended):\r\n * \r\n * (code)\r\n * wnd.setLocation = function(x, y)\r\n * {\r\n *   x = Math.max(0, x);\r\n *   y = Math.max(0, y);\r\n *   mxWindow.prototype.setLocation.apply(this, arguments);\r\n * };\r\n * (end)\r\n * \r\n * Or the following event handler can be used:\r\n * \r\n * (code)\r\n * wnd.addListener(mxEvent.MOVE, function(e)\r\n * {\r\n *   wnd.setLocation(Math.max(0, wnd.getX()), Math.max(0, wnd.getY()));\r\n * });\r\n * (end)\r\n * \r\n * To keep a window inside the current window:\r\n * \r\n * (code)\r\n * mxEvent.addListener(window, 'resize', mxUtils.bind(this, function()\r\n * {\r\n *   var iw = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;\r\n *   var ih = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;\r\n *   \r\n *   var x = this.window.getX();\r\n *   var y = this.window.getY();\r\n *   \r\n *   if (x + this.window.table.clientWidth > iw)\r\n *   {\r\n *     x = Math.max(0, iw - this.window.table.clientWidth);\r\n *   }\r\n *   \r\n *   if (y + this.window.table.clientHeight > ih)\r\n *   {\r\n *     y = Math.max(0, ih - this.window.table.clientHeight);\r\n *   }\r\n *   \r\n *   if (this.window.getX() != x || this.window.getY() != y)\r\n *   {\r\n *     this.window.setLocation(x, y);\r\n *   }\r\n * }));\r\n * (end)\r\n *\r\n * Event: mxEvent.MOVE_START\r\n *\r\n * Fires before the window is moved. The <code>event</code> property contains\r\n * the corresponding mouse event.\r\n *\r\n * Event: mxEvent.MOVE\r\n *\r\n * Fires while the window is being moved. The <code>event</code> property\r\n * contains the corresponding mouse event.\r\n *\r\n * Event: mxEvent.MOVE_END\r\n *\r\n * Fires after the window is moved. The <code>event</code> property contains\r\n * the corresponding mouse event.\r\n *\r\n * Event: mxEvent.RESIZE_START\r\n *\r\n * Fires before the window is resized. The <code>event</code> property contains\r\n * the corresponding mouse event.\r\n *\r\n * Event: mxEvent.RESIZE\r\n *\r\n * Fires while the window is being resized. The <code>event</code> property\r\n * contains the corresponding mouse event.\r\n *\r\n * Event: mxEvent.RESIZE_END\r\n *\r\n * Fires after the window is resized. The <code>event</code> property contains\r\n * the corresponding mouse event.\r\n *\r\n * Event: mxEvent.MAXIMIZE\r\n * \r\n * Fires after the window is maximized. The <code>event</code> property\r\n * contains the corresponding mouse event.\r\n * \r\n * Event: mxEvent.MINIMIZE\r\n * \r\n * Fires after the window is minimized. The <code>event</code> property\r\n * contains the corresponding mouse event.\r\n * \r\n * Event: mxEvent.NORMALIZE\r\n * \r\n * Fires after the window is normalized, that is, it returned from\r\n * maximized or minimized state. The <code>event</code> property contains the\r\n * corresponding mouse event.\r\n *  \r\n * Event: mxEvent.ACTIVATE\r\n * \r\n * Fires after a window is activated. The <code>previousWindow</code> property\r\n * contains the previous window. The event sender is the active window.\r\n * \r\n * Event: mxEvent.SHOW\r\n * \r\n * Fires after the window is shown. This event has no properties.\r\n * \r\n * Event: mxEvent.HIDE\r\n * \r\n * Fires after the window is hidden. This event has no properties.\r\n * \r\n * Event: mxEvent.CLOSE\r\n * \r\n * Fires before the window is closed. The <code>event</code> property contains\r\n * the corresponding mouse event.\r\n * \r\n * Event: mxEvent.DESTROY\r\n * \r\n * Fires before the window is destroyed. This event has no properties.\r\n * \r\n * Constructor: mxWindow\r\n * \r\n * Constructs a new window with the given dimension and title to display\r\n * the specified content. The window elements use the given style as a\r\n * prefix for the classnames of the respective window elements, namely,\r\n * the window title and window pane. The respective postfixes are appended\r\n * to the given stylename as follows:\r\n * \r\n *   style - Base style for the window.\r\n *   style+Title - Style for the window title.\r\n *   style+Pane - Style for the window pane.\r\n * \r\n * The default value for style is mxWindow, resulting in the following\r\n * classnames for the window elements: mxWindow, mxWindowTitle and\r\n * mxWindowPane.\r\n * \r\n * If replaceNode is given then the window replaces the given DOM node in\r\n * the document.\r\n * \r\n * Parameters:\r\n * \r\n * title - String that represents the title of the new window.\r\n * content - DOM node that is used as the window content.\r\n * x - X-coordinate of the window location.\r\n * y - Y-coordinate of the window location.\r\n * width - Width of the window.\r\n * height - Optional height of the window. Default is to match the height\r\n * of the content at the specified width.\r\n * minimizable - Optional boolean indicating if the window is minimizable.\r\n * Default is true.\r\n * movable - Optional boolean indicating if the window is movable. Default\r\n * is true.\r\n * replaceNode - Optional DOM node that the window should replace.\r\n * style - Optional base classname for the window elements. Default is\r\n * mxWindow.\r\n */\r\nfunction mxWindow(title, content, x, y, width, height, minimizable, movable, replaceNode, style)\r\n{\r\n\tif (content != null)\r\n\t{\r\n\t\tminimizable = (minimizable != null) ? minimizable : true;\r\n\t\tthis.content = content;\r\n\t\tthis.init(x, y, width, height, style);\r\n\t\t\r\n\t\tthis.installMaximizeHandler();\r\n\t\tthis.installMinimizeHandler();\r\n\t\tthis.installCloseHandler();\r\n\t\tthis.setMinimizable(minimizable);\r\n\t\tthis.setTitle(title);\r\n\t\t\r\n\t\tif (movable == null || movable)\r\n\t\t{\r\n\t\t\tthis.installMoveHandler();\r\n\t\t}\r\n\r\n\t\tif (replaceNode != null && replaceNode.parentNode != null)\r\n\t\t{\r\n\t\t\treplaceNode.parentNode.replaceChild(this.div, replaceNode);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tdocument.body.appendChild(this.div);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Extends mxEventSource.\r\n */\r\nmxWindow.prototype = new mxEventSource();\r\nmxWindow.prototype.constructor = mxWindow;\r\n\r\n/**\r\n * Variable: closeImage\r\n * \r\n * URL of the image to be used for the close icon in the titlebar.\r\n */\r\nmxWindow.prototype.closeImage = mxClient.imageBasePath + '/close.gif';\r\n\r\n/**\r\n * Variable: minimizeImage\r\n * \r\n * URL of the image to be used for the minimize icon in the titlebar.\r\n */\r\nmxWindow.prototype.minimizeImage = mxClient.imageBasePath + '/minimize.gif';\r\n\t\r\n/**\r\n * Variable: normalizeImage\r\n * \r\n * URL of the image to be used for the normalize icon in the titlebar.\r\n */\r\nmxWindow.prototype.normalizeImage = mxClient.imageBasePath + '/normalize.gif';\r\n\t\r\n/**\r\n * Variable: maximizeImage\r\n * \r\n * URL of the image to be used for the maximize icon in the titlebar.\r\n */\r\nmxWindow.prototype.maximizeImage = mxClient.imageBasePath + '/maximize.gif';\r\n\r\n/**\r\n * Variable: normalizeImage\r\n * \r\n * URL of the image to be used for the resize icon.\r\n */\r\nmxWindow.prototype.resizeImage = mxClient.imageBasePath + '/resize.gif';\r\n\r\n/**\r\n * Variable: visible\r\n * \r\n * Boolean flag that represents the visible state of the window.\r\n */\r\nmxWindow.prototype.visible = false;\r\n\r\n/**\r\n * Variable: minimumSize\r\n * \r\n * <mxRectangle> that specifies the minimum width and height of the window.\r\n * Default is (50, 40).\r\n */\r\nmxWindow.prototype.minimumSize = new mxRectangle(0, 0, 50, 40);\r\n\r\n/**\r\n * Variable: destroyOnClose\r\n * \r\n * Specifies if the window should be destroyed when it is closed. If this\r\n * is false then the window is hidden using <setVisible>. Default is true.\r\n */\r\nmxWindow.prototype.destroyOnClose = true;\r\n\r\n/**\r\n * Variable: contentHeightCorrection\r\n * \r\n * Defines the correction factor for computing the height of the contentWrapper.\r\n * Default is 6 for IE 7/8 standards mode and 2 for all other browsers and modes.\r\n */\r\nmxWindow.prototype.contentHeightCorrection = (document.documentMode == 8 || document.documentMode == 7) ? 6 : 2;\r\n\r\n/**\r\n * Variable: title\r\n * \r\n * Reference to the DOM node (TD) that contains the title.\r\n */\r\nmxWindow.prototype.title = null;\r\n\r\n/**\r\n * Variable: content\r\n * \r\n * Reference to the DOM node that represents the window content.\r\n */\r\nmxWindow.prototype.content = null;\r\n\r\n/**\r\n * Function: init\r\n * \r\n * Initializes the DOM tree that represents the window.\r\n */\r\nmxWindow.prototype.init = function(x, y, width, height, style)\r\n{\r\n\tstyle = (style != null) ? style : 'mxWindow';\r\n\t\r\n\tthis.div = document.createElement('div');\r\n\tthis.div.className = style;\r\n\r\n\tthis.div.style.left = x + 'px';\r\n\tthis.div.style.top = y + 'px';\r\n\tthis.table = document.createElement('table');\r\n\tthis.table.className = style;\r\n\r\n\t// Disables built-in pan and zoom in IE10 and later\r\n\tif (mxClient.IS_POINTER)\r\n\t{\r\n\t\tthis.div.style.touchAction = 'none';\r\n\t}\r\n\t\r\n\t// Workaround for table size problems in FF\r\n\tif (width != null)\r\n\t{\r\n\t\tif (!mxClient.IS_QUIRKS)\r\n\t\t{\r\n\t\t\tthis.div.style.width = width + 'px'; \r\n\t\t}\r\n\t\t\r\n\t\tthis.table.style.width = width + 'px';\r\n\t} \r\n\t\r\n\tif (height != null)\r\n\t{\r\n\t\tif (!mxClient.IS_QUIRKS)\r\n\t\t{\r\n\t\t\tthis.div.style.height = height + 'px';\r\n\t\t}\r\n\t\t\r\n\t\tthis.table.style.height = height + 'px';\r\n\t}\t\t\r\n\t\r\n\t// Creates title row\r\n\tvar tbody = document.createElement('tbody');\r\n\tvar tr = document.createElement('tr');\r\n\t\r\n\tthis.title = document.createElement('td');\r\n\tthis.title.className = style + 'Title';\r\n\t\r\n\tthis.buttons = document.createElement('div');\r\n\tthis.buttons.style.position = 'absolute';\r\n\tthis.buttons.style.display = 'inline-block';\r\n\tthis.buttons.style.right = '4px';\r\n\tthis.buttons.style.top = '5px';\r\n\tthis.title.appendChild(this.buttons);\r\n\t\r\n\ttr.appendChild(this.title);\r\n\ttbody.appendChild(tr);\r\n\t\r\n\t// Creates content row and table cell\r\n\ttr = document.createElement('tr');\r\n\tthis.td = document.createElement('td');\r\n\tthis.td.className = style + 'Pane';\r\n\t\r\n\tif (document.documentMode == 7)\r\n\t{\r\n\t\tthis.td.style.height = '100%';\r\n\t}\r\n\r\n\tthis.contentWrapper = document.createElement('div');\r\n\tthis.contentWrapper.className = style + 'Pane';\r\n\tthis.contentWrapper.style.width = '100%';\r\n\tthis.contentWrapper.appendChild(this.content);\r\n\r\n\t// Workaround for div around div restricts height\r\n\t// of inner div if outerdiv has hidden overflow\r\n\tif (mxClient.IS_QUIRKS || this.content.nodeName.toUpperCase() != 'DIV')\r\n\t{\r\n\t\tthis.contentWrapper.style.height = '100%';\r\n\t}\r\n\r\n\t// Puts all content into the DOM\r\n\tthis.td.appendChild(this.contentWrapper);\r\n\ttr.appendChild(this.td);\r\n\ttbody.appendChild(tr);\r\n\tthis.table.appendChild(tbody);\r\n\tthis.div.appendChild(this.table);\r\n\t\r\n\t// Puts the window on top of other windows when clicked\r\n\tvar activator = mxUtils.bind(this, function(evt)\r\n\t{\r\n\t\tthis.activate();\r\n\t});\r\n\t\r\n\tmxEvent.addGestureListeners(this.title, activator);\r\n\tmxEvent.addGestureListeners(this.table, activator);\r\n\r\n\tthis.hide();\r\n};\r\n\r\n/**\r\n * Function: setTitle\r\n * \r\n * Sets the window title to the given string. HTML markup inside the title\r\n * will be escaped.\r\n */\r\nmxWindow.prototype.setTitle = function(title)\r\n{\r\n\t// Removes all text content nodes (normally just one)\r\n\tvar child = this.title.firstChild;\r\n\t\r\n\twhile (child != null)\r\n\t{\r\n\t\tvar next = child.nextSibling;\r\n\t\t\r\n\t\tif (child.nodeType == mxConstants.NODETYPE_TEXT)\r\n\t\t{\r\n\t\t\tchild.parentNode.removeChild(child);\r\n\t\t}\r\n\t\t\r\n\t\tchild = next;\r\n\t}\r\n\t\r\n\tmxUtils.write(this.title, title || '');\r\n\tthis.title.appendChild(this.buttons);\r\n};\r\n\r\n/**\r\n * Function: setScrollable\r\n * \r\n * Sets if the window contents should be scrollable.\r\n */\r\nmxWindow.prototype.setScrollable = function(scrollable)\r\n{\r\n\t// Workaround for hang in Presto 2.5.22 (Opera 10.5)\r\n\tif (navigator.userAgent.indexOf('Presto/2.5') < 0)\r\n\t{\r\n\t\tif (scrollable)\r\n\t\t{\r\n\t\t\tthis.contentWrapper.style.overflow = 'auto';\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.contentWrapper.style.overflow = 'hidden';\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: activate\r\n * \r\n * Puts the window on top of all other windows.\r\n */\r\nmxWindow.prototype.activate = function()\r\n{\r\n\tif (mxWindow.activeWindow != this)\r\n\t{\r\n\t\tvar style = mxUtils.getCurrentStyle(this.getElement());\r\n\t\tvar index = (style != null) ? style.zIndex : 3;\r\n\r\n\t\tif (mxWindow.activeWindow)\r\n\t\t{\r\n\t\t\tvar elt = mxWindow.activeWindow.getElement();\r\n\t\t\t\r\n\t\t\tif (elt != null && elt.style != null)\r\n\t\t\t{\r\n\t\t\t\telt.style.zIndex = index;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tvar previousWindow = mxWindow.activeWindow;\r\n\t\tthis.getElement().style.zIndex = parseInt(index) + 1;\r\n\t\tmxWindow.activeWindow = this;\r\n\t\t\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.ACTIVATE, 'previousWindow', previousWindow));\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getElement\r\n * \r\n * Returuns the outermost DOM node that makes up the window.\r\n */\r\nmxWindow.prototype.getElement = function()\r\n{\r\n\treturn this.div;\r\n};\r\n\r\n/**\r\n * Function: fit\r\n * \r\n * Makes sure the window is inside the client area of the window.\r\n */\r\nmxWindow.prototype.fit = function()\r\n{\r\n\tmxUtils.fit(this.div);\r\n};\r\n\r\n/**\r\n * Function: isResizable\r\n * \r\n * Returns true if the window is resizable.\r\n */\r\nmxWindow.prototype.isResizable = function()\r\n{\r\n\tif (this.resize != null)\r\n\t{\r\n\t\treturn this.resize.style.display != 'none';\r\n\t}\r\n\t\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: setResizable\r\n * \r\n * Sets if the window should be resizable. To avoid interference with some\r\n * built-in features of IE10 and later, the use of the following code is\r\n * recommended if there are resizable <mxWindow>s in the page:\r\n * \r\n * (code)\r\n * if (mxClient.IS_POINTER)\r\n * {\r\n *   document.body.style.msTouchAction = 'none';\r\n * }\r\n * (end)\r\n */\r\nmxWindow.prototype.setResizable = function(resizable)\r\n{\r\n\tif (resizable)\r\n\t{\r\n\t\tif (this.resize == null)\r\n\t\t{\r\n\t\t\tthis.resize = document.createElement('img');\r\n\t\t\tthis.resize.style.position = 'absolute';\r\n\t\t\tthis.resize.style.bottom = '2px';\r\n\t\t\tthis.resize.style.right = '2px';\r\n\r\n\t\t\tthis.resize.setAttribute('src', this.resizeImage);\r\n\t\t\tthis.resize.style.cursor = 'nw-resize';\r\n\t\t\t\r\n\t\t\tvar startX = null;\r\n\t\t\tvar startY = null;\r\n\t\t\tvar width = null;\r\n\t\t\tvar height = null;\r\n\t\t\t\r\n\t\t\tvar start = mxUtils.bind(this, function(evt)\r\n\t\t\t{\r\n\t\t\t\t// LATER: pointerdown starting on border of resize does start\r\n\t\t\t\t// the drag operation but does not fire consecutive events via\r\n\t\t\t\t// one of the listeners below (does pan instead).\r\n\t\t\t\t// Workaround: document.body.style.msTouchAction = 'none'\r\n\t\t\t\tthis.activate();\r\n\t\t\t\tstartX = mxEvent.getClientX(evt);\r\n\t\t\t\tstartY = mxEvent.getClientY(evt);\r\n\t\t\t\twidth = this.div.offsetWidth;\r\n\t\t\t\theight = this.div.offsetHeight;\r\n\t\t\t\t\r\n\t\t\t\tmxEvent.addGestureListeners(document, null, dragHandler, dropHandler);\r\n\t\t\t\tthis.fireEvent(new mxEventObject(mxEvent.RESIZE_START, 'event', evt));\r\n\t\t\t\tmxEvent.consume(evt);\r\n\t\t\t});\r\n\r\n\t\t\t// Adds a temporary pair of listeners to intercept\r\n\t\t\t// the gesture event in the document\r\n\t\t\tvar dragHandler = mxUtils.bind(this, function(evt)\r\n\t\t\t{\r\n\t\t\t\tif (startX != null && startY != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar dx = mxEvent.getClientX(evt) - startX;\r\n\t\t\t\t\tvar dy = mxEvent.getClientY(evt) - startY;\r\n\t\r\n\t\t\t\t\tthis.setSize(width + dx, height + dy);\r\n\t\r\n\t\t\t\t\tthis.fireEvent(new mxEventObject(mxEvent.RESIZE, 'event', evt));\r\n\t\t\t\t\tmxEvent.consume(evt);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\tvar dropHandler = mxUtils.bind(this, function(evt)\r\n\t\t\t{\r\n\t\t\t\tif (startX != null && startY != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tstartX = null;\r\n\t\t\t\t\tstartY = null;\r\n\t\t\t\t\tmxEvent.removeGestureListeners(document, null, dragHandler, dropHandler);\r\n\t\t\t\t\tthis.fireEvent(new mxEventObject(mxEvent.RESIZE_END, 'event', evt));\r\n\t\t\t\t\tmxEvent.consume(evt);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\tmxEvent.addGestureListeners(this.resize, start, dragHandler, dropHandler);\r\n\t\t\tthis.div.appendChild(this.resize);\r\n\t\t}\r\n\t\telse \r\n\t\t{\r\n\t\t\tthis.resize.style.display = 'inline';\r\n\t\t}\r\n\t}\r\n\telse if (this.resize != null)\r\n\t{\r\n\t\tthis.resize.style.display = 'none';\r\n\t}\r\n};\r\n\t\r\n/**\r\n * Function: setSize\r\n * \r\n * Sets the size of the window.\r\n */\r\nmxWindow.prototype.setSize = function(width, height)\r\n{\r\n\twidth = Math.max(this.minimumSize.width, width);\r\n\theight = Math.max(this.minimumSize.height, height);\r\n\r\n\t// Workaround for table size problems in FF\r\n\tif (!mxClient.IS_QUIRKS)\r\n\t{\r\n\t\tthis.div.style.width =  width + 'px';\r\n\t\tthis.div.style.height = height + 'px';\r\n\t}\r\n\t\r\n\tthis.table.style.width =  width + 'px';\r\n\tthis.table.style.height = height + 'px';\r\n\r\n\tif (!mxClient.IS_QUIRKS)\r\n\t{\r\n\t\tthis.contentWrapper.style.height = (this.div.offsetHeight -\r\n\t\t\tthis.title.offsetHeight - this.contentHeightCorrection) + 'px';\r\n\t}\r\n};\r\n\t\r\n/**\r\n * Function: setMinimizable\r\n * \r\n * Sets if the window is minimizable.\r\n */\r\nmxWindow.prototype.setMinimizable = function(minimizable)\r\n{\r\n\tthis.minimize.style.display = (minimizable) ? '' : 'none';\r\n};\r\n\r\n/**\r\n * Function: getMinimumSize\r\n * \r\n * Returns an <mxRectangle> that specifies the size for the minimized window.\r\n * A width or height of 0 means keep the existing width or height. This\r\n * implementation returns the height of the window title and keeps the width.\r\n */\r\nmxWindow.prototype.getMinimumSize = function()\r\n{\r\n\treturn new mxRectangle(0, 0, 0, this.title.offsetHeight);\r\n};\r\n\r\n/**\r\n * Function: installMinimizeHandler\r\n * \r\n * Installs the event listeners required for minimizing the window.\r\n */\r\nmxWindow.prototype.installMinimizeHandler = function()\r\n{\r\n\tthis.minimize = document.createElement('img');\r\n\t\r\n\tthis.minimize.setAttribute('src', this.minimizeImage);\r\n\tthis.minimize.setAttribute('title', 'Minimize');\r\n\tthis.minimize.style.cursor = 'pointer';\r\n\tthis.minimize.style.marginLeft = '2px';\r\n\tthis.minimize.style.display = 'none';\r\n\t\r\n\tthis.buttons.appendChild(this.minimize);\r\n\t\r\n\tvar minimized = false;\r\n\tvar maxDisplay = null;\r\n\tvar height = null;\r\n\r\n\tvar funct = mxUtils.bind(this, function(evt)\r\n\t{\r\n\t\tthis.activate();\r\n\t\t\r\n\t\tif (!minimized)\r\n\t\t{\r\n\t\t\tminimized = true;\r\n\t\t\t\r\n\t\t\tthis.minimize.setAttribute('src', this.normalizeImage);\r\n\t\t\tthis.minimize.setAttribute('title', 'Normalize');\r\n\t\t\tthis.contentWrapper.style.display = 'none';\r\n\t\t\tmaxDisplay = this.maximize.style.display;\r\n\t\t\t\r\n\t\t\tthis.maximize.style.display = 'none';\r\n\t\t\theight = this.table.style.height;\r\n\t\t\t\r\n\t\t\tvar minSize = this.getMinimumSize();\r\n\t\t\t\r\n\t\t\tif (minSize.height > 0)\r\n\t\t\t{\r\n\t\t\t\tif (!mxClient.IS_QUIRKS)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.div.style.height = minSize.height + 'px';\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.table.style.height = minSize.height + 'px';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (minSize.width > 0)\r\n\t\t\t{\r\n\t\t\t\tif (!mxClient.IS_QUIRKS)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.div.style.width = minSize.width + 'px';\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.table.style.width = minSize.width + 'px';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (this.resize != null)\r\n\t\t\t{\r\n\t\t\t\tthis.resize.style.visibility = 'hidden';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.MINIMIZE, 'event', evt));\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tminimized = false;\r\n\t\t\t\r\n\t\t\tthis.minimize.setAttribute('src', this.minimizeImage);\r\n\t\t\tthis.minimize.setAttribute('title', 'Minimize');\r\n\t\t\tthis.contentWrapper.style.display = ''; // default\r\n\t\t\tthis.maximize.style.display = maxDisplay;\r\n\t\t\t\r\n\t\t\tif (!mxClient.IS_QUIRKS)\r\n\t\t\t{\r\n\t\t\t\tthis.div.style.height = height;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.table.style.height = height;\r\n\r\n\t\t\tif (this.resize != null)\r\n\t\t\t{\r\n\t\t\t\tthis.resize.style.visibility = '';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.NORMALIZE, 'event', evt));\r\n\t\t}\r\n\t\t\r\n\t\tmxEvent.consume(evt);\r\n\t});\r\n\t\r\n\tmxEvent.addGestureListeners(this.minimize, funct);\r\n};\r\n\t\r\n/**\r\n * Function: setMaximizable\r\n * \r\n * Sets if the window is maximizable.\r\n */\r\nmxWindow.prototype.setMaximizable = function(maximizable)\r\n{\r\n\tthis.maximize.style.display = (maximizable) ? '' : 'none';\r\n};\r\n\r\n/**\r\n * Function: installMaximizeHandler\r\n * \r\n * Installs the event listeners required for maximizing the window.\r\n */\r\nmxWindow.prototype.installMaximizeHandler = function()\r\n{\r\n\tthis.maximize = document.createElement('img');\r\n\t\r\n\tthis.maximize.setAttribute('src', this.maximizeImage);\r\n\tthis.maximize.setAttribute('title', 'Maximize');\r\n\tthis.maximize.style.cursor = 'default';\r\n\tthis.maximize.style.marginLeft = '2px';\r\n\tthis.maximize.style.cursor = 'pointer';\r\n\tthis.maximize.style.display = 'none';\r\n\t\r\n\tthis.buttons.appendChild(this.maximize);\r\n\t\r\n\tvar maximized = false;\r\n\tvar x = null;\r\n\tvar y = null;\r\n\tvar height = null;\r\n\tvar width = null;\r\n\tvar minDisplay = null;\r\n\r\n\tvar funct = mxUtils.bind(this, function(evt)\r\n\t{\r\n\t\tthis.activate();\r\n\t\t\r\n\t\tif (this.maximize.style.display != 'none')\r\n\t\t{\r\n\t\t\tif (!maximized)\r\n\t\t\t{\r\n\t\t\t\tmaximized = true;\r\n\t\t\t\t\r\n\t\t\t\tthis.maximize.setAttribute('src', this.normalizeImage);\r\n\t\t\t\tthis.maximize.setAttribute('title', 'Normalize');\r\n\t\t\t\tthis.contentWrapper.style.display = '';\r\n\t\t\t\tminDisplay = this.minimize.style.display;\r\n\t\t\t\tthis.minimize.style.display = 'none';\r\n\t\t\t\t\r\n\t\t\t\t// Saves window state\r\n\t\t\t\tx = parseInt(this.div.style.left);\r\n\t\t\t\ty = parseInt(this.div.style.top);\r\n\t\t\t\theight = this.table.style.height;\r\n\t\t\t\twidth = this.table.style.width;\r\n\r\n\t\t\t\tthis.div.style.left = '0px';\r\n\t\t\t\tthis.div.style.top = '0px';\r\n\t\t\t\tvar docHeight = Math.max(document.body.clientHeight || 0, document.documentElement.clientHeight || 0);\r\n\r\n\t\t\t\tif (!mxClient.IS_QUIRKS)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.div.style.width = (document.body.clientWidth - 2) + 'px';\r\n\t\t\t\t\tthis.div.style.height = (docHeight - 2) + 'px';\r\n\t\t\t\t}\r\n\r\n\t\t\t\tthis.table.style.width = (document.body.clientWidth - 2) + 'px';\r\n\t\t\t\tthis.table.style.height = (docHeight - 2) + 'px';\r\n\t\t\t\t\r\n\t\t\t\tif (this.resize != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.resize.style.visibility = 'hidden';\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (!mxClient.IS_QUIRKS)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar style = mxUtils.getCurrentStyle(this.contentWrapper);\r\n\t\t\r\n\t\t\t\t\tif (style.overflow == 'auto' || this.resize != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.contentWrapper.style.height = (this.div.offsetHeight -\r\n\t\t\t\t\t\t\tthis.title.offsetHeight - this.contentHeightCorrection) + 'px';\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tthis.fireEvent(new mxEventObject(mxEvent.MAXIMIZE, 'event', evt));\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tmaximized = false;\r\n\t\t\t\t\r\n\t\t\t\tthis.maximize.setAttribute('src', this.maximizeImage);\r\n\t\t\t\tthis.maximize.setAttribute('title', 'Maximize');\r\n\t\t\t\tthis.contentWrapper.style.display = '';\r\n\t\t\t\tthis.minimize.style.display = minDisplay;\r\n\r\n\t\t\t\t// Restores window state\r\n\t\t\t\tthis.div.style.left = x+'px';\r\n\t\t\t\tthis.div.style.top = y+'px';\r\n\t\t\t\t\r\n\t\t\t\tif (!mxClient.IS_QUIRKS)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.div.style.height = height;\r\n\t\t\t\t\tthis.div.style.width = width;\r\n\r\n\t\t\t\t\tvar style = mxUtils.getCurrentStyle(this.contentWrapper);\r\n\t\t\r\n\t\t\t\t\tif (style.overflow == 'auto' || this.resize != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.contentWrapper.style.height = (this.div.offsetHeight -\r\n\t\t\t\t\t\t\tthis.title.offsetHeight - this.contentHeightCorrection) + 'px';\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.table.style.height = height;\r\n\t\t\t\tthis.table.style.width = width;\r\n\r\n\t\t\t\tif (this.resize != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.resize.style.visibility = '';\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.fireEvent(new mxEventObject(mxEvent.NORMALIZE, 'event', evt));\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tmxEvent.consume(evt);\r\n\t\t}\r\n\t});\r\n\t\r\n\tmxEvent.addGestureListeners(this.maximize, funct);\r\n\tmxEvent.addListener(this.title, 'dblclick', funct);\r\n};\r\n\t\r\n/**\r\n * Function: installMoveHandler\r\n * \r\n * Installs the event listeners required for moving the window.\r\n */\r\nmxWindow.prototype.installMoveHandler = function()\r\n{\r\n\tthis.title.style.cursor = 'move';\r\n\t\r\n\tmxEvent.addGestureListeners(this.title,\r\n\t\tmxUtils.bind(this, function(evt)\r\n\t\t{\r\n\t\t\tvar startX = mxEvent.getClientX(evt);\r\n\t\t\tvar startY = mxEvent.getClientY(evt);\r\n\t\t\tvar x = this.getX();\r\n\t\t\tvar y = this.getY();\r\n\t\t\t\t\t\t\r\n\t\t\t// Adds a temporary pair of listeners to intercept\r\n\t\t\t// the gesture event in the document\r\n\t\t\tvar dragHandler = mxUtils.bind(this, function(evt)\r\n\t\t\t{\r\n\t\t\t\tvar dx = mxEvent.getClientX(evt) - startX;\r\n\t\t\t\tvar dy = mxEvent.getClientY(evt) - startY;\r\n\t\t\t\tthis.setLocation(x + dx, y + dy);\r\n\t\t\t\tthis.fireEvent(new mxEventObject(mxEvent.MOVE, 'event', evt));\r\n\t\t\t\tmxEvent.consume(evt);\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\tvar dropHandler = mxUtils.bind(this, function(evt)\r\n\t\t\t{\r\n\t\t\t\tmxEvent.removeGestureListeners(document, null, dragHandler, dropHandler);\r\n\t\t\t\tthis.fireEvent(new mxEventObject(mxEvent.MOVE_END, 'event', evt));\r\n\t\t\t\tmxEvent.consume(evt);\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\tmxEvent.addGestureListeners(document, null, dragHandler, dropHandler);\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.MOVE_START, 'event', evt));\r\n\t\t\tmxEvent.consume(evt);\r\n\t\t}));\r\n\t\r\n\t// Disables built-in pan and zoom in IE10 and later\r\n\tif (mxClient.IS_POINTER)\r\n\t{\r\n\t\tthis.title.style.touchAction = 'none';\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setLocation\r\n * \r\n * Sets the upper, left corner of the window.\r\n */\r\n mxWindow.prototype.setLocation = function(x, y)\r\n {\r\n\tthis.div.style.left = x + 'px';\r\n\tthis.div.style.top = y + 'px';\r\n };\r\n\r\n/**\r\n * Function: getX\r\n *\r\n * Returns the current position on the x-axis.\r\n */\r\nmxWindow.prototype.getX = function()\r\n{\r\n\treturn parseInt(this.div.style.left);\r\n};\r\n\r\n/**\r\n * Function: getY\r\n *\r\n * Returns the current position on the y-axis.\r\n */\r\nmxWindow.prototype.getY = function()\r\n{\r\n\treturn parseInt(this.div.style.top);\r\n};\r\n\r\n/**\r\n * Function: installCloseHandler\r\n *\r\n * Adds the <closeImage> as a new image node in <closeImg> and installs the\r\n * <close> event.\r\n */\r\nmxWindow.prototype.installCloseHandler = function()\r\n{\r\n\tthis.closeImg = document.createElement('img');\r\n\t\r\n\tthis.closeImg.setAttribute('src', this.closeImage);\r\n\tthis.closeImg.setAttribute('title', 'Close');\r\n\tthis.closeImg.style.marginLeft = '2px';\r\n\tthis.closeImg.style.cursor = 'pointer';\r\n\tthis.closeImg.style.display = 'none';\r\n\t\r\n\tthis.buttons.appendChild(this.closeImg);\r\n\r\n\tmxEvent.addGestureListeners(this.closeImg,\r\n\t\tmxUtils.bind(this, function(evt)\r\n\t\t{\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.CLOSE, 'event', evt));\r\n\t\t\t\r\n\t\t\tif (this.destroyOnClose)\r\n\t\t\t{\r\n\t\t\t\tthis.destroy();\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.setVisible(false);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tmxEvent.consume(evt);\r\n\t\t}));\r\n};\r\n\r\n/**\r\n * Function: setImage\r\n * \r\n * Sets the image associated with the window.\r\n * \r\n * Parameters:\r\n * \r\n * image - URL of the image to be used.\r\n */\r\nmxWindow.prototype.setImage = function(image)\r\n{\r\n\tthis.image = document.createElement('img');\r\n\tthis.image.setAttribute('src', image);\r\n\tthis.image.setAttribute('align', 'left');\r\n\tthis.image.style.marginRight = '4px';\r\n\tthis.image.style.marginLeft = '0px';\r\n\tthis.image.style.marginTop = '-2px';\r\n\t\r\n\tthis.title.insertBefore(this.image, this.title.firstChild);\r\n};\r\n\r\n/**\r\n * Function: setClosable\r\n * \r\n * Sets the image associated with the window.\r\n * \r\n * Parameters:\r\n * \r\n * closable - Boolean specifying if the window should be closable.\r\n */\r\nmxWindow.prototype.setClosable = function(closable)\r\n{\r\n\tthis.closeImg.style.display = (closable) ? '' : 'none';\r\n};\r\n\r\n/**\r\n * Function: isVisible\r\n * \r\n * Returns true if the window is visible.\r\n */\r\nmxWindow.prototype.isVisible = function()\r\n{\r\n\tif (this.div != null)\r\n\t{\r\n\t\treturn this.div.style.display != 'none';\r\n\t}\r\n\t\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: setVisible\r\n *\r\n * Shows or hides the window depending on the given flag.\r\n * \r\n * Parameters:\r\n * \r\n * visible - Boolean indicating if the window should be made visible.\r\n */\r\nmxWindow.prototype.setVisible = function(visible)\r\n{\r\n\tif (this.div != null && this.isVisible() != visible)\r\n\t{\r\n\t\tif (visible)\r\n\t\t{\r\n\t\t\tthis.show();\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.hide();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: show\r\n *\r\n * Shows the window.\r\n */\r\nmxWindow.prototype.show = function()\r\n{\r\n\tthis.div.style.display = '';\r\n\tthis.activate();\r\n\t\r\n\tvar style = mxUtils.getCurrentStyle(this.contentWrapper);\r\n\t\r\n\tif (!mxClient.IS_QUIRKS && (style.overflow == 'auto' || this.resize != null) &&\r\n\t\tthis.contentWrapper.style.display != 'none')\r\n\t{\r\n\t\tthis.contentWrapper.style.height = (this.div.offsetHeight -\r\n\t\t\t\tthis.title.offsetHeight - this.contentHeightCorrection) + 'px';\r\n\t}\r\n\t\r\n\tthis.fireEvent(new mxEventObject(mxEvent.SHOW));\r\n};\r\n\r\n/**\r\n * Function: hide\r\n *\r\n * Hides the window.\r\n */\r\nmxWindow.prototype.hide = function()\r\n{\r\n\tthis.div.style.display = 'none';\r\n\tthis.fireEvent(new mxEventObject(mxEvent.HIDE));\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n *\r\n * Destroys the window and removes all associated resources. Fires a\r\n * <destroy> event prior to destroying the window.\r\n */\r\nmxWindow.prototype.destroy = function()\r\n{\r\n\tthis.fireEvent(new mxEventObject(mxEvent.DESTROY));\r\n\t\r\n\tif (this.div != null)\r\n\t{\r\n\t\tmxEvent.release(this.div);\r\n\t\tthis.div.parentNode.removeChild(this.div);\r\n\t\tthis.div = null;\r\n\t}\r\n\t\r\n\tthis.title = null;\r\n\tthis.content = null;\r\n\tthis.contentWrapper = null;\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxForm\r\n * \r\n * A simple class for creating HTML forms.\r\n * \r\n * Constructor: mxForm\r\n * \r\n * Creates a HTML table using the specified classname.\r\n */\r\nfunction mxForm(className)\r\n{\r\n\tthis.table = document.createElement('table');\r\n\tthis.table.className = className;\r\n\tthis.body = document.createElement('tbody');\r\n\t\r\n\tthis.table.appendChild(this.body);\r\n};\r\n\r\n/**\r\n * Variable: table\r\n * \r\n * Holds the DOM node that represents the table.\r\n */\r\nmxForm.prototype.table = null;\r\n\r\n/**\r\n * Variable: body\r\n * \r\n * Holds the DOM node that represents the tbody (table body). New rows\r\n * can be added to this object using DOM API.\r\n */\r\nmxForm.prototype.body = false;\r\n\r\n/**\r\n * Function: getTable\r\n * \r\n * Returns the table that contains this form.\r\n */\r\nmxForm.prototype.getTable = function()\r\n{\r\n\treturn this.table;\r\n};\r\n\r\n/**\r\n * Function: addButtons\r\n * \r\n * Helper method to add an OK and Cancel button using the respective\r\n * functions.\r\n */\r\nmxForm.prototype.addButtons = function(okFunct, cancelFunct)\r\n{\r\n\tvar tr = document.createElement('tr');\r\n\tvar td = document.createElement('td');\r\n\ttr.appendChild(td);\r\n\ttd = document.createElement('td');\r\n\r\n\t// Adds the ok button\r\n\tvar button = document.createElement('button');\r\n\tmxUtils.write(button, mxResources.get('ok') || 'OK');\r\n\ttd.appendChild(button);\r\n\r\n\tmxEvent.addListener(button, 'click', function()\r\n\t{\r\n\t\tokFunct();\r\n\t});\r\n\t\r\n\t// Adds the cancel button\r\n\tbutton = document.createElement('button');\r\n\tmxUtils.write(button, mxResources.get('cancel') || 'Cancel');\r\n\ttd.appendChild(button);\r\n\t\r\n\tmxEvent.addListener(button, 'click', function()\r\n\t{\r\n\t\tcancelFunct();\r\n\t});\r\n\t\r\n\ttr.appendChild(td);\r\n\tthis.body.appendChild(tr);\r\n};\r\n\r\n/**\r\n * Function: addText\r\n * \r\n * Adds an input for the given name, type and value and returns it.\r\n */\r\nmxForm.prototype.addText = function(name, value, type)\r\n{\r\n\tvar input = document.createElement('input');\r\n\t\r\n\tinput.setAttribute('type', type || 'text');\r\n\tinput.value = value;\r\n\t\r\n\treturn this.addField(name, input);\r\n};\r\n\r\n/**\r\n * Function: addCheckbox\r\n * \r\n * Adds a checkbox for the given name and value and returns the textfield.\r\n */\r\nmxForm.prototype.addCheckbox = function(name, value)\r\n{\r\n\tvar input = document.createElement('input');\r\n\t\r\n\tinput.setAttribute('type', 'checkbox');\r\n\tthis.addField(name, input);\r\n\r\n\t// IE can only change the checked value if the input is inside the DOM\r\n\tif (value)\r\n\t{\r\n\t\tinput.checked = true;\r\n\t}\r\n\r\n\treturn input;\r\n};\r\n\r\n/**\r\n * Function: addTextarea\r\n * \r\n * Adds a textarea for the given name and value and returns the textarea.\r\n */\r\nmxForm.prototype.addTextarea = function(name, value, rows)\r\n{\r\n\tvar input = document.createElement('textarea');\r\n\t\r\n\tif (mxClient.IS_NS)\r\n\t{\r\n\t\trows--;\r\n\t}\r\n\t\r\n\tinput.setAttribute('rows', rows || 2);\r\n\tinput.value = value;\r\n\t\r\n\treturn this.addField(name, input);\r\n};\r\n\r\n/**\r\n * Function: addCombo\r\n * \r\n * Adds a combo for the given name and returns the combo.\r\n */\r\nmxForm.prototype.addCombo = function(name, isMultiSelect, size)\r\n{\r\n\tvar select = document.createElement('select');\r\n\t\r\n\tif (size != null)\r\n\t{\r\n\t\tselect.setAttribute('size', size);\r\n\t}\r\n\t\r\n\tif (isMultiSelect)\r\n\t{\r\n\t\tselect.setAttribute('multiple', 'true');\r\n\t}\r\n\t\r\n\treturn this.addField(name, select);\r\n};\r\n\r\n/**\r\n * Function: addOption\r\n * \r\n * Adds an option for the given label to the specified combo.\r\n */\r\nmxForm.prototype.addOption = function(combo, label, value, isSelected)\r\n{\r\n\tvar option = document.createElement('option');\r\n\t\r\n\tmxUtils.writeln(option, label);\r\n\toption.setAttribute('value', value);\r\n\t\r\n\tif (isSelected)\r\n\t{\r\n\t\toption.setAttribute('selected', isSelected);\r\n\t}\r\n\t\r\n\tcombo.appendChild(option);\r\n};\r\n\r\n/**\r\n * Function: addField\r\n * \r\n * Adds a new row with the name and the input field in two columns and\r\n * returns the given input.\r\n */\r\nmxForm.prototype.addField = function(name, input)\r\n{\r\n\tvar tr = document.createElement('tr');\r\n\tvar td = document.createElement('td');\r\n\tmxUtils.write(td, name);\r\n\ttr.appendChild(td);\r\n\t\r\n\ttd = document.createElement('td');\r\n\ttd.appendChild(input);\r\n\ttr.appendChild(td);\r\n\tthis.body.appendChild(tr);\r\n\t\r\n\treturn input;\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxImage\r\n *\r\n * Encapsulates the URL, width and height of an image.\r\n * \r\n * Constructor: mxImage\r\n * \r\n * Constructs a new image.\r\n */\r\nfunction mxImage(src, width, height)\r\n{\r\n\tthis.src = src;\r\n\tthis.width = width;\r\n\tthis.height = height;\r\n};\r\n\r\n/**\r\n * Variable: src\r\n *\r\n * String that specifies the URL of the image.\r\n */\r\nmxImage.prototype.src = null;\r\n\r\n/**\r\n * Variable: width\r\n *\r\n * Integer that specifies the width of the image.\r\n */\r\nmxImage.prototype.width = null;\r\n\r\n/**\r\n * Variable: height\r\n *\r\n * Integer that specifies the height of the image.\r\n */\r\nmxImage.prototype.height = null;\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxDivResizer\r\n * \r\n * Maintains the size of a div element in Internet Explorer. This is a\r\n * workaround for the right and bottom style being ignored in IE.\r\n * \r\n * If you need a div to cover the scrollwidth and -height of a document,\r\n * then you can use this class as follows:\r\n * \r\n * (code)\r\n * var resizer = new mxDivResizer(background);\r\n * resizer.getDocumentHeight = function()\r\n * {\r\n *   return document.body.scrollHeight;\r\n * }\r\n * resizer.getDocumentWidth = function()\r\n * {\r\n *   return document.body.scrollWidth;\r\n * }\r\n * resizer.resize();\r\n * (end)\r\n * \r\n * Constructor: mxDivResizer\r\n * \r\n * Constructs an object that maintains the size of a div\r\n * element when the window is being resized. This is only\r\n * required for Internet Explorer as it ignores the respective\r\n * stylesheet information for DIV elements.\r\n * \r\n * Parameters:\r\n * \r\n * div - Reference to the DOM node whose size should be maintained.\r\n * container - Optional Container that contains the div. Default is the\r\n * window.\r\n */\r\nfunction mxDivResizer(div, container)\r\n{\r\n\tif (div.nodeName.toLowerCase() == 'div')\r\n\t{\r\n\t\tif (container == null)\r\n\t\t{\r\n\t\t\tcontainer = window;\r\n\t\t}\r\n\r\n\t\tthis.div = div;\r\n\t\tvar style = mxUtils.getCurrentStyle(div);\r\n\t\t\r\n\t\tif (style != null)\r\n\t\t{\r\n\t\t\tthis.resizeWidth = style.width == 'auto';\r\n\t\t\tthis.resizeHeight = style.height == 'auto';\r\n\t\t}\r\n\t\t\r\n\t\tmxEvent.addListener(container, 'resize',\r\n\t\t\tmxUtils.bind(this, function(evt)\r\n\t\t\t{\r\n\t\t\t\tif (!this.handlingResize)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.handlingResize = true;\r\n\t\t\t\t\tthis.resize();\r\n\t\t\t\t\tthis.handlingResize = false;\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t);\r\n\t\t\r\n\t\tthis.resize();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: resizeWidth\r\n * \r\n * Boolean specifying if the width should be updated.\r\n */\r\nmxDivResizer.prototype.resizeWidth = true;\r\n\r\n/**\r\n * Function: resizeHeight\r\n * \r\n * Boolean specifying if the height should be updated.\r\n */\r\nmxDivResizer.prototype.resizeHeight = true;\r\n\r\n/**\r\n * Function: handlingResize\r\n * \r\n * Boolean specifying if the width should be updated.\r\n */\r\nmxDivResizer.prototype.handlingResize = false;\r\n\r\n/**\r\n * Function: resize\r\n * \r\n * Updates the style of the DIV after the window has been resized.\r\n */\r\nmxDivResizer.prototype.resize = function()\r\n{\r\n\tvar w = this.getDocumentWidth();\r\n\tvar h = this.getDocumentHeight();\r\n\r\n\tvar l = parseInt(this.div.style.left);\r\n\tvar r = parseInt(this.div.style.right);\r\n\tvar t = parseInt(this.div.style.top);\r\n\tvar b = parseInt(this.div.style.bottom);\r\n\t\r\n\tif (this.resizeWidth &&\r\n\t\t!isNaN(l) &&\r\n\t\t!isNaN(r) &&\r\n\t\tl >= 0 &&\r\n\t\tr >= 0 &&\r\n\t\tw - r - l > 0)\r\n\t{\r\n\t\tthis.div.style.width = (w - r - l)+'px';\r\n\t}\r\n\t\r\n\tif (this.resizeHeight &&\r\n\t\t!isNaN(t) &&\r\n\t\t!isNaN(b) &&\r\n\t\tt >= 0 &&\r\n\t\tb >= 0 &&\r\n\t\th - t - b > 0)\r\n\t{\r\n\t\tthis.div.style.height = (h - t - b)+'px';\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getDocumentWidth\r\n * \r\n * Hook for subclassers to return the width of the document (without\r\n * scrollbars).\r\n */\r\nmxDivResizer.prototype.getDocumentWidth = function()\r\n{\r\n\treturn document.body.clientWidth;\r\n};\r\n\r\n/**\r\n * Function: getDocumentHeight\r\n * \r\n * Hook for subclassers to return the height of the document (without\r\n * scrollbars).\r\n */\r\nmxDivResizer.prototype.getDocumentHeight = function()\r\n{\r\n\treturn document.body.clientHeight;\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxDragSource\r\n * \r\n * Wrapper to create a drag source from a DOM element so that the element can\r\n * be dragged over a graph and dropped into the graph as a new cell.\r\n * \r\n * Problem is that in the dropHandler the current preview location is not\r\n * available, so the preview and the dropHandler must match.\r\n * \r\n * Constructor: mxDragSource\r\n * \r\n * Constructs a new drag source for the given element.\r\n */\r\nfunction mxDragSource(element, dropHandler)\r\n{\r\n\tthis.element = element;\r\n\tthis.dropHandler = dropHandler;\r\n\t\r\n\t// Handles a drag gesture on the element\r\n\tmxEvent.addGestureListeners(element, mxUtils.bind(this, function(evt)\r\n\t{\r\n\t\tthis.mouseDown(evt);\r\n\t}));\r\n\t\r\n\t// Prevents native drag and drop\r\n\tmxEvent.addListener(element, 'dragstart', function(evt)\r\n\t{\r\n\t\tmxEvent.consume(evt);\r\n\t});\r\n\t\r\n\tthis.eventConsumer = function(sender, evt)\r\n\t{\r\n\t\tvar evtName = evt.getProperty('eventName');\r\n\t\tvar me = evt.getProperty('event');\r\n\t\t\r\n\t\tif (evtName != mxEvent.MOUSE_DOWN)\r\n\t\t{\r\n\t\t\tme.consume();\r\n\t\t}\r\n\t};\r\n};\r\n\r\n/**\r\n * Variable: element\r\n *\r\n * Reference to the DOM node which was made draggable.\r\n */\r\nmxDragSource.prototype.element = null;\r\n\r\n/**\r\n * Variable: dropHandler\r\n *\r\n * Holds the DOM node that is used to represent the drag preview. If this is\r\n * null then the source element will be cloned and used for the drag preview.\r\n */\r\nmxDragSource.prototype.dropHandler = null;\r\n\r\n/**\r\n * Variable: dragOffset\r\n *\r\n * <mxPoint> that specifies the offset of the <dragElement>. Default is null.\r\n */\r\nmxDragSource.prototype.dragOffset = null;\r\n\r\n/**\r\n * Variable: dragElement\r\n *\r\n * Holds the DOM node that is used to represent the drag preview. If this is\r\n * null then the source element will be cloned and used for the drag preview.\r\n */\r\nmxDragSource.prototype.dragElement = null;\r\n\r\n/**\r\n * Variable: previewElement\r\n *\r\n * Optional <mxRectangle> that specifies the unscaled size of the preview.\r\n */\r\nmxDragSource.prototype.previewElement = null;\r\n\r\n/**\r\n * Variable: enabled\r\n *\r\n * Specifies if this drag source is enabled. Default is true.\r\n */\r\nmxDragSource.prototype.enabled = true;\r\n\r\n/**\r\n * Variable: currentGraph\r\n *\r\n * Reference to the <mxGraph> that is the current drop target.\r\n */\r\nmxDragSource.prototype.currentGraph = null;\r\n\r\n/**\r\n * Variable: currentDropTarget\r\n *\r\n * Holds the current drop target under the mouse.\r\n */\r\nmxDragSource.prototype.currentDropTarget = null;\r\n\r\n/**\r\n * Variable: currentPoint\r\n *\r\n * Holds the current drop location.\r\n */\r\nmxDragSource.prototype.currentPoint = null;\r\n\r\n/**\r\n * Variable: currentGuide\r\n *\r\n * Holds an <mxGuide> for the <currentGraph> if <dragPreview> is not null.\r\n */\r\nmxDragSource.prototype.currentGuide = null;\r\n\r\n/**\r\n * Variable: currentGuide\r\n *\r\n * Holds an <mxGuide> for the <currentGraph> if <dragPreview> is not null.\r\n */\r\nmxDragSource.prototype.currentHighlight = null;\r\n\r\n/**\r\n * Variable: autoscroll\r\n *\r\n * Specifies if the graph should scroll automatically. Default is true.\r\n */\r\nmxDragSource.prototype.autoscroll = true;\r\n\r\n/**\r\n * Variable: guidesEnabled\r\n *\r\n * Specifies if <mxGuide> should be enabled. Default is true.\r\n */\r\nmxDragSource.prototype.guidesEnabled = true;\r\n\r\n/**\r\n * Variable: gridEnabled\r\n *\r\n * Specifies if the grid should be allowed. Default is true.\r\n */\r\nmxDragSource.prototype.gridEnabled = true;\r\n\r\n/**\r\n * Variable: highlightDropTargets\r\n *\r\n * Specifies if drop targets should be highlighted. Default is true.\r\n */\r\nmxDragSource.prototype.highlightDropTargets = true;\r\n\r\n/**\r\n * Variable: dragElementZIndex\r\n * \r\n * ZIndex for the drag element. Default is 100.\r\n */\r\nmxDragSource.prototype.dragElementZIndex = 100;\r\n\r\n/**\r\n * Variable: dragElementOpacity\r\n * \r\n * Opacity of the drag element in %. Default is 70.\r\n */\r\nmxDragSource.prototype.dragElementOpacity = 70;\r\n\r\n/**\r\n * Variable: checkEventSource\r\n * \r\n * Whether the event source should be checked in <graphContainerEvent>. Default\r\n * is true.\r\n */\r\nmxDragSource.prototype.checkEventSource = true;\r\n\r\n/**\r\n * Function: isEnabled\r\n * \r\n * Returns <enabled>.\r\n */\r\nmxDragSource.prototype.isEnabled = function()\r\n{\r\n\treturn this.enabled;\r\n};\r\n\r\n/**\r\n * Function: setEnabled\r\n * \r\n * Sets <enabled>.\r\n */\r\nmxDragSource.prototype.setEnabled = function(value)\r\n{\r\n\tthis.enabled = value;\r\n};\r\n\r\n/**\r\n * Function: isGuidesEnabled\r\n * \r\n * Returns <guidesEnabled>.\r\n */\r\nmxDragSource.prototype.isGuidesEnabled = function()\r\n{\r\n\treturn this.guidesEnabled;\r\n};\r\n\r\n/**\r\n * Function: setGuidesEnabled\r\n * \r\n * Sets <guidesEnabled>.\r\n */\r\nmxDragSource.prototype.setGuidesEnabled = function(value)\r\n{\r\n\tthis.guidesEnabled = value;\r\n};\r\n\r\n/**\r\n * Function: isGridEnabled\r\n * \r\n * Returns <gridEnabled>.\r\n */\r\nmxDragSource.prototype.isGridEnabled = function()\r\n{\r\n\treturn this.gridEnabled;\r\n};\r\n\r\n/**\r\n * Function: setGridEnabled\r\n * \r\n * Sets <gridEnabled>.\r\n */\r\nmxDragSource.prototype.setGridEnabled = function(value)\r\n{\r\n\tthis.gridEnabled = value;\r\n};\r\n\r\n/**\r\n * Function: getGraphForEvent\r\n * \r\n * Returns the graph for the given mouse event. This implementation returns\r\n * null.\r\n */\r\nmxDragSource.prototype.getGraphForEvent = function(evt)\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: getDropTarget\r\n * \r\n * Returns the drop target for the given graph and coordinates. This\r\n * implementation uses <mxGraph.getCellAt>.\r\n */\r\nmxDragSource.prototype.getDropTarget = function(graph, x, y, evt)\r\n{\r\n\treturn graph.getCellAt(x, y);\r\n};\r\n\r\n/**\r\n * Function: createDragElement\r\n * \r\n * Creates and returns a clone of the <dragElementPrototype> or the <element>\r\n * if the former is not defined.\r\n */\r\nmxDragSource.prototype.createDragElement = function(evt)\r\n{\r\n\treturn this.element.cloneNode(true);\r\n};\r\n\r\n/**\r\n * Function: createPreviewElement\r\n * \r\n * Creates and returns an element which can be used as a preview in the given\r\n * graph.\r\n */\r\nmxDragSource.prototype.createPreviewElement = function(graph)\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: isActive\r\n * \r\n * Returns true if this drag source is active.\r\n */\r\nmxDragSource.prototype.isActive = function()\r\n{\r\n\treturn this.mouseMoveHandler != null;\r\n};\r\n\r\n/**\r\n * Function: reset\r\n * \r\n * Stops and removes everything and restores the state of the object.\r\n */\r\nmxDragSource.prototype.reset = function()\r\n{\r\n\tif (this.currentGraph != null)\r\n\t{\r\n\t\tthis.dragExit(this.currentGraph);\r\n\t\tthis.currentGraph = null;\r\n\t}\r\n\t\r\n\tthis.removeDragElement();\r\n\tthis.removeListeners();\r\n\tthis.stopDrag();\r\n};\r\n\r\n/**\r\n * Function: mouseDown\r\n * \r\n * Returns the drop target for the given graph and coordinates. This\r\n * implementation uses <mxGraph.getCellAt>.\r\n * \r\n * To ignore popup menu events for a drag source, this function can be\r\n * overridden as follows.\r\n * \r\n * (code)\r\n * var mouseDown = dragSource.mouseDown;\r\n * \r\n * dragSource.mouseDown = function(evt)\r\n * {\r\n *   if (!mxEvent.isPopupTrigger(evt))\r\n *   {\r\n *     mouseDown.apply(this, arguments);\r\n *   }\r\n * };\r\n * (end)\r\n */\r\nmxDragSource.prototype.mouseDown = function(evt)\r\n{\r\n\tif (this.enabled && !mxEvent.isConsumed(evt) && this.mouseMoveHandler == null)\r\n\t{\r\n\t\tthis.startDrag(evt);\r\n\t\tthis.mouseMoveHandler = mxUtils.bind(this, this.mouseMove);\r\n\t\tthis.mouseUpHandler = mxUtils.bind(this, this.mouseUp);\t\t\r\n\t\tmxEvent.addGestureListeners(document, null, this.mouseMoveHandler, this.mouseUpHandler);\r\n\t\t\r\n\t\tif (mxClient.IS_TOUCH && !mxEvent.isMouseEvent(evt))\r\n\t\t{\r\n\t\t\tthis.eventSource = mxEvent.getSource(evt);\r\n\t\t\tmxEvent.addGestureListeners(this.eventSource, null, this.mouseMoveHandler, this.mouseUpHandler);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: startDrag\r\n * \r\n * Creates the <dragElement> using <createDragElement>.\r\n */\r\nmxDragSource.prototype.startDrag = function(evt)\r\n{\r\n\tthis.dragElement = this.createDragElement(evt);\r\n\tthis.dragElement.style.position = 'absolute';\r\n\tthis.dragElement.style.zIndex = this.dragElementZIndex;\r\n\tmxUtils.setOpacity(this.dragElement, this.dragElementOpacity);\r\n\r\n\tif (this.checkEventSource && mxClient.IS_SVG)\r\n\t{\r\n\t\tthis.dragElement.style.pointerEvents = 'none';\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: stopDrag\r\n * \r\n * Invokes <removeDragElement>.\r\n */\r\nmxDragSource.prototype.stopDrag = function()\r\n{\r\n\t// LATER: This used to have a mouse event. If that is still needed we need to add another\r\n\t// final call to the DnD protocol to add a cleanup step in the case of escape press, which\r\n\t// is not associated with a mouse event and which currently calles this method.\r\n\tthis.removeDragElement();\r\n};\r\n\r\n/**\r\n * Function: removeDragElement\r\n * \r\n * Removes and destroys the <dragElement>.\r\n */\r\nmxDragSource.prototype.removeDragElement = function()\r\n{\r\n\tif (this.dragElement != null)\r\n\t{\r\n\t\tif (this.dragElement.parentNode != null)\r\n\t\t{\r\n\t\t\tthis.dragElement.parentNode.removeChild(this.dragElement);\r\n\t\t}\r\n\t\t\r\n\t\tthis.dragElement = null;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getElementForEvent\r\n * \r\n * Returns the topmost element under the given event.\r\n */\r\nmxDragSource.prototype.getElementForEvent = function(evt)\r\n{\r\n\treturn ((mxEvent.isTouchEvent(evt) || mxEvent.isPenEvent(evt)) ?\r\n\t\t\tdocument.elementFromPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt)) :\r\n\t\t\t\tmxEvent.getSource(evt));\r\n};\r\n\r\n/**\r\n * Function: graphContainsEvent\r\n * \r\n * Returns true if the given graph contains the given event.\r\n */\r\nmxDragSource.prototype.graphContainsEvent = function(graph, evt)\r\n{\r\n\tvar x = mxEvent.getClientX(evt);\r\n\tvar y = mxEvent.getClientY(evt);\r\n\tvar offset = mxUtils.getOffset(graph.container);\r\n\tvar origin = mxUtils.getScrollOrigin();\r\n\tvar elt = this.getElementForEvent(evt);\r\n\t\r\n\tif (this.checkEventSource)\r\n\t{\r\n\t\twhile (elt != null && elt != graph.container)\r\n\t\t{\r\n\t\t\telt = elt.parentNode;\r\n\t\t}\r\n\t}\r\n\r\n\t// Checks if event is inside the bounds of the graph container\r\n\treturn elt != null && x >= offset.x - origin.x && y >= offset.y - origin.y &&\r\n\t\tx <= offset.x - origin.x + graph.container.offsetWidth &&\r\n\t\ty <= offset.y - origin.y + graph.container.offsetHeight;\r\n};\r\n\r\n/**\r\n * Function: mouseMove\r\n * \r\n * Gets the graph for the given event using <getGraphForEvent>, updates the\r\n * <currentGraph>, calling <dragEnter> and <dragExit> on the new and old graph,\r\n * respectively, and invokes <dragOver> if <currentGraph> is not null.\r\n */\r\nmxDragSource.prototype.mouseMove = function(evt)\r\n{\r\n\tvar graph = this.getGraphForEvent(evt);\r\n\t\r\n\t// Checks if event is inside the bounds of the graph container\r\n\tif (graph != null && !this.graphContainsEvent(graph, evt))\r\n\t{\r\n\t\tgraph = null;\r\n\t}\r\n\r\n\tif (graph != this.currentGraph)\r\n\t{\r\n\t\tif (this.currentGraph != null)\r\n\t\t{\r\n\t\t\tthis.dragExit(this.currentGraph, evt);\r\n\t\t}\r\n\t\t\r\n\t\tthis.currentGraph = graph;\r\n\t\t\r\n\t\tif (this.currentGraph != null)\r\n\t\t{\r\n\t\t\tthis.dragEnter(this.currentGraph, evt);\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (this.currentGraph != null)\r\n\t{\r\n\t\tthis.dragOver(this.currentGraph, evt);\r\n\t}\r\n\r\n\tif (this.dragElement != null && (this.previewElement == null || this.previewElement.style.visibility != 'visible'))\r\n\t{\r\n\t\tvar x = mxEvent.getClientX(evt);\r\n\t\tvar y = mxEvent.getClientY(evt);\r\n\t\t\r\n\t\tif (this.dragElement.parentNode == null)\r\n\t\t{\r\n\t\t\tdocument.body.appendChild(this.dragElement);\r\n\t\t}\r\n\r\n\t\tthis.dragElement.style.visibility = 'visible';\r\n\t\t\r\n\t\tif (this.dragOffset != null)\r\n\t\t{\r\n\t\t\tx += this.dragOffset.x;\r\n\t\t\ty += this.dragOffset.y;\r\n\t\t}\r\n\t\t\r\n\t\tvar offset = mxUtils.getDocumentScrollOrigin(document);\r\n\t\t\r\n\t\tthis.dragElement.style.left = (x + offset.x) + 'px';\r\n\t\tthis.dragElement.style.top = (y + offset.y) + 'px';\r\n\t}\r\n\telse if (this.dragElement != null)\r\n\t{\r\n\t\tthis.dragElement.style.visibility = 'hidden';\r\n\t}\r\n\t\r\n\tmxEvent.consume(evt);\r\n};\r\n\r\n/**\r\n * Function: mouseUp\r\n * \r\n * Processes the mouse up event and invokes <drop>, <dragExit> and <stopDrag>\r\n * as required.\r\n */\r\nmxDragSource.prototype.mouseUp = function(evt)\r\n{\r\n\tif (this.currentGraph != null)\r\n\t{\r\n\t\tif (this.currentPoint != null && (this.previewElement == null ||\r\n\t\t\tthis.previewElement.style.visibility != 'hidden'))\r\n\t\t{\r\n\t\t\tvar scale = this.currentGraph.view.scale;\r\n\t\t\tvar tr = this.currentGraph.view.translate;\r\n\t\t\tvar x = this.currentPoint.x / scale - tr.x;\r\n\t\t\tvar y = this.currentPoint.y / scale - tr.y;\r\n\t\t\t\r\n\t\t\tthis.drop(this.currentGraph, evt, this.currentDropTarget, x, y);\r\n\t\t}\r\n\t\t\r\n\t\tthis.dragExit(this.currentGraph);\r\n\t\tthis.currentGraph = null;\r\n\t}\r\n\r\n\tthis.stopDrag();\r\n\tthis.removeListeners();\r\n\t\r\n\tmxEvent.consume(evt);\r\n};\r\n\r\n/**\r\n * Function: removeListeners\r\n * \r\n * Actives the given graph as a drop target.\r\n */\r\nmxDragSource.prototype.removeListeners = function()\r\n{\r\n\tif (this.eventSource != null)\r\n\t{\r\n\t\tmxEvent.removeGestureListeners(this.eventSource, null, this.mouseMoveHandler, this.mouseUpHandler);\r\n\t\tthis.eventSource = null;\r\n\t}\r\n\t\r\n\tmxEvent.removeGestureListeners(document, null, this.mouseMoveHandler, this.mouseUpHandler);\r\n\tthis.mouseMoveHandler = null;\r\n\tthis.mouseUpHandler = null;\r\n};\r\n\r\n/**\r\n * Function: dragEnter\r\n * \r\n * Actives the given graph as a drop target.\r\n */\r\nmxDragSource.prototype.dragEnter = function(graph, evt)\r\n{\r\n\tgraph.isMouseDown = true;\r\n\tgraph.isMouseTrigger = mxEvent.isMouseEvent(evt);\r\n\tthis.previewElement = this.createPreviewElement(graph);\r\n\t\r\n\tif (this.previewElement != null && this.checkEventSource && mxClient.IS_SVG)\r\n\t{\r\n\t\tthis.previewElement.style.pointerEvents = 'none';\r\n\t}\r\n\t\r\n\t// Guide is only needed if preview element is used\r\n\tif (this.isGuidesEnabled() && this.previewElement != null)\r\n\t{\r\n\t\tthis.currentGuide = new mxGuide(graph, graph.graphHandler.getGuideStates());\r\n\t}\r\n\t\r\n\tif (this.highlightDropTargets)\r\n\t{\r\n\t\tthis.currentHighlight = new mxCellHighlight(graph, mxConstants.DROP_TARGET_COLOR);\r\n\t}\r\n\t\r\n\t// Consumes all events in the current graph before they are fired\r\n\tgraph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.eventConsumer);\r\n};\r\n\r\n/**\r\n * Function: dragExit\r\n * \r\n * Deactivates the given graph as a drop target.\r\n */\r\nmxDragSource.prototype.dragExit = function(graph, evt)\r\n{\r\n\tthis.currentDropTarget = null;\r\n\tthis.currentPoint = null;\r\n\tgraph.isMouseDown = false;\r\n\t\r\n\t// Consumes all events in the current graph before they are fired\r\n\tgraph.removeListener(this.eventConsumer);\r\n\t\r\n\tif (this.previewElement != null)\r\n\t{\r\n\t\tif (this.previewElement.parentNode != null)\r\n\t\t{\r\n\t\t\tthis.previewElement.parentNode.removeChild(this.previewElement);\r\n\t\t}\r\n\t\t\r\n\t\tthis.previewElement = null;\r\n\t}\r\n\t\r\n\tif (this.currentGuide != null)\r\n\t{\r\n\t\tthis.currentGuide.destroy();\r\n\t\tthis.currentGuide = null;\r\n\t}\r\n\t\r\n\tif (this.currentHighlight != null)\r\n\t{\r\n\t\tthis.currentHighlight.destroy();\r\n\t\tthis.currentHighlight = null;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: dragOver\r\n * \r\n * Implements autoscroll, updates the <currentPoint>, highlights any drop\r\n * targets and updates the preview.\r\n */\r\nmxDragSource.prototype.dragOver = function(graph, evt)\r\n{\r\n\tvar offset = mxUtils.getOffset(graph.container);\r\n\tvar origin = mxUtils.getScrollOrigin(graph.container);\r\n\tvar x = mxEvent.getClientX(evt) - offset.x + origin.x - graph.panDx;\r\n\tvar y = mxEvent.getClientY(evt) - offset.y + origin.y - graph.panDy;\r\n\r\n\tif (graph.autoScroll && (this.autoscroll == null || this.autoscroll))\r\n\t{\r\n\t\tgraph.scrollPointToVisible(x, y, graph.autoExtend);\r\n\t}\r\n\r\n\t// Highlights the drop target under the mouse\r\n\tif (this.currentHighlight != null && graph.isDropEnabled())\r\n\t{\r\n\t\tthis.currentDropTarget = this.getDropTarget(graph, x, y, evt);\r\n\t\tvar state = graph.getView().getState(this.currentDropTarget);\r\n\t\tthis.currentHighlight.highlight(state);\r\n\t}\r\n\r\n\t// Updates the location of the preview\r\n\tif (this.previewElement != null)\r\n\t{\r\n\t\tif (this.previewElement.parentNode == null)\r\n\t\t{\r\n\t\t\tgraph.container.appendChild(this.previewElement);\r\n\t\t\t\r\n\t\t\tthis.previewElement.style.zIndex = '3';\r\n\t\t\tthis.previewElement.style.position = 'absolute';\r\n\t\t}\r\n\t\t\r\n\t\tvar gridEnabled = this.isGridEnabled() && graph.isGridEnabledEvent(evt);\r\n\t\tvar hideGuide = true;\r\n\r\n\t\t// Grid and guides\r\n\t\tif (this.currentGuide != null && this.currentGuide.isEnabledForEvent(evt))\r\n\t\t{\r\n\t\t\t// LATER: HTML preview appears smaller than SVG preview\r\n\t\t\tvar w = parseInt(this.previewElement.style.width);\r\n\t\t\tvar h = parseInt(this.previewElement.style.height);\r\n\t\t\tvar bounds = new mxRectangle(0, 0, w, h);\r\n\t\t\tvar delta = new mxPoint(x, y);\r\n\t\t\tdelta = this.currentGuide.move(bounds, delta, gridEnabled, true);\r\n\t\t\thideGuide = false;\r\n\t\t\tx = delta.x;\r\n\t\t\ty = delta.y;\r\n\t\t}\r\n\t\telse if (gridEnabled)\r\n\t\t{\r\n\t\t\tvar scale = graph.view.scale;\r\n\t\t\tvar tr = graph.view.translate;\r\n\t\t\tvar off = graph.gridSize / 2;\r\n\t\t\tx = (graph.snap(x / scale - tr.x - off) + tr.x) * scale;\r\n\t\t\ty = (graph.snap(y / scale - tr.y - off) + tr.y) * scale;\r\n\t\t}\r\n\t\t\r\n\t\tif (this.currentGuide != null && hideGuide)\r\n\t\t{\r\n\t\t\tthis.currentGuide.hide();\r\n\t\t}\r\n\t\t\r\n\t\tif (this.previewOffset != null)\r\n\t\t{\r\n\t\t\tx += this.previewOffset.x;\r\n\t\t\ty += this.previewOffset.y;\r\n\t\t}\r\n\r\n\t\tthis.previewElement.style.left = Math.round(x) + 'px';\r\n\t\tthis.previewElement.style.top = Math.round(y) + 'px';\r\n\t\tthis.previewElement.style.visibility = 'visible';\r\n\t}\r\n\t\r\n\tthis.currentPoint = new mxPoint(x, y);\r\n};\r\n\r\n/**\r\n * Function: drop\r\n * \r\n * Returns the drop target for the given graph and coordinates. This\r\n * implementation uses <mxGraph.getCellAt>.\r\n */\r\nmxDragSource.prototype.drop = function(graph, evt, dropTarget, x, y)\r\n{\r\n\tthis.dropHandler.apply(this, arguments);\r\n\t\r\n\t// Had to move this to after the insert because it will\r\n\t// affect the scrollbars of the window in IE to try and\r\n\t// make the complete container visible.\r\n\t// LATER: Should be made optional.\r\n\tif (graph.container.style.visibility != 'hidden')\r\n\t{\r\n\t\tgraph.container.focus();\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxToolbar\r\n * \r\n * Creates a toolbar inside a given DOM node. The toolbar may contain icons,\r\n * buttons and combo boxes.\r\n * \r\n * Event: mxEvent.SELECT\r\n * \r\n * Fires when an item was selected in the toolbar. The <code>function</code>\r\n * property contains the function that was selected in <selectMode>.\r\n * \r\n * Constructor: mxToolbar\r\n * \r\n * Constructs a toolbar in the specified container.\r\n *\r\n * Parameters:\r\n *\r\n * container - DOM node that contains the toolbar.\r\n */\r\nfunction mxToolbar(container)\r\n{\r\n\tthis.container = container;\r\n};\r\n\r\n/**\r\n * Extends mxEventSource.\r\n */\r\nmxToolbar.prototype = new mxEventSource();\r\nmxToolbar.prototype.constructor = mxToolbar;\r\n\r\n/**\r\n * Variable: container\r\n * \r\n * Reference to the DOM nodes that contains the toolbar.\r\n */\r\nmxToolbar.prototype.container = null;\r\n\r\n/**\r\n * Variable: enabled\r\n * \r\n * Specifies if events are handled. Default is true.\r\n */\r\nmxToolbar.prototype.enabled = true;\r\n\r\n/**\r\n * Variable: noReset\r\n * \r\n * Specifies if <resetMode> requires a forced flag of true for resetting\r\n * the current mode in the toolbar. Default is false. This is set to true\r\n * if the toolbar item is double clicked to avoid a reset after a single\r\n * use of the item.\r\n */\r\nmxToolbar.prototype.noReset = false;\r\n\r\n/**\r\n * Variable: updateDefaultMode\r\n * \r\n * Boolean indicating if the default mode should be the last selected\r\n * switch mode or the first inserted switch mode. Default is true, that\r\n * is the last selected switch mode is the default mode. The default mode\r\n * is the mode to be selected after a reset of the toolbar. If this is\r\n * false, then the default mode is the first inserted mode item regardless\r\n * of what was last selected. Otherwise, the selected item after a reset is\r\n * the previously selected item.\r\n */\r\nmxToolbar.prototype.updateDefaultMode = true;\r\n\r\n/**\r\n * Function: addItem\r\n * \r\n * Adds the given function as an image with the specified title and icon\r\n * and returns the new image node.\r\n * \r\n * Parameters:\r\n * \r\n * title - Optional string that is used as the tooltip.\r\n * icon - Optional URL of the image to be used. If no URL is given, then a\r\n * button is created.\r\n * funct - Function to execute on a mouse click.\r\n * pressedIcon - Optional URL of the pressed image. Default is a gray\r\n * background.\r\n * style - Optional style classname. Default is mxToolbarItem.\r\n * factoryMethod - Optional factory method for popup menu, eg.\r\n * function(menu, evt, cell) { menu.addItem('Hello, World!'); }\r\n */\r\nmxToolbar.prototype.addItem = function(title, icon, funct, pressedIcon, style, factoryMethod)\r\n{\r\n\tvar img = document.createElement((icon != null) ? 'img' : 'button');\r\n\tvar initialClassName = style || ((factoryMethod != null) ?\r\n\t\t\t'mxToolbarMode' : 'mxToolbarItem');\r\n\timg.className = initialClassName;\r\n\timg.setAttribute('src', icon);\r\n\t\r\n\tif (title != null)\r\n\t{\r\n\t\tif (icon != null)\r\n\t\t{\r\n\t\t\timg.setAttribute('title', title);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tmxUtils.write(img, title);\r\n\t\t}\r\n\t}\r\n\t\r\n\tthis.container.appendChild(img);\r\n\r\n\t// Invokes the function on a click on the toolbar item\r\n\tif (funct != null)\r\n\t{\r\n\t\tmxEvent.addListener(img, 'click', funct);\r\n\t\t\r\n\t\tif (mxClient.IS_TOUCH)\r\n\t\t{\r\n\t\t\tmxEvent.addListener(img, 'touchend', funct);\r\n\t\t}\r\n\t}\r\n\r\n\tvar mouseHandler = mxUtils.bind(this, function(evt)\r\n\t{\r\n\t\tif (pressedIcon != null)\r\n\t\t{\r\n\t\t\timg.setAttribute('src', icon);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\timg.style.backgroundColor = '';\r\n\t\t}\r\n\t});\r\n\r\n\t// Highlights the toolbar item with a gray background\r\n\t// while it is being clicked with the mouse\r\n\tmxEvent.addGestureListeners(img, mxUtils.bind(this, function(evt)\r\n\t{\r\n\t\tif (pressedIcon != null)\r\n\t\t{\r\n\t\t\timg.setAttribute('src', pressedIcon);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\timg.style.backgroundColor = 'gray';\r\n\t\t}\r\n\t\t\r\n\t\t// Popup Menu\r\n\t\tif (factoryMethod != null)\r\n\t\t{\r\n\t\t\tif (this.menu == null)\r\n\t\t\t{\r\n\t\t\t\tthis.menu = new mxPopupMenu();\r\n\t\t\t\tthis.menu.init();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar last = this.currentImg;\r\n\t\t\t\r\n\t\t\tif (this.menu.isMenuShowing())\r\n\t\t\t{\r\n\t\t\t\tthis.menu.hideMenu();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (last != img)\r\n\t\t\t{\r\n\t\t\t\t// Redirects factory method to local factory method\r\n\t\t\t\tthis.currentImg = img;\r\n\t\t\t\tthis.menu.factoryMethod = factoryMethod;\r\n\t\t\t\t\r\n\t\t\t\tvar point = new mxPoint(\r\n\t\t\t\t\timg.offsetLeft,\r\n\t\t\t\t\timg.offsetTop + img.offsetHeight);\r\n\t\t\t\tthis.menu.popup(point.x, point.y, null, evt);\r\n\r\n\t\t\t\t// Sets and overrides to restore classname\r\n\t\t\t\tif (this.menu.isMenuShowing())\r\n\t\t\t\t{\r\n\t\t\t\t\timg.className = initialClassName + 'Selected';\r\n\t\t\t\t\t\r\n\t\t\t\t\tthis.menu.hideMenu = function()\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tmxPopupMenu.prototype.hideMenu.apply(this);\r\n\t\t\t\t\t\timg.className = initialClassName;\r\n\t\t\t\t\t\tthis.currentImg = null;\r\n\t\t\t\t\t};\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}), null, mouseHandler);\r\n\r\n\tmxEvent.addListener(img, 'mouseout', mouseHandler);\r\n\t\r\n\treturn img;\r\n};\r\n\r\n/**\r\n * Function: addCombo\r\n * \r\n * Adds and returns a new SELECT element using the given style. The element\r\n * is placed inside a DIV with the mxToolbarComboContainer style classname.\r\n * \r\n * Parameters:\r\n * \r\n * style - Optional style classname. Default is mxToolbarCombo.\r\n */\r\nmxToolbar.prototype.addCombo = function(style)\r\n{\r\n\tvar div = document.createElement('div');\r\n\tdiv.style.display = 'inline';\r\n\tdiv.className = 'mxToolbarComboContainer';\r\n\t\r\n\tvar select = document.createElement('select');\r\n\tselect.className = style || 'mxToolbarCombo';\r\n\tdiv.appendChild(select);\r\n\t\r\n\tthis.container.appendChild(div);\r\n\t\r\n\treturn select;\r\n};\r\n\r\n/**\r\n * Function: addCombo\r\n * \r\n * Adds and returns a new SELECT element using the given title as the\r\n * default element. The selection is reset to this element after each\r\n * change.\r\n * \r\n * Parameters:\r\n * \r\n * title - String that specifies the title of the default element.\r\n * style - Optional style classname. Default is mxToolbarCombo.\r\n */\r\nmxToolbar.prototype.addActionCombo = function(title, style)\r\n{\r\n\tvar select = document.createElement('select');\r\n\tselect.className = style || 'mxToolbarCombo';\r\n\tthis.addOption(select, title, null);\r\n\t\r\n\tmxEvent.addListener(select, 'change', function(evt)\r\n\t{\r\n\t\tvar value = select.options[select.selectedIndex];\r\n\t\tselect.selectedIndex = 0;\r\n\t\t\r\n\t\tif (value.funct != null)\r\n\t\t{\r\n\t\t\tvalue.funct(evt);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.container.appendChild(select);\r\n\t\r\n\treturn select;\r\n};\r\n\r\n/**\r\n * Function: addOption\r\n * \r\n * Adds and returns a new OPTION element inside the given SELECT element.\r\n * If the given value is a function then it is stored in the option's funct\r\n * field.\r\n * \r\n * Parameters:\r\n * \r\n * combo - SELECT element that will contain the new entry.\r\n * title - String that specifies the title of the option.\r\n * value - Specifies the value associated with this option.\r\n */\r\nmxToolbar.prototype.addOption = function(combo, title, value)\r\n{\r\n\tvar option = document.createElement('option');\r\n\tmxUtils.writeln(option, title);\r\n\t\r\n\tif (typeof(value) == 'function')\r\n\t{\r\n\t\toption.funct = value;\r\n\t}\r\n\telse\r\n\t{\r\n\t\toption.setAttribute('value', value);\r\n\t}\r\n\t\r\n\tcombo.appendChild(option);\r\n\t\r\n\treturn option;\r\n};\r\n\r\n/**\r\n * Function: addSwitchMode\r\n * \r\n * Adds a new selectable item to the toolbar. Only one switch mode item may\r\n * be selected at a time. The currently selected item is the default item\r\n * after a reset of the toolbar.\r\n */\r\nmxToolbar.prototype.addSwitchMode = function(title, icon, funct, pressedIcon, style)\r\n{\r\n\tvar img = document.createElement('img');\r\n\timg.initialClassName = style || 'mxToolbarMode';\r\n\timg.className = img.initialClassName;\r\n\timg.setAttribute('src', icon);\r\n\timg.altIcon = pressedIcon;\r\n\t\r\n\tif (title != null)\r\n\t{\r\n\t\timg.setAttribute('title', title);\r\n\t}\r\n\t\r\n\tmxEvent.addListener(img, 'click', mxUtils.bind(this, function(evt)\r\n\t{\r\n\t\tvar tmp = this.selectedMode.altIcon;\r\n\t\t\r\n\t\tif (tmp != null)\r\n\t\t{\r\n\t\t\tthis.selectedMode.altIcon = this.selectedMode.getAttribute('src');\r\n\t\t\tthis.selectedMode.setAttribute('src', tmp);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.selectedMode.className = this.selectedMode.initialClassName;\r\n\t\t}\r\n\t\t\r\n\t\tif (this.updateDefaultMode)\r\n\t\t{\r\n\t\t\tthis.defaultMode = img;\r\n\t\t}\r\n\t\t\r\n\t\tthis.selectedMode = img;\r\n\t\t\r\n\t\tvar tmp = img.altIcon;\r\n\t\t\r\n\t\tif (tmp != null)\r\n\t\t{\r\n\t\t\timg.altIcon = img.getAttribute('src');\r\n\t\t\timg.setAttribute('src', tmp);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\timg.className = img.initialClassName+'Selected';\r\n\t\t}\r\n\t\t\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.SELECT));\r\n\t\tfunct();\r\n\t}));\r\n\t\r\n\tthis.container.appendChild(img);\r\n\t\r\n\tif (this.defaultMode == null)\r\n\t{\r\n\t\tthis.defaultMode = img;\r\n\t\t\r\n\t\t// Function should fire only once so\r\n\t\t// do not pass it with the select event\r\n\t\tthis.selectMode(img);\r\n\t\tfunct();\r\n\t}\r\n\t\r\n\treturn img;\r\n};\r\n\r\n/**\r\n * Function: addMode\r\n * \r\n * Adds a new item to the toolbar. The selection is typically reset after\r\n * the item has been consumed, for example by adding a new vertex to the\r\n * graph. The reset is not carried out if the item is double clicked.\r\n * \r\n * The function argument uses the following signature: funct(evt, cell) where\r\n * evt is the native mouse event and cell is the cell under the mouse.\r\n */\r\nmxToolbar.prototype.addMode = function(title, icon, funct, pressedIcon, style, toggle)\r\n{\r\n\ttoggle = (toggle != null) ? toggle : true;\r\n\tvar img = document.createElement((icon != null) ? 'img' : 'button');\r\n\t\r\n\timg.initialClassName = style || 'mxToolbarMode';\r\n\timg.className = img.initialClassName;\r\n\timg.setAttribute('src', icon);\r\n\timg.altIcon = pressedIcon;\r\n\r\n\tif (title != null)\r\n\t{\r\n\t\timg.setAttribute('title', title);\r\n\t}\r\n\t\r\n\tif (this.enabled && toggle)\r\n\t{\r\n\t\tmxEvent.addListener(img, 'click', mxUtils.bind(this, function(evt)\r\n\t\t{\r\n\t\t\tthis.selectMode(img, funct);\r\n\t\t\tthis.noReset = false;\r\n\t\t}));\r\n\t\t\r\n\t\tmxEvent.addListener(img, 'dblclick', mxUtils.bind(this, function(evt)\r\n\t\t{\r\n\t\t\tthis.selectMode(img, funct);\r\n\t\t\tthis.noReset = true;\r\n\t\t}));\r\n\t\t\r\n\t\tif (this.defaultMode == null)\r\n\t\t{\r\n\t\t\tthis.defaultMode = img;\r\n\t\t\tthis.defaultFunction = funct;\r\n\t\t\tthis.selectMode(img, funct);\r\n\t\t}\r\n\t}\r\n\r\n\tthis.container.appendChild(img);\t\t\t\t\t\r\n\r\n\treturn img;\r\n};\r\n\r\n/**\r\n * Function: selectMode\r\n * \r\n * Resets the state of the previously selected mode and displays the given\r\n * DOM node as selected. This function fires a select event with the given\r\n * function as a parameter.\r\n */\r\nmxToolbar.prototype.selectMode = function(domNode, funct)\r\n{\r\n\tif (this.selectedMode != domNode)\r\n\t{\r\n\t\tif (this.selectedMode != null)\r\n\t\t{\r\n\t\t\tvar tmp = this.selectedMode.altIcon;\r\n\t\t\t\r\n\t\t\tif (tmp != null)\r\n\t\t\t{\r\n\t\t\t\tthis.selectedMode.altIcon = this.selectedMode.getAttribute('src');\r\n\t\t\t\tthis.selectedMode.setAttribute('src', tmp);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.selectedMode.className = this.selectedMode.initialClassName;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tthis.selectedMode = domNode;\r\n\t\tvar tmp = this.selectedMode.altIcon;\r\n\t\t\r\n\t\tif (tmp != null)\r\n\t\t{\r\n\t\t\tthis.selectedMode.altIcon = this.selectedMode.getAttribute('src');\r\n\t\t\tthis.selectedMode.setAttribute('src', tmp);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.selectedMode.className = this.selectedMode.initialClassName+'Selected';\r\n\t\t}\r\n\t\t\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.SELECT, \"function\", funct));\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: resetMode\r\n * \r\n * Selects the default mode and resets the state of the previously selected\r\n * mode.\r\n */\r\nmxToolbar.prototype.resetMode = function(forced)\r\n{\r\n\tif ((forced || !this.noReset) && this.selectedMode != this.defaultMode)\r\n\t{\r\n\t\t// The last selected switch mode will be activated\r\n\t\t// so the function was already executed and is\r\n\t\t// no longer required here\r\n\t\tthis.selectMode(this.defaultMode, this.defaultFunction);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: addSeparator\r\n * \r\n * Adds the specifies image as a separator.\r\n * \r\n * Parameters:\r\n * \r\n * icon - URL of the separator icon.\r\n */\r\nmxToolbar.prototype.addSeparator = function(icon)\r\n{\r\n\treturn this.addItem(null, icon, null);\r\n};\r\n\r\n/**\r\n * Function: addBreak\r\n * \r\n * Adds a break to the container.\r\n */\r\nmxToolbar.prototype.addBreak = function()\r\n{\r\n\tmxUtils.br(this.container);\r\n};\r\n\r\n/**\r\n * Function: addLine\r\n * \r\n * Adds a horizontal line to the container.\r\n */\r\nmxToolbar.prototype.addLine = function()\r\n{\r\n\tvar hr = document.createElement('hr');\r\n\t\r\n\thr.style.marginRight = '6px';\r\n\thr.setAttribute('size', '1');\r\n\t\r\n\tthis.container.appendChild(hr);\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Removes the toolbar and all its associated resources.\r\n */\r\nmxToolbar.prototype.destroy = function ()\r\n{\r\n\tmxEvent.release(this.container);\r\n\tthis.container = null;\r\n\tthis.defaultMode = null;\r\n\tthis.defaultFunction = null;\r\n\tthis.selectedMode = null;\r\n\t\r\n\tif (this.menu != null)\r\n\t{\r\n\t\tthis.menu.destroy();\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxUndoableEdit\r\n * \r\n * Implements a composite undoable edit. Here is an example for a custom change\r\n * which gets executed via the model:\r\n * \r\n * (code)\r\n * function CustomChange(model, name)\r\n * {\r\n *   this.model = model;\r\n *   this.name = name;\r\n *   this.previous = name;\r\n * };\r\n * \r\n * CustomChange.prototype.execute = function()\r\n * {\r\n *   var tmp = this.model.name;\r\n *   this.model.name = this.previous;\r\n *   this.previous = tmp;\r\n * };\r\n * \r\n * var name = prompt('Enter name');\r\n * graph.model.execute(new CustomChange(graph.model, name));\r\n * (end)\r\n * \r\n * Event: mxEvent.EXECUTED\r\n * \r\n * Fires between START_EDIT and END_EDIT after an atomic change was executed.\r\n * The <code>change</code> property contains the change that was executed.\r\n * \r\n * Event: mxEvent.START_EDIT\r\n * \r\n * Fires before a set of changes will be executed in <undo> or <redo>.\r\n * This event contains no properties.\r\n * \r\n * Event: mxEvent.END_EDIT\r\n *\r\n * Fires after a set of changeswas executed in <undo> or <redo>.\r\n * This event contains no properties.\r\n * \r\n * Constructor: mxUndoableEdit\r\n * \r\n * Constructs a new undoable edit for the given source.\r\n */\r\nfunction mxUndoableEdit(source, significant)\r\n{\r\n\tthis.source = source;\r\n\tthis.changes = [];\r\n\tthis.significant = (significant != null) ? significant : true;\r\n};\r\n\r\n/**\r\n * Variable: source\r\n * \r\n * Specifies the source of the edit.\r\n */\r\nmxUndoableEdit.prototype.source = null;\r\n\r\n/**\r\n * Variable: changes\r\n * \r\n * Array that contains the changes that make up this edit. The changes are\r\n * expected to either have an undo and redo function, or an execute\r\n * function. Default is an empty array.\r\n */\r\nmxUndoableEdit.prototype.changes = null;\r\n\r\n/**\r\n * Variable: significant\r\n * \r\n * Specifies if the undoable change is significant.\r\n * Default is true.\r\n */\r\nmxUndoableEdit.prototype.significant = null;\r\n\r\n/**\r\n * Variable: undone\r\n * \r\n * Specifies if this edit has been undone. Default is false.\r\n */\r\nmxUndoableEdit.prototype.undone = false;\r\n\r\n/**\r\n * Variable: redone\r\n * \r\n * Specifies if this edit has been redone. Default is false.\r\n */\r\nmxUndoableEdit.prototype.redone = false;\r\n\r\n/**\r\n * Function: isEmpty\r\n * \r\n * Returns true if the this edit contains no changes.\r\n */\r\nmxUndoableEdit.prototype.isEmpty = function()\r\n{\r\n\treturn this.changes.length == 0;\r\n};\r\n\r\n/**\r\n * Function: isSignificant\r\n * \r\n * Returns <significant>.\r\n */\r\nmxUndoableEdit.prototype.isSignificant = function()\r\n{\r\n\treturn this.significant;\r\n};\r\n\r\n/**\r\n * Function: add\r\n * \r\n * Adds the specified change to this edit. The change is an object that is\r\n * expected to either have an undo and redo, or an execute function.\r\n */\r\nmxUndoableEdit.prototype.add = function(change)\r\n{\r\n\tthis.changes.push(change);\r\n};\r\n\r\n/**\r\n * Function: notify\r\n * \r\n * Hook to notify any listeners of the changes after an <undo> or <redo>\r\n * has been carried out. This implementation is empty.\r\n */\r\nmxUndoableEdit.prototype.notify = function() { };\r\n\r\n/**\r\n * Function: die\r\n * \r\n * Hook to free resources after the edit has been removed from the command\r\n * history. This implementation is empty.\r\n */\r\nmxUndoableEdit.prototype.die = function() { };\r\n\r\n/**\r\n * Function: undo\r\n * \r\n * Undoes all changes in this edit.\r\n */\r\nmxUndoableEdit.prototype.undo = function()\r\n{\r\n\tif (!this.undone)\r\n\t{\r\n\t\tthis.source.fireEvent(new mxEventObject(mxEvent.START_EDIT));\r\n\t\tvar count = this.changes.length;\r\n\t\t\r\n\t\tfor (var i = count - 1; i >= 0; i--)\r\n\t\t{\r\n\t\t\tvar change = this.changes[i];\r\n\t\t\t\r\n\t\t\tif (change.execute != null)\r\n\t\t\t{\r\n\t\t\t\tchange.execute();\r\n\t\t\t}\r\n\t\t\telse if (change.undo != null)\r\n\t\t\t{\r\n\t\t\t\tchange.undo();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// New global executed event\r\n\t\t\tthis.source.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change));\r\n\t\t}\r\n\t\t\r\n\t\tthis.undone = true;\r\n\t\tthis.redone = false;\r\n\t\tthis.source.fireEvent(new mxEventObject(mxEvent.END_EDIT));\r\n\t}\r\n\t\r\n\tthis.notify();\r\n};\r\n\r\n/**\r\n * Function: redo\r\n * \r\n * Redoes all changes in this edit.\r\n */\r\nmxUndoableEdit.prototype.redo = function()\r\n{\r\n\tif (!this.redone)\r\n\t{\r\n\t\tthis.source.fireEvent(new mxEventObject(mxEvent.START_EDIT));\r\n\t\tvar count = this.changes.length;\r\n\t\t\r\n\t\tfor (var i = 0; i < count; i++)\r\n\t\t{\r\n\t\t\tvar change = this.changes[i];\r\n\t\t\t\r\n\t\t\tif (change.execute != null)\r\n\t\t\t{\r\n\t\t\t\tchange.execute();\r\n\t\t\t}\r\n\t\t\telse if (change.redo != null)\r\n\t\t\t{\r\n\t\t\t\tchange.redo();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// New global executed event\r\n\t\t\tthis.source.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change));\r\n\t\t}\r\n\t\t\r\n\t\tthis.undone = false;\r\n\t\tthis.redone = true;\r\n\t\tthis.source.fireEvent(new mxEventObject(mxEvent.END_EDIT));\r\n\t}\r\n\t\r\n\tthis.notify();\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxUndoManager\r\n *\r\n * Implements a command history. When changing the graph model, an\r\n * <mxUndoableChange> object is created at the start of the transaction (when\r\n * model.beginUpdate is called). All atomic changes are then added to this\r\n * object until the last model.endUpdate call, at which point the\r\n * <mxUndoableEdit> is dispatched in an event, and added to the history inside\r\n * <mxUndoManager>. This is done by an event listener in\r\n * <mxEditor.installUndoHandler>.\r\n * \r\n * Each atomic change of the model is represented by an object (eg.\r\n * <mxRootChange>, <mxChildChange>, <mxTerminalChange> etc) which contains the\r\n * complete undo information. The <mxUndoManager> also listens to the\r\n * <mxGraphView> and stores it's changes to the current root as insignificant\r\n * undoable changes, so that drilling (step into, step up) is undone.\r\n * \r\n * This means when you execute an atomic change on the model, then change the\r\n * current root on the view and click undo, the change of the root will be\r\n * undone together with the change of the model so that the display represents\r\n * the state at which the model was changed. However, these changes are not\r\n * transmitted for sharing as they do not represent a state change.\r\n *\r\n * Example:\r\n * \r\n * When adding an undo manager to a graph, make sure to add it\r\n * to the model and the view as well to maintain a consistent\r\n * display across multiple undo/redo steps.\r\n *\r\n * (code)\r\n * var undoManager = new mxUndoManager();\r\n * var listener = function(sender, evt)\r\n * {\r\n *   undoManager.undoableEditHappened(evt.getProperty('edit'));\r\n * };\r\n * graph.getModel().addListener(mxEvent.UNDO, listener);\r\n * graph.getView().addListener(mxEvent.UNDO, listener);\r\n * (end)\r\n * \r\n * The code creates a function that informs the undoManager\r\n * of an undoable edit and binds it to the undo event of\r\n * <mxGraphModel> and <mxGraphView> using\r\n * <mxEventSource.addListener>.\r\n * \r\n * Event: mxEvent.CLEAR\r\n * \r\n * Fires after <clear> was invoked. This event has no properties.\r\n * \r\n * Event: mxEvent.UNDO\r\n * \r\n * Fires afer a significant edit was undone in <undo>. The <code>edit</code>\r\n * property contains the <mxUndoableEdit> that was undone.\r\n * \r\n * Event: mxEvent.REDO\r\n * \r\n * Fires afer a significant edit was redone in <redo>. The <code>edit</code>\r\n * property contains the <mxUndoableEdit> that was redone.\r\n * \r\n * Event: mxEvent.ADD\r\n * \r\n * Fires after an undoable edit was added to the history. The <code>edit</code>\r\n * property contains the <mxUndoableEdit> that was added.\r\n * \r\n * Constructor: mxUndoManager\r\n *\r\n * Constructs a new undo manager with the given history size. If no history\r\n * size is given, then a default size of 100 steps is used.\r\n */\r\nfunction mxUndoManager(size)\r\n{\r\n\tthis.size = (size != null) ? size : 100;\r\n\tthis.clear();\r\n};\r\n\r\n/**\r\n * Extends mxEventSource.\r\n */\r\nmxUndoManager.prototype = new mxEventSource();\r\nmxUndoManager.prototype.constructor = mxUndoManager;\r\n\r\n/**\r\n * Variable: size\r\n * \r\n * Maximum command history size. 0 means unlimited history. Default is\r\n * 100.\r\n */\r\nmxUndoManager.prototype.size = null;\r\n\r\n/**\r\n * Variable: history\r\n * \r\n * Array that contains the steps of the command history.\r\n */\r\nmxUndoManager.prototype.history = null;\r\n\r\n/**\r\n * Variable: indexOfNextAdd\r\n * \r\n * Index of the element to be added next.\r\n */\r\nmxUndoManager.prototype.indexOfNextAdd = 0;\r\n\r\n/**\r\n * Function: isEmpty\r\n * \r\n * Returns true if the history is empty.\r\n */\r\nmxUndoManager.prototype.isEmpty = function()\r\n{\r\n\treturn this.history.length == 0;\r\n};\r\n\r\n/**\r\n * Function: clear\r\n * \r\n * Clears the command history.\r\n */\r\nmxUndoManager.prototype.clear = function()\r\n{\r\n\tthis.history = [];\r\n\tthis.indexOfNextAdd = 0;\r\n\tthis.fireEvent(new mxEventObject(mxEvent.CLEAR));\r\n};\r\n\r\n/**\r\n * Function: canUndo\r\n * \r\n * Returns true if an undo is possible.\r\n */\r\nmxUndoManager.prototype.canUndo = function()\r\n{\r\n\treturn this.indexOfNextAdd > 0;\r\n};\r\n\r\n/**\r\n * Function: undo\r\n * \r\n * Undoes the last change.\r\n */\r\nmxUndoManager.prototype.undo = function()\r\n{\r\n    while (this.indexOfNextAdd > 0)\r\n    {\r\n        var edit = this.history[--this.indexOfNextAdd];\r\n        edit.undo();\r\n\r\n\t\tif (edit.isSignificant())\r\n        {\r\n        \tthis.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));\r\n            break;\r\n        }\r\n    }\r\n};\r\n\r\n/**\r\n * Function: canRedo\r\n * \r\n * Returns true if a redo is possible.\r\n */\r\nmxUndoManager.prototype.canRedo = function()\r\n{\r\n\treturn this.indexOfNextAdd < this.history.length;\r\n};\r\n\r\n/**\r\n * Function: redo\r\n * \r\n * Redoes the last change.\r\n */\r\nmxUndoManager.prototype.redo = function()\r\n{\r\n    var n = this.history.length;\r\n    \r\n    while (this.indexOfNextAdd < n)\r\n    {\r\n        var edit =  this.history[this.indexOfNextAdd++];\r\n        edit.redo();\r\n        \r\n        if (edit.isSignificant())\r\n        {\r\n        \tthis.fireEvent(new mxEventObject(mxEvent.REDO, 'edit', edit));\r\n            break;\r\n        }\r\n    }\r\n};\r\n\r\n/**\r\n * Function: undoableEditHappened\r\n * \r\n * Method to be called to add new undoable edits to the <history>.\r\n */\r\nmxUndoManager.prototype.undoableEditHappened = function(undoableEdit)\r\n{\r\n\tthis.trim();\r\n\t\r\n\tif (this.size > 0 &&\r\n\t\tthis.size == this.history.length)\r\n\t{\r\n\t\tthis.history.shift();\r\n\t}\r\n\t\r\n\tthis.history.push(undoableEdit);\r\n\tthis.indexOfNextAdd = this.history.length;\r\n\tthis.fireEvent(new mxEventObject(mxEvent.ADD, 'edit', undoableEdit));\r\n};\r\n\r\n/**\r\n * Function: trim\r\n * \r\n * Removes all pending steps after <indexOfNextAdd> from the history,\r\n * invoking die on each edit. This is called from <undoableEditHappened>.\r\n */\r\nmxUndoManager.prototype.trim = function()\r\n{\r\n\tif (this.history.length > this.indexOfNextAdd)\r\n\t{\r\n\t\tvar edits = this.history.splice(this.indexOfNextAdd,\r\n\t\t\tthis.history.length - this.indexOfNextAdd);\r\n\t\t\t\r\n\t\tfor (var i = 0; i < edits.length; i++)\r\n\t\t{\r\n\t\t\tedits[i].die();\r\n\t\t}\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n *\r\n * Class: mxUrlConverter\r\n * \r\n * Converts relative and absolute URLs to absolute URLs with protocol and domain.\r\n */\r\nvar mxUrlConverter = function()\r\n{\r\n\t// Empty constructor\r\n};\r\n\r\n/**\r\n * Variable: enabled\r\n * \r\n * Specifies if the converter is enabled. Default is true.\r\n */\r\nmxUrlConverter.prototype.enabled = true;\r\n\r\n/**\r\n * Variable: baseUrl\r\n * \r\n * Specifies the base URL to be used as a prefix for relative URLs.\r\n */\r\nmxUrlConverter.prototype.baseUrl = null;\r\n\r\n/**\r\n * Variable: baseDomain\r\n * \r\n * Specifies the base domain to be used as a prefix for absolute URLs.\r\n */\r\nmxUrlConverter.prototype.baseDomain = null;\r\n\r\n/**\r\n * Function: updateBaseUrl\r\n * \r\n * Private helper function to update the base URL.\r\n */\r\nmxUrlConverter.prototype.updateBaseUrl = function()\r\n{\r\n\tthis.baseDomain = location.protocol + '//' + location.host;\r\n\tthis.baseUrl = this.baseDomain + location.pathname;\r\n\tvar tmp = this.baseUrl.lastIndexOf('/');\r\n\t\r\n\t// Strips filename etc\r\n\tif (tmp > 0)\r\n\t{\r\n\t\tthis.baseUrl = this.baseUrl.substring(0, tmp + 1);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isEnabled\r\n * \r\n * Returns <enabled>.\r\n */\r\nmxUrlConverter.prototype.isEnabled = function()\r\n{\r\n\treturn this.enabled;\r\n};\r\n\r\n/**\r\n * Function: setEnabled\r\n * \r\n * Sets <enabled>.\r\n */\r\nmxUrlConverter.prototype.setEnabled = function(value)\r\n{\r\n\tthis.enabled = value;\r\n};\r\n\r\n/**\r\n * Function: getBaseUrl\r\n * \r\n * Returns <baseUrl>.\r\n */\r\nmxUrlConverter.prototype.getBaseUrl = function()\r\n{\r\n\treturn this.baseUrl;\r\n};\r\n\r\n/**\r\n * Function: setBaseUrl\r\n * \r\n * Sets <baseUrl>.\r\n */\r\nmxUrlConverter.prototype.setBaseUrl = function(value)\r\n{\r\n\tthis.baseUrl = value;\r\n};\r\n\r\n/**\r\n * Function: getBaseDomain\r\n * \r\n * Returns <baseDomain>.\r\n */\r\nmxUrlConverter.prototype.getBaseDomain = function()\r\n{\r\n\treturn this.baseDomain;\r\n},\r\n\r\n/**\r\n * Function: setBaseDomain\r\n * \r\n * Sets <baseDomain>.\r\n */\r\nmxUrlConverter.prototype.setBaseDomain = function(value)\r\n{\r\n\tthis.baseDomain = value;\r\n},\r\n\r\n/**\r\n * Function: isRelativeUrl\r\n * \r\n * Returns true if the given URL is relative.\r\n */\r\nmxUrlConverter.prototype.isRelativeUrl = function(url)\r\n{\r\n\treturn url.substring(0, 2) != '//' && url.substring(0, 7) != 'http://' &&\r\n\t\turl.substring(0, 8) != 'https://' && url.substring(0, 10) != 'data:image' &&\r\n\t\turl.substring(0, 7) != 'file://';\r\n};\r\n\r\n/**\r\n * Function: convert\r\n * \r\n * Converts the given URL to an absolute URL with protol and domain.\r\n * Relative URLs are first converted to absolute URLs.\r\n */\r\nmxUrlConverter.prototype.convert = function(url)\r\n{\r\n\tif (this.isEnabled() && this.isRelativeUrl(url))\r\n\t{\r\n\t\tif (this.getBaseUrl() == null)\r\n\t\t{\r\n\t\t\tthis.updateBaseUrl();\r\n\t\t}\r\n\t\t\r\n\t\tif (url.charAt(0) == '/')\r\n\t\t{\r\n\t\t\turl = this.getBaseDomain() + url;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\turl = this.getBaseUrl() + url;\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn url;\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxPanningManager\r\n *\r\n * Implements a handler for panning.\r\n */\r\nfunction mxPanningManager(graph)\r\n{\r\n\tthis.thread = null;\r\n\tthis.active = false;\r\n\tthis.tdx = 0;\r\n\tthis.tdy = 0;\r\n\tthis.t0x = 0;\r\n\tthis.t0y = 0;\r\n\tthis.dx = 0;\r\n\tthis.dy = 0;\r\n\tthis.scrollbars = false;\r\n\tthis.scrollLeft = 0;\r\n\tthis.scrollTop = 0;\r\n\t\r\n\tthis.mouseListener =\r\n\t{\r\n\t    mouseDown: function(sender, me) { },\r\n\t    mouseMove: function(sender, me) { },\r\n\t    mouseUp: mxUtils.bind(this, function(sender, me)\r\n\t    {\r\n\t    \tif (this.active)\r\n\t    \t{\r\n\t    \t\tthis.stop();\r\n\t    \t}\r\n\t    })\r\n\t};\r\n\t\r\n\tgraph.addMouseListener(this.mouseListener);\r\n\t\r\n\tthis.mouseUpListener = mxUtils.bind(this, function()\r\n\t{\r\n\t    \tif (this.active)\r\n\t    \t{\r\n\t    \t\tthis.stop();\r\n\t    \t}\r\n\t});\r\n\t\r\n\t// Stops scrolling on every mouseup anywhere in the document\r\n\tmxEvent.addListener(document, 'mouseup', this.mouseUpListener);\r\n\t\r\n\tvar createThread = mxUtils.bind(this, function()\r\n\t{\r\n\t    \tthis.scrollbars = mxUtils.hasScrollbars(graph.container);\r\n\t    \tthis.scrollLeft = graph.container.scrollLeft;\r\n\t    \tthis.scrollTop = graph.container.scrollTop;\r\n\t\r\n\t    \treturn window.setInterval(mxUtils.bind(this, function()\r\n\t\t{\r\n\t\t\tthis.tdx -= this.dx;\r\n\t\t\tthis.tdy -= this.dy;\r\n\r\n\t\t\tif (this.scrollbars)\r\n\t\t\t{\r\n\t\t\t\tvar left = -graph.container.scrollLeft - Math.ceil(this.dx);\r\n\t\t\t\tvar top = -graph.container.scrollTop - Math.ceil(this.dy);\r\n\t\t\t\tgraph.panGraph(left, top);\r\n\t\t\t\tgraph.panDx = this.scrollLeft - graph.container.scrollLeft;\r\n\t\t\t\tgraph.panDy = this.scrollTop - graph.container.scrollTop;\r\n\t\t\t\tgraph.fireEvent(new mxEventObject(mxEvent.PAN));\r\n\t\t\t\t// TODO: Implement graph.autoExtend\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tgraph.panGraph(this.getDx(), this.getDy());\r\n\t\t\t}\r\n\t\t}), this.delay);\r\n\t});\r\n\t\r\n\tthis.isActive = function()\r\n\t{\r\n\t\treturn active;\r\n\t};\r\n\t\r\n\tthis.getDx = function()\r\n\t{\r\n\t\treturn Math.round(this.tdx);\r\n\t};\r\n\t\r\n\tthis.getDy = function()\r\n\t{\r\n\t\treturn Math.round(this.tdy);\r\n\t};\r\n\t\r\n\tthis.start = function()\r\n\t{\r\n\t\tthis.t0x = graph.view.translate.x;\r\n\t\tthis.t0y = graph.view.translate.y;\r\n\t\tthis.active = true;\r\n\t};\r\n\t\r\n\tthis.panTo = function(x, y, w, h)\r\n\t{\r\n\t\tif (!this.active)\r\n\t\t{\r\n\t\t\tthis.start();\r\n\t\t}\r\n\t\t\r\n    \tthis.scrollLeft = graph.container.scrollLeft;\r\n    \tthis.scrollTop = graph.container.scrollTop;\r\n\t\t\r\n\t\tw = (w != null) ? w : 0;\r\n\t\th = (h != null) ? h : 0;\r\n\t\t\r\n\t\tvar c = graph.container;\r\n\t\tthis.dx = x + w - c.scrollLeft - c.clientWidth;\r\n\t\t\r\n\t\tif (this.dx < 0 && Math.abs(this.dx) < this.border)\r\n\t\t{\r\n\t\t\tthis.dx = this.border + this.dx;\r\n\t\t}\r\n\t\telse if (this.handleMouseOut)\r\n\t\t{\r\n\t\t\tthis.dx = Math.max(this.dx, 0);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.dx = 0;\r\n\t\t}\r\n\t\t\r\n\t\tif (this.dx == 0)\r\n\t\t{\r\n\t\t\tthis.dx = x - c.scrollLeft;\r\n\t\t\t\r\n\t\t\tif (this.dx > 0 && this.dx < this.border)\r\n\t\t\t{\r\n\t\t\t\tthis.dx = this.dx - this.border;\r\n\t\t\t}\r\n\t\t\telse if (this.handleMouseOut)\r\n\t\t\t{\r\n\t\t\t\tthis.dx = Math.min(0, this.dx);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.dx = 0;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tthis.dy = y + h - c.scrollTop - c.clientHeight;\r\n\r\n\t\tif (this.dy < 0 && Math.abs(this.dy) < this.border)\r\n\t\t{\r\n\t\t\tthis.dy = this.border + this.dy;\r\n\t\t}\r\n\t\telse if (this.handleMouseOut)\r\n\t\t{\r\n\t\t\tthis.dy = Math.max(this.dy, 0);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.dy = 0;\r\n\t\t}\r\n\t\t\r\n\t\tif (this.dy == 0)\r\n\t\t{\r\n\t\t\tthis.dy = y - c.scrollTop;\r\n\t\t\t\r\n\t\t\tif (this.dy > 0 && this.dy < this.border)\r\n\t\t\t{\r\n\t\t\t\tthis.dy = this.dy - this.border;\r\n\t\t\t}\r\n\t\t\telse if (this.handleMouseOut)\r\n\t\t\t{\r\n\t\t\t\tthis.dy = Math.min(0, this.dy);\r\n\t\t\t} \r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.dy = 0;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (this.dx != 0 || this.dy != 0)\r\n\t\t{\r\n\t\t\tthis.dx *= this.damper;\r\n\t\t\tthis.dy *= this.damper;\r\n\t\t\t\r\n\t\t\tif (this.thread == null)\r\n\t\t\t{\r\n\t\t\t\tthis.thread = createThread();\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (this.thread != null)\r\n\t\t{\r\n\t\t\twindow.clearInterval(this.thread);\r\n\t\t\tthis.thread = null;\r\n\t\t}\r\n\t};\r\n\t\r\n\tthis.stop = function()\r\n\t{\r\n\t\tif (this.active)\r\n\t\t{\r\n\t\t\tthis.active = false;\r\n\t\t\r\n\t\t\tif (this.thread != null)\r\n\t    \t{\r\n\t\t\t\twindow.clearInterval(this.thread);\r\n\t\t\t\tthis.thread = null;\r\n\t    \t}\r\n\t\t\t\r\n\t\t\tthis.tdx = 0;\r\n\t\t\tthis.tdy = 0;\r\n\t\t\t\r\n\t\t\tif (!this.scrollbars)\r\n\t\t\t{\r\n\t\t\t\tvar px = graph.panDx;\r\n\t\t\t\tvar py = graph.panDy;\r\n\t\t    \t\r\n\t\t    \tif (px != 0 || py != 0)\r\n\t\t    \t{\r\n\t\t    \t\tgraph.panGraph(0, 0);\r\n\t\t\t    \tgraph.view.setTranslate(this.t0x + px / graph.view.scale, this.t0y + py / graph.view.scale);\r\n\t\t    \t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tgraph.panDx = 0;\r\n\t\t\t\tgraph.panDy = 0;\r\n\t\t\t\tgraph.fireEvent(new mxEventObject(mxEvent.PAN));\r\n\t\t\t}\r\n\t\t}\r\n\t};\r\n\t\r\n\tthis.destroy = function()\r\n\t{\r\n\t\tgraph.removeMouseListener(this.mouseListener);\r\n\t\tmxEvent.removeListener(document, 'mouseup', this.mouseUpListener);\r\n\t};\r\n};\r\n\r\n/**\r\n * Variable: damper\r\n * \r\n * Damper value for the panning. Default is 1/6.\r\n */\r\nmxPanningManager.prototype.damper = 1/6;\r\n\r\n/**\r\n * Variable: delay\r\n * \r\n * Delay in milliseconds for the panning. Default is 10.\r\n */\r\nmxPanningManager.prototype.delay = 10;\r\n\r\n/**\r\n * Variable: handleMouseOut\r\n * \r\n * Specifies if mouse events outside of the component should be handled. Default is true. \r\n */\r\nmxPanningManager.prototype.handleMouseOut = true;\r\n\r\n/**\r\n * Variable: border\r\n * \r\n * Border to handle automatic panning inside the component. Default is 0 (disabled).\r\n */\r\nmxPanningManager.prototype.border = 0;\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxPopupMenu\r\n * \r\n * Basic popup menu. To add a vertical scrollbar to a given submenu, the\r\n * following code can be used.\r\n * \r\n * (code)\r\n * var mxPopupMenuShowMenu = mxPopupMenu.prototype.showMenu;\r\n * mxPopupMenu.prototype.showMenu = function()\r\n * {\r\n *   mxPopupMenuShowMenu.apply(this, arguments);\r\n *   \r\n *   this.div.style.overflowY = 'auto';\r\n *   this.div.style.overflowX = 'hidden';\r\n *   this.div.style.maxHeight = '160px';\r\n * };\r\n * (end)\r\n * \r\n * Constructor: mxPopupMenu\r\n * \r\n * Constructs a popupmenu.\r\n * \r\n * Event: mxEvent.SHOW\r\n *\r\n * Fires after the menu has been shown in <popup>.\r\n */\r\nfunction mxPopupMenu(factoryMethod)\r\n{\r\n\tthis.factoryMethod = factoryMethod;\r\n\t\r\n\tif (factoryMethod != null)\r\n\t{\r\n\t\tthis.init();\r\n\t}\r\n};\r\n\r\n/**\r\n * Extends mxEventSource.\r\n */\r\nmxPopupMenu.prototype = new mxEventSource();\r\nmxPopupMenu.prototype.constructor = mxPopupMenu;\r\n\r\n/**\r\n * Variable: submenuImage\r\n * \r\n * URL of the image to be used for the submenu icon.\r\n */\r\nmxPopupMenu.prototype.submenuImage = mxClient.imageBasePath + '/submenu.gif';\r\n\r\n/**\r\n * Variable: zIndex\r\n * \r\n * Specifies the zIndex for the popupmenu and its shadow. Default is 1006.\r\n */\r\nmxPopupMenu.prototype.zIndex = 10006;\r\n\r\n/**\r\n * Variable: factoryMethod\r\n * \r\n * Function that is used to create the popup menu. The function takes the\r\n * current panning handler, the <mxCell> under the mouse and the mouse\r\n * event that triggered the call as arguments.\r\n */\r\nmxPopupMenu.prototype.factoryMethod = null;\r\n\r\n/**\r\n * Variable: useLeftButtonForPopup\r\n * \r\n * Specifies if popupmenus should be activated by clicking the left mouse\r\n * button. Default is false.\r\n */\r\nmxPopupMenu.prototype.useLeftButtonForPopup = false;\r\n\r\n/**\r\n * Variable: enabled\r\n * \r\n * Specifies if events are handled. Default is true.\r\n */\r\nmxPopupMenu.prototype.enabled = true;\r\n\r\n/**\r\n * Variable: itemCount\r\n * \r\n * Contains the number of times <addItem> has been called for a new menu.\r\n */\r\nmxPopupMenu.prototype.itemCount = 0;\r\n\r\n/**\r\n * Variable: autoExpand\r\n * \r\n * Specifies if submenus should be expanded on mouseover. Default is false.\r\n */\r\nmxPopupMenu.prototype.autoExpand = false;\r\n\r\n/**\r\n * Variable: smartSeparators\r\n * \r\n * Specifies if separators should only be added if a menu item follows them.\r\n * Default is false.\r\n */\r\nmxPopupMenu.prototype.smartSeparators = false;\r\n\r\n/**\r\n * Variable: labels\r\n * \r\n * Specifies if any labels should be visible. Default is true.\r\n */\r\nmxPopupMenu.prototype.labels = true;\r\n\r\n/**\r\n * Function: init\r\n * \r\n * Initializes the shapes required for this vertex handler.\r\n */\r\nmxPopupMenu.prototype.init = function()\r\n{\r\n\t// Adds the inner table\r\n\tthis.table = document.createElement('table');\r\n\tthis.table.className = 'mxPopupMenu';\r\n\t\r\n\tthis.tbody = document.createElement('tbody');\r\n\tthis.table.appendChild(this.tbody);\r\n\r\n\t// Adds the outer div\r\n\tthis.div = document.createElement('div');\r\n\tthis.div.className = 'mxPopupMenu';\r\n\tthis.div.style.display = 'inline';\r\n\tthis.div.style.zIndex = this.zIndex;\r\n\tthis.div.appendChild(this.table);\r\n\r\n\t// Disables the context menu on the outer div\r\n\tmxEvent.disableContextMenu(this.div);\r\n};\r\n\r\n/**\r\n * Function: isEnabled\r\n * \r\n * Returns true if events are handled. This implementation\r\n * returns <enabled>.\r\n */\r\nmxPopupMenu.prototype.isEnabled = function()\r\n{\r\n\treturn this.enabled;\r\n};\r\n\t\r\n/**\r\n * Function: setEnabled\r\n * \r\n * Enables or disables event handling. This implementation\r\n * updates <enabled>.\r\n */\r\nmxPopupMenu.prototype.setEnabled = function(enabled)\r\n{\r\n\tthis.enabled = enabled;\r\n};\r\n\r\n/**\r\n * Function: isPopupTrigger\r\n * \r\n * Returns true if the given event is a popupmenu trigger for the optional\r\n * given cell.\r\n * \r\n * Parameters:\r\n * \r\n * me - <mxMouseEvent> that represents the mouse event.\r\n */\r\nmxPopupMenu.prototype.isPopupTrigger = function(me)\r\n{\r\n\treturn me.isPopupTrigger() || (this.useLeftButtonForPopup && mxEvent.isLeftMouseButton(me.getEvent()));\r\n};\r\n\r\n/**\r\n * Function: addItem\r\n * \r\n * Adds the given item to the given parent item. If no parent item is specified\r\n * then the item is added to the top-level menu. The return value may be used\r\n * as the parent argument, ie. as a submenu item. The return value is the table\r\n * row that represents the item.\r\n * \r\n * Paramters:\r\n * \r\n * title - String that represents the title of the menu item.\r\n * image - Optional URL for the image icon.\r\n * funct - Function associated that takes a mouseup or touchend event.\r\n * parent - Optional item returned by <addItem>.\r\n * iconCls - Optional string that represents the CSS class for the image icon.\r\n * IconsCls is ignored if image is given.\r\n * enabled - Optional boolean indicating if the item is enabled. Default is true.\r\n * active - Optional boolean indicating if the menu should implement any event handling.\r\n * Default is true.\r\n */\r\nmxPopupMenu.prototype.addItem = function(title, image, funct, parent, iconCls, enabled, active)\r\n{\r\n\tparent = parent || this;\r\n\tthis.itemCount++;\r\n\t\r\n\t// Smart separators only added if element contains items\r\n\tif (parent.willAddSeparator)\r\n\t{\r\n\t\tif (parent.containsItems)\r\n\t\t{\r\n\t\t\tthis.addSeparator(parent, true);\r\n\t\t}\r\n\r\n\t\tparent.willAddSeparator = false;\r\n\t}\r\n\r\n\tparent.containsItems = true;\r\n\tvar tr = document.createElement('tr');\r\n\ttr.className = 'mxPopupMenuItem';\r\n\tvar col1 = document.createElement('td');\r\n\tcol1.className = 'mxPopupMenuIcon';\r\n\r\n\t// Adds the given image into the first column\r\n\tif (image != null)\r\n\t{\r\n\t\tvar img = document.createElement('img');\r\n\t\timg.src = image;\r\n\t\tcol1.appendChild(img);\r\n\t}\r\n\telse if (iconCls != null)\r\n\t{\r\n\t\tvar div = document.createElement('div');\r\n\t\tdiv.className = iconCls;\r\n\t\tcol1.appendChild(div);\r\n\t}\r\n\t\r\n\ttr.appendChild(col1);\r\n\t\r\n\tif (this.labels)\r\n\t{\r\n\t\tvar col2 = document.createElement('td');\r\n\t\tcol2.className = 'mxPopupMenuItem' +\r\n\t\t\t((enabled != null && !enabled) ? ' mxDisabled' : '');\r\n\t\t\r\n\t\tmxUtils.write(col2, title);\r\n\t\tcol2.align = 'left';\r\n\t\ttr.appendChild(col2);\r\n\t\r\n\t\tvar col3 = document.createElement('td');\r\n\t\tcol3.className = 'mxPopupMenuItem' +\r\n\t\t\t((enabled != null && !enabled) ? ' mxDisabled' : '');\r\n\t\tcol3.style.paddingRight = '6px';\r\n\t\tcol3.style.textAlign = 'right';\r\n\t\t\r\n\t\ttr.appendChild(col3);\r\n\t\t\r\n\t\tif (parent.div == null)\r\n\t\t{\r\n\t\t\tthis.createSubmenu(parent);\r\n\t\t}\r\n\t}\r\n\t\r\n\tparent.tbody.appendChild(tr);\r\n\r\n\tif (active != false && enabled != false)\r\n\t{\r\n\t\tvar currentSelection = null;\r\n\t\t\r\n\t\tmxEvent.addGestureListeners(tr,\r\n\t\t\tmxUtils.bind(this, function(evt)\r\n\t\t\t{\r\n\t\t\t\tthis.eventReceiver = tr;\r\n\t\t\t\t\r\n\t\t\t\tif (parent.activeRow != tr && parent.activeRow != parent)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (parent.activeRow != null && parent.activeRow.div.parentNode != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.hideSubmenu(parent);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (tr.div != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.showSubmenu(parent, tr);\r\n\t\t\t\t\t\tparent.activeRow = tr;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Workaround for lost current selection in page because of focus in IE\r\n\t\t\t\tif (document.selection != null && (mxClient.IS_QUIRKS || document.documentMode == 8))\r\n\t\t\t\t{\r\n\t\t\t\t\tcurrentSelection = document.selection.createRange();\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tmxEvent.consume(evt);\r\n\t\t\t}),\r\n\t\t\tmxUtils.bind(this, function(evt)\r\n\t\t\t{\r\n\t\t\t\tif (parent.activeRow != tr && parent.activeRow != parent)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (parent.activeRow != null && parent.activeRow.div.parentNode != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.hideSubmenu(parent);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (this.autoExpand && tr.div != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.showSubmenu(parent, tr);\r\n\t\t\t\t\t\tparent.activeRow = tr;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\r\n\t\t\t\t// Sets hover style because TR in IE doesn't have hover\r\n\t\t\t\ttr.className = 'mxPopupMenuItemHover';\r\n\t\t\t}),\r\n\t\t\tmxUtils.bind(this, function(evt)\r\n\t\t\t{\r\n\t\t\t\t// EventReceiver avoids clicks on a submenu item\r\n\t\t\t\t// which has just been shown in the mousedown\r\n\t\t\t\tif (this.eventReceiver == tr)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (parent.activeRow != tr)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.hideMenu();\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Workaround for lost current selection in page because of focus in IE\r\n\t\t\t\t\tif (currentSelection != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t// Workaround for \"unspecified error\" in IE8 standards\r\n\t\t\t\t\t\ttry\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tcurrentSelection.select();\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tcatch (e)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t// ignore\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tcurrentSelection = null;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (funct != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tfunct(evt);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.eventReceiver = null;\r\n\t\t\t\tmxEvent.consume(evt);\r\n\t\t\t})\r\n\t\t);\r\n\t\r\n\t\t// Resets hover style because TR in IE doesn't have hover\r\n\t\tmxEvent.addListener(tr, 'mouseout',\r\n\t\t\tmxUtils.bind(this, function(evt)\r\n\t\t\t{\r\n\t\t\t\ttr.className = 'mxPopupMenuItem';\r\n\t\t\t})\r\n\t\t);\r\n\t}\r\n\t\r\n\treturn tr;\r\n};\r\n\r\n/**\r\n * Adds a checkmark to the given menuitem.\r\n */\r\nmxPopupMenu.prototype.addCheckmark = function(item, img)\r\n{\r\n\tvar td = item.firstChild.nextSibling;\r\n\ttd.style.backgroundImage = 'url(\\'' + img + '\\')';\r\n\ttd.style.backgroundRepeat = 'no-repeat';\r\n\ttd.style.backgroundPosition = '2px 50%';\r\n};\r\n\r\n/**\r\n * Function: createSubmenu\r\n * \r\n * Creates the nodes required to add submenu items inside the given parent\r\n * item. This is called in <addItem> if a parent item is used for the first\r\n * time. This adds various DOM nodes and a <submenuImage> to the parent.\r\n * \r\n * Parameters:\r\n * \r\n * parent - An item returned by <addItem>.\r\n */\r\nmxPopupMenu.prototype.createSubmenu = function(parent)\r\n{\r\n\tparent.table = document.createElement('table');\r\n\tparent.table.className = 'mxPopupMenu';\r\n\r\n\tparent.tbody = document.createElement('tbody');\r\n\tparent.table.appendChild(parent.tbody);\r\n\r\n\tparent.div = document.createElement('div');\r\n\tparent.div.className = 'mxPopupMenu';\r\n\r\n\tparent.div.style.position = 'absolute';\r\n\tparent.div.style.display = 'inline';\r\n\tparent.div.style.zIndex = this.zIndex;\r\n\t\r\n\tparent.div.appendChild(parent.table);\r\n\t\r\n\tvar img = document.createElement('img');\r\n\timg.setAttribute('src', this.submenuImage);\r\n\t\r\n\t// Last column of the submenu item in the parent menu\r\n\ttd = parent.firstChild.nextSibling.nextSibling;\r\n\ttd.appendChild(img);\r\n};\r\n\r\n/**\r\n * Function: showSubmenu\r\n * \r\n * Shows the submenu inside the given parent row.\r\n */\r\nmxPopupMenu.prototype.showSubmenu = function(parent, row)\r\n{\r\n\tif (row.div != null)\r\n\t{\r\n\t\trow.div.style.left = (parent.div.offsetLeft +\r\n\t\t\trow.offsetLeft+row.offsetWidth - 1) + 'px';\r\n\t\trow.div.style.top = (parent.div.offsetTop+row.offsetTop) + 'px';\r\n\t\tdocument.body.appendChild(row.div);\r\n\t\t\r\n\t\t// Moves the submenu to the left side if there is no space\r\n\t\tvar left = parseInt(row.div.offsetLeft);\r\n\t\tvar width = parseInt(row.div.offsetWidth);\r\n\t\tvar offset = mxUtils.getDocumentScrollOrigin(document);\r\n\t\t\r\n\t\tvar b = document.body;\r\n\t\tvar d = document.documentElement;\r\n\t\t\r\n\t\tvar right = offset.x + (b.clientWidth || d.clientWidth);\r\n\t\t\r\n\t\tif (left + width > right)\r\n\t\t{\r\n\t\t\trow.div.style.left = Math.max(0, (parent.div.offsetLeft - width + ((mxClient.IS_IE) ? 6 : -6))) + 'px';\r\n\t\t}\r\n\t\t\r\n\t\tmxUtils.fit(row.div);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: addSeparator\r\n * \r\n * Adds a horizontal separator in the given parent item or the top-level menu\r\n * if no parent is specified.\r\n * \r\n * Parameters:\r\n * \r\n * parent - Optional item returned by <addItem>.\r\n * force - Optional boolean to ignore <smartSeparators>. Default is false.\r\n */\r\nmxPopupMenu.prototype.addSeparator = function(parent, force)\r\n{\r\n\tparent = parent || this;\r\n\t\r\n\tif (this.smartSeparators && !force)\r\n\t{\r\n\t\tparent.willAddSeparator = true;\r\n\t}\r\n\telse if (parent.tbody != null)\r\n\t{\r\n\t\tparent.willAddSeparator = false;\r\n\t\tvar tr = document.createElement('tr');\r\n\t\t\r\n\t\tvar col1 = document.createElement('td');\r\n\t\tcol1.className = 'mxPopupMenuIcon';\r\n\t\tcol1.style.padding = '0 0 0 0px';\r\n\t\t\r\n\t\ttr.appendChild(col1);\r\n\t\t\r\n\t\tvar col2 = document.createElement('td');\r\n\t\tcol2.style.padding = '0 0 0 0px';\r\n\t\tcol2.setAttribute('colSpan', '2');\r\n\t\r\n\t\tvar hr = document.createElement('hr');\r\n\t\thr.setAttribute('size', '1');\r\n\t\tcol2.appendChild(hr);\r\n\t\t\r\n\t\ttr.appendChild(col2);\r\n\t\t\r\n\t\tparent.tbody.appendChild(tr);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: popup\r\n * \r\n * Shows the popup menu for the given event and cell.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * graph.panningHandler.popup = function(x, y, cell, evt)\r\n * {\r\n *   mxUtils.alert('Hello, World!');\r\n * }\r\n * (end)\r\n */\r\nmxPopupMenu.prototype.popup = function(x, y, cell, evt)\r\n{\r\n\tif (this.div != null && this.tbody != null && this.factoryMethod != null)\r\n\t{\r\n\t\tthis.div.style.left = x + 'px';\r\n\t\tthis.div.style.top = y + 'px';\r\n\t\t\r\n\t\t// Removes all child nodes from the existing menu\r\n\t\twhile (this.tbody.firstChild != null)\r\n\t\t{\r\n\t\t\tmxEvent.release(this.tbody.firstChild);\r\n\t\t\tthis.tbody.removeChild(this.tbody.firstChild);\r\n\t\t}\r\n\t\t\r\n\t\tthis.itemCount = 0;\r\n\t\tthis.factoryMethod(this, cell, evt);\r\n\t\t\r\n\t\tif (this.itemCount > 0)\r\n\t\t{\r\n\t\t\tthis.showMenu();\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.SHOW));\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isMenuShowing\r\n * \r\n * Returns true if the menu is showing.\r\n */\r\nmxPopupMenu.prototype.isMenuShowing = function()\r\n{\r\n\treturn this.div != null && this.div.parentNode == document.body;\r\n};\r\n\r\n/**\r\n * Function: showMenu\r\n * \r\n * Shows the menu.\r\n */\r\nmxPopupMenu.prototype.showMenu = function()\r\n{\r\n\t// Disables filter-based shadow in IE9 standards mode\r\n\tif (document.documentMode >= 9)\r\n\t{\r\n\t\tthis.div.style.filter = 'none';\r\n\t}\r\n\t\r\n\t// Fits the div inside the viewport\r\n\tdocument.body.appendChild(this.div);\r\n\tmxUtils.fit(this.div);\r\n};\r\n\r\n/**\r\n * Function: hideMenu\r\n * \r\n * Removes the menu and all submenus.\r\n */\r\nmxPopupMenu.prototype.hideMenu = function()\r\n{\r\n\tif (this.div != null)\r\n\t{\r\n\t\tif (this.div.parentNode != null)\r\n\t\t{\r\n\t\t\tthis.div.parentNode.removeChild(this.div);\r\n\t\t}\r\n\t\t\r\n\t\tthis.hideSubmenu(this);\r\n\t\tthis.containsItems = false;\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.HIDE));\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: hideSubmenu\r\n * \r\n * Removes all submenus inside the given parent.\r\n * \r\n * Parameters:\r\n * \r\n * parent - An item returned by <addItem>.\r\n */\r\nmxPopupMenu.prototype.hideSubmenu = function(parent)\r\n{\r\n\tif (parent.activeRow != null)\r\n\t{\r\n\t\tthis.hideSubmenu(parent.activeRow);\r\n\t\t\r\n\t\tif (parent.activeRow.div.parentNode != null)\r\n\t\t{\r\n\t\t\tparent.activeRow.div.parentNode.removeChild(parent.activeRow.div);\r\n\t\t}\r\n\t\t\r\n\t\tparent.activeRow = null;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Destroys the handler and all its resources and DOM nodes.\r\n */\r\nmxPopupMenu.prototype.destroy = function()\r\n{\r\n\tif (this.div != null)\r\n\t{\r\n\t\tmxEvent.release(this.div);\r\n\t\t\r\n\t\tif (this.div.parentNode != null)\r\n\t\t{\r\n\t\t\tthis.div.parentNode.removeChild(this.div);\r\n\t\t}\r\n\t\t\r\n\t\tthis.div = null;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxAutoSaveManager\r\n * \r\n * Manager for automatically saving diagrams. The <save> hook must be\r\n * implemented.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * var mgr = new mxAutoSaveManager(editor.graph);\r\n * mgr.save = function()\r\n * {\r\n *   mxLog.show();\r\n *   mxLog.debug('save');\r\n * };\r\n * (end)\r\n * \r\n * Constructor: mxAutoSaveManager\r\n *\r\n * Constructs a new automatic layout for the given graph.\r\n *\r\n * Arguments:\r\n * \r\n * graph - Reference to the enclosing graph. \r\n */\r\nfunction mxAutoSaveManager(graph)\r\n{\r\n\t// Notifies the manager of a change\r\n\tthis.changeHandler = mxUtils.bind(this, function(sender, evt)\r\n\t{\r\n\t\tif (this.isEnabled())\r\n\t\t{\r\n\t\t\tthis.graphModelChanged(evt.getProperty('edit').changes);\r\n\t\t}\r\n\t});\r\n\r\n\tthis.setGraph(graph);\r\n};\r\n\r\n/**\r\n * Extends mxEventSource.\r\n */\r\nmxAutoSaveManager.prototype = new mxEventSource();\r\nmxAutoSaveManager.prototype.constructor = mxAutoSaveManager;\r\n\r\n/**\r\n * Variable: graph\r\n * \r\n * Reference to the enclosing <mxGraph>.\r\n */\r\nmxAutoSaveManager.prototype.graph = null;\r\n\r\n/**\r\n * Variable: autoSaveDelay\r\n * \r\n * Minimum amount of seconds between two consecutive autosaves. Eg. a\r\n * value of 1 (s) means the graph is not stored more than once per second.\r\n * Default is 10.\r\n */\r\nmxAutoSaveManager.prototype.autoSaveDelay = 10;\r\n\r\n/**\r\n * Variable: autoSaveThrottle\r\n * \r\n * Minimum amount of seconds between two consecutive autosaves triggered by\r\n * more than <autoSaveThreshhold> changes within a timespan of less than\r\n * <autoSaveDelay> seconds. Eg. a value of 1 (s) means the graph is not\r\n * stored more than once per second even if there are more than\r\n * <autoSaveThreshold> changes within that timespan. Default is 2.\r\n */\r\nmxAutoSaveManager.prototype.autoSaveThrottle = 2;\r\n\r\n/**\r\n * Variable: autoSaveThreshold\r\n * \r\n * Minimum amount of ignored changes before an autosave. Eg. a value of 2\r\n * means after 2 change of the graph model the autosave will trigger if the\r\n * condition below is true. Default is 5.\r\n */\r\nmxAutoSaveManager.prototype.autoSaveThreshold = 5;\r\n\r\n/**\r\n * Variable: ignoredChanges\r\n * \r\n * Counter for ignored changes in autosave.\r\n */\r\nmxAutoSaveManager.prototype.ignoredChanges = 0;\r\n\r\n/**\r\n * Variable: lastSnapshot\r\n * \r\n * Used for autosaving. See <autosave>.\r\n */\r\nmxAutoSaveManager.prototype.lastSnapshot = 0;\r\n\r\n/**\r\n * Variable: enabled\r\n * \r\n * Specifies if event handling is enabled. Default is true.\r\n */\r\nmxAutoSaveManager.prototype.enabled = true;\r\n\r\n/**\r\n * Variable: changeHandler\r\n * \r\n * Holds the function that handles graph model changes.\r\n */\r\nmxAutoSaveManager.prototype.changeHandler = null;\r\n\r\n/**\r\n * Function: isEnabled\r\n * \r\n * Returns true if events are handled. This implementation\r\n * returns <enabled>.\r\n */\r\nmxAutoSaveManager.prototype.isEnabled = function()\r\n{\r\n\treturn this.enabled;\r\n};\r\n\r\n/**\r\n * Function: setEnabled\r\n * \r\n * Enables or disables event handling. This implementation\r\n * updates <enabled>.\r\n * \r\n * Parameters:\r\n * \r\n * enabled - Boolean that specifies the new enabled state.\r\n */\r\nmxAutoSaveManager.prototype.setEnabled = function(value)\r\n{\r\n\tthis.enabled = value;\r\n};\r\n\r\n/**\r\n * Function: setGraph\r\n * \r\n * Sets the graph that the layouts operate on.\r\n */\r\nmxAutoSaveManager.prototype.setGraph = function(graph)\r\n{\r\n\tif (this.graph != null)\r\n\t{\r\n\t\tthis.graph.getModel().removeListener(this.changeHandler);\r\n\t}\r\n\t\r\n\tthis.graph = graph;\r\n\t\r\n\tif (this.graph != null)\r\n\t{\r\n\t\tthis.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: save\r\n * \r\n * Empty hook that is called if the graph should be saved.\r\n */\r\nmxAutoSaveManager.prototype.save = function()\r\n{\r\n\t// empty\r\n};\r\n\r\n/**\r\n * Function: graphModelChanged\r\n * \r\n * Invoked when the graph model has changed.\r\n */\r\nmxAutoSaveManager.prototype.graphModelChanged = function(changes)\r\n{\r\n\tvar now = new Date().getTime();\r\n\tvar dt = (now - this.lastSnapshot) / 1000;\r\n\t\r\n\tif (dt > this.autoSaveDelay ||\r\n\t\t(this.ignoredChanges >= this.autoSaveThreshold &&\r\n\t\t dt > this.autoSaveThrottle))\r\n\t{\r\n\t\tthis.save();\r\n\t\tthis.reset();\r\n\t}\r\n\telse\r\n\t{\r\n\t\t// Increments the number of ignored changes\r\n\t\tthis.ignoredChanges++;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: reset\r\n * \r\n * Resets all counters.\r\n */\r\nmxAutoSaveManager.prototype.reset = function()\r\n{\r\n\tthis.lastSnapshot = new Date().getTime();\r\n\tthis.ignoredChanges = 0;\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Removes all handlers from the <graph> and deletes the reference to it.\r\n */\r\nmxAutoSaveManager.prototype.destroy = function()\r\n{\r\n\tthis.setGraph(null);\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n *\r\n * Class: mxAnimation\r\n * \r\n * Implements a basic animation in JavaScript.\r\n * \r\n * Constructor: mxAnimation\r\n * \r\n * Constructs an animation.\r\n * \r\n * Parameters:\r\n * \r\n * graph - Reference to the enclosing <mxGraph>.\r\n */\r\nfunction mxAnimation(delay)\r\n{\r\n\tthis.delay = (delay != null) ? delay : 20;\r\n};\r\n\r\n/**\r\n * Extends mxEventSource.\r\n */\r\nmxAnimation.prototype = new mxEventSource();\r\nmxAnimation.prototype.constructor = mxAnimation;\r\n\r\n/**\r\n * Variable: delay\r\n * \r\n * Specifies the delay between the animation steps. Defaul is 30ms.\r\n */\r\nmxAnimation.prototype.delay = null;\r\n\r\n/**\r\n * Variable: thread\r\n * \r\n * Reference to the thread while the animation is running.\r\n */\r\nmxAnimation.prototype.thread = null;\r\n\r\n/**\r\n * Function: isRunning\r\n * \r\n * Returns true if the animation is running.\r\n */\r\nmxAnimation.prototype.isRunning = function()\r\n{\r\n\treturn this.thread != null;\r\n};\r\n\r\n/**\r\n * Function: startAnimation\r\n *\r\n * Starts the animation by repeatedly invoking updateAnimation.\r\n */\r\nmxAnimation.prototype.startAnimation = function()\r\n{\r\n\tif (this.thread == null)\r\n\t{\r\n\t\tthis.thread = window.setInterval(mxUtils.bind(this, this.updateAnimation), this.delay);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: updateAnimation\r\n *\r\n * Hook for subclassers to implement the animation. Invoke stopAnimation\r\n * when finished, startAnimation to resume. This is called whenever the\r\n * timer fires and fires an mxEvent.EXECUTE event with no properties.\r\n */\r\nmxAnimation.prototype.updateAnimation = function()\r\n{\r\n\tthis.fireEvent(new mxEventObject(mxEvent.EXECUTE));\r\n};\r\n\r\n/**\r\n * Function: stopAnimation\r\n *\r\n * Stops the animation by deleting the timer and fires an <mxEvent.DONE>.\r\n */\r\nmxAnimation.prototype.stopAnimation = function()\r\n{\r\n\tif (this.thread != null)\r\n\t{\r\n\t\twindow.clearInterval(this.thread);\r\n\t\tthis.thread = null;\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.DONE));\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n *\r\n * Class: mxMorphing\r\n * \r\n * Implements animation for morphing cells. Here is an example of\r\n * using this class for animating the result of a layout algorithm:\r\n * \r\n * (code)\r\n * graph.getModel().beginUpdate();\r\n * try\r\n * {\r\n *   var circleLayout = new mxCircleLayout(graph);\r\n *   circleLayout.execute(graph.getDefaultParent());\r\n * }\r\n * finally\r\n * {\r\n *   var morph = new mxMorphing(graph);\r\n *   morph.addListener(mxEvent.DONE, function()\r\n *   {\r\n *     graph.getModel().endUpdate();\r\n *   });\r\n *   \r\n *   morph.startAnimation();\r\n * }\r\n * (end)\r\n * \r\n * Constructor: mxMorphing\r\n * \r\n * Constructs an animation.\r\n * \r\n * Parameters:\r\n * \r\n * graph - Reference to the enclosing <mxGraph>.\r\n * steps - Optional number of steps in the morphing animation. Default is 6.\r\n * ease - Optional easing constant for the animation. Default is 1.5.\r\n * delay - Optional delay between the animation steps. Passed to <mxAnimation>.\r\n */\r\nfunction mxMorphing(graph, steps, ease, delay)\r\n{\r\n\tmxAnimation.call(this, delay);\r\n\tthis.graph = graph;\r\n\tthis.steps = (steps != null) ? steps : 6;\r\n\tthis.ease = (ease != null) ? ease : 1.5;\r\n};\r\n\r\n/**\r\n * Extends mxEventSource.\r\n */\r\nmxMorphing.prototype = new mxAnimation();\r\nmxMorphing.prototype.constructor = mxMorphing;\r\n\r\n/**\r\n * Variable: graph\r\n * \r\n * Specifies the delay between the animation steps. Defaul is 30ms.\r\n */\r\nmxMorphing.prototype.graph = null;\r\n\r\n/**\r\n * Variable: steps\r\n * \r\n * Specifies the maximum number of steps for the morphing.\r\n */\r\nmxMorphing.prototype.steps = null;\r\n\r\n/**\r\n * Variable: step\r\n * \r\n * Contains the current step.\r\n */\r\nmxMorphing.prototype.step = 0;\r\n\r\n/**\r\n * Variable: ease\r\n * \r\n * Ease-off for movement towards the given vector. Larger values are\r\n * slower and smoother. Default is 4.\r\n */\r\nmxMorphing.prototype.ease = null;\r\n\r\n/**\r\n * Variable: cells\r\n * \r\n * Optional array of cells to be animated. If this is not specified\r\n * then all cells are checked and animated if they have been moved\r\n * in the current transaction.\r\n */\r\nmxMorphing.prototype.cells = null;\r\n\r\n/**\r\n * Function: updateAnimation\r\n *\r\n * Animation step.\r\n */\r\nmxMorphing.prototype.updateAnimation = function()\r\n{\r\n\tmxAnimation.prototype.updateAnimation.apply(this, arguments);\r\n\tvar move = new mxCellStatePreview(this.graph);\r\n\r\n\tif (this.cells != null)\r\n\t{\r\n\t\t// Animates the given cells individually without recursion\r\n\t\tfor (var i = 0; i < this.cells.length; i++)\r\n\t\t{\r\n\t\t\tthis.animateCell(this.cells[i], move, false);\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\t// Animates all changed cells by using recursion to find\r\n\t\t// the changed cells but not for the animation itself\r\n\t\tthis.animateCell(this.graph.getModel().getRoot(), move, true);\r\n\t}\r\n\t\r\n\tthis.show(move);\r\n\t\r\n\tif (move.isEmpty() || this.step++ >= this.steps)\r\n\t{\r\n\t\tthis.stopAnimation();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: show\r\n *\r\n * Shows the changes in the given <mxCellStatePreview>.\r\n */\r\nmxMorphing.prototype.show = function(move)\r\n{\r\n\tmove.show();\r\n};\r\n\r\n/**\r\n * Function: animateCell\r\n *\r\n * Animates the given cell state using <mxCellStatePreview.moveState>.\r\n */\r\nmxMorphing.prototype.animateCell = function(cell, move, recurse)\r\n{\r\n\tvar state = this.graph.getView().getState(cell);\r\n\tvar delta = null;\r\n\r\n\tif (state != null)\r\n\t{\r\n\t\t// Moves the animated state from where it will be after the model\r\n\t\t// change by subtracting the given delta vector from that location\r\n\t\tdelta = this.getDelta(state);\r\n\r\n\t\tif (this.graph.getModel().isVertex(cell) && (delta.x != 0 || delta.y != 0))\r\n\t\t{\r\n\t\t\tvar translate = this.graph.view.getTranslate();\r\n\t\t\tvar scale = this.graph.view.getScale();\r\n\t\t\t\r\n\t\t\tdelta.x += translate.x * scale;\r\n\t\t\tdelta.y += translate.y * scale;\r\n\t\t\t\r\n\t\t\tmove.moveState(state, -delta.x / this.ease, -delta.y / this.ease);\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (recurse && !this.stopRecursion(state, delta))\r\n\t{\r\n\t\tvar childCount = this.graph.getModel().getChildCount(cell);\r\n\r\n\t\tfor (var i = 0; i < childCount; i++)\r\n\t\t{\r\n\t\t\tthis.animateCell(this.graph.getModel().getChildAt(cell, i), move, recurse);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: stopRecursion\r\n *\r\n * Returns true if the animation should not recursively find more\r\n * deltas for children if the given parent state has been animated.\r\n */\r\nmxMorphing.prototype.stopRecursion = function(state, delta)\r\n{\r\n\treturn delta != null && (delta.x != 0 || delta.y != 0);\r\n};\r\n\r\n/**\r\n * Function: getDelta\r\n *\r\n * Returns the vector between the current rendered state and the future\r\n * location of the state after the display will be updated.\r\n */\r\nmxMorphing.prototype.getDelta = function(state)\r\n{\r\n\tvar origin = this.getOriginForCell(state.cell);\r\n\tvar translate = this.graph.getView().getTranslate();\r\n\tvar scale = this.graph.getView().getScale();\r\n\tvar x = state.x / scale - translate.x;\r\n\tvar y = state.y / scale - translate.y;\r\n\r\n\treturn new mxPoint((origin.x - x) * scale, (origin.y - y) * scale);\r\n};\r\n\r\n/**\r\n * Function: getOriginForCell\r\n *\r\n * Returns the top, left corner of the given cell. TODO: Improve performance\r\n * by using caching inside this method as the result per cell never changes\r\n * during the lifecycle of this object.\r\n */\r\nmxMorphing.prototype.getOriginForCell = function(cell)\r\n{\r\n\tvar result = null;\r\n\t\r\n\tif (cell != null)\r\n\t{\r\n\t\tvar parent = this.graph.getModel().getParent(cell);\r\n\t\tvar geo = this.graph.getCellGeometry(cell);\r\n\t\tresult = this.getOriginForCell(parent);\r\n\t\t\r\n\t\t// TODO: Handle offsets\r\n\t\tif (geo != null)\r\n\t\t{\r\n\t\t\tif (geo.relative)\r\n\t\t\t{\r\n\t\t\t\tvar pgeo = this.graph.getCellGeometry(parent);\r\n\t\t\t\t\r\n\t\t\t\tif (pgeo != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tresult.x += geo.x * pgeo.width;\r\n\t\t\t\t\tresult.y += geo.y * pgeo.height;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tresult.x += geo.x;\r\n\t\t\t\tresult.y += geo.y;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (result == null)\r\n\t{\r\n\t\tvar t = this.graph.view.getTranslate();\r\n\t\tresult = new mxPoint(-t.x, -t.y);\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxImageBundle\r\n *\r\n * Maps from keys to base64 encoded images or file locations. All values must\r\n * be URLs or use the format data:image/format followed by a comma and the base64\r\n * encoded image data, eg. \"data:image/gif,XYZ\", where XYZ is the base64 encoded\r\n * image data.\r\n * \r\n * To add a new image bundle to an existing graph, the following code is used:\r\n * \r\n * (code)\r\n * var bundle = new mxImageBundle(alt);\r\n * bundle.putImage('myImage', 'data:image/gif,R0lGODlhEAAQAMIGAAAAAICAAICAgP' +\r\n *   '//AOzp2O3r2////////yH+FUNyZWF0ZWQgd2l0aCBUaGUgR0lNUAAh+QQBCgAHACwAAAAA' +\r\n *   'EAAQAAADTXi63AowynnAMDfjPUDlnAAJhmeBFxAEloliKltWmiYCQvfVr6lBPB1ggxN1hi' +\r\n *   'laSSASFQpIV5HJBDyHpqK2ejVRm2AAgZCdmCGO9CIBADs=', fallback);\r\n * bundle.putImage('mySvgImage', 'data:image/svg+xml,' + encodeURIComponent(\r\n *   '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"100%\" height=\"100%\">' +\r\n *   '<linearGradient id=\"gradient\"><stop offset=\"10%\" stop-color=\"#F00\"/>' +\r\n *   '<stop offset=\"90%\" stop-color=\"#fcc\"/></linearGradient>' +\r\n *   '<rect fill=\"url(#gradient)\" width=\"100%\" height=\"100%\"/></svg>'), fallback);\r\n * graph.addImageBundle(bundle);\r\n * (end);\r\n * \r\n * Alt is an optional boolean (default is false) that specifies if the value\r\n * or the fallback should be returned in <getImage>.\r\n * \r\n * The image can then be referenced in any cell style using image=myImage.\r\n * If you are using mxOutline, you should use the same image bundles in the\r\n * graph that renders the outline.\r\n * \r\n * The keys for images are resolved in <mxGraph.postProcessCellStyle> and\r\n * turned into a data URI if the returned value has a short data URI format\r\n * as specified above.\r\n * \r\n * A typical value for the fallback is a MTHML link as defined in RFC 2557.\r\n * Note that this format requires a file to be dynamically created on the\r\n * server-side, or the page that contains the graph to be modified to contain\r\n * the resources, this can be done by adding a comment that contains the\r\n * resource in the HEAD section of the page after the title tag.\r\n * \r\n * This type of fallback mechanism should be used in IE6 and IE7. IE8 does\r\n * support data URIs, but the maximum size is limited to 32 KB, which means\r\n * all data URIs should be limited to 32 KB.\r\n */\r\nfunction mxImageBundle(alt)\r\n{\r\n\tthis.images = [];\r\n\tthis.alt = (alt != null) ? alt : false;\r\n};\r\n\r\n/**\r\n * Variable: images\r\n * \r\n * Maps from keys to images.\r\n */\r\nmxImageBundle.prototype.images = null;\r\n\r\n/**\r\n * Variable: alt\r\n * \r\n * Specifies if the fallback representation should be returned.\r\n */\r\nmxImageBundle.prototype.images = null;\r\n\r\n/**\r\n * Function: putImage\r\n * \r\n * Adds the specified entry to the map. The entry is an object with a value and\r\n * fallback property as specified in the arguments.\r\n */\r\nmxImageBundle.prototype.putImage = function(key, value, fallback)\r\n{\r\n\tthis.images[key] = {value: value, fallback: fallback};\r\n};\r\n\r\n/**\r\n * Function: getImage\r\n * \r\n * Returns the value for the given key. This returns the value\r\n * or fallback, depending on <alt>. The fallback is returned if\r\n * <alt> is true, the value is returned otherwise.\r\n */\r\nmxImageBundle.prototype.getImage = function(key)\r\n{\r\n\tvar result = null;\r\n\t\r\n\tif (key != null)\r\n\t{\r\n\t\tvar img = this.images[key];\r\n\t\t\r\n\t\tif (img != null)\r\n\t\t{\r\n\t\t\tresult = (this.alt) ? img.fallback : img.value;\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxImageExport\r\n * \r\n * Creates a new image export instance to be used with an export canvas. Here\r\n * is an example that uses this class to create an image via a backend using\r\n * <mxXmlExportCanvas>.\r\n * \r\n * (code)\r\n * var xmlDoc = mxUtils.createXmlDocument();\r\n * var root = xmlDoc.createElement('output');\r\n * xmlDoc.appendChild(root);\r\n * \r\n * var xmlCanvas = new mxXmlCanvas2D(root);\r\n * var imgExport = new mxImageExport();\r\n * imgExport.drawState(graph.getView().getState(graph.model.root), xmlCanvas);\r\n * \r\n * var bounds = graph.getGraphBounds();\r\n * var w = Math.ceil(bounds.x + bounds.width);\r\n * var h = Math.ceil(bounds.y + bounds.height);\r\n * \r\n * var xml = mxUtils.getXml(root);\r\n * new mxXmlRequest('export', 'format=png&w=' + w +\r\n * \t\t'&h=' + h + '&bg=#F9F7ED&xml=' + encodeURIComponent(xml))\r\n * \t\t.simulate(document, '_blank');\r\n * (end)\r\n * \r\n * Constructor: mxImageExport\r\n * \r\n * Constructs a new image export.\r\n */\r\nfunction mxImageExport() { };\r\n\r\n/**\r\n * Variable: includeOverlays\r\n * \r\n * Specifies if overlays should be included in the export. Default is false.\r\n */\r\nmxImageExport.prototype.includeOverlays = false;\r\n\r\n/**\r\n * Function: drawState\r\n * \r\n * Draws the given state and all its descendants to the given canvas.\r\n */\r\nmxImageExport.prototype.drawState = function(state, canvas)\r\n{\r\n\tif (state != null)\r\n\t{\r\n\t\tthis.visitStatesRecursive(state, canvas, mxUtils.bind(this, function()\r\n\t\t{\r\n\t\t\tthis.drawCellState.apply(this, arguments);\r\n\t\t}));\r\n\t\t\t\t\r\n\t\t// Paints the overlays\r\n\t\tif (this.includeOverlays)\r\n\t\t{\r\n\t\t\tthis.visitStatesRecursive(state, canvas, mxUtils.bind(this, function()\r\n\t\t\t{\r\n\t\t\t\tthis.drawOverlays.apply(this, arguments);\r\n\t\t\t}));\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: drawState\r\n * \r\n * Draws the given state and all its descendants to the given canvas.\r\n */\r\nmxImageExport.prototype.visitStatesRecursive = function(state, canvas, visitor)\r\n{\r\n\tif (state != null)\r\n\t{\r\n\t\tvisitor(state, canvas);\r\n\t\t\r\n\t\tvar graph = state.view.graph;\r\n\t\tvar childCount = graph.model.getChildCount(state.cell);\r\n\t\t\r\n\t\tfor (var i = 0; i < childCount; i++)\r\n\t\t{\r\n\t\t\tvar childState = graph.view.getState(graph.model.getChildAt(state.cell, i));\r\n\t\t\tthis.visitStatesRecursive(childState, canvas, visitor);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getLinkForCellState\r\n * \r\n * Returns the link for the given cell state and canvas. This returns null.\r\n */\r\nmxImageExport.prototype.getLinkForCellState = function(state, canvas)\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: drawCellState\r\n * \r\n * Draws the given state to the given canvas.\r\n */\r\nmxImageExport.prototype.drawCellState = function(state, canvas)\r\n{\r\n\t// Experimental feature\r\n\tvar link = this.getLinkForCellState(state, canvas);\r\n\t\r\n\tif (link != null)\r\n\t{\r\n\t\tcanvas.setLink(link);\r\n\t}\r\n\t\r\n\t// Paints the shape and text\r\n\tthis.drawShape(state, canvas);\r\n\tthis.drawText(state, canvas);\r\n\r\n\tif (link != null)\r\n\t{\r\n\t\tcanvas.setLink(null);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: drawShape\r\n * \r\n * Draws the shape of the given state.\r\n */\r\nmxImageExport.prototype.drawShape = function(state, canvas)\r\n{\r\n\tif (state.shape instanceof mxShape && state.shape.checkBounds())\r\n\t{\r\n\t\tcanvas.save();\r\n\t\tstate.shape.paint(canvas);\r\n\t\tcanvas.restore();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: drawText\r\n * \r\n * Draws the text of the given state.\r\n */\r\nmxImageExport.prototype.drawText = function(state, canvas)\r\n{\r\n\tif (state.text != null && state.text.checkBounds())\r\n\t{\r\n\t\tcanvas.save();\r\n\t\tstate.text.paint(canvas);\r\n\t\tcanvas.restore();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: drawOverlays\r\n * \r\n * Draws the overlays for the given state. This is called if <includeOverlays>\r\n * is true.\r\n */\r\nmxImageExport.prototype.drawOverlays = function(state, canvas)\r\n{\r\n\tif (state.overlays != null)\r\n\t{\r\n\t\tstate.overlays.visit(function(id, shape)\r\n\t\t{\r\n\t\t\tif (shape instanceof mxShape)\r\n\t\t\t{\r\n\t\t\t\tshape.paint(canvas);\r\n\t\t\t}\r\n\t\t});\r\n\t}\r\n};\r\n\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxAbstractCanvas2D\r\n *\r\n * Base class for all canvases. A description of the public API is available in <mxXmlCanvas2D>.\r\n * All color values of <mxConstants.NONE> will be converted to null in the state.\r\n * \r\n * Constructor: mxAbstractCanvas2D\r\n *\r\n * Constructs a new abstract canvas.\r\n */\r\nfunction mxAbstractCanvas2D()\r\n{\r\n\t/**\r\n\t * Variable: converter\r\n\t * \r\n\t * Holds the <mxUrlConverter> to convert image URLs.\r\n\t */\r\n\tthis.converter = this.createUrlConverter();\r\n\t\r\n\tthis.reset();\r\n};\r\n\r\n/**\r\n * Variable: state\r\n * \r\n * Holds the current state.\r\n */\r\nmxAbstractCanvas2D.prototype.state = null;\r\n\r\n/**\r\n * Variable: states\r\n * \r\n * Stack of states.\r\n */\r\nmxAbstractCanvas2D.prototype.states = null;\r\n\r\n/**\r\n * Variable: path\r\n * \r\n * Holds the current path as an array.\r\n */\r\nmxAbstractCanvas2D.prototype.path = null;\r\n\r\n/**\r\n * Variable: rotateHtml\r\n * \r\n * Switch for rotation of HTML. Default is false.\r\n */\r\nmxAbstractCanvas2D.prototype.rotateHtml = true;\r\n\r\n/**\r\n * Variable: lastX\r\n * \r\n * Holds the last x coordinate.\r\n */\r\nmxAbstractCanvas2D.prototype.lastX = 0;\r\n\r\n/**\r\n * Variable: lastY\r\n * \r\n * Holds the last y coordinate.\r\n */\r\nmxAbstractCanvas2D.prototype.lastY = 0;\r\n\r\n/**\r\n * Variable: moveOp\r\n * \r\n * Contains the string used for moving in paths. Default is 'M'.\r\n */\r\nmxAbstractCanvas2D.prototype.moveOp = 'M';\r\n\r\n/**\r\n * Variable: lineOp\r\n * \r\n * Contains the string used for moving in paths. Default is 'L'.\r\n */\r\nmxAbstractCanvas2D.prototype.lineOp = 'L';\r\n\r\n/**\r\n * Variable: quadOp\r\n * \r\n * Contains the string used for quadratic paths. Default is 'Q'.\r\n */\r\nmxAbstractCanvas2D.prototype.quadOp = 'Q';\r\n\r\n/**\r\n * Variable: curveOp\r\n * \r\n * Contains the string used for bezier curves. Default is 'C'.\r\n */\r\nmxAbstractCanvas2D.prototype.curveOp = 'C';\r\n\r\n/**\r\n * Variable: closeOp\r\n * \r\n * Holds the operator for closing curves. Default is 'Z'.\r\n */\r\nmxAbstractCanvas2D.prototype.closeOp = 'Z';\r\n\r\n/**\r\n * Variable: pointerEvents\r\n * \r\n * Boolean value that specifies if events should be handled. Default is false.\r\n */\r\nmxAbstractCanvas2D.prototype.pointerEvents = false;\r\n\r\n/**\r\n * Function: createUrlConverter\r\n * \r\n * Create a new <mxUrlConverter> and returns it.\r\n */\r\nmxAbstractCanvas2D.prototype.createUrlConverter = function()\r\n{\r\n\treturn new mxUrlConverter();\r\n};\r\n\r\n/**\r\n * Function: reset\r\n * \r\n * Resets the state of this canvas.\r\n */\r\nmxAbstractCanvas2D.prototype.reset = function()\r\n{\r\n\tthis.state = this.createState();\r\n\tthis.states = [];\r\n};\r\n\r\n/**\r\n * Function: createState\r\n * \r\n * Creates the state of the this canvas.\r\n */\r\nmxAbstractCanvas2D.prototype.createState = function()\r\n{\r\n\treturn {\r\n\t\tdx: 0,\r\n\t\tdy: 0,\r\n\t\tscale: 1,\r\n\t\talpha: 1,\r\n\t\tfillAlpha: 1,\r\n\t\tstrokeAlpha: 1,\r\n\t\tfillColor: null,\r\n\t\tgradientFillAlpha: 1,\r\n\t\tgradientColor: null,\r\n\t\tgradientAlpha: 1,\r\n\t\tgradientDirection: null,\r\n\t\tstrokeColor: null,\r\n\t\tstrokeWidth: 1,\r\n\t\tdashed: false,\r\n\t\tdashPattern: '3 3',\r\n\t\tfixDash: false,\r\n\t\tlineCap: 'flat',\r\n\t\tlineJoin: 'miter',\r\n\t\tmiterLimit: 10,\r\n\t\tfontColor: '#000000',\r\n\t\tfontBackgroundColor: null,\r\n\t\tfontBorderColor: null,\r\n\t\tfontSize: mxConstants.DEFAULT_FONTSIZE,\r\n\t\tfontFamily: mxConstants.DEFAULT_FONTFAMILY,\r\n\t\tfontStyle: 0,\r\n\t\tshadow: false,\r\n\t\tshadowColor: mxConstants.SHADOWCOLOR,\r\n\t\tshadowAlpha: mxConstants.SHADOW_OPACITY,\r\n\t\tshadowDx: mxConstants.SHADOW_OFFSET_X,\r\n\t\tshadowDy: mxConstants.SHADOW_OFFSET_Y,\r\n\t\trotation: 0,\r\n\t\trotationCx: 0,\r\n\t\trotationCy: 0\r\n\t};\r\n};\r\n\r\n/**\r\n * Function: format\r\n * \r\n * Rounds all numbers to integers.\r\n */\r\nmxAbstractCanvas2D.prototype.format = function(value)\r\n{\r\n\treturn Math.round(parseFloat(value));\r\n};\r\n\r\n/**\r\n * Function: addOp\r\n * \r\n * Adds the given operation to the path.\r\n */\r\nmxAbstractCanvas2D.prototype.addOp = function()\r\n{\r\n\tif (this.path != null)\r\n\t{\r\n\t\tthis.path.push(arguments[0]);\r\n\t\t\r\n\t\tif (arguments.length > 2)\r\n\t\t{\r\n\t\t\tvar s = this.state;\r\n\r\n\t\t\tfor (var i = 2; i < arguments.length; i += 2)\r\n\t\t\t{\r\n\t\t\t\tthis.lastX = arguments[i - 1];\r\n\t\t\t\tthis.lastY = arguments[i];\r\n\t\t\t\t\r\n\t\t\t\tthis.path.push(this.format((this.lastX + s.dx) * s.scale));\r\n\t\t\t\tthis.path.push(this.format((this.lastY + s.dy) * s.scale));\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: rotatePoint\r\n * \r\n * Rotates the given point and returns the result as an <mxPoint>.\r\n */\r\nmxAbstractCanvas2D.prototype.rotatePoint = function(x, y, theta, cx, cy)\r\n{\r\n\tvar rad = theta * (Math.PI / 180);\r\n\t\r\n\treturn mxUtils.getRotatedPoint(new mxPoint(x, y), Math.cos(rad),\r\n\t\tMath.sin(rad), new mxPoint(cx, cy));\r\n};\r\n\r\n/**\r\n * Function: save\r\n * \r\n * Saves the current state.\r\n */\r\nmxAbstractCanvas2D.prototype.save = function()\r\n{\r\n\tthis.states.push(this.state);\r\n\tthis.state = mxUtils.clone(this.state);\r\n};\r\n\r\n/**\r\n * Function: restore\r\n * \r\n * Restores the current state.\r\n */\r\nmxAbstractCanvas2D.prototype.restore = function()\r\n{\r\n\tif (this.states.length > 0)\r\n\t{\r\n\t\tthis.state = this.states.pop();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setLink\r\n * \r\n * Sets the current link. Hook for subclassers.\r\n */\r\nmxAbstractCanvas2D.prototype.setLink = function(link)\r\n{\r\n\t// nop\r\n};\r\n\r\n/**\r\n * Function: scale\r\n * \r\n * Scales the current state.\r\n */\r\nmxAbstractCanvas2D.prototype.scale = function(value)\r\n{\r\n\tthis.state.scale *= value;\r\n\tthis.state.strokeWidth *= value;\r\n};\r\n\r\n/**\r\n * Function: translate\r\n * \r\n * Translates the current state.\r\n */\r\nmxAbstractCanvas2D.prototype.translate = function(dx, dy)\r\n{\r\n\tthis.state.dx += dx;\r\n\tthis.state.dy += dy;\r\n};\r\n\r\n/**\r\n * Function: rotate\r\n * \r\n * Rotates the current state.\r\n */\r\nmxAbstractCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)\r\n{\r\n\t// nop\r\n};\r\n\r\n/**\r\n * Function: setAlpha\r\n * \r\n * Sets the current alpha.\r\n */\r\nmxAbstractCanvas2D.prototype.setAlpha = function(value)\r\n{\r\n\tthis.state.alpha = value;\r\n};\r\n\r\n/**\r\n * Function: setFillAlpha\r\n * \r\n * Sets the current solid fill alpha.\r\n */\r\nmxAbstractCanvas2D.prototype.setFillAlpha = function(value)\r\n{\r\n\tthis.state.fillAlpha = value;\r\n};\r\n\r\n/**\r\n * Function: setStrokeAlpha\r\n * \r\n * Sets the current stroke alpha.\r\n */\r\nmxAbstractCanvas2D.prototype.setStrokeAlpha = function(value)\r\n{\r\n\tthis.state.strokeAlpha = value;\r\n};\r\n\r\n/**\r\n * Function: setFillColor\r\n * \r\n * Sets the current fill color.\r\n */\r\nmxAbstractCanvas2D.prototype.setFillColor = function(value)\r\n{\r\n\tif (value == mxConstants.NONE)\r\n\t{\r\n\t\tvalue = null;\r\n\t}\r\n\t\r\n\tthis.state.fillColor = value;\r\n\tthis.state.gradientColor = null;\r\n};\r\n\r\n/**\r\n * Function: setGradient\r\n * \r\n * Sets the current gradient.\r\n */\r\nmxAbstractCanvas2D.prototype.setGradient = function(color1, color2, x, y, w, h, direction, alpha1, alpha2)\r\n{\r\n\tvar s = this.state;\r\n\ts.fillColor = color1;\r\n\ts.gradientFillAlpha = (alpha1 != null) ? alpha1 : 1;\r\n\ts.gradientColor = color2;\r\n\ts.gradientAlpha = (alpha2 != null) ? alpha2 : 1;\r\n\ts.gradientDirection = direction;\r\n};\r\n\r\n/**\r\n * Function: setStrokeColor\r\n * \r\n * Sets the current stroke color.\r\n */\r\nmxAbstractCanvas2D.prototype.setStrokeColor = function(value)\r\n{\r\n\tif (value == mxConstants.NONE)\r\n\t{\r\n\t\tvalue = null;\r\n\t}\r\n\t\r\n\tthis.state.strokeColor = value;\r\n};\r\n\r\n/**\r\n * Function: setStrokeWidth\r\n * \r\n * Sets the current stroke width.\r\n */\r\nmxAbstractCanvas2D.prototype.setStrokeWidth = function(value)\r\n{\r\n\tthis.state.strokeWidth = value;\r\n};\r\n\r\n/**\r\n * Function: setDashed\r\n * \r\n * Enables or disables dashed lines.\r\n */\r\nmxAbstractCanvas2D.prototype.setDashed = function(value, fixDash)\r\n{\r\n\tthis.state.dashed = value;\r\n\tthis.state.fixDash = fixDash;\r\n};\r\n\r\n/**\r\n * Function: setDashPattern\r\n * \r\n * Sets the current dash pattern.\r\n */\r\nmxAbstractCanvas2D.prototype.setDashPattern = function(value)\r\n{\r\n\tthis.state.dashPattern = value;\r\n};\r\n\r\n/**\r\n * Function: setLineCap\r\n * \r\n * Sets the current line cap.\r\n */\r\nmxAbstractCanvas2D.prototype.setLineCap = function(value)\r\n{\r\n\tthis.state.lineCap = value;\r\n};\r\n\r\n/**\r\n * Function: setLineJoin\r\n * \r\n * Sets the current line join.\r\n */\r\nmxAbstractCanvas2D.prototype.setLineJoin = function(value)\r\n{\r\n\tthis.state.lineJoin = value;\r\n};\r\n\r\n/**\r\n * Function: setMiterLimit\r\n * \r\n * Sets the current miter limit.\r\n */\r\nmxAbstractCanvas2D.prototype.setMiterLimit = function(value)\r\n{\r\n\tthis.state.miterLimit = value;\r\n};\r\n\r\n/**\r\n * Function: setFontColor\r\n * \r\n * Sets the current font color.\r\n */\r\nmxAbstractCanvas2D.prototype.setFontColor = function(value)\r\n{\r\n\tif (value == mxConstants.NONE)\r\n\t{\r\n\t\tvalue = null;\r\n\t}\r\n\t\r\n\tthis.state.fontColor = value;\r\n};\r\n\r\n/**\r\n * Function: setFontColor\r\n * \r\n * Sets the current font color.\r\n */\r\nmxAbstractCanvas2D.prototype.setFontBackgroundColor = function(value)\r\n{\r\n\tif (value == mxConstants.NONE)\r\n\t{\r\n\t\tvalue = null;\r\n\t}\r\n\t\r\n\tthis.state.fontBackgroundColor = value;\r\n};\r\n\r\n/**\r\n * Function: setFontColor\r\n * \r\n * Sets the current font color.\r\n */\r\nmxAbstractCanvas2D.prototype.setFontBorderColor = function(value)\r\n{\r\n\tif (value == mxConstants.NONE)\r\n\t{\r\n\t\tvalue = null;\r\n\t}\r\n\t\r\n\tthis.state.fontBorderColor = value;\r\n};\r\n\r\n/**\r\n * Function: setFontSize\r\n * \r\n * Sets the current font size.\r\n */\r\nmxAbstractCanvas2D.prototype.setFontSize = function(value)\r\n{\r\n\tthis.state.fontSize = parseFloat(value);\r\n};\r\n\r\n/**\r\n * Function: setFontFamily\r\n * \r\n * Sets the current font family.\r\n */\r\nmxAbstractCanvas2D.prototype.setFontFamily = function(value)\r\n{\r\n\tthis.state.fontFamily = value;\r\n};\r\n\r\n/**\r\n * Function: setFontStyle\r\n * \r\n * Sets the current font style.\r\n */\r\nmxAbstractCanvas2D.prototype.setFontStyle = function(value)\r\n{\r\n\tif (value == null)\r\n\t{\r\n\t\tvalue = 0;\r\n\t}\r\n\t\r\n\tthis.state.fontStyle = value;\r\n};\r\n\r\n/**\r\n * Function: setShadow\r\n * \r\n * Enables or disables and configures the current shadow.\r\n */\r\nmxAbstractCanvas2D.prototype.setShadow = function(enabled)\r\n{\r\n\tthis.state.shadow = enabled;\r\n};\r\n\r\n/**\r\n * Function: setShadowColor\r\n * \r\n * Enables or disables and configures the current shadow.\r\n */\r\nmxAbstractCanvas2D.prototype.setShadowColor = function(value)\r\n{\r\n\tif (value == mxConstants.NONE)\r\n\t{\r\n\t\tvalue = null;\r\n\t}\r\n\t\r\n\tthis.state.shadowColor = value;\r\n};\r\n\r\n/**\r\n * Function: setShadowAlpha\r\n * \r\n * Enables or disables and configures the current shadow.\r\n */\r\nmxAbstractCanvas2D.prototype.setShadowAlpha = function(value)\r\n{\r\n\tthis.state.shadowAlpha = value;\r\n};\r\n\r\n/**\r\n * Function: setShadowOffset\r\n * \r\n * Enables or disables and configures the current shadow.\r\n */\r\nmxAbstractCanvas2D.prototype.setShadowOffset = function(dx, dy)\r\n{\r\n\tthis.state.shadowDx = dx;\r\n\tthis.state.shadowDy = dy;\r\n};\r\n\r\n/**\r\n * Function: begin\r\n * \r\n * Starts a new path.\r\n */\r\nmxAbstractCanvas2D.prototype.begin = function()\r\n{\r\n\tthis.lastX = 0;\r\n\tthis.lastY = 0;\r\n\tthis.path = [];\r\n};\r\n\r\n/**\r\n * Function: moveTo\r\n * \r\n *  Moves the current path the given coordinates.\r\n */\r\nmxAbstractCanvas2D.prototype.moveTo = function(x, y)\r\n{\r\n\tthis.addOp(this.moveOp, x, y);\r\n};\r\n\r\n/**\r\n * Function: lineTo\r\n * \r\n * Draws a line to the given coordinates. Uses moveTo with the op argument.\r\n */\r\nmxAbstractCanvas2D.prototype.lineTo = function(x, y)\r\n{\r\n\tthis.addOp(this.lineOp, x, y);\r\n};\r\n\r\n/**\r\n * Function: quadTo\r\n * \r\n * Adds a quadratic curve to the current path.\r\n */\r\nmxAbstractCanvas2D.prototype.quadTo = function(x1, y1, x2, y2)\r\n{\r\n\tthis.addOp(this.quadOp, x1, y1, x2, y2);\r\n};\r\n\r\n/**\r\n * Function: curveTo\r\n * \r\n * Adds a bezier curve to the current path.\r\n */\r\nmxAbstractCanvas2D.prototype.curveTo = function(x1, y1, x2, y2, x3, y3)\r\n{\r\n\tthis.addOp(this.curveOp, x1, y1, x2, y2, x3, y3);\r\n};\r\n\r\n/**\r\n * Function: arcTo\r\n * \r\n * Adds the given arc to the current path. This is a synthetic operation that\r\n * is broken down into curves.\r\n */\r\nmxAbstractCanvas2D.prototype.arcTo = function(rx, ry, angle, largeArcFlag, sweepFlag, x, y)\r\n{\r\n\tvar curves = mxUtils.arcToCurves(this.lastX, this.lastY, rx, ry, angle, largeArcFlag, sweepFlag, x, y);\r\n\t\r\n\tif (curves != null)\r\n\t{\r\n\t\tfor (var i = 0; i < curves.length; i += 6) \r\n\t\t{\r\n\t\t\tthis.curveTo(curves[i], curves[i + 1], curves[i + 2],\r\n\t\t\t\tcurves[i + 3], curves[i + 4], curves[i + 5]);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: close\r\n * \r\n * Closes the current path.\r\n */\r\nmxAbstractCanvas2D.prototype.close = function(x1, y1, x2, y2, x3, y3)\r\n{\r\n\tthis.addOp(this.closeOp);\r\n};\r\n\r\n/**\r\n * Function: end\r\n * \r\n * Empty implementation for backwards compatibility. This will be removed.\r\n */\r\nmxAbstractCanvas2D.prototype.end = function() { };\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxXmlCanvas2D\r\n *\r\n * Base class for all canvases. The following methods make up the public\r\n * interface of the canvas 2D for all painting in mxGraph:\r\n * \r\n * - <save>, <restore>\r\n * - <scale>, <translate>, <rotate>\r\n * - <setAlpha>, <setFillAlpha>, <setStrokeAlpha>, <setFillColor>, <setGradient>,\r\n *   <setStrokeColor>, <setStrokeWidth>, <setDashed>, <setDashPattern>, <setLineCap>, \r\n *   <setLineJoin>, <setMiterLimit>\r\n * - <setFontColor>, <setFontBackgroundColor>, <setFontBorderColor>, <setFontSize>,\r\n *   <setFontFamily>, <setFontStyle>\r\n * - <setShadow>, <setShadowColor>, <setShadowAlpha>, <setShadowOffset>\r\n * - <rect>, <roundrect>, <ellipse>, <image>, <text>\r\n * - <begin>, <moveTo>, <lineTo>, <quadTo>, <curveTo>\r\n * - <stroke>, <fill>, <fillAndStroke>\r\n * \r\n * <mxAbstractCanvas2D.arcTo> is an additional method for drawing paths. This is\r\n * a synthetic method, meaning that it is turned into a sequence of curves by\r\n * default. Subclassers may add native support for arcs.\r\n * \r\n * Constructor: mxXmlCanvas2D\r\n *\r\n * Constructs a new abstract canvas.\r\n */\r\nfunction mxXmlCanvas2D(root)\r\n{\r\n\tmxAbstractCanvas2D.call(this);\r\n\r\n\t/**\r\n\t * Variable: root\r\n\t * \r\n\t * Reference to the container for the SVG content.\r\n\t */\r\n\tthis.root = root;\r\n\r\n\t// Writes default settings;\r\n\tthis.writeDefaults();\r\n};\r\n\r\n/**\r\n * Extends mxAbstractCanvas2D\r\n */\r\nmxUtils.extend(mxXmlCanvas2D, mxAbstractCanvas2D);\r\n\r\n/**\r\n * Variable: textEnabled\r\n * \r\n * Specifies if text output should be enabled. Default is true.\r\n */\r\nmxXmlCanvas2D.prototype.textEnabled = true;\r\n\r\n/**\r\n * Variable: compressed\r\n * \r\n * Specifies if the output should be compressed by removing redundant calls.\r\n * Default is true.\r\n */\r\nmxXmlCanvas2D.prototype.compressed = true;\r\n\r\n/**\r\n * Function: writeDefaults\r\n * \r\n * Writes the rendering defaults to <root>:\r\n */\r\nmxXmlCanvas2D.prototype.writeDefaults = function()\r\n{\r\n\tvar elem;\r\n\t\r\n\t// Writes font defaults\r\n\telem = this.createElement('fontfamily');\r\n\telem.setAttribute('family', mxConstants.DEFAULT_FONTFAMILY);\r\n\tthis.root.appendChild(elem);\r\n\t\r\n\telem = this.createElement('fontsize');\r\n\telem.setAttribute('size', mxConstants.DEFAULT_FONTSIZE);\r\n\tthis.root.appendChild(elem);\r\n\t\r\n\t// Writes shadow defaults\r\n\telem = this.createElement('shadowcolor');\r\n\telem.setAttribute('color', mxConstants.SHADOWCOLOR);\r\n\tthis.root.appendChild(elem);\r\n\t\r\n\telem = this.createElement('shadowalpha');\r\n\telem.setAttribute('alpha', mxConstants.SHADOW_OPACITY);\r\n\tthis.root.appendChild(elem);\r\n\t\r\n\telem = this.createElement('shadowoffset');\r\n\telem.setAttribute('dx', mxConstants.SHADOW_OFFSET_X);\r\n\telem.setAttribute('dy', mxConstants.SHADOW_OFFSET_Y);\r\n\tthis.root.appendChild(elem);\r\n};\r\n\r\n/**\r\n * Function: format\r\n * \r\n * Returns a formatted number with 2 decimal places.\r\n */\r\nmxXmlCanvas2D.prototype.format = function(value)\r\n{\r\n\treturn parseFloat(parseFloat(value).toFixed(2));\r\n};\r\n\r\n/**\r\n * Function: createElement\r\n * \r\n * Creates the given element using the owner document of <root>.\r\n */\r\nmxXmlCanvas2D.prototype.createElement = function(name)\r\n{\r\n\treturn this.root.ownerDocument.createElement(name);\r\n};\r\n\r\n/**\r\n * Function: save\r\n * \r\n * Saves the drawing state.\r\n */\r\nmxXmlCanvas2D.prototype.save = function()\r\n{\r\n\tif (this.compressed)\r\n\t{\r\n\t\tmxAbstractCanvas2D.prototype.save.apply(this, arguments);\r\n\t}\r\n\t\r\n\tthis.root.appendChild(this.createElement('save'));\r\n};\r\n\r\n/**\r\n * Function: restore\r\n * \r\n * Restores the drawing state.\r\n */\r\nmxXmlCanvas2D.prototype.restore = function()\r\n{\r\n\tif (this.compressed)\r\n\t{\r\n\t\tmxAbstractCanvas2D.prototype.restore.apply(this, arguments);\r\n\t}\r\n\t\r\n\tthis.root.appendChild(this.createElement('restore'));\r\n};\r\n\r\n/**\r\n * Function: scale\r\n * \r\n * Scales the output.\r\n * \r\n * Parameters:\r\n * \r\n * scale - Number that represents the scale where 1 is equal to 100%.\r\n */\r\nmxXmlCanvas2D.prototype.scale = function(value)\r\n{\r\n        var elem = this.createElement('scale');\r\n        elem.setAttribute('scale', value);\r\n        this.root.appendChild(elem);\r\n};\r\n\r\n/**\r\n * Function: translate\r\n * \r\n * Translates the output.\r\n * \r\n * Parameters:\r\n * \r\n * dx - Number that specifies the horizontal translation.\r\n * dy - Number that specifies the vertical translation.\r\n */\r\nmxXmlCanvas2D.prototype.translate = function(dx, dy)\r\n{\r\n\tvar elem = this.createElement('translate');\r\n\telem.setAttribute('dx', this.format(dx));\r\n\telem.setAttribute('dy', this.format(dy));\r\n\tthis.root.appendChild(elem);\r\n};\r\n\r\n/**\r\n * Function: rotate\r\n * \r\n * Rotates and/or flips the output around a given center. (Note: Due to\r\n * limitations in VML, the rotation cannot be concatenated.)\r\n * \r\n * Parameters:\r\n * \r\n * theta - Number that represents the angle of the rotation (in degrees).\r\n * flipH - Boolean indicating if the output should be flipped horizontally.\r\n * flipV - Boolean indicating if the output should be flipped vertically.\r\n * cx - Number that represents the x-coordinate of the rotation center.\r\n * cy - Number that represents the y-coordinate of the rotation center.\r\n */\r\nmxXmlCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)\r\n{\r\n\tvar elem = this.createElement('rotate');\r\n\t\r\n\tif (theta != 0 || flipH || flipV)\r\n\t{\r\n\t\telem.setAttribute('theta', this.format(theta));\r\n\t\telem.setAttribute('flipH', (flipH) ? '1' : '0');\r\n\t\telem.setAttribute('flipV', (flipV) ? '1' : '0');\r\n\t\telem.setAttribute('cx', this.format(cx));\r\n\t\telem.setAttribute('cy', this.format(cy));\r\n\t\tthis.root.appendChild(elem);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setAlpha\r\n * \r\n * Sets the current alpha.\r\n * \r\n * Parameters:\r\n * \r\n * value - Number that represents the new alpha. Possible values are between\r\n * 1 (opaque) and 0 (transparent).\r\n */\r\nmxXmlCanvas2D.prototype.setAlpha = function(value)\r\n{\r\n\tif (this.compressed)\r\n\t{\r\n\t\tif (this.state.alpha == value)\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tmxAbstractCanvas2D.prototype.setAlpha.apply(this, arguments);\r\n\t}\r\n\t\r\n\tvar elem = this.createElement('alpha');\r\n\telem.setAttribute('alpha', this.format(value));\r\n\tthis.root.appendChild(elem);\r\n};\r\n\r\n/**\r\n * Function: setFillAlpha\r\n * \r\n * Sets the current fill alpha.\r\n * \r\n * Parameters:\r\n * \r\n * value - Number that represents the new fill alpha. Possible values are between\r\n * 1 (opaque) and 0 (transparent).\r\n */\r\nmxXmlCanvas2D.prototype.setFillAlpha = function(value)\r\n{\r\n\tif (this.compressed)\r\n\t{\r\n\t\tif (this.state.fillAlpha == value)\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tmxAbstractCanvas2D.prototype.setFillAlpha.apply(this, arguments);\r\n\t}\r\n\t\r\n\tvar elem = this.createElement('fillalpha');\r\n\telem.setAttribute('alpha', this.format(value));\r\n\tthis.root.appendChild(elem);\r\n};\r\n\r\n/**\r\n * Function: setStrokeAlpha\r\n * \r\n * Sets the current stroke alpha.\r\n * \r\n * Parameters:\r\n * \r\n * value - Number that represents the new stroke alpha. Possible values are between\r\n * 1 (opaque) and 0 (transparent).\r\n */\r\nmxXmlCanvas2D.prototype.setStrokeAlpha = function(value)\r\n{\r\n\tif (this.compressed)\r\n\t{\r\n\t\tif (this.state.strokeAlpha == value)\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tmxAbstractCanvas2D.prototype.setStrokeAlpha.apply(this, arguments);\r\n\t}\r\n\t\r\n\tvar elem = this.createElement('strokealpha');\r\n\telem.setAttribute('alpha', this.format(value));\r\n\tthis.root.appendChild(elem);\r\n};\r\n\r\n/**\r\n * Function: setFillColor\r\n * \r\n * Sets the current fill color.\r\n * \r\n * Parameters:\r\n * \r\n * value - Hexadecimal representation of the color or 'none'.\r\n */\r\nmxXmlCanvas2D.prototype.setFillColor = function(value)\r\n{\r\n\tif (value == mxConstants.NONE)\r\n\t{\r\n\t\tvalue = null;\r\n\t}\r\n\t\r\n\tif (this.compressed)\r\n\t{\r\n\t\tif (this.state.fillColor == value)\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tmxAbstractCanvas2D.prototype.setFillColor.apply(this, arguments);\r\n\t}\r\n\t\r\n\tvar elem = this.createElement('fillcolor');\r\n\telem.setAttribute('color', (value != null) ? value : mxConstants.NONE);\r\n\tthis.root.appendChild(elem);\r\n};\r\n\r\n/**\r\n * Function: setGradient\r\n * \r\n * Sets the gradient. Note that the coordinates may be ignored by some implementations.\r\n * \r\n * Parameters:\r\n * \r\n * color1 - Hexadecimal representation of the start color.\r\n * color2 - Hexadecimal representation of the end color.\r\n * x - X-coordinate of the gradient region.\r\n * y - y-coordinate of the gradient region.\r\n * w - Width of the gradient region.\r\n * h - Height of the gradient region.\r\n * direction - One of <mxConstants.DIRECTION_NORTH>, <mxConstants.DIRECTION_EAST>,\r\n * <mxConstants.DIRECTION_SOUTH> or <mxConstants.DIRECTION_WEST>.\r\n * alpha1 - Optional alpha of the start color. Default is 1. Possible values\r\n * are between 1 (opaque) and 0 (transparent).\r\n * alpha2 - Optional alpha of the end color. Default is 1. Possible values\r\n * are between 1 (opaque) and 0 (transparent).\r\n */\r\nmxXmlCanvas2D.prototype.setGradient = function(color1, color2, x, y, w, h, direction, alpha1, alpha2)\r\n{\r\n\tif (color1 != null && color2 != null)\r\n\t{\r\n\t\tmxAbstractCanvas2D.prototype.setGradient.apply(this, arguments);\r\n\t\t\r\n\t\tvar elem = this.createElement('gradient');\r\n\t\telem.setAttribute('c1', color1);\r\n\t\telem.setAttribute('c2', color2);\r\n\t\telem.setAttribute('x', this.format(x));\r\n\t\telem.setAttribute('y', this.format(y));\r\n\t\telem.setAttribute('w', this.format(w));\r\n\t\telem.setAttribute('h', this.format(h));\r\n\t\t\r\n\t\t// Default direction is south\r\n\t\tif (direction != null)\r\n\t\t{\r\n\t\t\telem.setAttribute('direction', direction);\r\n\t\t}\r\n\t\t\r\n\t\tif (alpha1 != null)\r\n\t\t{\r\n\t\t\telem.setAttribute('alpha1', alpha1);\r\n\t\t}\r\n\t\t\r\n\t\tif (alpha2 != null)\r\n\t\t{\r\n\t\t\telem.setAttribute('alpha2', alpha2);\r\n\t\t}\r\n\t\t\r\n\t\tthis.root.appendChild(elem);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setStrokeColor\r\n * \r\n * Sets the current stroke color.\r\n * \r\n * Parameters:\r\n * \r\n * value - Hexadecimal representation of the color or 'none'.\r\n */\r\nmxXmlCanvas2D.prototype.setStrokeColor = function(value)\r\n{\r\n\tif (value == mxConstants.NONE)\r\n\t{\r\n\t\tvalue = null;\r\n\t}\r\n\t\r\n\tif (this.compressed)\r\n\t{\r\n\t\tif (this.state.strokeColor == value)\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tmxAbstractCanvas2D.prototype.setStrokeColor.apply(this, arguments);\r\n\t}\r\n\t\r\n\tvar elem = this.createElement('strokecolor');\r\n\telem.setAttribute('color', (value != null) ? value : mxConstants.NONE);\r\n\tthis.root.appendChild(elem);\r\n};\r\n\r\n/**\r\n * Function: setStrokeWidth\r\n * \r\n * Sets the current stroke width.\r\n * \r\n * Parameters:\r\n * \r\n * value - Numeric representation of the stroke width.\r\n */\r\nmxXmlCanvas2D.prototype.setStrokeWidth = function(value)\r\n{\r\n\tif (this.compressed)\r\n\t{\r\n\t\tif (this.state.strokeWidth == value)\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tmxAbstractCanvas2D.prototype.setStrokeWidth.apply(this, arguments);\r\n\t}\r\n\t\r\n\tvar elem = this.createElement('strokewidth');\r\n\telem.setAttribute('width', this.format(value));\r\n\tthis.root.appendChild(elem);\r\n};\r\n\r\n/**\r\n * Function: setDashed\r\n * \r\n * Enables or disables dashed lines.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean that specifies if dashed lines should be enabled.\r\n * value - Boolean that specifies if the stroke width should be ignored\r\n * for the dash pattern. Default is false.\r\n */\r\nmxXmlCanvas2D.prototype.setDashed = function(value, fixDash)\r\n{\r\n\tif (this.compressed)\r\n\t{\r\n\t\tif (this.state.dashed == value)\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tmxAbstractCanvas2D.prototype.setDashed.apply(this, arguments);\r\n\t}\r\n\t\r\n\tvar elem = this.createElement('dashed');\r\n\telem.setAttribute('dashed', (value) ? '1' : '0');\r\n\t\r\n\tif (fixDash != null)\r\n\t{\r\n\t\telem.setAttribute('fixDash', (fixDash) ? '1' : '0');\r\n\t}\r\n\t\r\n\tthis.root.appendChild(elem);\r\n};\r\n\r\n/**\r\n * Function: setDashPattern\r\n * \r\n * Sets the current dash pattern. Default is '3 3'.\r\n * \r\n * Parameters:\r\n * \r\n * value - String that represents the dash pattern, which is a sequence of\r\n * numbers defining the length of the dashes and the length of the spaces\r\n * between the dashes. The lengths are relative to the line width - a length\r\n * of 1 is equals to the line width.\r\n */\r\nmxXmlCanvas2D.prototype.setDashPattern = function(value)\r\n{\r\n\tif (this.compressed)\r\n\t{\r\n\t\tif (this.state.dashPattern == value)\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tmxAbstractCanvas2D.prototype.setDashPattern.apply(this, arguments);\r\n\t}\r\n\t\r\n\tvar elem = this.createElement('dashpattern');\r\n\telem.setAttribute('pattern', value);\r\n\tthis.root.appendChild(elem);\r\n};\r\n\r\n/**\r\n * Function: setLineCap\r\n * \r\n * Sets the line cap. Default is 'flat' which corresponds to 'butt' in SVG.\r\n * \r\n * Parameters:\r\n * \r\n * value - String that represents the line cap. Possible values are flat, round\r\n * and square.\r\n */\r\nmxXmlCanvas2D.prototype.setLineCap = function(value)\r\n{\r\n\tif (this.compressed)\r\n\t{\r\n\t\tif (this.state.lineCap == value)\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tmxAbstractCanvas2D.prototype.setLineCap.apply(this, arguments);\r\n\t}\r\n\t\r\n\tvar elem = this.createElement('linecap');\r\n\telem.setAttribute('cap', value);\r\n\tthis.root.appendChild(elem);\r\n};\r\n\r\n/**\r\n * Function: setLineJoin\r\n * \r\n * Sets the line join. Default is 'miter'.\r\n * \r\n * Parameters:\r\n * \r\n * value - String that represents the line join. Possible values are miter,\r\n * round and bevel.\r\n */\r\nmxXmlCanvas2D.prototype.setLineJoin = function(value)\r\n{\r\n\tif (this.compressed)\r\n\t{\r\n\t\tif (this.state.lineJoin == value)\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tmxAbstractCanvas2D.prototype.setLineJoin.apply(this, arguments);\r\n\t}\r\n\t\r\n\tvar elem = this.createElement('linejoin');\r\n\telem.setAttribute('join', value);\r\n\tthis.root.appendChild(elem);\r\n};\r\n\r\n/**\r\n * Function: setMiterLimit\r\n * \r\n * Sets the miter limit. Default is 10.\r\n * \r\n * Parameters:\r\n * \r\n * value - Number that represents the miter limit.\r\n */\r\nmxXmlCanvas2D.prototype.setMiterLimit = function(value)\r\n{\r\n\tif (this.compressed)\r\n\t{\r\n\t\tif (this.state.miterLimit == value)\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tmxAbstractCanvas2D.prototype.setMiterLimit.apply(this, arguments);\r\n\t}\r\n\t\r\n\tvar elem = this.createElement('miterlimit');\r\n\telem.setAttribute('limit', value);\r\n\tthis.root.appendChild(elem);\r\n};\r\n\r\n/**\r\n * Function: setFontColor\r\n * \r\n * Sets the current font color. Default is '#000000'.\r\n * \r\n * Parameters:\r\n * \r\n * value - Hexadecimal representation of the color or 'none'.\r\n */\r\nmxXmlCanvas2D.prototype.setFontColor = function(value)\r\n{\r\n\tif (this.textEnabled)\r\n\t{\r\n\t\tif (value == mxConstants.NONE)\r\n\t\t{\r\n\t\t\tvalue = null;\r\n\t\t}\r\n\t\t\r\n\t\tif (this.compressed)\r\n\t\t{\r\n\t\t\tif (this.state.fontColor == value)\r\n\t\t\t{\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tmxAbstractCanvas2D.prototype.setFontColor.apply(this, arguments);\r\n\t\t}\r\n\t\t\r\n\t\tvar elem = this.createElement('fontcolor');\r\n\t\telem.setAttribute('color', (value != null) ? value : mxConstants.NONE);\r\n\t\tthis.root.appendChild(elem);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setFontBackgroundColor\r\n * \r\n * Sets the current font background color.\r\n * \r\n * Parameters:\r\n * \r\n * value - Hexadecimal representation of the color or 'none'.\r\n */\r\nmxXmlCanvas2D.prototype.setFontBackgroundColor = function(value)\r\n{\r\n\tif (this.textEnabled)\r\n\t{\r\n\t\tif (value == mxConstants.NONE)\r\n\t\t{\r\n\t\t\tvalue = null;\r\n\t\t}\r\n\t\t\r\n\t\tif (this.compressed)\r\n\t\t{\r\n\t\t\tif (this.state.fontBackgroundColor == value)\r\n\t\t\t{\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tmxAbstractCanvas2D.prototype.setFontBackgroundColor.apply(this, arguments);\r\n\t\t}\r\n\r\n\t\tvar elem = this.createElement('fontbackgroundcolor');\r\n\t\telem.setAttribute('color', (value != null) ? value : mxConstants.NONE);\r\n\t\tthis.root.appendChild(elem);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setFontBorderColor\r\n * \r\n * Sets the current font border color.\r\n * \r\n * Parameters:\r\n * \r\n * value - Hexadecimal representation of the color or 'none'.\r\n */\r\nmxXmlCanvas2D.prototype.setFontBorderColor = function(value)\r\n{\r\n\tif (this.textEnabled)\r\n\t{\r\n\t\tif (value == mxConstants.NONE)\r\n\t\t{\r\n\t\t\tvalue = null;\r\n\t\t}\r\n\t\t\r\n\t\tif (this.compressed)\r\n\t\t{\r\n\t\t\tif (this.state.fontBorderColor == value)\r\n\t\t\t{\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tmxAbstractCanvas2D.prototype.setFontBorderColor.apply(this, arguments);\r\n\t\t}\r\n\t\t\r\n\t\tvar elem = this.createElement('fontbordercolor');\r\n\t\telem.setAttribute('color', (value != null) ? value : mxConstants.NONE);\r\n\t\tthis.root.appendChild(elem);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setFontSize\r\n * \r\n * Sets the current font size. Default is <mxConstants.DEFAULT_FONTSIZE>.\r\n * \r\n * Parameters:\r\n * \r\n * value - Numeric representation of the font size.\r\n */\r\nmxXmlCanvas2D.prototype.setFontSize = function(value)\r\n{\r\n\tif (this.textEnabled)\r\n\t{\r\n\t\tif (this.compressed)\r\n\t\t{\r\n\t\t\tif (this.state.fontSize == value)\r\n\t\t\t{\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tmxAbstractCanvas2D.prototype.setFontSize.apply(this, arguments);\r\n\t\t}\r\n\t\t\r\n\t\tvar elem = this.createElement('fontsize');\r\n\t\telem.setAttribute('size', value);\r\n\t\tthis.root.appendChild(elem);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setFontFamily\r\n * \r\n * Sets the current font family. Default is <mxConstants.DEFAULT_FONTFAMILY>.\r\n * \r\n * Parameters:\r\n * \r\n * value - String representation of the font family. This handles the same\r\n * values as the CSS font-family property.\r\n */\r\nmxXmlCanvas2D.prototype.setFontFamily = function(value)\r\n{\r\n\tif (this.textEnabled)\r\n\t{\r\n\t\tif (this.compressed)\r\n\t\t{\r\n\t\t\tif (this.state.fontFamily == value)\r\n\t\t\t{\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tmxAbstractCanvas2D.prototype.setFontFamily.apply(this, arguments);\r\n\t\t}\r\n\t\t\r\n\t\tvar elem = this.createElement('fontfamily');\r\n\t\telem.setAttribute('family', value);\r\n\t\tthis.root.appendChild(elem);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setFontStyle\r\n * \r\n * Sets the current font style.\r\n * \r\n * Parameters:\r\n * \r\n * value - Numeric representation of the font family. This is the sum of the\r\n * font styles from <mxConstants>.\r\n */\r\nmxXmlCanvas2D.prototype.setFontStyle = function(value)\r\n{\r\n\tif (this.textEnabled)\r\n\t{\r\n\t\tif (value == null)\r\n\t\t{\r\n\t\t\tvalue = 0;\r\n\t\t}\r\n\t\t\r\n\t\tif (this.compressed)\r\n\t\t{\r\n\t\t\tif (this.state.fontStyle == value)\r\n\t\t\t{\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tmxAbstractCanvas2D.prototype.setFontStyle.apply(this, arguments);\r\n\t\t}\r\n\t\t\r\n\t\tvar elem = this.createElement('fontstyle');\r\n\t\telem.setAttribute('style', value);\r\n\t\tthis.root.appendChild(elem);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setShadow\r\n * \r\n * Enables or disables shadows.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean that specifies if shadows should be enabled.\r\n */\r\nmxXmlCanvas2D.prototype.setShadow = function(value)\r\n{\r\n\tif (this.compressed)\r\n\t{\r\n\t\tif (this.state.shadow == value)\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tmxAbstractCanvas2D.prototype.setShadow.apply(this, arguments);\r\n\t}\r\n\t\r\n\tvar elem = this.createElement('shadow');\r\n\telem.setAttribute('enabled', (value) ? '1' : '0');\r\n\tthis.root.appendChild(elem);\r\n};\r\n\r\n/**\r\n * Function: setShadowColor\r\n * \r\n * Sets the current shadow color. Default is <mxConstants.SHADOWCOLOR>.\r\n * \r\n * Parameters:\r\n * \r\n * value - Hexadecimal representation of the color or 'none'.\r\n */\r\nmxXmlCanvas2D.prototype.setShadowColor = function(value)\r\n{\r\n\tif (this.compressed)\r\n\t{\r\n\t\tif (value == mxConstants.NONE)\r\n\t\t{\r\n\t\t\tvalue = null;\r\n\t\t}\r\n\t\t\r\n\t\tif (this.state.shadowColor == value)\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tmxAbstractCanvas2D.prototype.setShadowColor.apply(this, arguments);\r\n\t}\r\n\t\r\n\tvar elem = this.createElement('shadowcolor');\r\n\telem.setAttribute('color', (value != null) ? value : mxConstants.NONE);\r\n\tthis.root.appendChild(elem);\r\n};\r\n\r\n/**\r\n * Function: setShadowAlpha\r\n * \r\n * Sets the current shadows alpha. Default is <mxConstants.SHADOW_OPACITY>.\r\n * \r\n * Parameters:\r\n * \r\n * value - Number that represents the new alpha. Possible values are between\r\n * 1 (opaque) and 0 (transparent).\r\n */\r\nmxXmlCanvas2D.prototype.setShadowAlpha = function(value)\r\n{\r\n\tif (this.compressed)\r\n\t{\r\n\t\tif (this.state.shadowAlpha == value)\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tmxAbstractCanvas2D.prototype.setShadowAlpha.apply(this, arguments);\r\n\t}\r\n\t\r\n\tvar elem = this.createElement('shadowalpha');\r\n\telem.setAttribute('alpha', value);\r\n\tthis.root.appendChild(elem);\r\n\t\r\n};\r\n\r\n/**\r\n * Function: setShadowOffset\r\n * \r\n * Sets the current shadow offset.\r\n * \r\n * Parameters:\r\n * \r\n * dx - Number that represents the horizontal offset of the shadow.\r\n * dy - Number that represents the vertical offset of the shadow.\r\n */\r\nmxXmlCanvas2D.prototype.setShadowOffset = function(dx, dy)\r\n{\r\n\tif (this.compressed)\r\n\t{\r\n\t\tif (this.state.shadowDx == dx && this.state.shadowDy == dy)\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tmxAbstractCanvas2D.prototype.setShadowOffset.apply(this, arguments);\r\n\t}\r\n\t\r\n\tvar elem = this.createElement('shadowoffset');\r\n\telem.setAttribute('dx', dx);\r\n\telem.setAttribute('dy', dy);\r\n\tthis.root.appendChild(elem);\r\n\t\r\n};\r\n\r\n/**\r\n * Function: rect\r\n * \r\n * Puts a rectangle into the drawing buffer.\r\n * \r\n * Parameters:\r\n * \r\n * x - Number that represents the x-coordinate of the rectangle.\r\n * y - Number that represents the y-coordinate of the rectangle.\r\n * w - Number that represents the width of the rectangle.\r\n * h - Number that represents the height of the rectangle.\r\n */\r\nmxXmlCanvas2D.prototype.rect = function(x, y, w, h)\r\n{\r\n\tvar elem = this.createElement('rect');\r\n\telem.setAttribute('x', this.format(x));\r\n\telem.setAttribute('y', this.format(y));\r\n\telem.setAttribute('w', this.format(w));\r\n\telem.setAttribute('h', this.format(h));\r\n\tthis.root.appendChild(elem);\r\n};\r\n\r\n/**\r\n * Function: roundrect\r\n * \r\n * Puts a rounded rectangle into the drawing buffer.\r\n * \r\n * Parameters:\r\n * \r\n * x - Number that represents the x-coordinate of the rectangle.\r\n * y - Number that represents the y-coordinate of the rectangle.\r\n * w - Number that represents the width of the rectangle.\r\n * h - Number that represents the height of the rectangle.\r\n * dx - Number that represents the horizontal rounding.\r\n * dy - Number that represents the vertical rounding.\r\n */\r\nmxXmlCanvas2D.prototype.roundrect = function(x, y, w, h, dx, dy)\r\n{\r\n\tvar elem = this.createElement('roundrect');\r\n\telem.setAttribute('x', this.format(x));\r\n\telem.setAttribute('y', this.format(y));\r\n\telem.setAttribute('w', this.format(w));\r\n\telem.setAttribute('h', this.format(h));\r\n\telem.setAttribute('dx', this.format(dx));\r\n\telem.setAttribute('dy', this.format(dy));\r\n\tthis.root.appendChild(elem);\r\n};\r\n\r\n/**\r\n * Function: ellipse\r\n * \r\n * Puts an ellipse into the drawing buffer.\r\n * \r\n * Parameters:\r\n * \r\n * x - Number that represents the x-coordinate of the ellipse.\r\n * y - Number that represents the y-coordinate of the ellipse.\r\n * w - Number that represents the width of the ellipse.\r\n * h - Number that represents the height of the ellipse.\r\n */\r\nmxXmlCanvas2D.prototype.ellipse = function(x, y, w, h)\r\n{\r\n\tvar elem = this.createElement('ellipse');\r\n\telem.setAttribute('x', this.format(x));\r\n\telem.setAttribute('y', this.format(y));\r\n\telem.setAttribute('w', this.format(w));\r\n\telem.setAttribute('h', this.format(h));\r\n\tthis.root.appendChild(elem);\r\n};\r\n\r\n/**\r\n * Function: image\r\n * \r\n * Paints an image.\r\n * \r\n * Parameters:\r\n * \r\n * x - Number that represents the x-coordinate of the image.\r\n * y - Number that represents the y-coordinate of the image.\r\n * w - Number that represents the width of the image.\r\n * h - Number that represents the height of the image.\r\n * src - String that specifies the URL of the image.\r\n * aspect - Boolean indicating if the aspect of the image should be preserved.\r\n * flipH - Boolean indicating if the image should be flipped horizontally.\r\n * flipV - Boolean indicating if the image should be flipped vertically.\r\n */\r\nmxXmlCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)\r\n{\r\n\tsrc = this.converter.convert(src);\r\n\t\r\n\t// LATER: Add option for embedding images as base64.\r\n\tvar elem = this.createElement('image');\r\n\telem.setAttribute('x', this.format(x));\r\n\telem.setAttribute('y', this.format(y));\r\n\telem.setAttribute('w', this.format(w));\r\n\telem.setAttribute('h', this.format(h));\r\n\telem.setAttribute('src', src);\r\n\telem.setAttribute('aspect', (aspect) ? '1' : '0');\r\n\telem.setAttribute('flipH', (flipH) ? '1' : '0');\r\n\telem.setAttribute('flipV', (flipV) ? '1' : '0');\r\n\tthis.root.appendChild(elem);\r\n};\r\n\r\n/**\r\n * Function: begin\r\n * \r\n * Starts a new path and puts it into the drawing buffer.\r\n */\r\nmxXmlCanvas2D.prototype.begin = function()\r\n{\r\n\tthis.root.appendChild(this.createElement('begin'));\r\n\tthis.lastX = 0;\r\n\tthis.lastY = 0;\r\n};\r\n\r\n/**\r\n * Function: moveTo\r\n * \r\n * Moves the current path the given point.\r\n * \r\n * Parameters:\r\n * \r\n * x - Number that represents the x-coordinate of the point.\r\n * y - Number that represents the y-coordinate of the point.\r\n */\r\nmxXmlCanvas2D.prototype.moveTo = function(x, y)\r\n{\r\n\tvar elem = this.createElement('move');\r\n\telem.setAttribute('x', this.format(x));\r\n\telem.setAttribute('y', this.format(y));\r\n\tthis.root.appendChild(elem);\r\n\tthis.lastX = x;\r\n\tthis.lastY = y;\r\n};\r\n\r\n/**\r\n * Function: lineTo\r\n * \r\n * Draws a line to the given coordinates.\r\n * \r\n * Parameters:\r\n * \r\n * x - Number that represents the x-coordinate of the endpoint.\r\n * y - Number that represents the y-coordinate of the endpoint.\r\n */\r\nmxXmlCanvas2D.prototype.lineTo = function(x, y)\r\n{\r\n\tvar elem = this.createElement('line');\r\n\telem.setAttribute('x', this.format(x));\r\n\telem.setAttribute('y', this.format(y));\r\n\tthis.root.appendChild(elem);\r\n\tthis.lastX = x;\r\n\tthis.lastY = y;\r\n};\r\n\r\n/**\r\n * Function: quadTo\r\n * \r\n * Adds a quadratic curve to the current path.\r\n * \r\n * Parameters:\r\n * \r\n * x1 - Number that represents the x-coordinate of the control point.\r\n * y1 - Number that represents the y-coordinate of the control point.\r\n * x2 - Number that represents the x-coordinate of the endpoint.\r\n * y2 - Number that represents the y-coordinate of the endpoint.\r\n */\r\nmxXmlCanvas2D.prototype.quadTo = function(x1, y1, x2, y2)\r\n{\r\n\tvar elem = this.createElement('quad');\r\n\telem.setAttribute('x1', this.format(x1));\r\n\telem.setAttribute('y1', this.format(y1));\r\n\telem.setAttribute('x2', this.format(x2));\r\n\telem.setAttribute('y2', this.format(y2));\r\n\tthis.root.appendChild(elem);\r\n\tthis.lastX = x2;\r\n\tthis.lastY = y2;\r\n};\r\n\r\n/**\r\n * Function: curveTo\r\n * \r\n * Adds a bezier curve to the current path.\r\n * \r\n * Parameters:\r\n * \r\n * x1 - Number that represents the x-coordinate of the first control point.\r\n * y1 - Number that represents the y-coordinate of the first control point.\r\n * x2 - Number that represents the x-coordinate of the second control point.\r\n * y2 - Number that represents the y-coordinate of the second control point.\r\n * x3 - Number that represents the x-coordinate of the endpoint.\r\n * y3 - Number that represents the y-coordinate of the endpoint.\r\n */\r\nmxXmlCanvas2D.prototype.curveTo = function(x1, y1, x2, y2, x3, y3)\r\n{\r\n\tvar elem = this.createElement('curve');\r\n\telem.setAttribute('x1', this.format(x1));\r\n\telem.setAttribute('y1', this.format(y1));\r\n\telem.setAttribute('x2', this.format(x2));\r\n\telem.setAttribute('y2', this.format(y2));\r\n\telem.setAttribute('x3', this.format(x3));\r\n\telem.setAttribute('y3', this.format(y3));\r\n\tthis.root.appendChild(elem);\r\n\tthis.lastX = x3;\r\n\tthis.lastY = y3;\r\n};\r\n\r\n/**\r\n * Function: close\r\n * \r\n * Closes the current path.\r\n */\r\nmxXmlCanvas2D.prototype.close = function()\r\n{\r\n\tthis.root.appendChild(this.createElement('close'));\r\n};\r\n\r\n/**\r\n * Function: text\r\n * \r\n * Paints the given text. Possible values for format are empty string for\r\n * plain text and html for HTML markup. Background and border color as well\r\n * as clipping is not available in plain text labels for VML. HTML labels\r\n * are not available as part of shapes with no foreignObject support in SVG\r\n * (eg. IE9, IE10).\r\n * \r\n * Parameters:\r\n * \r\n * x - Number that represents the x-coordinate of the text.\r\n * y - Number that represents the y-coordinate of the text.\r\n * w - Number that represents the available width for the text or 0 for automatic width.\r\n * h - Number that represents the available height for the text or 0 for automatic height.\r\n * str - String that specifies the text to be painted.\r\n * align - String that represents the horizontal alignment.\r\n * valign - String that represents the vertical alignment.\r\n * wrap - Boolean that specifies if word-wrapping is enabled. Requires w > 0.\r\n * format - Empty string for plain text or 'html' for HTML markup.\r\n * overflow - Specifies the overflow behaviour of the label. Requires w > 0 and/or h > 0.\r\n * clip - Boolean that specifies if the label should be clipped. Requires w > 0 and/or h > 0.\r\n * rotation - Number that specifies the angle of the rotation around the anchor point of the text.\r\n * dir - Optional string that specifies the text direction. Possible values are rtl and lrt.\r\n */\r\nmxXmlCanvas2D.prototype.text = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)\r\n{\r\n\tif (this.textEnabled && str != null)\r\n\t{\r\n\t\tif (mxUtils.isNode(str))\r\n\t\t{\r\n\t\t\tstr = mxUtils.getOuterHtml(str);\r\n\t\t}\r\n\t\t\r\n\t\tvar elem = this.createElement('text');\r\n\t\telem.setAttribute('x', this.format(x));\r\n\t\telem.setAttribute('y', this.format(y));\r\n\t\telem.setAttribute('w', this.format(w));\r\n\t\telem.setAttribute('h', this.format(h));\r\n\t\telem.setAttribute('str', str);\r\n\t\t\r\n\t\tif (align != null)\r\n\t\t{\r\n\t\t\telem.setAttribute('align', align);\r\n\t\t}\r\n\t\t\r\n\t\tif (valign != null)\r\n\t\t{\r\n\t\t\telem.setAttribute('valign', valign);\r\n\t\t}\r\n\t\t\r\n\t\telem.setAttribute('wrap', (wrap) ? '1' : '0');\r\n\t\t\r\n\t\tif (format == null)\r\n\t\t{\r\n\t\t\tformat = '';\r\n\t\t}\r\n\t\t\r\n\t\telem.setAttribute('format', format);\r\n\t\t\r\n\t\tif (overflow != null)\r\n\t\t{\r\n\t\t\telem.setAttribute('overflow', overflow);\r\n\t\t}\r\n\t\t\r\n\t\tif (clip != null)\r\n\t\t{\r\n\t\t\telem.setAttribute('clip', (clip) ? '1' : '0');\r\n\t\t}\r\n\t\t\r\n\t\tif (rotation != null)\r\n\t\t{\r\n\t\t\telem.setAttribute('rotation', rotation);\r\n\t\t}\r\n\t\t\r\n\t\tif (dir != null)\r\n\t\t{\r\n\t\t\telem.setAttribute('dir', dir);\r\n\t\t}\r\n\t\t\r\n\t\tthis.root.appendChild(elem);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: stroke\r\n * \r\n * Paints the outline of the current drawing buffer.\r\n */\r\nmxXmlCanvas2D.prototype.stroke = function()\r\n{\r\n\tthis.root.appendChild(this.createElement('stroke'));\r\n};\r\n\r\n/**\r\n * Function: fill\r\n * \r\n * Fills the current drawing buffer.\r\n */\r\nmxXmlCanvas2D.prototype.fill = function()\r\n{\r\n\tthis.root.appendChild(this.createElement('fill'));\r\n};\r\n\r\n/**\r\n * Function: fillAndStroke\r\n * \r\n * Fills the current drawing buffer and its outline.\r\n */\r\nmxXmlCanvas2D.prototype.fillAndStroke = function()\r\n{\r\n\tthis.root.appendChild(this.createElement('fillstroke'));\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxSvgCanvas2D\r\n *\r\n * Extends <mxAbstractCanvas2D> to implement a canvas for SVG. This canvas writes all\r\n * calls as SVG output to the given SVG root node.\r\n * \r\n * (code)\r\n * var svgDoc = mxUtils.createXmlDocument();\r\n * var root = (svgDoc.createElementNS != null) ?\r\n * \t\tsvgDoc.createElementNS(mxConstants.NS_SVG, 'svg') : svgDoc.createElement('svg');\r\n * \r\n * if (svgDoc.createElementNS == null)\r\n * {\r\n *   root.setAttribute('xmlns', mxConstants.NS_SVG);\r\n *   root.setAttribute('xmlns:xlink', mxConstants.NS_XLINK);\r\n * }\r\n * else\r\n * {\r\n *   root.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xlink', mxConstants.NS_XLINK);\r\n * }\r\n * \r\n * var bounds = graph.getGraphBounds();\r\n * root.setAttribute('width', (bounds.x + bounds.width + 4) + 'px');\r\n * root.setAttribute('height', (bounds.y + bounds.height + 4) + 'px');\r\n * root.setAttribute('version', '1.1');\r\n * \r\n * svgDoc.appendChild(root);\r\n * \r\n * var svgCanvas = new mxSvgCanvas2D(root);\r\n * (end)\r\n * \r\n * A description of the public API is available in <mxXmlCanvas2D>.\r\n * \r\n * To disable anti-aliasing in the output, use the following code.\r\n * \r\n * (code)\r\n * graph.view.canvas.ownerSVGElement.setAttribute('shape-rendering', 'crispEdges');\r\n * (end)\r\n * \r\n * Or set the respective attribute in the SVG element directly.\r\n * \r\n * Constructor: mxSvgCanvas2D\r\n *\r\n * Constructs a new SVG canvas.\r\n * \r\n * Parameters:\r\n * \r\n * root - SVG container for the output.\r\n * styleEnabled - Optional boolean that specifies if a style section should be\r\n * added. The style section sets the default font-size, font-family and\r\n * stroke-miterlimit globally. Default is false.\r\n */\r\nfunction mxSvgCanvas2D(root, styleEnabled)\r\n{\r\n\tmxAbstractCanvas2D.call(this);\r\n\r\n\t/**\r\n\t * Variable: root\r\n\t * \r\n\t * Reference to the container for the SVG content.\r\n\t */\r\n\tthis.root = root;\r\n\r\n\t/**\r\n\t * Variable: gradients\r\n\t * \r\n\t * Local cache of gradients for quick lookups.\r\n\t */\r\n\tthis.gradients = [];\r\n\r\n\t/**\r\n\t * Variable: defs\r\n\t * \r\n\t * Reference to the defs section of the SVG document. Only for export.\r\n\t */\r\n\tthis.defs = null;\r\n\t\r\n\t/**\r\n\t * Variable: styleEnabled\r\n\t * \r\n\t * Stores the value of styleEnabled passed to the constructor.\r\n\t */\r\n\tthis.styleEnabled = (styleEnabled != null) ? styleEnabled : false;\r\n\t\r\n\tvar svg = null;\r\n\t\r\n\t// Adds optional defs section for export\r\n\tif (root.ownerDocument != document)\r\n\t{\r\n\t\tvar node = root;\r\n\r\n\t\t// Finds owner SVG element in XML DOM\r\n\t\twhile (node != null && node.nodeName != 'svg')\r\n\t\t{\r\n\t\t\tnode = node.parentNode;\r\n\t\t}\r\n\t\t\r\n\t\tsvg = node;\r\n\t}\r\n\r\n\tif (svg != null)\r\n\t{\r\n\t\t// Tries to get existing defs section\r\n\t\tvar tmp = svg.getElementsByTagName('defs');\r\n\t\t\r\n\t\tif (tmp.length > 0)\r\n\t\t{\r\n\t\t\tthis.defs = svg.getElementsByTagName('defs')[0];\r\n\t\t}\r\n\t\t\r\n\t\t// Adds defs section if none exists\r\n\t\tif (this.defs == null)\r\n\t\t{\r\n\t\t\tthis.defs = this.createElement('defs');\r\n\t\t\t\r\n\t\t\tif (svg.firstChild != null)\r\n\t\t\t{\r\n\t\t\t\tsvg.insertBefore(this.defs, svg.firstChild);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tsvg.appendChild(this.defs);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Adds stylesheet\r\n\t\tif (this.styleEnabled)\r\n\t\t{\r\n\t\t\tthis.defs.appendChild(this.createStyle());\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Extends mxAbstractCanvas2D\r\n */\r\nmxUtils.extend(mxSvgCanvas2D, mxAbstractCanvas2D);\r\n\r\n/**\r\n * Capability check for DOM parser.\r\n */\r\n(function()\r\n{\r\n\tmxSvgCanvas2D.prototype.useDomParser = !mxClient.IS_IE && typeof DOMParser === 'function' && typeof XMLSerializer === 'function';\r\n\t\r\n\tif (mxSvgCanvas2D.prototype.useDomParser)\r\n\t{\r\n\t\t// Checks using a generic test text if the parsing actually works. This is a workaround\r\n\t\t// for older browsers where the capability check returns true but the parsing fails.\r\n\t\ttry\r\n\t\t{\r\n\t\t\tvar doc = new DOMParser().parseFromString('test text', 'text/html');\r\n\t\t\tmxSvgCanvas2D.prototype.useDomParser = doc != null;\r\n\t\t}\r\n\t\tcatch (e)\r\n\t\t{\r\n\t\t\tmxSvgCanvas2D.prototype.useDomParser = false;\r\n\t\t}\r\n\t}\r\n})();\r\n\r\n/**\r\n * Variable: path\r\n * \r\n * Holds the current DOM node.\r\n */\r\nmxSvgCanvas2D.prototype.node = null;\r\n\r\n/**\r\n * Variable: matchHtmlAlignment\r\n * \r\n * Specifies if plain text output should match the vertical HTML alignment.\r\n * Defaul is true.\r\n */\r\nmxSvgCanvas2D.prototype.matchHtmlAlignment = true;\r\n\r\n/**\r\n * Variable: textEnabled\r\n * \r\n * Specifies if text output should be enabled. Default is true.\r\n */\r\nmxSvgCanvas2D.prototype.textEnabled = true;\r\n\r\n/**\r\n * Variable: foEnabled\r\n * \r\n * Specifies if use of foreignObject for HTML markup is allowed. Default is true.\r\n */\r\nmxSvgCanvas2D.prototype.foEnabled = true;\r\n\r\n/**\r\n * Variable: foAltText\r\n * \r\n * Specifies the fallback text for unsupported foreignObjects in exported\r\n * documents. Default is '[Object]'. If this is set to null then no fallback\r\n * text is added to the exported document.\r\n */\r\nmxSvgCanvas2D.prototype.foAltText = '[Object]';\r\n\r\n/**\r\n * Variable: foOffset\r\n * \r\n * Offset to be used for foreignObjects.\r\n */\r\nmxSvgCanvas2D.prototype.foOffset = 0;\r\n\r\n/**\r\n * Variable: textOffset\r\n * \r\n * Offset to be used for text elements.\r\n */\r\nmxSvgCanvas2D.prototype.textOffset = 0;\r\n\r\n/**\r\n * Variable: imageOffset\r\n * \r\n * Offset to be used for image elements.\r\n */\r\nmxSvgCanvas2D.prototype.imageOffset = 0;\r\n\r\n/**\r\n * Variable: strokeTolerance\r\n * \r\n * Adds transparent paths for strokes.\r\n */\r\nmxSvgCanvas2D.prototype.strokeTolerance = 0;\r\n\r\n/**\r\n * Variable: minStrokeWidth\r\n * \r\n * Minimum stroke width for output.\r\n */\r\nmxSvgCanvas2D.prototype.minStrokeWidth = 1;\r\n\r\n/**\r\n * Variable: refCount\r\n * \r\n * Local counter for references in SVG export.\r\n */\r\nmxSvgCanvas2D.prototype.refCount = 0;\r\n\r\n/**\r\n * Variable: blockImagePointerEvents\r\n * \r\n * Specifies if a transparent rectangle should be added on top of images to absorb\r\n * all pointer events. Default is false. This is only needed in Firefox to disable\r\n * control-clicks on images.\r\n */\r\nmxSvgCanvas2D.prototype.blockImagePointerEvents = false;\r\n\r\n/**\r\n * Variable: lineHeightCorrection\r\n * \r\n * Correction factor for <mxConstants.LINE_HEIGHT> in HTML output. Default is 1.\r\n */\r\nmxSvgCanvas2D.prototype.lineHeightCorrection = 1;\r\n\r\n/**\r\n * Variable: pointerEventsValue\r\n * \r\n * Default value for active pointer events. Default is all.\r\n */\r\nmxSvgCanvas2D.prototype.pointerEventsValue = 'all';\r\n\r\n/**\r\n * Variable: fontMetricsPadding\r\n * \r\n * Padding to be added for text that is not wrapped to account for differences\r\n * in font metrics on different platforms in pixels. Default is 10.\r\n */\r\nmxSvgCanvas2D.prototype.fontMetricsPadding = 10;\r\n\r\n/**\r\n * Variable: cacheOffsetSize\r\n * \r\n * Specifies if offsetWidth and offsetHeight should be cached. Default is true.\r\n * This is used to speed up repaint of text in <updateText>.\r\n */\r\nmxSvgCanvas2D.prototype.cacheOffsetSize = true;\r\n\r\n/**\r\n * Function: format\r\n * \r\n * Rounds all numbers to 2 decimal points.\r\n */\r\nmxSvgCanvas2D.prototype.format = function(value)\r\n{\r\n\treturn parseFloat(parseFloat(value).toFixed(2));\r\n};\r\n\r\n/**\r\n * Function: getBaseUrl\r\n * \r\n * Returns the URL of the page without the hash part. This needs to use href to\r\n * include any search part with no params (ie question mark alone). This is a\r\n * workaround for the fact that window.location.search is empty if there is\r\n * no search string behind the question mark.\r\n */\r\nmxSvgCanvas2D.prototype.getBaseUrl = function()\r\n{\r\n\tvar href = window.location.href;\r\n\tvar hash = href.lastIndexOf('#');\r\n\t\r\n\tif (hash > 0)\r\n\t{\r\n\t\thref = href.substring(0, hash);\r\n\t}\r\n\t\r\n\treturn href;\r\n};\r\n\r\n/**\r\n * Function: reset\r\n * \r\n * Returns any offsets for rendering pixels.\r\n */\r\nmxSvgCanvas2D.prototype.reset = function()\r\n{\r\n\tmxAbstractCanvas2D.prototype.reset.apply(this, arguments);\r\n\tthis.gradients = [];\r\n};\r\n\r\n/**\r\n * Function: createStyle\r\n * \r\n * Creates the optional style section.\r\n */\r\nmxSvgCanvas2D.prototype.createStyle = function(x)\r\n{\r\n\tvar style = this.createElement('style');\r\n\tstyle.setAttribute('type', 'text/css');\r\n\tmxUtils.write(style, 'svg{font-family:' + mxConstants.DEFAULT_FONTFAMILY +\r\n\t\t\t';font-size:' + mxConstants.DEFAULT_FONTSIZE +\r\n\t\t\t';fill:none;stroke-miterlimit:10}');\r\n\t\r\n\treturn style;\r\n};\r\n\r\n/**\r\n * Function: createElement\r\n * \r\n * Private helper function to create SVG elements\r\n */\r\nmxSvgCanvas2D.prototype.createElement = function(tagName, namespace)\r\n{\r\n\tif (this.root.ownerDocument.createElementNS != null)\r\n\t{\r\n\t\treturn this.root.ownerDocument.createElementNS(namespace || mxConstants.NS_SVG, tagName);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar elt = this.root.ownerDocument.createElement(tagName);\r\n\t\t\r\n\t\tif (namespace != null)\r\n\t\t{\r\n\t\t\telt.setAttribute('xmlns', namespace);\r\n\t\t}\r\n\t\t\r\n\t\treturn elt;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getAlternateContent\r\n * \r\n * Returns the alternate content for the given foreignObject.\r\n */\r\nmxSvgCanvas2D.prototype.createAlternateContent = function(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation)\r\n{\r\n\tif (this.foAltText != null)\r\n\t{\r\n\t\tvar s = this.state;\r\n\t\tvar alt = this.createElement('text');\r\n\t\talt.setAttribute('x', Math.round(w / 2));\r\n\t\talt.setAttribute('y', Math.round((h + s.fontSize) / 2));\r\n\t\talt.setAttribute('fill', s.fontColor || 'black');\r\n\t\talt.setAttribute('text-anchor', 'middle');\r\n\t\talt.setAttribute('font-size', s.fontSize + 'px');\r\n\t\t// Quotes are workaround for font name \"m+\"\r\n\t\talt.setAttribute('font-family', '\\'' + s.fontFamily + '\\'');\r\n\t\t\r\n\t\tif ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)\r\n\t\t{\r\n\t\t\talt.setAttribute('font-weight', 'bold');\r\n\t\t}\r\n\t\t\r\n\t\tif ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)\r\n\t\t{\r\n\t\t\talt.setAttribute('font-style', 'italic');\r\n\t\t}\r\n\t\t\r\n\t\tif ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)\r\n\t\t{\r\n\t\t\talt.setAttribute('text-decoration', 'underline');\r\n\t\t}\r\n\t\t\r\n\t\tmxUtils.write(alt, this.foAltText);\r\n\t\t\r\n\t\treturn alt;\r\n\t}\r\n\telse\r\n\t{\r\n\t\treturn null;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createGradientId\r\n * \r\n * Private helper function to create SVG elements\r\n */\r\nmxSvgCanvas2D.prototype.createGradientId = function(start, end, alpha1, alpha2, direction)\r\n{\r\n\t// Removes illegal characters from gradient ID\r\n\tif (start.charAt(0) == '#')\r\n\t{\r\n\t\tstart = start.substring(1);\r\n\t}\r\n\t\r\n\tif (end.charAt(0) == '#')\r\n\t{\r\n\t\tend = end.substring(1);\r\n\t}\r\n\t\r\n\t// Workaround for gradient IDs not working in Safari 5 / Chrome 6\r\n\t// if they contain uppercase characters\r\n\tstart = start.toLowerCase() + '-' + alpha1;\r\n\tend = end.toLowerCase() + '-' + alpha2;\r\n\r\n\t// Wrong gradient directions possible?\r\n\tvar dir = null;\r\n\t\r\n\tif (direction == null || direction == mxConstants.DIRECTION_SOUTH)\r\n\t{\r\n\t\tdir = 's';\r\n\t}\r\n\telse if (direction == mxConstants.DIRECTION_EAST)\r\n\t{\r\n\t\tdir = 'e';\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar tmp = start;\r\n\t\tstart = end;\r\n\t\tend = tmp;\r\n\t\t\r\n\t\tif (direction == mxConstants.DIRECTION_NORTH)\r\n\t\t{\r\n\t\t\tdir = 's';\r\n\t\t}\r\n\t\telse if (direction == mxConstants.DIRECTION_WEST)\r\n\t\t{\r\n\t\t\tdir = 'e';\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn 'mx-gradient-' + start + '-' + end + '-' + dir;\r\n};\r\n\r\n/**\r\n * Function: getSvgGradient\r\n * \r\n * Private helper function to create SVG elements\r\n */\r\nmxSvgCanvas2D.prototype.getSvgGradient = function(start, end, alpha1, alpha2, direction)\r\n{\r\n\tvar id = this.createGradientId(start, end, alpha1, alpha2, direction);\r\n\tvar gradient = this.gradients[id];\r\n\t\r\n\tif (gradient == null)\r\n\t{\r\n\t\tvar svg = this.root.ownerSVGElement;\r\n\r\n\t\tvar counter = 0;\r\n\t\tvar tmpId = id + '-' + counter;\r\n\r\n\t\tif (svg != null)\r\n\t\t{\r\n\t\t\tgradient = svg.ownerDocument.getElementById(tmpId);\r\n\t\t\t\r\n\t\t\twhile (gradient != null && gradient.ownerSVGElement != svg)\r\n\t\t\t{\r\n\t\t\t\ttmpId = id + '-' + counter++;\r\n\t\t\t\tgradient = svg.ownerDocument.getElementById(tmpId);\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Uses shorter IDs for export\r\n\t\t\ttmpId = 'id' + (++this.refCount);\r\n\t\t}\r\n\t\t\r\n\t\tif (gradient == null)\r\n\t\t{\r\n\t\t\tgradient = this.createSvgGradient(start, end, alpha1, alpha2, direction);\r\n\t\t\tgradient.setAttribute('id', tmpId);\r\n\t\t\t\r\n\t\t\tif (this.defs != null)\r\n\t\t\t{\r\n\t\t\t\tthis.defs.appendChild(gradient);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tsvg.appendChild(gradient);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.gradients[id] = gradient;\r\n\t}\r\n\r\n\treturn gradient.getAttribute('id');\r\n};\r\n\r\n/**\r\n * Function: createSvgGradient\r\n * \r\n * Creates the given SVG gradient.\r\n */\r\nmxSvgCanvas2D.prototype.createSvgGradient = function(start, end, alpha1, alpha2, direction)\r\n{\r\n\tvar gradient = this.createElement('linearGradient');\r\n\tgradient.setAttribute('x1', '0%');\r\n\tgradient.setAttribute('y1', '0%');\r\n\tgradient.setAttribute('x2', '0%');\r\n\tgradient.setAttribute('y2', '0%');\r\n\t\r\n\tif (direction == null || direction == mxConstants.DIRECTION_SOUTH)\r\n\t{\r\n\t\tgradient.setAttribute('y2', '100%');\r\n\t}\r\n\telse if (direction == mxConstants.DIRECTION_EAST)\r\n\t{\r\n\t\tgradient.setAttribute('x2', '100%');\r\n\t}\r\n\telse if (direction == mxConstants.DIRECTION_NORTH)\r\n\t{\r\n\t\tgradient.setAttribute('y1', '100%');\r\n\t}\r\n\telse if (direction == mxConstants.DIRECTION_WEST)\r\n\t{\r\n\t\tgradient.setAttribute('x1', '100%');\r\n\t}\r\n\t\r\n\tvar op = (alpha1 < 1) ? ';stop-opacity:' + alpha1 : '';\r\n\t\r\n\tvar stop = this.createElement('stop');\r\n\tstop.setAttribute('offset', '0%');\r\n\tstop.setAttribute('style', 'stop-color:' + start + op);\r\n\tgradient.appendChild(stop);\r\n\t\r\n\top = (alpha2 < 1) ? ';stop-opacity:' + alpha2 : '';\r\n\t\r\n\tstop = this.createElement('stop');\r\n\tstop.setAttribute('offset', '100%');\r\n\tstop.setAttribute('style', 'stop-color:' + end + op);\r\n\tgradient.appendChild(stop);\r\n\t\r\n\treturn gradient;\r\n};\r\n\r\n/**\r\n * Function: addNode\r\n * \r\n * Private helper function to create SVG elements\r\n */\r\nmxSvgCanvas2D.prototype.addNode = function(filled, stroked)\r\n{\r\n\tvar node = this.node;\r\n\tvar s = this.state;\r\n\r\n\tif (node != null)\r\n\t{\r\n\t\tif (node.nodeName == 'path')\r\n\t\t{\r\n\t\t\t// Checks if the path is not empty\r\n\t\t\tif (this.path != null && this.path.length > 0)\r\n\t\t\t{\r\n\t\t\t\tnode.setAttribute('d', this.path.join(' '));\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (filled && s.fillColor != null)\r\n\t\t{\r\n\t\t\tthis.updateFill();\r\n\t\t}\r\n\t\telse if (!this.styleEnabled)\r\n\t\t{\r\n\t\t\t// Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=814952\r\n\t\t\tif (node.nodeName == 'ellipse' && mxClient.IS_FF)\r\n\t\t\t{\r\n\t\t\t\tnode.setAttribute('fill', 'transparent');\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tnode.setAttribute('fill', 'none');\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Sets the actual filled state for stroke tolerance\r\n\t\t\tfilled = false;\r\n\t\t}\r\n\t\t\r\n\t\tif (stroked && s.strokeColor != null)\r\n\t\t{\r\n\t\t\tthis.updateStroke();\r\n\t\t}\r\n\t\telse if (!this.styleEnabled)\r\n\t\t{\r\n\t\t\tnode.setAttribute('stroke', 'none');\r\n\t\t}\r\n\t\t\r\n\t\tif (s.transform != null && s.transform.length > 0)\r\n\t\t{\r\n\t\t\tnode.setAttribute('transform', s.transform);\r\n\t\t}\r\n\t\t\r\n\t\tif (s.shadow)\r\n\t\t{\r\n\t\t\tthis.root.appendChild(this.createShadow(node));\r\n\t\t}\r\n\t\r\n\t\t// Adds stroke tolerance\r\n\t\tif (this.strokeTolerance > 0 && !filled)\r\n\t\t{\r\n\t\t\tthis.root.appendChild(this.createTolerance(node));\r\n\t\t}\r\n\r\n\t\t// Adds pointer events\r\n\t\tif (this.pointerEvents)\r\n\t\t{\r\n\t\t\tnode.setAttribute('pointer-events', this.pointerEventsValue);\r\n\t\t}\r\n\t\t// Enables clicks for nodes inside a link element\r\n\t\telse if (!this.pointerEvents && this.originalRoot == null)\r\n\t\t{\r\n\t\t\tnode.setAttribute('pointer-events', 'none');\r\n\t\t}\r\n\t\t\r\n\t\t// Removes invisible nodes from output if they don't handle events\r\n\t\tif ((node.nodeName != 'rect' && node.nodeName != 'path' && node.nodeName != 'ellipse') ||\r\n\t\t\t(node.getAttribute('fill') != 'none' && node.getAttribute('fill') != 'transparent') ||\r\n\t\t\tnode.getAttribute('stroke') != 'none' || node.getAttribute('pointer-events') != 'none')\r\n\t\t{\r\n\t\t\t// LATER: Update existing DOM for performance\t\t\r\n\t\t\tthis.root.appendChild(node);\r\n\t\t}\r\n\t\t\r\n\t\tthis.node = null;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: updateFill\r\n * \r\n * Transfers the stroke attributes from <state> to <node>.\r\n */\r\nmxSvgCanvas2D.prototype.updateFill = function()\r\n{\r\n\tvar s = this.state;\r\n\t\r\n\tif (s.alpha < 1 || s.fillAlpha < 1)\r\n\t{\r\n\t\tthis.node.setAttribute('fill-opacity', s.alpha * s.fillAlpha);\r\n\t}\r\n\t\r\n\tif (s.fillColor != null)\r\n\t{\r\n\t\tif (s.gradientColor != null)\r\n\t\t{\r\n\t\t\tvar id = this.getSvgGradient(String(s.fillColor), String(s.gradientColor),\r\n\t\t\t\ts.gradientFillAlpha, s.gradientAlpha, s.gradientDirection);\r\n\t\t\t\r\n\t\t\tif (!mxClient.IS_CHROME_APP && !mxClient.IS_IE && !mxClient.IS_IE11 &&\r\n\t\t\t\t!mxClient.IS_EDGE && this.root.ownerDocument == document)\r\n\t\t\t{\r\n\t\t\t\t// Workaround for potential base tag and brackets must be escaped\r\n\t\t\t\tvar base = this.getBaseUrl().replace(/([\\(\\)])/g, '\\\\$1');\r\n\t\t\t\tthis.node.setAttribute('fill', 'url(' + base + '#' + id + ')');\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.node.setAttribute('fill', 'url(#' + id + ')');\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.node.setAttribute('fill', String(s.fillColor).toLowerCase());\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getCurrentStrokeWidth\r\n * \r\n * Returns the current stroke width (>= 1), ie. max(1, this.format(this.state.strokeWidth * this.state.scale)).\r\n */\r\nmxSvgCanvas2D.prototype.getCurrentStrokeWidth = function()\r\n{\r\n\treturn Math.max(this.minStrokeWidth, Math.max(0.01, this.format(this.state.strokeWidth * this.state.scale)));\r\n};\r\n\r\n/**\r\n * Function: updateStroke\r\n * \r\n * Transfers the stroke attributes from <state> to <node>.\r\n */\r\nmxSvgCanvas2D.prototype.updateStroke = function()\r\n{\r\n\tvar s = this.state;\r\n\r\n\tthis.node.setAttribute('stroke', String(s.strokeColor).toLowerCase());\r\n\t\r\n\tif (s.alpha < 1 || s.strokeAlpha < 1)\r\n\t{\r\n\t\tthis.node.setAttribute('stroke-opacity', s.alpha * s.strokeAlpha);\r\n\t}\r\n\t\r\n\tvar sw = this.getCurrentStrokeWidth();\r\n\t\r\n\tif (sw != 1)\r\n\t{\r\n\t\tthis.node.setAttribute('stroke-width', sw);\r\n\t}\r\n\t\r\n\tif (this.node.nodeName == 'path')\r\n\t{\r\n\t\tthis.updateStrokeAttributes();\r\n\t}\r\n\t\r\n\tif (s.dashed)\r\n\t{\r\n\t\tthis.node.setAttribute('stroke-dasharray', this.createDashPattern(\r\n\t\t\t((s.fixDash) ? 1 : s.strokeWidth) * s.scale));\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: updateStrokeAttributes\r\n * \r\n * Transfers the stroke attributes from <state> to <node>.\r\n */\r\nmxSvgCanvas2D.prototype.updateStrokeAttributes = function()\r\n{\r\n\tvar s = this.state;\r\n\t\r\n\t// Linejoin miter is default in SVG\r\n\tif (s.lineJoin != null && s.lineJoin != 'miter')\r\n\t{\r\n\t\tthis.node.setAttribute('stroke-linejoin', s.lineJoin);\r\n\t}\r\n\t\r\n\tif (s.lineCap != null)\r\n\t{\r\n\t\t// flat is called butt in SVG\r\n\t\tvar value = s.lineCap;\r\n\t\t\r\n\t\tif (value == 'flat')\r\n\t\t{\r\n\t\t\tvalue = 'butt';\r\n\t\t}\r\n\t\t\r\n\t\t// Linecap butt is default in SVG\r\n\t\tif (value != 'butt')\r\n\t\t{\r\n\t\t\tthis.node.setAttribute('stroke-linecap', value);\r\n\t\t}\r\n\t}\r\n\t\r\n\t// Miterlimit 10 is default in our document\r\n\tif (s.miterLimit != null && (!this.styleEnabled || s.miterLimit != 10))\r\n\t{\r\n\t\tthis.node.setAttribute('stroke-miterlimit', s.miterLimit);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createDashPattern\r\n * \r\n * Creates the SVG dash pattern for the given state.\r\n */\r\nmxSvgCanvas2D.prototype.createDashPattern = function(scale)\r\n{\r\n\tvar pat = [];\r\n\t\r\n\tif (typeof(this.state.dashPattern) === 'string')\r\n\t{\r\n\t\tvar dash = this.state.dashPattern.split(' ');\r\n\t\t\r\n\t\tif (dash.length > 0)\r\n\t\t{\r\n\t\t\tfor (var i = 0; i < dash.length; i++)\r\n\t\t\t{\r\n\t\t\t\tpat[i] = Number(dash[i]) * scale;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn pat.join(' ');\r\n};\r\n\r\n/**\r\n * Function: createTolerance\r\n * \r\n * Creates a hit detection tolerance shape for the given node.\r\n */\r\nmxSvgCanvas2D.prototype.createTolerance = function(node)\r\n{\r\n\tvar tol = node.cloneNode(true);\r\n\tvar sw = parseFloat(tol.getAttribute('stroke-width') || 1) + this.strokeTolerance;\r\n\ttol.setAttribute('pointer-events', 'stroke');\r\n\ttol.setAttribute('visibility', 'hidden');\r\n\ttol.removeAttribute('stroke-dasharray');\r\n\ttol.setAttribute('stroke-width', sw);\r\n\ttol.setAttribute('fill', 'none');\r\n\t\r\n\t// Workaround for Opera ignoring the visiblity attribute above while\r\n\t// other browsers need a stroke color to perform the hit-detection but\r\n\t// do not ignore the visibility attribute. Side-effect is that Opera's\r\n\t// hit detection for horizontal/vertical edges seems to ignore the tol.\r\n\ttol.setAttribute('stroke', (mxClient.IS_OT) ? 'none' : 'white');\r\n\t\r\n\treturn tol;\r\n};\r\n\r\n/**\r\n * Function: createShadow\r\n * \r\n * Creates a shadow for the given node.\r\n */\r\nmxSvgCanvas2D.prototype.createShadow = function(node)\r\n{\r\n\tvar shadow = node.cloneNode(true);\r\n\tvar s = this.state;\r\n\r\n\t// Firefox uses transparent for no fill in ellipses\r\n\tif (shadow.getAttribute('fill') != 'none' && (!mxClient.IS_FF || shadow.getAttribute('fill') != 'transparent'))\r\n\t{\r\n\t\tshadow.setAttribute('fill', s.shadowColor);\r\n\t}\r\n\t\r\n\tif (shadow.getAttribute('stroke') != 'none')\r\n\t{\r\n\t\tshadow.setAttribute('stroke', s.shadowColor);\r\n\t}\r\n\r\n\tshadow.setAttribute('transform', 'translate(' + this.format(s.shadowDx * s.scale) +\r\n\t\t',' + this.format(s.shadowDy * s.scale) + ')' + (s.transform || ''));\r\n\tshadow.setAttribute('opacity', s.shadowAlpha);\r\n\t\r\n\treturn shadow;\r\n};\r\n\r\n/**\r\n * Function: setLink\r\n * \r\n * Experimental implementation for hyperlinks.\r\n */\r\nmxSvgCanvas2D.prototype.setLink = function(link)\r\n{\r\n\tif (link == null)\r\n\t{\r\n\t\tthis.root = this.originalRoot;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.originalRoot = this.root;\r\n\t\t\r\n\t\tvar node = this.createElement('a');\r\n\t\t\r\n\t\t// Workaround for implicit namespace handling in HTML5 export, IE adds NS1 namespace so use code below\r\n\t\t// in all IE versions except quirks mode. KNOWN: Adds xlink namespace to each image tag in output.\r\n\t\tif (node.setAttributeNS == null || (this.root.ownerDocument != document && document.documentMode == null))\r\n\t\t{\r\n\t\t\tnode.setAttribute('xlink:href', link);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tnode.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', link);\r\n\t\t}\r\n\t\t\r\n\t\tthis.root.appendChild(node);\r\n\t\tthis.root = node;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: rotate\r\n * \r\n * Sets the rotation of the canvas. Note that rotation cannot be concatenated.\r\n */\r\nmxSvgCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)\r\n{\r\n\tif (theta != 0 || flipH || flipV)\r\n\t{\r\n\t\tvar s = this.state;\r\n\t\tcx += s.dx;\r\n\t\tcy += s.dy;\r\n\t\r\n\t\tcx *= s.scale;\r\n\t\tcy *= s.scale;\r\n\r\n\t\ts.transform = s.transform || '';\r\n\t\t\r\n\t\t// This implementation uses custom scale/translate and built-in rotation\r\n\t\t// Rotation state is part of the AffineTransform in state.transform\r\n\t\tif (flipH && flipV)\r\n\t\t{\r\n\t\t\ttheta += 180;\r\n\t\t}\r\n\t\telse if (flipH != flipV)\r\n\t\t{\r\n\t\t\tvar tx = (flipH) ? cx : 0;\r\n\t\t\tvar sx = (flipH) ? -1 : 1;\r\n\t\r\n\t\t\tvar ty = (flipV) ? cy : 0;\r\n\t\t\tvar sy = (flipV) ? -1 : 1;\r\n\r\n\t\t\ts.transform += 'translate(' + this.format(tx) + ',' + this.format(ty) + ')' +\r\n\t\t\t\t'scale(' + this.format(sx) + ',' + this.format(sy) + ')' +\r\n\t\t\t\t'translate(' + this.format(-tx) + ',' + this.format(-ty) + ')';\r\n\t\t}\r\n\t\t\r\n\t\tif (flipH ? !flipV : flipV)\r\n\t\t{\r\n\t\t\ttheta *= -1;\r\n\t\t}\r\n\t\t\r\n\t\tif (theta != 0)\r\n\t\t{\r\n\t\t\ts.transform += 'rotate(' + this.format(theta) + ',' + this.format(cx) + ',' + this.format(cy) + ')';\r\n\t\t}\r\n\t\t\r\n\t\ts.rotation = s.rotation + theta;\r\n\t\ts.rotationCx = cx;\r\n\t\ts.rotationCy = cy;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: begin\r\n * \r\n * Extends superclass to create path.\r\n */\r\nmxSvgCanvas2D.prototype.begin = function()\r\n{\r\n\tmxAbstractCanvas2D.prototype.begin.apply(this, arguments);\r\n\tthis.node = this.createElement('path');\r\n};\r\n\r\n/**\r\n * Function: rect\r\n * \r\n * Private helper function to create SVG elements\r\n */\r\nmxSvgCanvas2D.prototype.rect = function(x, y, w, h)\r\n{\r\n\tvar s = this.state;\r\n\tvar n = this.createElement('rect');\r\n\tn.setAttribute('x', this.format((x + s.dx) * s.scale));\r\n\tn.setAttribute('y', this.format((y + s.dy) * s.scale));\r\n\tn.setAttribute('width', this.format(w * s.scale));\r\n\tn.setAttribute('height', this.format(h * s.scale));\r\n\t\r\n\tthis.node = n;\r\n};\r\n\r\n/**\r\n * Function: roundrect\r\n * \r\n * Private helper function to create SVG elements\r\n */\r\nmxSvgCanvas2D.prototype.roundrect = function(x, y, w, h, dx, dy)\r\n{\r\n\tthis.rect(x, y, w, h);\r\n\t\r\n\tif (dx > 0)\r\n\t{\r\n\t\tthis.node.setAttribute('rx', this.format(dx * this.state.scale));\r\n\t}\r\n\t\r\n\tif (dy > 0)\r\n\t{\r\n\t\tthis.node.setAttribute('ry', this.format(dy * this.state.scale));\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: ellipse\r\n * \r\n * Private helper function to create SVG elements\r\n */\r\nmxSvgCanvas2D.prototype.ellipse = function(x, y, w, h)\r\n{\r\n\tvar s = this.state;\r\n\tvar n = this.createElement('ellipse');\r\n\t// No rounding for consistent output with 1.x\r\n\tn.setAttribute('cx', Math.round((x + w / 2 + s.dx) * s.scale));\r\n\tn.setAttribute('cy', Math.round((y + h / 2 + s.dy) * s.scale));\r\n\tn.setAttribute('rx', w / 2 * s.scale);\r\n\tn.setAttribute('ry', h / 2 * s.scale);\r\n\tthis.node = n;\r\n};\r\n\r\n/**\r\n * Function: image\r\n * \r\n * Private helper function to create SVG elements\r\n */\r\nmxSvgCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)\r\n{\r\n\tsrc = this.converter.convert(src);\r\n\t\r\n\t// LATER: Add option for embedding images as base64.\r\n\taspect = (aspect != null) ? aspect : true;\r\n\tflipH = (flipH != null) ? flipH : false;\r\n\tflipV = (flipV != null) ? flipV : false;\r\n\t\r\n\tvar s = this.state;\r\n\tx += s.dx;\r\n\ty += s.dy;\r\n\t\r\n\tvar node = this.createElement('image');\r\n\tnode.setAttribute('x', this.format(x * s.scale) + this.imageOffset);\r\n\tnode.setAttribute('y', this.format(y * s.scale) + this.imageOffset);\r\n\tnode.setAttribute('width', this.format(w * s.scale));\r\n\tnode.setAttribute('height', this.format(h * s.scale));\r\n\t\r\n\t// Workaround for missing namespace support\r\n\tif (node.setAttributeNS == null)\r\n\t{\r\n\t\tnode.setAttribute('xlink:href', src);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tnode.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', src);\r\n\t}\r\n\t\r\n\tif (!aspect)\r\n\t{\r\n\t\tnode.setAttribute('preserveAspectRatio', 'none');\r\n\t}\r\n\r\n\tif (s.alpha < 1 || s.fillAlpha < 1)\r\n\t{\r\n\t\tnode.setAttribute('opacity', s.alpha * s.fillAlpha);\r\n\t}\r\n\t\r\n\tvar tr = this.state.transform || '';\r\n\t\r\n\tif (flipH || flipV)\r\n\t{\r\n\t\tvar sx = 1;\r\n\t\tvar sy = 1;\r\n\t\tvar dx = 0;\r\n\t\tvar dy = 0;\r\n\t\t\r\n\t\tif (flipH)\r\n\t\t{\r\n\t\t\tsx = -1;\r\n\t\t\tdx = -w - 2 * x;\r\n\t\t}\r\n\t\t\r\n\t\tif (flipV)\r\n\t\t{\r\n\t\t\tsy = -1;\r\n\t\t\tdy = -h - 2 * y;\r\n\t\t}\r\n\t\t\r\n\t\t// Adds image tansformation to existing transform\r\n\t\ttr += 'scale(' + sx + ',' + sy + ')translate(' + (dx * s.scale) + ',' + (dy * s.scale) + ')';\r\n\t}\r\n\r\n\tif (tr.length > 0)\r\n\t{\r\n\t\tnode.setAttribute('transform', tr);\r\n\t}\r\n\t\r\n\tif (!this.pointerEvents)\r\n\t{\r\n\t\tnode.setAttribute('pointer-events', 'none');\r\n\t}\r\n\t\r\n\tthis.root.appendChild(node);\r\n\t\r\n\t// Disables control-clicks on images in Firefox to open in new tab\r\n\t// by putting a rect in the foreground that absorbs all events and\r\n\t// disabling all pointer-events on the original image tag.\r\n\tif (this.blockImagePointerEvents)\r\n\t{\r\n\t\tnode.setAttribute('style', 'pointer-events:none');\r\n\t\t\r\n\t\tnode = this.createElement('rect');\r\n\t\tnode.setAttribute('visibility', 'hidden');\r\n\t\tnode.setAttribute('pointer-events', 'fill');\r\n\t\tnode.setAttribute('x', this.format(x * s.scale));\r\n\t\tnode.setAttribute('y', this.format(y * s.scale));\r\n\t\tnode.setAttribute('width', this.format(w * s.scale));\r\n\t\tnode.setAttribute('height', this.format(h * s.scale));\r\n\t\tthis.root.appendChild(node);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: convertHtml\r\n * \r\n * Converts the given HTML string to XHTML.\r\n */\r\nmxSvgCanvas2D.prototype.convertHtml = function(val)\r\n{\r\n\tif (this.useDomParser)\r\n\t{\r\n\t\tvar doc = new DOMParser().parseFromString(val, 'text/html');\r\n\r\n\t\tif (doc != null)\r\n\t\t{\r\n\t\t\tval = new XMLSerializer().serializeToString(doc.body);\r\n\t\t\t\r\n\t\t\t// Extracts body content from DOM\r\n\t\t\tif (val.substring(0, 5) == '<body')\r\n\t\t\t{\r\n\t\t\t\tval = val.substring(val.indexOf('>', 5) + 1);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (val.substring(val.length - 7, val.length) == '</body>')\r\n\t\t\t{\r\n\t\t\t\tval = val.substring(0, val.length - 7);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\telse if (document.implementation != null && document.implementation.createDocument != null)\r\n\t{\r\n\t\tvar xd = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null);\r\n\t\tvar xb = xd.createElement('body');\r\n\t\txd.documentElement.appendChild(xb);\r\n\t\t\r\n\t\tvar div = document.createElement('div');\r\n\t\tdiv.innerHTML = val;\r\n\t\tvar child = div.firstChild;\r\n\t\t\r\n\t\twhile (child != null)\r\n\t\t{\r\n\t\t\tvar next = child.nextSibling;\r\n\t\t\txb.appendChild(xd.adoptNode(child));\r\n\t\t\tchild = next;\r\n\t\t}\r\n\t\t\r\n\t\treturn xb.innerHTML;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar ta = document.createElement('textarea');\r\n\t\t\r\n\t\t// Handles special HTML entities < and > and double escaping\r\n\t\t// and converts unclosed br, hr and img tags to XHTML\r\n\t\t// LATER: Convert all unclosed tags\r\n\t\tta.innerHTML = val.replace(/&amp;/g, '&amp;amp;').\r\n\t\t\treplace(/&#60;/g, '&amp;lt;').replace(/&#62;/g, '&amp;gt;').\r\n\t\t\treplace(/&lt;/g, '&amp;lt;').replace(/&gt;/g, '&amp;gt;').\r\n\t\t\treplace(/</g, '&lt;').replace(/>/g, '&gt;');\r\n\t\tval = ta.value.replace(/&/g, '&amp;').replace(/&amp;lt;/g, '&lt;').\r\n\t\t\treplace(/&amp;gt;/g, '&gt;').replace(/&amp;amp;/g, '&amp;').\r\n\t\t\treplace(/<br>/g, '<br />').replace(/<hr>/g, '<hr />').\r\n\t\t\treplace(/(<img[^>]+)>/gm, \"$1 />\");\r\n\t}\r\n\t\r\n\treturn val;\r\n};\r\n\r\n/**\r\n * Function: createDiv\r\n * \r\n * Private helper function to create SVG elements\r\n */\r\nmxSvgCanvas2D.prototype.createDiv = function(str, align, valign, style, overflow, whiteSpace)\r\n{\r\n\tvar s = this.state;\r\n\r\n\t// Inline block for rendering HTML background over SVG in Safari\r\n\tvar lh = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (s.fontSize * mxConstants.LINE_HEIGHT) + 'px' :\r\n\t\t(mxConstants.LINE_HEIGHT * this.lineHeightCorrection);\r\n\t\r\n\t// Quotes are workaround for font name \"m+\"\r\n\tstyle = 'display:inline-block;font-size:' + s.fontSize + 'px;font-family:\"' + s.fontFamily +\r\n\t\t'\";color:' + s.fontColor + ';line-height:' + lh + ';' + style;\r\n\r\n\tif ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)\r\n\t{\r\n\t\tstyle += 'font-weight:bold;';\r\n\t}\r\n\r\n\tif ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)\r\n\t{\r\n\t\tstyle += 'font-style:italic;';\r\n\t}\r\n\t\r\n\tif ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)\r\n\t{\r\n\t\tstyle += 'text-decoration:underline;';\r\n\t}\r\n\t\r\n\tif (align == mxConstants.ALIGN_CENTER)\r\n\t{\r\n\t\tstyle += 'text-align:center;';\r\n\t}\r\n\telse if (align == mxConstants.ALIGN_RIGHT)\r\n\t{\r\n\t\tstyle += 'text-align:right;';\r\n\t}\r\n\r\n\tvar css = '';\r\n\t\r\n\tif (s.fontBackgroundColor != null)\r\n\t{\r\n\t\tcss += 'background-color:' + s.fontBackgroundColor + ';';\r\n\t}\r\n\t\r\n\tif (s.fontBorderColor != null)\r\n\t{\r\n\t\tcss += 'border:1px solid ' + s.fontBorderColor + ';';\r\n\t}\r\n\r\n\tvar val = str;\r\n\t\r\n\tif (!mxUtils.isNode(val))\r\n\t{\r\n\t\tval = this.convertHtml(val);\r\n\t\t\r\n\t\tif (overflow != 'fill' && overflow != 'width')\r\n\t\t{\r\n\t\t\t// Workaround for no wrapping in HTML canvas for image\r\n\t\t\t// export if the inner HTML contains a DIV with width\r\n\t\t\tif (whiteSpace != null)\r\n\t\t\t{\r\n\t\t\t\tcss += 'white-space:' + whiteSpace + ';';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Inner div always needed to measure wrapped text\r\n\t\t\tval = '<div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"display:inline-block;text-align:inherit;text-decoration:inherit;' + css + '\">' + val + '</div>';\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tstyle += css;\r\n\t\t}\r\n\t}\r\n\r\n\t// Uses DOM API where available. This cannot be used in IE to avoid\r\n\t// an opening and two (!) closing TBODY tags being added to tables.\r\n\tif (!mxClient.IS_IE && document.createElementNS)\r\n\t{\r\n\t\tvar div = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');\r\n\t\tdiv.setAttribute('style', style);\r\n\t\t\r\n\t\tif (mxUtils.isNode(val))\r\n\t\t{\r\n\t\t\t// Creates a copy for export\r\n\t\t\tif (this.root.ownerDocument != document)\r\n\t\t\t{\r\n\t\t\t\tdiv.appendChild(val.cloneNode(true));\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tdiv.appendChild(val);\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tdiv.innerHTML = val;\r\n\t\t}\r\n\t\t\r\n\t\treturn div;\r\n\t}\r\n\telse\r\n\t{\r\n\t\t// Serializes for export\r\n\t\tif (mxUtils.isNode(val) && this.root.ownerDocument != document)\r\n\t\t{\r\n\t\t\tval = val.outerHTML;\r\n\t\t}\r\n\r\n\t\t// NOTE: FF 3.6 crashes if content CSS contains \"height:100%\"\r\n\t\treturn mxUtils.parseXml('<div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"' + style + \r\n\t\t\t'\">' + val + '</div>').documentElement;\r\n\t}\r\n};\r\n\r\n/**\r\n * Invalidates the cached offset size for the given node.\r\n */\r\nmxSvgCanvas2D.prototype.invalidateCachedOffsetSize = function(node)\r\n{\r\n\tdelete node.firstChild.mxCachedOffsetWidth;\r\n\tdelete node.firstChild.mxCachedFinalOffsetWidth;\r\n\tdelete node.firstChild.mxCachedFinalOffsetHeight;\r\n};\r\n\r\n/**\r\n * Updates existing DOM nodes for text rendering. LATER: Merge common parts with text function below.\r\n */\r\nmxSvgCanvas2D.prototype.updateText = function(x, y, w, h, align, valign, wrap, overflow, clip, rotation, node)\r\n{\r\n\tif (node != null && node.firstChild != null && node.firstChild.firstChild != null &&\r\n\t\tnode.firstChild.firstChild.firstChild != null)\r\n\t{\r\n\t\t// Uses outer group for opacity and transforms to\r\n\t\t// fix rendering order in Chrome\r\n\t\tvar group = node.firstChild;\r\n\t\tvar fo = group.firstChild;\r\n\t\tvar div = fo.firstChild;\r\n\r\n\t\trotation = (rotation != null) ? rotation : 0;\r\n\t\t\r\n\t\tvar s = this.state;\r\n\t\tx += s.dx;\r\n\t\ty += s.dy;\r\n\t\t\r\n\t\tif (clip)\r\n\t\t{\r\n\t\t\tdiv.style.maxHeight = Math.round(h) + 'px';\r\n\t\t\tdiv.style.maxWidth = Math.round(w) + 'px';\r\n\t\t}\r\n\t\telse if (overflow == 'fill')\r\n\t\t{\r\n\t\t\tdiv.style.width = Math.round(w + 1) + 'px';\r\n\t\t\tdiv.style.height = Math.round(h + 1) + 'px';\r\n\t\t}\r\n\t\telse if (overflow == 'width')\r\n\t\t{\r\n\t\t\tdiv.style.width = Math.round(w + 1) + 'px';\r\n\t\t\t\r\n\t\t\tif (h > 0)\r\n\t\t\t{\r\n\t\t\t\tdiv.style.maxHeight = Math.round(h) + 'px';\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (wrap && w > 0)\r\n\t\t{\r\n\t\t\tdiv.style.width = Math.round(w + 1) + 'px';\r\n\t\t}\r\n\t\t\r\n\t\t// Code that depends on the size which is computed after\r\n\t\t// the element was added to the DOM.\r\n\t\tvar ow = 0;\r\n\t\tvar oh = 0;\r\n\t\t\r\n\t\t// Padding avoids clipping on border and wrapping for differing font metrics on platforms\r\n\t\tvar padX = 0;\r\n\t\tvar padY = 2;\r\n\r\n\t\tvar sizeDiv = div;\r\n\t\t\r\n\t\tif (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')\r\n\t\t{\r\n\t\t\tsizeDiv = sizeDiv.firstChild;\r\n\t\t}\r\n\t\t\r\n\t\tvar tmp = (group.mxCachedOffsetWidth != null) ? group.mxCachedOffsetWidth : sizeDiv.offsetWidth;\r\n\t\tow = tmp + padX;\r\n\r\n\t\t// Recomputes the height of the element for wrapped width\r\n\t\tif (wrap && overflow != 'fill')\r\n\t\t{\r\n\t\t\tif (clip)\r\n\t\t\t{\r\n\t\t\t\tow = Math.min(ow, w);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tdiv.style.width = Math.round(ow + 1) + 'px';\r\n\t\t}\r\n\r\n\t\tow = (group.mxCachedFinalOffsetWidth != null) ? group.mxCachedFinalOffsetWidth : sizeDiv.offsetWidth;\r\n\t\toh = (group.mxCachedFinalOffsetHeight != null) ? group.mxCachedFinalOffsetHeight : sizeDiv.offsetHeight;\r\n\t\t\r\n\t\tif (this.cacheOffsetSize)\r\n\t\t{\r\n\t\t\tgroup.mxCachedOffsetWidth = tmp;\r\n\t\t\tgroup.mxCachedFinalOffsetWidth = ow;\r\n\t\t\tgroup.mxCachedFinalOffsetHeight = oh;\r\n\t\t}\r\n\t\t\r\n\t\tow += padX;\r\n\t\toh -= 2;\r\n\t\t\r\n\t\tif (clip)\r\n\t\t{\r\n\t\t\toh = Math.min(oh, h);\r\n\t\t\tow = Math.min(ow, w);\r\n\t\t}\r\n\r\n\t\tif (overflow == 'width')\r\n\t\t{\r\n\t\t\th = oh;\r\n\t\t}\r\n\t\telse if (overflow != 'fill')\r\n\t\t{\r\n\t\t\tw = ow;\r\n\t\t\th = oh;\r\n\t\t}\r\n\r\n\t\tvar dx = 0;\r\n\t\tvar dy = 0;\r\n\r\n\t\tif (align == mxConstants.ALIGN_CENTER)\r\n\t\t{\r\n\t\t\tdx -= w / 2;\r\n\t\t}\r\n\t\telse if (align == mxConstants.ALIGN_RIGHT)\r\n\t\t{\r\n\t\t\tdx -= w;\r\n\t\t}\r\n\t\t\r\n\t\tx += dx;\r\n\t\t\r\n\t\t// FIXME: LINE_HEIGHT not ideal for all text sizes, fix for export\r\n\t\tif (valign == mxConstants.ALIGN_MIDDLE)\r\n\t\t{\r\n\t\t\tdy -= h / 2;\r\n\t\t}\r\n\t\telse if (valign == mxConstants.ALIGN_BOTTOM)\r\n\t\t{\r\n\t\t\tdy -= h;\r\n\t\t}\r\n\t\t\r\n\t\t// Workaround for rendering offsets\r\n\t\t// TODO: Check if export needs these fixes, too\r\n\t\tif (overflow != 'fill' && mxClient.IS_FF && mxClient.IS_WIN)\r\n\t\t{\r\n\t\t\tdy -= 2;\r\n\t\t}\r\n\t\t\r\n\t\ty += dy;\r\n\r\n\t\tvar tr = (s.scale != 1) ? 'scale(' + s.scale + ')' : '';\r\n\r\n\t\tif (s.rotation != 0 && this.rotateHtml)\r\n\t\t{\r\n\t\t\ttr += 'rotate(' + (s.rotation) + ',' + (w / 2) + ',' + (h / 2) + ')';\r\n\t\t\tvar pt = this.rotatePoint((x + w / 2) * s.scale, (y + h / 2) * s.scale,\r\n\t\t\t\ts.rotation, s.rotationCx, s.rotationCy);\r\n\t\t\tx = pt.x - w * s.scale / 2;\r\n\t\t\ty = pt.y - h * s.scale / 2;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tx *= s.scale;\r\n\t\t\ty *= s.scale;\r\n\t\t}\r\n\r\n\t\tif (rotation != 0)\r\n\t\t{\r\n\t\t\ttr += 'rotate(' + (rotation) + ',' + (-dx) + ',' + (-dy) + ')';\r\n\t\t}\r\n\r\n\t\tgroup.setAttribute('transform', 'translate(' + Math.round(x) + ',' + Math.round(y) + ')' + tr);\r\n\t\tfo.setAttribute('width', Math.round(Math.max(1, w)));\r\n\t\tfo.setAttribute('height', Math.round(Math.max(1, h)));\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: text\r\n * \r\n * Paints the given text. Possible values for format are empty string for plain\r\n * text and html for HTML markup. Note that HTML markup is only supported if\r\n * foreignObject is supported and <foEnabled> is true. (This means IE9 and later\r\n * does currently not support HTML text as part of shapes.)\r\n */\r\nmxSvgCanvas2D.prototype.text = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)\r\n{\r\n\tif (this.textEnabled && str != null)\r\n\t{\r\n\t\trotation = (rotation != null) ? rotation : 0;\r\n\t\t\r\n\t\tvar s = this.state;\r\n\t\tx += s.dx;\r\n\t\ty += s.dy;\r\n\t\t\r\n\t\tif (this.foEnabled && format == 'html')\r\n\t\t{\r\n\t\t\tvar style = 'vertical-align:top;';\r\n\t\t\t\r\n\t\t\tif (clip)\r\n\t\t\t{\r\n\t\t\t\tstyle += 'overflow:hidden;max-height:' + Math.round(h) + 'px;max-width:' + Math.round(w) + 'px;';\r\n\t\t\t}\r\n\t\t\telse if (overflow == 'fill')\r\n\t\t\t{\r\n\t\t\t\tstyle += 'width:' + Math.round(w + 1) + 'px;height:' + Math.round(h + 1) + 'px;overflow:hidden;';\r\n\t\t\t}\r\n\t\t\telse if (overflow == 'width')\r\n\t\t\t{\r\n\t\t\t\tstyle += 'width:' + Math.round(w + 1) + 'px;';\r\n\t\t\t\t\r\n\t\t\t\tif (h > 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tstyle += 'max-height:' + Math.round(h) + 'px;overflow:hidden;';\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (wrap && w > 0)\r\n\t\t\t{\r\n\t\t\t\tstyle += 'width:' + Math.round(w + 1) + 'px;white-space:normal;word-wrap:' +\r\n\t\t\t\t\tmxConstants.WORD_WRAP + ';';\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tstyle += 'white-space:nowrap;';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Uses outer group for opacity and transforms to\r\n\t\t\t// fix rendering order in Chrome\r\n\t\t\tvar group = this.createElement('g');\r\n\t\t\t\r\n\t\t\tif (s.alpha < 1)\r\n\t\t\t{\r\n\t\t\t\tgroup.setAttribute('opacity', s.alpha);\r\n\t\t\t}\r\n\r\n\t\t\tvar fo = this.createElement('foreignObject');\r\n\t\t\tfo.setAttribute('style', 'overflow:visible;');\r\n\t\t\tfo.setAttribute('pointer-events', 'all');\r\n\t\t\t\r\n\t\t\tvar div = this.createDiv(str, align, valign, style, overflow, (wrap && w > 0) ? 'normal' : null);\r\n\t\t\t\r\n\t\t\t// Ignores invalid XHTML labels\r\n\t\t\tif (div == null)\r\n\t\t\t{\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\telse if (dir != null)\r\n\t\t\t{\r\n\t\t\t\tdiv.setAttribute('dir', dir);\r\n\t\t\t}\r\n\r\n\t\t\tgroup.appendChild(fo);\r\n\t\t\tthis.root.appendChild(group);\r\n\t\t\t\r\n\t\t\t// Code that depends on the size which is computed after\r\n\t\t\t// the element was added to the DOM.\r\n\t\t\tvar ow = 0;\r\n\t\t\tvar oh = 0;\r\n\t\t\t\r\n\t\t\t// Padding avoids clipping on border and wrapping for differing font metrics on platforms\r\n\t\t\tvar padX = 2;\r\n\t\t\tvar padY = 2;\r\n\r\n\t\t\t// NOTE: IE is always export as it does not support foreign objects\r\n\t\t\tif (mxClient.IS_IE && (document.documentMode == 9 || !mxClient.IS_SVG))\r\n\t\t\t{\r\n\t\t\t\t// Handles non-standard namespace for getting size in IE\r\n\t\t\t\tvar clone = document.createElement('div');\r\n\t\t\t\t\r\n\t\t\t\tclone.style.cssText = div.getAttribute('style');\r\n\t\t\t\tclone.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';\r\n\t\t\t\tclone.style.position = 'absolute';\r\n\t\t\t\tclone.style.visibility = 'hidden';\r\n\r\n\t\t\t\t// Inner DIV is needed for text measuring\r\n\t\t\t\tvar div2 = document.createElement('div');\r\n\t\t\t\tdiv2.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';\r\n\t\t\t\tdiv2.style.wordWrap = mxConstants.WORD_WRAP;\r\n\t\t\t\tdiv2.innerHTML = (mxUtils.isNode(str)) ? str.outerHTML : str;\r\n\t\t\t\tclone.appendChild(div2);\r\n\r\n\t\t\t\tdocument.body.appendChild(clone);\r\n\r\n\t\t\t\t// Workaround for different box models\r\n\t\t\t\tif (document.documentMode != 8 && document.documentMode != 9 && s.fontBorderColor != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tpadX += 2;\r\n\t\t\t\t\tpadY += 2;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (wrap && w > 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar tmp = div2.offsetWidth;\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Workaround for adding padding twice in IE8/IE9 standards mode if label is wrapped\r\n\t\t\t\t\tpadDx = 0;\r\n\t\t\t\t\t\r\n\t\t\t\t\t// For export, if no wrapping occurs, we add a large padding to make\r\n\t\t\t\t\t// sure there is no wrapping even if the text metrics are different.\r\n\t\t\t\t\t// This adds support for text metrics on different operating systems.\r\n\t\t\t\t\t// Disables wrapping if text is not wrapped for given width\r\n\t\t\t\t\tif (!clip && wrap && w > 0 && this.root.ownerDocument != document && overflow != 'fill')\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar ws = clone.style.whiteSpace;\r\n\t\t\t\t\t\tdiv2.style.whiteSpace = 'nowrap';\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (tmp < div2.offsetWidth)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tclone.style.whiteSpace = ws;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (clip)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ttmp = Math.min(tmp, w);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tclone.style.width = tmp + 'px';\r\n\t\r\n\t\t\t\t\t// Padding avoids clipping on border\r\n\t\t\t\t\tow = div2.offsetWidth + padX + padDx;\r\n\t\t\t\t\toh = div2.offsetHeight + padY;\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Overrides the width of the DIV via XML DOM by using the\r\n\t\t\t\t\t// clone DOM style, getting the CSS text for that and\r\n\t\t\t\t\t// then setting that on the DIV via setAttribute\r\n\t\t\t\t\tclone.style.display = 'inline-block';\r\n\t\t\t\t\tclone.style.position = '';\r\n\t\t\t\t\tclone.style.visibility = '';\r\n\t\t\t\t\tclone.style.width = ow + 'px';\r\n\t\t\t\t\t\r\n\t\t\t\t\tdiv.setAttribute('style', clone.style.cssText);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\t// Padding avoids clipping on border\r\n\t\t\t\t\tow = div2.offsetWidth + padX;\r\n\t\t\t\t\toh = div2.offsetHeight + padY;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tclone.parentNode.removeChild(clone);\r\n\t\t\t\tfo.appendChild(div);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\t// Uses document for text measuring during export\r\n\t\t\t\tif (this.root.ownerDocument != document)\r\n\t\t\t\t{\r\n\t\t\t\t\tdiv.style.visibility = 'hidden';\r\n\t\t\t\t\tdocument.body.appendChild(div);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tfo.appendChild(div);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tvar sizeDiv = div;\r\n\t\t\t\t\r\n\t\t\t\tif (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')\r\n\t\t\t\t{\r\n\t\t\t\t\tsizeDiv = sizeDiv.firstChild;\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (wrap && div.style.wordWrap == 'break-word')\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tsizeDiv.style.width = '100%';\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tvar tmp = sizeDiv.offsetWidth;\r\n\t\t\t\t\r\n\t\t\t\t// Workaround for text measuring in hidden containers\r\n\t\t\t\tif (tmp == 0 && div.parentNode == fo)\r\n\t\t\t\t{\r\n\t\t\t\t\tdiv.style.visibility = 'hidden';\r\n\t\t\t\t\tdocument.body.appendChild(div);\r\n\t\t\t\t\t\r\n\t\t\t\t\ttmp = sizeDiv.offsetWidth;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (this.cacheOffsetSize)\r\n\t\t\t\t{\r\n\t\t\t\t\tgroup.mxCachedOffsetWidth = tmp;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Disables wrapping if text is not wrapped for given width\r\n\t\t\t\tif (!clip && wrap && w > 0 && this.root.ownerDocument != document &&\r\n\t\t\t\t\toverflow != 'fill' && overflow != 'width')\r\n\t\t\t\t{\r\n\t\t\t\t\tvar ws = div.style.whiteSpace;\r\n\t\t\t\t\tdiv.style.whiteSpace = 'nowrap';\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (tmp < sizeDiv.offsetWidth)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tdiv.style.whiteSpace = ws;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tow = tmp + padX - 1;\r\n\r\n\t\t\t\t// Recomputes the height of the element for wrapped width\r\n\t\t\t\tif (wrap && overflow != 'fill' && overflow != 'width')\r\n\t\t\t\t{\r\n\t\t\t\t\tif (clip)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tow = Math.min(ow, w);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tdiv.style.width = ow + 'px';\r\n\t\t\t\t}\r\n\r\n\t\t\t\tow = sizeDiv.offsetWidth;\r\n\t\t\t\toh = sizeDiv.offsetHeight;\r\n\t\t\t\t\r\n\t\t\t\tif (this.cacheOffsetSize)\r\n\t\t\t\t{\r\n\t\t\t\t\tgroup.mxCachedFinalOffsetWidth = ow;\r\n\t\t\t\t\tgroup.mxCachedFinalOffsetHeight = oh;\r\n\t\t\t\t}\r\n\r\n\t\t\t\toh -= padY;\r\n\t\t\t\t\r\n\t\t\t\tif (div.parentNode != fo)\r\n\t\t\t\t{\r\n\t\t\t\t\tfo.appendChild(div);\r\n\t\t\t\t\tdiv.style.visibility = '';\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (clip)\r\n\t\t\t{\r\n\t\t\t\toh = Math.min(oh, h);\r\n\t\t\t\tow = Math.min(ow, w);\r\n\t\t\t}\r\n\r\n\t\t\tif (overflow == 'width')\r\n\t\t\t{\r\n\t\t\t\th = oh;\r\n\t\t\t}\r\n\t\t\telse if (overflow != 'fill')\r\n\t\t\t{\r\n\t\t\t\tw = ow;\r\n\t\t\t\th = oh;\r\n\t\t\t}\r\n\r\n\t\t\tif (s.alpha < 1)\r\n\t\t\t{\r\n\t\t\t\tgroup.setAttribute('opacity', s.alpha);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar dx = 0;\r\n\t\t\tvar dy = 0;\r\n\r\n\t\t\tif (align == mxConstants.ALIGN_CENTER)\r\n\t\t\t{\r\n\t\t\t\tdx -= w / 2;\r\n\t\t\t}\r\n\t\t\telse if (align == mxConstants.ALIGN_RIGHT)\r\n\t\t\t{\r\n\t\t\t\tdx -= w;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tx += dx;\r\n\t\t\t\r\n\t\t\t// FIXME: LINE_HEIGHT not ideal for all text sizes, fix for export\r\n\t\t\tif (valign == mxConstants.ALIGN_MIDDLE)\r\n\t\t\t{\r\n\t\t\t\tdy -= h / 2;\r\n\t\t\t}\r\n\t\t\telse if (valign == mxConstants.ALIGN_BOTTOM)\r\n\t\t\t{\r\n\t\t\t\tdy -= h;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Workaround for rendering offsets\r\n\t\t\t// TODO: Check if export needs these fixes, too\r\n\t\t\t//if (this.root.ownerDocument == document)\r\n\t\t\tif (overflow != 'fill' && mxClient.IS_FF && mxClient.IS_WIN)\r\n\t\t\t{\r\n\t\t\t\tdy -= 2;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\ty += dy;\r\n\r\n\t\t\tvar tr = (s.scale != 1) ? 'scale(' + s.scale + ')' : '';\r\n\r\n\t\t\tif (s.rotation != 0 && this.rotateHtml)\r\n\t\t\t{\r\n\t\t\t\ttr += 'rotate(' + (s.rotation) + ',' + (w / 2) + ',' + (h / 2) + ')';\r\n\t\t\t\tvar pt = this.rotatePoint((x + w / 2) * s.scale, (y + h / 2) * s.scale,\r\n\t\t\t\t\ts.rotation, s.rotationCx, s.rotationCy);\r\n\t\t\t\tx = pt.x - w * s.scale / 2;\r\n\t\t\t\ty = pt.y - h * s.scale / 2;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tx *= s.scale;\r\n\t\t\t\ty *= s.scale;\r\n\t\t\t}\r\n\r\n\t\t\tif (rotation != 0)\r\n\t\t\t{\r\n\t\t\t\ttr += 'rotate(' + (rotation) + ',' + (-dx) + ',' + (-dy) + ')';\r\n\t\t\t}\r\n\r\n\t\t\tgroup.setAttribute('transform', 'translate(' + (Math.round(x) + this.foOffset) + ',' +\r\n\t\t\t\t(Math.round(y) + this.foOffset) + ')' + tr);\r\n\t\t\tfo.setAttribute('width', Math.round(Math.max(1, w)));\r\n\t\t\tfo.setAttribute('height', Math.round(Math.max(1, h)));\r\n\t\t\t\r\n\t\t\t// Adds alternate content if foreignObject not supported in viewer\r\n\t\t\tif (this.root.ownerDocument != document)\r\n\t\t\t{\r\n\t\t\t\tvar alt = this.createAlternateContent(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation);\r\n\t\t\t\t\r\n\t\t\t\tif (alt != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tfo.setAttribute('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility');\r\n\t\t\t\t\tvar sw = this.createElement('switch');\r\n\t\t\t\t\tsw.appendChild(fo);\r\n\t\t\t\t\tsw.appendChild(alt);\r\n\t\t\t\t\tgroup.appendChild(sw);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.plainText(x, y, w, h, str, align, valign, wrap, overflow, clip, rotation, dir);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createClip\r\n * \r\n * Creates a clip for the given coordinates.\r\n */\r\nmxSvgCanvas2D.prototype.createClip = function(x, y, w, h)\r\n{\r\n\tx = Math.round(x);\r\n\ty = Math.round(y);\r\n\tw = Math.round(w);\r\n\th = Math.round(h);\r\n\t\r\n\tvar id = 'mx-clip-' + x + '-' + y + '-' + w + '-' + h;\r\n\r\n\tvar counter = 0;\r\n\tvar tmp = id + '-' + counter;\r\n\t\r\n\t// Resolves ID conflicts\r\n\twhile (document.getElementById(tmp) != null)\r\n\t{\r\n\t\ttmp = id + '-' + (++counter);\r\n\t}\r\n\t\r\n\tclip = this.createElement('clipPath');\r\n\tclip.setAttribute('id', tmp);\r\n\t\r\n\tvar rect = this.createElement('rect');\r\n\trect.setAttribute('x', x);\r\n\trect.setAttribute('y', y);\r\n\trect.setAttribute('width', w);\r\n\trect.setAttribute('height', h);\r\n\t\t\r\n\tclip.appendChild(rect);\r\n\t\r\n\treturn clip;\r\n};\r\n\r\n/**\r\n * Function: text\r\n * \r\n * Paints the given text. Possible values for format are empty string for\r\n * plain text and html for HTML markup.\r\n */\r\nmxSvgCanvas2D.prototype.plainText = function(x, y, w, h, str, align, valign, wrap, overflow, clip, rotation, dir)\r\n{\r\n\trotation = (rotation != null) ? rotation : 0;\r\n\tvar s = this.state;\r\n\tvar size = s.fontSize;\r\n\tvar node = this.createElement('g');\r\n\tvar tr = s.transform || '';\r\n\tthis.updateFont(node);\r\n\t\r\n\t// Non-rotated text\r\n\tif (rotation != 0)\r\n\t{\r\n\t\ttr += 'rotate(' + rotation  + ',' + this.format(x * s.scale) + ',' + this.format(y * s.scale) + ')';\r\n\t}\r\n\t\r\n\tif (dir != null)\r\n\t{\r\n\t\tnode.setAttribute('direction', dir);\r\n\t}\r\n\r\n\tif (clip && w > 0 && h > 0)\r\n\t{\r\n\t\tvar cx = x;\r\n\t\tvar cy = y;\r\n\t\t\r\n\t\tif (align == mxConstants.ALIGN_CENTER)\r\n\t\t{\r\n\t\t\tcx -= w / 2;\r\n\t\t}\r\n\t\telse if (align == mxConstants.ALIGN_RIGHT)\r\n\t\t{\r\n\t\t\tcx -= w;\r\n\t\t}\r\n\t\t\r\n\t\tif (overflow != 'fill')\r\n\t\t{\r\n\t\t\tif (valign == mxConstants.ALIGN_MIDDLE)\r\n\t\t\t{\r\n\t\t\t\tcy -= h / 2;\r\n\t\t\t}\r\n\t\t\telse if (valign == mxConstants.ALIGN_BOTTOM)\r\n\t\t\t{\r\n\t\t\t\tcy -= h;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// LATER: Remove spacing from clip rectangle\r\n\t\tvar c = this.createClip(cx * s.scale - 2, cy * s.scale - 2, w * s.scale + 4, h * s.scale + 4);\r\n\t\t\r\n\t\tif (this.defs != null)\r\n\t\t{\r\n\t\t\tthis.defs.appendChild(c);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Makes sure clip is removed with referencing node\r\n\t\t\tthis.root.appendChild(c);\r\n\t\t}\r\n\t\t\r\n\t\tif (!mxClient.IS_CHROME_APP && !mxClient.IS_IE && !mxClient.IS_IE11 &&\r\n\t\t\t!mxClient.IS_EDGE && this.root.ownerDocument == document)\r\n\t\t{\r\n\t\t\t// Workaround for potential base tag\r\n\t\t\tvar base = this.getBaseUrl().replace(/([\\(\\)])/g, '\\\\$1');\r\n\t\t\tnode.setAttribute('clip-path', 'url(' + base + '#' + c.getAttribute('id') + ')');\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tnode.setAttribute('clip-path', 'url(#' + c.getAttribute('id') + ')');\r\n\t\t}\r\n\t}\r\n\r\n\t// Default is left\r\n\tvar anchor = (align == mxConstants.ALIGN_RIGHT) ? 'end' :\r\n\t\t\t\t\t(align == mxConstants.ALIGN_CENTER) ? 'middle' :\r\n\t\t\t\t\t'start';\r\n\r\n\t// Text-anchor start is default in SVG\r\n\tif (anchor != 'start')\r\n\t{\r\n\t\tnode.setAttribute('text-anchor', anchor);\r\n\t}\r\n\t\r\n\tif (!this.styleEnabled || size != mxConstants.DEFAULT_FONTSIZE)\r\n\t{\r\n\t\tnode.setAttribute('font-size', (size * s.scale) + 'px');\r\n\t}\r\n\t\r\n\tif (tr.length > 0)\r\n\t{\r\n\t\tnode.setAttribute('transform', tr);\r\n\t}\r\n\t\r\n\tif (s.alpha < 1)\r\n\t{\r\n\t\tnode.setAttribute('opacity', s.alpha);\r\n\t}\r\n\t\r\n\tvar lines = str.split('\\n');\r\n\tvar lh = Math.round(size * mxConstants.LINE_HEIGHT);\r\n\tvar textHeight = size + (lines.length - 1) * lh;\r\n\r\n\tvar cy = y + size - 1;\r\n\r\n\tif (valign == mxConstants.ALIGN_MIDDLE)\r\n\t{\r\n\t\tif (overflow == 'fill')\r\n\t\t{\r\n\t\t\tcy -= h / 2;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar dy = ((this.matchHtmlAlignment && clip && h > 0) ? Math.min(textHeight, h) : textHeight) / 2;\r\n\t\t\tcy -= dy + 1;\r\n\t\t}\r\n\t}\r\n\telse if (valign == mxConstants.ALIGN_BOTTOM)\r\n\t{\r\n\t\tif (overflow == 'fill')\r\n\t\t{\r\n\t\t\tcy -= h;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar dy = (this.matchHtmlAlignment && clip && h > 0) ? Math.min(textHeight, h) : textHeight;\r\n\t\t\tcy -= dy + 2;\r\n\t\t}\r\n\t}\r\n\r\n\tfor (var i = 0; i < lines.length; i++)\r\n\t{\r\n\t\t// Workaround for bounding box of empty lines and spaces\r\n\t\tif (lines[i].length > 0 && mxUtils.trim(lines[i]).length > 0)\r\n\t\t{\r\n\t\t\tvar text = this.createElement('text');\r\n\t\t\t// LATER: Match horizontal HTML alignment\r\n\t\t\ttext.setAttribute('x', this.format(x * s.scale) + this.textOffset);\r\n\t\t\ttext.setAttribute('y', this.format(cy * s.scale) + this.textOffset);\r\n\t\t\t\r\n\t\t\tmxUtils.write(text, lines[i]);\r\n\t\t\tnode.appendChild(text);\r\n\t\t}\r\n\r\n\t\tcy += lh;\r\n\t}\r\n\r\n\tthis.root.appendChild(node);\r\n\tthis.addTextBackground(node, str, x, y, w, (overflow == 'fill') ? h : textHeight, align, valign, overflow);\r\n};\r\n\r\n/**\r\n * Function: updateFont\r\n * \r\n * Updates the text properties for the given node. (NOTE: For this to work in\r\n * IE, the given node must be a text or tspan element.)\r\n */\r\nmxSvgCanvas2D.prototype.updateFont = function(node)\r\n{\r\n\tvar s = this.state;\r\n\r\n\tnode.setAttribute('fill', s.fontColor);\r\n\t\r\n\tif (!this.styleEnabled || s.fontFamily != mxConstants.DEFAULT_FONTFAMILY)\r\n\t{\r\n\t\tnode.setAttribute('font-family', '\\'' + s.fontFamily + '\\'');\r\n\t}\r\n\r\n\tif ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)\r\n\t{\r\n\t\tnode.setAttribute('font-weight', 'bold');\r\n\t}\r\n\r\n\tif ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)\r\n\t{\r\n\t\tnode.setAttribute('font-style', 'italic');\r\n\t}\r\n\t\r\n\tif ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)\r\n\t{\r\n\t\tnode.setAttribute('text-decoration', 'underline');\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: addTextBackground\r\n * \r\n * Background color and border\r\n */\r\nmxSvgCanvas2D.prototype.addTextBackground = function(node, str, x, y, w, h, align, valign, overflow)\r\n{\r\n\tvar s = this.state;\r\n\r\n\tif (s.fontBackgroundColor != null || s.fontBorderColor != null)\r\n\t{\r\n\t\tvar bbox = null;\r\n\t\t\r\n\t\tif (overflow == 'fill' || overflow == 'width')\r\n\t\t{\r\n\t\t\tif (align == mxConstants.ALIGN_CENTER)\r\n\t\t\t{\r\n\t\t\t\tx -= w / 2;\r\n\t\t\t}\r\n\t\t\telse if (align == mxConstants.ALIGN_RIGHT)\r\n\t\t\t{\r\n\t\t\t\tx -= w;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (valign == mxConstants.ALIGN_MIDDLE)\r\n\t\t\t{\r\n\t\t\t\ty -= h / 2;\r\n\t\t\t}\r\n\t\t\telse if (valign == mxConstants.ALIGN_BOTTOM)\r\n\t\t\t{\r\n\t\t\t\ty -= h;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tbbox = new mxRectangle((x + 1) * s.scale, y * s.scale, (w - 2) * s.scale, (h + 2) * s.scale);\r\n\t\t}\r\n\t\telse if (node.getBBox != null && this.root.ownerDocument == document)\r\n\t\t{\r\n\t\t\t// Uses getBBox only if inside document for correct size\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tbbox = node.getBBox();\r\n\t\t\t\tvar ie = mxClient.IS_IE && mxClient.IS_SVG;\r\n\t\t\t\tbbox = new mxRectangle(bbox.x, bbox.y + ((ie) ? 0 : 1), bbox.width, bbox.height + ((ie) ? 1 : 0));\r\n\t\t\t}\r\n\t\t\tcatch (e)\r\n\t\t\t{\r\n\t\t\t\t// Ignores NS_ERROR_FAILURE in FF if container display is none.\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Computes size if not in document or no getBBox available\r\n\t\t\tvar div = document.createElement('div');\r\n\r\n\t\t\t// Wrapping and clipping can be ignored here\r\n\t\t\tdiv.style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (s.fontSize * mxConstants.LINE_HEIGHT) + 'px' : mxConstants.LINE_HEIGHT;\r\n\t\t\tdiv.style.fontSize = s.fontSize + 'px';\r\n\t\t\t// Quotes are workaround for font name \"m+\"\r\n\t\t\tdiv.style.fontFamily = '\"' + s.fontFamily + '\"';\r\n\t\t\tdiv.style.whiteSpace = 'nowrap';\r\n\t\t\tdiv.style.position = 'absolute';\r\n\t\t\tdiv.style.visibility = 'hidden';\r\n\t\t\tdiv.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';\r\n\t\t\tdiv.style.zoom = '1';\r\n\t\t\t\r\n\t\t\tif ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)\r\n\t\t\t{\r\n\t\t\t\tdiv.style.fontWeight = 'bold';\r\n\t\t\t}\r\n\r\n\t\t\tif ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)\r\n\t\t\t{\r\n\t\t\t\tdiv.style.fontStyle = 'italic';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tstr = mxUtils.htmlEntities(str, false);\r\n\t\t\tdiv.innerHTML = str.replace(/\\n/g, '<br/>');\r\n\t\t\t\r\n\t\t\tdocument.body.appendChild(div);\r\n\t\t\tvar w = div.offsetWidth;\r\n\t\t\tvar h = div.offsetHeight;\r\n\t\t\tdiv.parentNode.removeChild(div);\r\n\t\t\t\r\n\t\t\tif (align == mxConstants.ALIGN_CENTER)\r\n\t\t\t{\r\n\t\t\t\tx -= w / 2;\r\n\t\t\t}\r\n\t\t\telse if (align == mxConstants.ALIGN_RIGHT)\r\n\t\t\t{\r\n\t\t\t\tx -= w;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (valign == mxConstants.ALIGN_MIDDLE)\r\n\t\t\t{\r\n\t\t\t\ty -= h / 2;\r\n\t\t\t}\r\n\t\t\telse if (valign == mxConstants.ALIGN_BOTTOM)\r\n\t\t\t{\r\n\t\t\t\ty -= h;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tbbox = new mxRectangle((x + 1) * s.scale, (y + 2) * s.scale, w * s.scale, (h + 1) * s.scale);\r\n\t\t}\r\n\t\t\r\n\t\tif (bbox != null)\r\n\t\t{\r\n\t\t\tvar n = this.createElement('rect');\r\n\t\t\tn.setAttribute('fill', s.fontBackgroundColor || 'none');\r\n\t\t\tn.setAttribute('stroke', s.fontBorderColor || 'none');\r\n\t\t\tn.setAttribute('x', Math.floor(bbox.x - 1));\r\n\t\t\tn.setAttribute('y', Math.floor(bbox.y - 1));\r\n\t\t\tn.setAttribute('width', Math.ceil(bbox.width + 2));\r\n\t\t\tn.setAttribute('height', Math.ceil(bbox.height));\r\n\r\n\t\t\tvar sw = (s.fontBorderColor != null) ? Math.max(1, this.format(s.scale)) : 0;\r\n\t\t\tn.setAttribute('stroke-width', sw);\r\n\t\t\t\r\n\t\t\t// Workaround for crisp rendering - only required if not exporting\r\n\t\t\tif (this.root.ownerDocument == document && mxUtils.mod(sw, 2) == 1)\r\n\t\t\t{\r\n\t\t\t\tn.setAttribute('transform', 'translate(0.5, 0.5)');\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tnode.insertBefore(n, node.firstChild);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: stroke\r\n * \r\n * Paints the outline of the current path.\r\n */\r\nmxSvgCanvas2D.prototype.stroke = function()\r\n{\r\n\tthis.addNode(false, true);\r\n};\r\n\r\n/**\r\n * Function: fill\r\n * \r\n * Fills the current path.\r\n */\r\nmxSvgCanvas2D.prototype.fill = function()\r\n{\r\n\tthis.addNode(true, false);\r\n};\r\n\r\n/**\r\n * Function: fillAndStroke\r\n * \r\n * Fills and paints the outline of the current path.\r\n */\r\nmxSvgCanvas2D.prototype.fillAndStroke = function()\r\n{\r\n\tthis.addNode(true, true);\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n *\r\n * Class: mxVmlCanvas2D\r\n * \r\n * Implements a canvas to be used for rendering VML. Here is an example of implementing a\r\n * fallback for SVG images which are not supported in VML-based browsers.\r\n * \r\n * (code)\r\n * var mxVmlCanvas2DImage = mxVmlCanvas2D.prototype.image;\r\n * mxVmlCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)\r\n * {\r\n *   if (src.substring(src.length - 4, src.length) == '.svg')\r\n *   {\r\n *     src = 'http://www.jgraph.com/images/mxgraph.gif';\r\n *   }\r\n *   \r\n *   mxVmlCanvas2DImage.apply(this, arguments);\r\n * };\r\n * (end)\r\n * \r\n * To disable anti-aliasing in the output, use the following code.\r\n * \r\n * (code)\r\n * document.createStyleSheet().cssText = mxClient.VML_PREFIX + '\\\\:*{antialias:false;)}';\r\n * (end)\r\n * \r\n * A description of the public API is available in <mxXmlCanvas2D>. Note that\r\n * there is a known issue in VML where gradients are painted using the outer\r\n * bounding box of rotated shapes, not the actual bounds of the shape. See\r\n * also <text> for plain text label restrictions in shapes for VML.\r\n */\r\nvar mxVmlCanvas2D = function(root)\r\n{\r\n\tmxAbstractCanvas2D.call(this);\r\n\r\n\t/**\r\n\t * Variable: root\r\n\t * \r\n\t * Reference to the container for the SVG content.\r\n\t */\r\n\tthis.root = root;\r\n};\r\n\r\n/**\r\n * Extends mxAbstractCanvas2D\r\n */\r\nmxUtils.extend(mxVmlCanvas2D, mxAbstractCanvas2D);\r\n\r\n/**\r\n * Variable: path\r\n * \r\n * Holds the current DOM node.\r\n */\r\nmxVmlCanvas2D.prototype.node = null;\r\n\r\n/**\r\n * Variable: textEnabled\r\n * \r\n * Specifies if text output should be enabledetB. Default is true.\r\n */\r\nmxVmlCanvas2D.prototype.textEnabled = true;\r\n\r\n/**\r\n * Variable: moveOp\r\n * \r\n * Contains the string used for moving in paths. Default is 'm'.\r\n */\r\nmxVmlCanvas2D.prototype.moveOp = 'm';\r\n\r\n/**\r\n * Variable: lineOp\r\n * \r\n * Contains the string used for moving in paths. Default is 'l'.\r\n */\r\nmxVmlCanvas2D.prototype.lineOp = 'l';\r\n\r\n/**\r\n * Variable: curveOp\r\n * \r\n * Contains the string used for bezier curves. Default is 'c'.\r\n */\r\nmxVmlCanvas2D.prototype.curveOp = 'c';\r\n\r\n/**\r\n * Variable: closeOp\r\n * \r\n * Holds the operator for closing curves. Default is 'x e'.\r\n */\r\nmxVmlCanvas2D.prototype.closeOp = 'x';\r\n\r\n/**\r\n * Variable: rotatedHtmlBackground\r\n * \r\n * Background color for rotated HTML. Default is ''. This can be set to eg.\r\n * white to improve rendering of rotated text in VML for IE9.\r\n */\r\nmxVmlCanvas2D.prototype.rotatedHtmlBackground = '';\r\n\r\n/**\r\n * Variable: vmlScale\r\n * \r\n * Specifies the scale used to draw VML shapes.\r\n */\r\nmxVmlCanvas2D.prototype.vmlScale = 1;\r\n\r\n/**\r\n * Function: createElement\r\n * \r\n * Creates the given element using the document.\r\n */\r\nmxVmlCanvas2D.prototype.createElement = function(name)\r\n{\r\n\treturn document.createElement(name);\r\n};\r\n\r\n/**\r\n * Function: createVmlElement\r\n * \r\n * Creates a new element using <createElement> and prefixes the given name with\r\n * <mxClient.VML_PREFIX>.\r\n */\r\nmxVmlCanvas2D.prototype.createVmlElement = function(name)\r\n{\r\n\treturn this.createElement(mxClient.VML_PREFIX + ':' + name);\r\n};\r\n\r\n/**\r\n * Function: addNode\r\n * \r\n * Adds the current node to the <root>.\r\n */\r\nmxVmlCanvas2D.prototype.addNode = function(filled, stroked)\r\n{\r\n\tvar node = this.node;\r\n\tvar s = this.state;\r\n\t\r\n\tif (node != null)\r\n\t{\r\n\t\tif (node.nodeName == 'shape')\r\n\t\t{\r\n\t\t\t// Checks if the path is not empty\r\n\t\t\tif (this.path != null && this.path.length > 0)\r\n\t\t\t{\r\n\t\t\t\tnode.path = this.path.join(' ') + ' e';\r\n\t\t\t\tnode.style.width = this.root.style.width;\r\n\t\t\t\tnode.style.height = this.root.style.height;\r\n\t\t\t\tnode.coordsize = parseInt(node.style.width) + ' ' + parseInt(node.style.height);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tnode.strokeweight = this.format(Math.max(1, s.strokeWidth * s.scale / this.vmlScale)) + 'px';\r\n\t\t\r\n\t\tif (s.shadow)\r\n\t\t{\r\n\t\t\tthis.root.appendChild(this.createShadow(node,\r\n\t\t\t\tfilled && s.fillColor != null,\r\n\t\t\t\tstroked && s.strokeColor != null));\r\n\t\t}\r\n\t\t\r\n\t\tif (stroked && s.strokeColor != null)\r\n\t\t{\r\n\t\t\tnode.stroked = 'true';\r\n\t\t\tnode.strokecolor = s.strokeColor;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tnode.stroked = 'false';\r\n\t\t}\r\n\r\n\t\tnode.appendChild(this.createStroke());\r\n\r\n\t\tif (filled && s.fillColor != null)\r\n\t\t{\r\n\t\t\tnode.appendChild(this.createFill());\r\n\t\t}\r\n\t\telse if (this.pointerEvents && (node.nodeName != 'shape' ||\r\n\t\t\tthis.path[this.path.length - 1] == this.closeOp))\r\n\t\t{\r\n\t\t\tnode.appendChild(this.createTransparentFill());\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tnode.filled = 'false';\r\n\t\t}\r\n\r\n\t\t// LATER: Update existing DOM for performance\r\n\t\tthis.root.appendChild(node);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createTransparentFill\r\n * \r\n * Creates a transparent fill.\r\n */\r\nmxVmlCanvas2D.prototype.createTransparentFill = function()\r\n{\r\n\tvar fill = this.createVmlElement('fill');\r\n\tfill.src = mxClient.imageBasePath + '/transparent.gif';\r\n\tfill.type = 'tile';\r\n\t\r\n\treturn fill;\r\n};\r\n\r\n/**\r\n * Function: createFill\r\n * \r\n * Creates a fill for the current state.\r\n */\r\nmxVmlCanvas2D.prototype.createFill = function()\r\n{\r\n\tvar s = this.state;\r\n\t\r\n\t// Gradients in foregrounds not supported because special gradients\r\n\t// with bounds must be created for each element in graphics-canvases\r\n\tvar fill = this.createVmlElement('fill');\r\n\tfill.color = s.fillColor;\r\n\r\n\tif (s.gradientColor != null)\r\n\t{\r\n\t\tfill.type = 'gradient';\r\n\t\tfill.method = 'none';\r\n\t\tfill.color2 = s.gradientColor;\r\n\t\tvar angle = 180 - s.rotation;\r\n\t\t\r\n\t\tif (s.gradientDirection == mxConstants.DIRECTION_WEST)\r\n\t\t{\r\n\t\t\tangle -= 90 + ((this.root.style.flip == 'x') ? 180 : 0);\r\n\t\t}\r\n\t\telse if (s.gradientDirection == mxConstants.DIRECTION_EAST)\r\n\t\t{\r\n\t\t\tangle += 90 + ((this.root.style.flip == 'x') ? 180 : 0);\r\n\t\t}\r\n\t\telse if (s.gradientDirection == mxConstants.DIRECTION_NORTH)\r\n\t\t{\r\n\t\t\tangle -= 180 + ((this.root.style.flip == 'y') ? -180 : 0);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t angle += ((this.root.style.flip == 'y') ? -180 : 0);\r\n\t\t}\r\n\t\t\r\n\t\tif (this.root.style.flip == 'x' || this.root.style.flip == 'y')\r\n\t\t{\r\n\t\t\tangle *= -1;\r\n\t\t}\r\n\r\n\t\t// LATER: Fix outer bounding box for rotated shapes used in VML.\r\n\t\tfill.angle = mxUtils.mod(angle, 360);\r\n\t\tfill.opacity = (s.alpha * s.gradientFillAlpha * 100) + '%';\r\n\t\tfill.setAttribute(mxClient.OFFICE_PREFIX + ':opacity2', (s.alpha * s.gradientAlpha * 100) + '%');\r\n\t}\r\n\telse if (s.alpha < 1 || s.fillAlpha < 1)\r\n\t{\r\n\t\tfill.opacity = (s.alpha * s.fillAlpha * 100) + '%';\t\t\t\r\n\t}\r\n\t\r\n\treturn fill;\r\n};\r\n/**\r\n * Function: createStroke\r\n * \r\n * Creates a fill for the current state.\r\n */\r\nmxVmlCanvas2D.prototype.createStroke = function()\r\n{\r\n\tvar s = this.state;\r\n\tvar stroke = this.createVmlElement('stroke');\r\n\tstroke.endcap = s.lineCap || 'flat';\r\n\tstroke.joinstyle = s.lineJoin || 'miter';\r\n\tstroke.miterlimit = s.miterLimit || '10';\r\n\t\r\n\tif (s.alpha < 1 || s.strokeAlpha < 1)\r\n\t{\r\n\t\tstroke.opacity = (s.alpha * s.strokeAlpha * 100) + '%';\r\n\t}\r\n\t\r\n\tif (s.dashed)\r\n\t{\r\n\t\tstroke.dashstyle = this.getVmlDashStyle();\r\n\t}\r\n\t\r\n\treturn stroke;\r\n};\r\n\r\n/**\r\n * Function: getVmlDashPattern\r\n * \r\n * Returns a VML dash pattern for the current dashPattern.\r\n * See http://msdn.microsoft.com/en-us/library/bb264085(v=vs.85).aspx\r\n */\r\nmxVmlCanvas2D.prototype.getVmlDashStyle = function()\r\n{\r\n\tvar result = 'dash';\r\n\t\r\n\tif (typeof(this.state.dashPattern) === 'string')\r\n\t{\r\n\t\tvar tok = this.state.dashPattern.split(' ');\r\n\t\t\r\n\t\tif (tok.length > 0 && tok[0] == 1)\r\n\t\t{\r\n\t\t\tresult = '0 2';\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: createShadow\r\n * \r\n * Creates a shadow for the given node.\r\n */\r\nmxVmlCanvas2D.prototype.createShadow = function(node, filled, stroked)\r\n{\r\n\tvar s = this.state;\r\n\tvar rad = -s.rotation * (Math.PI / 180);\r\n\tvar cos = Math.cos(rad);\r\n\tvar sin = Math.sin(rad);\r\n\r\n\tvar dx = s.shadowDx * s.scale;\r\n\tvar dy = s.shadowDy * s.scale;\r\n\r\n\tif (this.root.style.flip == 'x')\r\n\t{\r\n\t\tdx *= -1;\r\n\t}\r\n\telse if (this.root.style.flip == 'y')\r\n\t{\r\n\t\tdy *= -1;\r\n\t}\r\n\t\r\n\tvar shadow = node.cloneNode(true);\r\n\tshadow.style.marginLeft = Math.round(dx * cos - dy * sin) + 'px';\r\n\tshadow.style.marginTop = Math.round(dx * sin + dy * cos) + 'px';\r\n\r\n\t// Workaround for wrong cloning in IE8 standards mode\r\n\tif (document.documentMode == 8)\r\n\t{\r\n\t\tshadow.strokeweight = node.strokeweight;\r\n\t\t\r\n\t\tif (node.nodeName == 'shape')\r\n\t\t{\r\n\t\t\tshadow.path = this.path.join(' ') + ' e';\r\n\t\t\tshadow.style.width = this.root.style.width;\r\n\t\t\tshadow.style.height = this.root.style.height;\r\n\t\t\tshadow.coordsize = parseInt(node.style.width) + ' ' + parseInt(node.style.height);\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (stroked)\r\n\t{\r\n\t\tshadow.strokecolor = s.shadowColor;\r\n\t\tshadow.appendChild(this.createShadowStroke());\r\n\t}\r\n\telse\r\n\t{\r\n\t\tshadow.stroked = 'false';\r\n\t}\r\n\t\r\n\tif (filled)\r\n\t{\r\n\t\tshadow.appendChild(this.createShadowFill());\r\n\t}\r\n\telse\r\n\t{\r\n\t\tshadow.filled = 'false';\r\n\t}\r\n\t\r\n\treturn shadow;\r\n};\r\n\r\n/**\r\n * Function: createShadowFill\r\n * \r\n * Creates the fill for the shadow.\r\n */\r\nmxVmlCanvas2D.prototype.createShadowFill = function()\r\n{\r\n\tvar fill = this.createVmlElement('fill');\r\n\tfill.color = this.state.shadowColor;\r\n\tfill.opacity = (this.state.alpha * this.state.shadowAlpha * 100) + '%';\r\n\t\r\n\treturn fill;\r\n};\r\n\r\n/**\r\n * Function: createShadowStroke\r\n * \r\n * Creates the stroke for the shadow.\r\n */\r\nmxVmlCanvas2D.prototype.createShadowStroke = function()\r\n{\r\n\tvar stroke = this.createStroke();\r\n\tstroke.opacity = (this.state.alpha * this.state.shadowAlpha * 100) + '%';\r\n\t\r\n\treturn stroke;\r\n};\r\n\r\n/**\r\n * Function: rotate\r\n * \r\n * Sets the rotation of the canvas. Note that rotation cannot be concatenated.\r\n */\r\nmxVmlCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)\r\n{\r\n\tif (flipH && flipV)\r\n\t{\r\n\t\ttheta += 180;\r\n\t}\r\n\telse if (flipH)\r\n\t{\r\n\t\tthis.root.style.flip = 'x';\r\n\t}\r\n\telse if (flipV)\r\n\t{\r\n\t\tthis.root.style.flip = 'y';\r\n\t}\r\n\r\n\tif (flipH ? !flipV : flipV)\r\n\t{\r\n\t\ttheta *= -1;\r\n\t}\r\n\r\n\tthis.root.style.rotation = theta;\r\n\tthis.state.rotation = this.state.rotation + theta;\r\n\tthis.state.rotationCx = cx;\r\n\tthis.state.rotationCy = cy;\r\n};\r\n\r\n/**\r\n * Function: begin\r\n * \r\n * Extends superclass to create path.\r\n */\r\nmxVmlCanvas2D.prototype.begin = function()\r\n{\r\n\tmxAbstractCanvas2D.prototype.begin.apply(this, arguments);\r\n\tthis.node = this.createVmlElement('shape');\r\n\tthis.node.style.position = 'absolute';\r\n};\r\n\r\n/**\r\n * Function: quadTo\r\n * \r\n * Replaces quadratic curve with bezier curve in VML.\r\n */\r\nmxVmlCanvas2D.prototype.quadTo = function(x1, y1, x2, y2)\r\n{\r\n\tvar s = this.state;\r\n\r\n\tvar cpx0 = (this.lastX + s.dx) * s.scale;\r\n\tvar cpy0 = (this.lastY + s.dy) * s.scale;\r\n\tvar qpx1 = (x1 + s.dx) * s.scale;\r\n\tvar qpy1 = (y1 + s.dy) * s.scale;\r\n\tvar cpx3 = (x2 + s.dx) * s.scale;\r\n\tvar cpy3 = (y2 + s.dy) * s.scale;\r\n\t\r\n\tvar cpx1 = cpx0 + 2/3 * (qpx1 - cpx0);\r\n\tvar cpy1 = cpy0 + 2/3 * (qpy1 - cpy0);\r\n\t\r\n\tvar cpx2 = cpx3 + 2/3 * (qpx1 - cpx3);\r\n\tvar cpy2 = cpy3 + 2/3 * (qpy1 - cpy3);\r\n\t\r\n\tthis.path.push('c ' + this.format(cpx1) + ' ' + this.format(cpy1) +\r\n\t\t\t' ' + this.format(cpx2) + ' ' + this.format(cpy2) +\r\n\t\t\t' ' + this.format(cpx3) + ' ' + this.format(cpy3));\r\n\tthis.lastX = (cpx3 / s.scale) - s.dx;\r\n\tthis.lastY = (cpy3 / s.scale) - s.dy;\r\n\t\r\n};\r\n\r\n/**\r\n * Function: createRect\r\n * \r\n * Sets the glass gradient.\r\n */\r\nmxVmlCanvas2D.prototype.createRect = function(nodeName, x, y, w, h)\r\n{\r\n\tvar s = this.state;\r\n\tvar n = this.createVmlElement(nodeName);\r\n\tn.style.position = 'absolute';\r\n\tn.style.left = this.format((x + s.dx) * s.scale) + 'px';\r\n\tn.style.top = this.format((y + s.dy) * s.scale) + 'px';\r\n\tn.style.width = this.format(w * s.scale) + 'px';\r\n\tn.style.height = this.format(h * s.scale) + 'px';\r\n\t\r\n\treturn n;\r\n};\r\n\r\n/**\r\n * Function: rect\r\n * \r\n * Sets the current path to a rectangle.\r\n */\r\nmxVmlCanvas2D.prototype.rect = function(x, y, w, h)\r\n{\r\n\tthis.node = this.createRect('rect', x, y, w, h);\r\n};\r\n\r\n/**\r\n * Function: roundrect\r\n * \r\n * Sets the current path to a rounded rectangle.\r\n */\r\nmxVmlCanvas2D.prototype.roundrect = function(x, y, w, h, dx, dy)\r\n{\r\n\tthis.node = this.createRect('roundrect', x, y, w, h);\r\n\t// SetAttribute needed here for IE8\r\n\tthis.node.setAttribute('arcsize', Math.max(dx * 100 / w, dy * 100 / h) + '%');\r\n};\r\n\r\n/**\r\n * Function: ellipse\r\n * \r\n * Sets the current path to an ellipse.\r\n */\r\nmxVmlCanvas2D.prototype.ellipse = function(x, y, w, h)\r\n{\r\n\tthis.node = this.createRect('oval', x, y, w, h);\r\n};\r\n\r\n/**\r\n * Function: image\r\n * \r\n * Paints an image.\r\n */\r\nmxVmlCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)\r\n{\r\n\tvar node = null;\r\n\t\r\n\tif (!aspect)\r\n\t{\r\n\t\tnode = this.createRect('image', x, y, w, h);\r\n\t\tnode.src = src;\r\n\t}\r\n\telse\r\n\t{\r\n\t\t// Uses fill with aspect to avoid asynchronous update of size\r\n\t\tnode = this.createRect('rect', x, y, w, h);\r\n\t\tnode.stroked = 'false';\r\n\t\t\r\n\t\t// Handles image aspect via fill\r\n\t\tvar fill = this.createVmlElement('fill');\r\n\t\tfill.aspect = (aspect) ? 'atmost' : 'ignore';\r\n\t\tfill.rotate = 'true';\r\n\t\tfill.type = 'frame';\r\n\t\tfill.src = src;\r\n\r\n\t\tnode.appendChild(fill);\r\n\t}\r\n\t\r\n\tif (flipH && flipV)\r\n\t{\r\n\t\tnode.style.rotation = '180';\r\n\t}\r\n\telse if (flipH)\r\n\t{\r\n\t\tnode.style.flip = 'x';\r\n\t}\r\n\telse if (flipV)\r\n\t{\r\n\t\tnode.style.flip = 'y';\r\n\t}\r\n\t\r\n\tif (this.state.alpha < 1 || this.state.fillAlpha < 1)\r\n\t{\r\n\t\t// KNOWN: Borders around transparent images in IE<9. Using fill.opacity\r\n\t\t// fixes this problem by adding a white background in all IE versions.\r\n\t\tnode.style.filter += 'alpha(opacity=' + (this.state.alpha * this.state.fillAlpha * 100) + ')';\r\n\t}\r\n\r\n\tthis.root.appendChild(node);\r\n};\r\n\r\n/**\r\n * Function: createText\r\n * \r\n * Creates the innermost element that contains the HTML text.\r\n */\r\nmxVmlCanvas2D.prototype.createDiv = function(str, align, valign, overflow)\r\n{\r\n\tvar div = this.createElement('div');\r\n\tvar state = this.state;\r\n\r\n\tvar css = '';\r\n\t\r\n\tif (state.fontBackgroundColor != null)\r\n\t{\r\n\t\tcss += 'background-color:' + state.fontBackgroundColor + ';';\r\n\t}\r\n\t\r\n\tif (state.fontBorderColor != null)\r\n\t{\r\n\t\tcss += 'border:1px solid ' + state.fontBorderColor + ';';\r\n\t}\r\n\t\r\n\tif (mxUtils.isNode(str))\r\n\t{\r\n\t\tdiv.appendChild(str);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tif (overflow != 'fill' && overflow != 'width')\r\n\t\t{\r\n\t\t\tvar div2 = this.createElement('div');\r\n\t\t\tdiv2.style.cssText = css;\r\n\t\t\tdiv2.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';\r\n\t\t\tdiv2.style.zoom = '1';\r\n\t\t\tdiv2.style.textDecoration = 'inherit';\r\n\t\t\tdiv2.innerHTML = str;\r\n\t\t\tdiv.appendChild(div2);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tdiv.style.cssText = css;\r\n\t\t\tdiv.innerHTML = str;\r\n\t\t}\r\n\t}\r\n\t\r\n\tvar style = div.style;\r\n\r\n\tstyle.fontSize = (state.fontSize / this.vmlScale) + 'px';\r\n\tstyle.fontFamily = state.fontFamily;\r\n\tstyle.color = state.fontColor;\r\n\tstyle.verticalAlign = 'top';\r\n\tstyle.textAlign = align || 'left';\r\n\tstyle.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (state.fontSize * mxConstants.LINE_HEIGHT / this.vmlScale) + 'px' : mxConstants.LINE_HEIGHT;\r\n\r\n\tif ((state.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)\r\n\t{\r\n\t\tstyle.fontWeight = 'bold';\r\n\t}\r\n\r\n\tif ((state.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)\r\n\t{\r\n\t\tstyle.fontStyle = 'italic';\r\n\t}\r\n\t\r\n\tif ((state.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)\r\n\t{\r\n\t\tstyle.textDecoration = 'underline';\r\n\t}\r\n\t\r\n\treturn div;\r\n};\r\n\r\n/**\r\n * Function: text\r\n * \r\n * Paints the given text. Possible values for format are empty string for plain\r\n * text and html for HTML markup. Clipping, text background and border are not\r\n * supported for plain text in VML.\r\n */\r\nmxVmlCanvas2D.prototype.text = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)\r\n{\r\n\tif (this.textEnabled && str != null)\r\n\t{\r\n\t\tvar s = this.state;\r\n\t\t\r\n\t\tif (format == 'html')\r\n\t\t{\r\n\t\t\tif (s.rotation != null)\r\n\t\t\t{\r\n\t\t\t\tvar pt = this.rotatePoint(x, y, s.rotation, s.rotationCx, s.rotationCy);\r\n\t\t\t\t\r\n\t\t\t\tx = pt.x;\r\n\t\t\t\ty = pt.y;\r\n\t\t\t}\r\n\r\n\t\t\tif (document.documentMode == 8 && !mxClient.IS_EM)\r\n\t\t\t{\r\n\t\t\t\tx += s.dx;\r\n\t\t\t\ty += s.dy;\r\n\t\t\t\t\r\n\t\t\t\t// Workaround for rendering offsets\r\n\t\t\t\tif (overflow != 'fill' && valign == mxConstants.ALIGN_TOP)\r\n\t\t\t\t{\r\n\t\t\t\t\ty -= 1;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tx *= s.scale;\r\n\t\t\t\ty *= s.scale;\r\n\t\t\t}\r\n\r\n\t\t\t// Adds event transparency in IE8 standards without the transparent background\r\n\t\t\t// filter which cannot be used due to bugs in the zoomed bounding box (too slow)\r\n\t\t\t// FIXME: No event transparency if inside v:rect (ie part of shape)\r\n\t\t\t// KNOWN: Offset wrong for rotated text with word that are longer than the wrapping\r\n\t\t\t// width in IE8 because real width of text cannot be determined here.\r\n\t\t\t// This should be fixed in mxText.updateBoundingBox by calling before this and\r\n\t\t\t// passing the real width to this method if not clipped and wrapped.\r\n\t\t\tvar abs = (document.documentMode == 8 && !mxClient.IS_EM) ? this.createVmlElement('group') : this.createElement('div');\r\n\t\t\tabs.style.position = 'absolute';\r\n\t\t\tabs.style.display = 'inline';\r\n\t\t\tabs.style.left = this.format(x) + 'px';\r\n\t\t\tabs.style.top = this.format(y) + 'px';\r\n\t\t\tabs.style.zoom = s.scale;\r\n\r\n\t\t\tvar box = this.createElement('div');\r\n\t\t\tbox.style.position = 'relative';\r\n\t\t\tbox.style.display = 'inline';\r\n\t\t\t\r\n\t\t\tvar margin = mxUtils.getAlignmentAsPoint(align, valign);\r\n\t\t\tvar dx = margin.x;\r\n\t\t\tvar dy = margin.y;\r\n\r\n\t\t\tvar div = this.createDiv(str, align, valign, overflow);\r\n\t\t\tvar inner = this.createElement('div');\r\n\t\t\t\r\n\t\t\tif (dir != null)\r\n\t\t\t{\r\n\t\t\t\tdiv.setAttribute('dir', dir);\r\n\t\t\t}\r\n\r\n\t\t\tif (wrap && w > 0)\r\n\t\t\t{\r\n\t\t\t\tif (!clip)\r\n\t\t\t\t{\r\n\t\t\t\t\tdiv.style.width = Math.round(w) + 'px';\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tdiv.style.wordWrap = mxConstants.WORD_WRAP;\r\n\t\t\t\tdiv.style.whiteSpace = 'normal';\r\n\t\t\t\t\r\n\t\t\t\t// LATER: Check if other cases need to be handled\r\n\t\t\t\tif (div.style.wordWrap == 'break-word')\r\n\t\t\t\t{\r\n\t\t\t\t\tvar tmp = div;\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (tmp.firstChild != null && tmp.firstChild.nodeName == 'DIV')\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ttmp.firstChild.style.width = '100%';\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tdiv.style.whiteSpace = 'nowrap';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar rot = s.rotation + (rotation || 0);\r\n\t\t\t\r\n\t\t\tif (this.rotateHtml && rot != 0)\r\n\t\t\t{\r\n\t\t\t\tinner.style.display = 'inline';\r\n\t\t\t\tinner.style.zoom = '1';\r\n\t\t\t\tinner.appendChild(div);\r\n\r\n\t\t\t\t// Box not needed for rendering in IE8 standards\r\n\t\t\t\tif (document.documentMode == 8 && !mxClient.IS_EM && this.root.nodeName != 'DIV')\r\n\t\t\t\t{\r\n\t\t\t\t\tbox.appendChild(inner);\r\n\t\t\t\t\tabs.appendChild(box);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tabs.appendChild(inner);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if (document.documentMode == 8 && !mxClient.IS_EM)\r\n\t\t\t{\r\n\t\t\t\tbox.appendChild(div);\r\n\t\t\t\tabs.appendChild(box);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tdiv.style.display = 'inline';\r\n\t\t\t\tabs.appendChild(div);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Inserts the node into the DOM\r\n\t\t\tif (this.root.nodeName != 'DIV')\r\n\t\t\t{\r\n\t\t\t\t// Rectangle to fix position in group\r\n\t\t\t\tvar rect = this.createVmlElement('rect');\r\n\t\t\t\trect.stroked = 'false';\r\n\t\t\t\trect.filled = 'false';\r\n\r\n\t\t\t\trect.appendChild(abs);\r\n\t\t\t\tthis.root.appendChild(rect);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.root.appendChild(abs);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (clip)\r\n\t\t\t{\r\n\t\t\t\tdiv.style.overflow = 'hidden';\r\n\t\t\t\tdiv.style.width = Math.round(w) + 'px';\r\n\t\t\t\t\r\n\t\t\t\tif (!mxClient.IS_QUIRKS)\r\n\t\t\t\t{\r\n\t\t\t\t\tdiv.style.maxHeight = Math.round(h) + 'px';\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if (overflow == 'fill')\r\n\t\t\t{\r\n\t\t\t\t// KNOWN: Affects horizontal alignment in quirks\r\n\t\t\t\t// but fill should only be used with align=left\r\n\t\t\t\tdiv.style.overflow = 'hidden';\r\n\t\t\t\tdiv.style.width = (Math.max(0, w) + 1) + 'px';\r\n\t\t\t\tdiv.style.height = (Math.max(0, h) + 1) + 'px';\r\n\t\t\t}\r\n\t\t\telse if (overflow == 'width')\r\n\t\t\t{\r\n\t\t\t\t// KNOWN: Affects horizontal alignment in quirks\r\n\t\t\t\t// but fill should only be used with align=left\r\n\t\t\t\tdiv.style.overflow = 'hidden';\r\n\t\t\t\tdiv.style.width = (Math.max(0, w) + 1) + 'px';\r\n\t\t\t\tdiv.style.maxHeight = (Math.max(0, h) + 1) + 'px';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (this.rotateHtml && rot != 0)\r\n\t\t\t{\r\n\t\t\t\tvar rad = rot * (Math.PI / 180);\r\n\t\t\t\t\r\n\t\t\t\t// Precalculate cos and sin for the rotation\r\n\t\t\t\tvar real_cos = parseFloat(parseFloat(Math.cos(rad)).toFixed(8));\r\n\t\t\t\tvar real_sin = parseFloat(parseFloat(Math.sin(-rad)).toFixed(8));\r\n\r\n\t\t\t\trad %= 2 * Math.PI;\r\n\t\t\t\tif (rad < 0) rad += 2 * Math.PI;\r\n\t\t\t\trad %= Math.PI;\r\n\t\t\t\tif (rad > Math.PI / 2) rad = Math.PI - rad;\r\n\t\t\t\t\r\n\t\t\t\tvar cos = Math.cos(rad);\r\n\t\t\t\tvar sin = Math.sin(rad);\r\n\r\n\t\t\t\t// Adds div to document to measure size\r\n\t\t\t\tif (document.documentMode == 8 && !mxClient.IS_EM)\r\n\t\t\t\t{\r\n\t\t\t\t\tdiv.style.display = 'inline-block';\r\n\t\t\t\t\tinner.style.display = 'inline-block';\r\n\t\t\t\t\tbox.style.display = 'inline-block';\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tdiv.style.visibility = 'hidden';\r\n\t\t\t\tdiv.style.position = 'absolute';\r\n\t\t\t\tdocument.body.appendChild(div);\r\n\t\t\t\t\r\n\t\t\t\tvar sizeDiv = div;\r\n\t\t\t\t\r\n\t\t\t\tif (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')\r\n\t\t\t\t{\r\n\t\t\t\t\tsizeDiv = sizeDiv.firstChild;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tvar tmp = sizeDiv.offsetWidth + 3;\r\n\t\t\t\tvar oh = sizeDiv.offsetHeight;\r\n\t\t\t\t\r\n\t\t\t\tif (clip)\r\n\t\t\t\t{\r\n\t\t\t\t\tw = Math.min(w, tmp);\r\n\t\t\t\t\toh = Math.min(oh, h);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tw = tmp;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Handles words that are longer than the given wrapping width\r\n\t\t\t\tif (wrap)\r\n\t\t\t\t{\r\n\t\t\t\t\tdiv.style.width = w + 'px';\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Simulates max-height in quirks\r\n\t\t\t\tif (mxClient.IS_QUIRKS && (clip || overflow == 'width') && oh > h)\r\n\t\t\t\t{\r\n\t\t\t\t\toh = h;\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Quirks does not support maxHeight\r\n\t\t\t\t\tdiv.style.height = oh + 'px';\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\th = oh;\r\n\r\n\t\t\t\tvar top_fix = (h - h * cos + w * -sin) / 2 - real_sin * w * (dx + 0.5) + real_cos * h * (dy + 0.5);\r\n\t\t\t\tvar left_fix = (w - w * cos + h * -sin) / 2 + real_cos * w * (dx + 0.5) + real_sin * h * (dy + 0.5);\r\n\r\n\t\t\t\tif (abs.nodeName == 'group' && this.root.nodeName == 'DIV')\r\n\t\t\t\t{\r\n\t\t\t\t\t// Workaround for bug where group gets moved away if left and top are non-zero in IE8 standards\r\n\t\t\t\t\tvar pos = this.createElement('div');\r\n\t\t\t\t\tpos.style.display = 'inline-block';\r\n\t\t\t\t\tpos.style.position = 'absolute';\r\n\t\t\t\t\tpos.style.left = this.format(x + (left_fix - w / 2) * s.scale) + 'px';\r\n\t\t\t\t\tpos.style.top = this.format(y + (top_fix - h / 2) * s.scale) + 'px';\r\n\t\t\t\t\t\r\n\t\t\t\t\tabs.parentNode.appendChild(pos);\r\n\t\t\t\t\tpos.appendChild(abs);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tvar sc = (document.documentMode == 8 && !mxClient.IS_EM) ? 1 : s.scale;\r\n\t\t\t\t\t\r\n\t\t\t\t\tabs.style.left = this.format(x + (left_fix - w / 2) * sc) + 'px';\r\n\t\t\t\t\tabs.style.top = this.format(y + (top_fix - h / 2) * sc) + 'px';\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// KNOWN: Rotated text rendering quality is bad for IE9 quirks\r\n\t\t\t\tinner.style.filter = \"progid:DXImageTransform.Microsoft.Matrix(M11=\"+real_cos+\", M12=\"+\r\n\t\t\t\t\treal_sin+\", M21=\"+(-real_sin)+\", M22=\"+real_cos+\", sizingMethod='auto expand')\";\r\n\t\t\t\tinner.style.backgroundColor = this.rotatedHtmlBackground;\r\n\t\t\t\t\r\n\t\t\t\tif (this.state.alpha < 1)\r\n\t\t\t\t{\r\n\t\t\t\t\tinner.style.filter += 'alpha(opacity=' + (this.state.alpha * 100) + ')';\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Restore parent node for DIV\r\n\t\t\t\tinner.appendChild(div);\r\n\t\t\t\tdiv.style.position = '';\r\n\t\t\t\tdiv.style.visibility = '';\r\n\t\t\t}\r\n\t\t\telse if (document.documentMode != 8 || mxClient.IS_EM)\r\n\t\t\t{\r\n\t\t\t\tdiv.style.verticalAlign = 'top';\r\n\t\t\t\t\r\n\t\t\t\tif (this.state.alpha < 1)\r\n\t\t\t\t{\r\n\t\t\t\t\tabs.style.filter = 'alpha(opacity=' + (this.state.alpha * 100) + ')';\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Adds div to document to measure size\r\n\t\t\t\tvar divParent = div.parentNode;\r\n\t\t\t\tdiv.style.visibility = 'hidden';\r\n\t\t\t\tdocument.body.appendChild(div);\r\n\t\t\t\t\r\n\t\t\t\tw = div.offsetWidth;\r\n\t\t\t\tvar oh = div.offsetHeight;\r\n\t\t\t\t\r\n\t\t\t\t// Simulates max-height in quirks\r\n\t\t\t\tif (mxClient.IS_QUIRKS && clip && oh > h)\r\n\t\t\t\t{\r\n\t\t\t\t\toh = h;\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Quirks does not support maxHeight\r\n\t\t\t\t\tdiv.style.height = oh + 'px';\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\th = oh;\r\n\t\t\t\t\r\n\t\t\t\tdiv.style.visibility = '';\r\n\t\t\t\tdivParent.appendChild(div);\r\n\t\t\t\t\r\n\t\t\t\tabs.style.left = this.format(x + w * dx * this.state.scale) + 'px';\r\n\t\t\t\tabs.style.top = this.format(y + h * dy * this.state.scale) + 'px';\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tif (this.state.alpha < 1)\r\n\t\t\t\t{\r\n\t\t\t\t\tdiv.style.filter = 'alpha(opacity=' + (this.state.alpha * 100) + ')';\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Faster rendering in IE8 without offsetWidth/Height\r\n\t\t\t\tbox.style.left = (dx * 100) + '%';\r\n\t\t\t\tbox.style.top = (dy * 100) + '%';\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.plainText(x, y, w, h, mxUtils.htmlEntities(str, false), align, valign, wrap, format, overflow, clip, rotation, dir);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: plainText\r\n * \r\n * Paints the outline of the current path.\r\n */\r\nmxVmlCanvas2D.prototype.plainText = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)\r\n{\r\n\t// TextDirection is ignored since this code is not used (format is always HTML in the text function)\r\n\tvar s = this.state;\r\n\tx = (x + s.dx) * s.scale;\r\n\ty = (y + s.dy) * s.scale;\r\n\t\r\n\tvar node = this.createVmlElement('shape');\r\n\tnode.style.width = '1px';\r\n\tnode.style.height = '1px';\r\n\tnode.stroked = 'false';\r\n\r\n\tvar fill = this.createVmlElement('fill');\r\n\tfill.color = s.fontColor;\r\n\tfill.opacity = (s.alpha * 100) + '%';\r\n\tnode.appendChild(fill);\r\n\t\r\n\tvar path = this.createVmlElement('path');\r\n\tpath.textpathok = 'true';\r\n\tpath.v = 'm ' + this.format(0) + ' ' + this.format(0) + ' l ' + this.format(1) + ' ' + this.format(0);\r\n\t\r\n\tnode.appendChild(path);\r\n\t\r\n\t// KNOWN: Font family and text decoration ignored\r\n\tvar tp = this.createVmlElement('textpath');\r\n\ttp.style.cssText = 'v-text-align:' + align;\r\n\ttp.style.align = align;\r\n\ttp.style.fontFamily = s.fontFamily;\r\n\ttp.string = str;\r\n\ttp.on = 'true';\r\n\t\r\n\t// Scale via fontsize instead of node.style.zoom for correct offsets in IE8\r\n\tvar size = s.fontSize * s.scale / this.vmlScale;\r\n\ttp.style.fontSize = size + 'px';\r\n\t\r\n\t// Bold\r\n\tif ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)\r\n\t{\r\n\t\ttp.style.fontWeight = 'bold';\r\n\t}\r\n\t\r\n\t// Italic\r\n\tif ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)\r\n\t{\r\n\t\ttp.style.fontStyle = 'italic';\r\n\t}\r\n\r\n\t// Underline\r\n\tif ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)\r\n\t{\r\n\t\ttp.style.textDecoration = 'underline';\r\n\t}\r\n\r\n\tvar lines = str.split('\\n');\r\n\tvar textHeight = size + (lines.length - 1) * size * mxConstants.LINE_HEIGHT;\r\n\tvar dx = 0;\r\n\tvar dy = 0;\r\n\r\n\tif (valign == mxConstants.ALIGN_BOTTOM)\r\n\t{\r\n\t\tdy = - textHeight / 2;\r\n\t}\r\n\telse if (valign != mxConstants.ALIGN_MIDDLE) // top\r\n\t{\r\n\t\tdy = textHeight / 2;\r\n\t}\r\n\r\n\tif (rotation != null)\r\n\t{\r\n\t\tnode.style.rotation = rotation;\r\n\t\tvar rad = rotation * (Math.PI / 180);\r\n\t\tdx = Math.sin(rad) * dy;\r\n\t\tdy = Math.cos(rad) * dy;\r\n\t}\r\n\r\n\t// FIXME: Clipping is relative to bounding box\r\n\t/*if (clip)\r\n\t{\r\n\t\tnode.style.clip = 'rect(0px ' + this.format(w) + 'px ' + this.format(h) + 'px 0px)';\r\n\t}*/\r\n\t\r\n\tnode.appendChild(tp);\r\n\tnode.style.left = this.format(x - dx) + 'px';\r\n\tnode.style.top = this.format(y + dy) + 'px';\r\n\t\r\n\tthis.root.appendChild(node);\r\n};\r\n\r\n/**\r\n * Function: stroke\r\n * \r\n * Paints the outline of the current path.\r\n */\r\nmxVmlCanvas2D.prototype.stroke = function()\r\n{\r\n\tthis.addNode(false, true);\r\n};\r\n\r\n/**\r\n * Function: fill\r\n * \r\n * Fills the current path.\r\n */\r\nmxVmlCanvas2D.prototype.fill = function()\r\n{\r\n\tthis.addNode(true, false);\r\n};\r\n\r\n/**\r\n * Function: fillAndStroke\r\n * \r\n * Fills and paints the outline of the current path.\r\n */\r\nmxVmlCanvas2D.prototype.fillAndStroke = function()\r\n{\r\n\tthis.addNode(true, true);\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxGuide\r\n *\r\n * Implements the alignment of selection cells to other cells in the graph.\r\n * \r\n * Constructor: mxGuide\r\n * \r\n * Constructs a new guide object.\r\n */\r\nfunction mxGuide(graph, states)\r\n{\r\n\tthis.graph = graph;\r\n\tthis.setStates(states);\r\n};\r\n\r\n/**\r\n * Variable: graph\r\n *\r\n * Reference to the enclosing <mxGraph> instance.\r\n */\r\nmxGuide.prototype.graph = null;\r\n\r\n/**\r\n * Variable: states\r\n * \r\n * Contains the <mxCellStates> that are used for alignment.\r\n */\r\nmxGuide.prototype.states = null;\r\n\r\n/**\r\n * Variable: horizontal\r\n *\r\n * Specifies if horizontal guides are enabled. Default is true.\r\n */\r\nmxGuide.prototype.horizontal = true;\r\n\r\n/**\r\n * Variable: vertical\r\n *\r\n * Specifies if vertical guides are enabled. Default is true.\r\n */\r\nmxGuide.prototype.vertical = true;\r\n\r\n/**\r\n * Variable: vertical\r\n *\r\n * Holds the <mxShape> for the horizontal guide.\r\n */\r\nmxGuide.prototype.guideX = null;\r\n\r\n/**\r\n * Variable: vertical\r\n *\r\n * Holds the <mxShape> for the vertical guide.\r\n */\r\nmxGuide.prototype.guideY = null;\r\n\r\n/**\r\n * Variable: rounded\r\n *\r\n * Specifies if rounded coordinates should be used. Default is false.\r\n */\r\nmxGuide.prototype.rounded = false;\r\n\r\n/**\r\n * Function: setStates\r\n * \r\n * Sets the <mxCellStates> that should be used for alignment.\r\n */\r\nmxGuide.prototype.setStates = function(states)\r\n{\r\n\tthis.states = states;\r\n};\r\n\r\n/**\r\n * Function: isEnabledForEvent\r\n * \r\n * Returns true if the guide should be enabled for the given native event. This\r\n * implementation always returns true.\r\n */\r\nmxGuide.prototype.isEnabledForEvent = function(evt)\r\n{\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: getGuideTolerance\r\n * \r\n * Returns the tolerance for the guides. Default value is gridSize / 2.\r\n */\r\nmxGuide.prototype.getGuideTolerance = function()\r\n{\r\n\treturn this.graph.gridSize / 2;\r\n};\r\n\r\n/**\r\n * Function: createGuideShape\r\n * \r\n * Returns the mxShape to be used for painting the respective guide. This\r\n * implementation returns a new, dashed and crisp <mxPolyline> using\r\n * <mxConstants.GUIDE_COLOR> and <mxConstants.GUIDE_STROKEWIDTH> as the format.\r\n * \r\n * Parameters:\r\n * \r\n * horizontal - Boolean that specifies which guide should be created.\r\n */\r\nmxGuide.prototype.createGuideShape = function(horizontal)\r\n{\r\n\tvar guide = new mxPolyline([], mxConstants.GUIDE_COLOR, mxConstants.GUIDE_STROKEWIDTH);\r\n\tguide.isDashed = true;\r\n\t\r\n\treturn guide;\r\n};\r\n\r\n/**\r\n * Function: move\r\n * \r\n * Moves the <bounds> by the given <mxPoint> and returnt the snapped point.\r\n */\r\nmxGuide.prototype.move = function(bounds, delta, gridEnabled, clone)\r\n{\r\n\tif (this.states != null && (this.horizontal || this.vertical) && bounds != null && delta != null)\r\n\t{\r\n\t\tvar trx = this.graph.getView().translate;\r\n\t\tvar scale = this.graph.getView().scale;\r\n\t\tvar dx = delta.x;\r\n\t\tvar dy = delta.y;\r\n\t\t\r\n\t\tvar overrideX = false;\r\n\t\tvar stateX = null;\r\n\t\tvar valueX = null;\r\n\t\tvar overrideY = false;\r\n\t\tvar stateY = null;\r\n\t\tvar valueY = null;\r\n\t\t\r\n\t\tvar tt = this.getGuideTolerance();\r\n\t\tvar ttX = tt;\r\n\t\tvar ttY = tt;\r\n\t\t\r\n\t\tvar b = bounds.clone();\r\n\t\tb.x += delta.x;\r\n\t\tb.y += delta.y;\r\n\t\t\r\n\t\tvar left = b.x;\r\n\t\tvar right = b.x + b.width;\r\n\t\tvar center = b.getCenterX();\r\n\t\tvar top = b.y;\r\n\t\tvar bottom = b.y + b.height;\r\n\t\tvar middle = b.getCenterY();\r\n\t\r\n\t\t// Snaps the left, center and right to the given x-coordinate\r\n\t\tfunction snapX(x, state)\r\n\t\t{\r\n\t\t\tx += this.graph.panDx;\r\n\t\t\tvar override = false;\r\n\t\t\t\r\n\t\t\tif (Math.abs(x - center) < ttX)\r\n\t\t\t{\r\n\t\t\t\tdx = x - bounds.getCenterX();\r\n\t\t\t\tttX = Math.abs(x - center);\r\n\t\t\t\toverride = true;\r\n\t\t\t}\r\n\t\t\telse if (Math.abs(x - left) < ttX)\r\n\t\t\t{\r\n\t\t\t\tdx = x - bounds.x;\r\n\t\t\t\tttX = Math.abs(x - left);\r\n\t\t\t\toverride = true;\r\n\t\t\t}\r\n\t\t\telse if (Math.abs(x - right) < ttX)\r\n\t\t\t{\r\n\t\t\t\tdx = x - bounds.x - bounds.width;\r\n\t\t\t\tttX = Math.abs(x - right);\r\n\t\t\t\toverride = true;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (override)\r\n\t\t\t{\r\n\t\t\t\tstateX = state;\r\n\t\t\t\tvalueX = Math.round(x - this.graph.panDx);\r\n\t\t\t\t\r\n\t\t\t\tif (this.guideX == null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.guideX = this.createGuideShape(true);\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Makes sure to use either VML or SVG shapes in order to implement\r\n\t\t\t\t\t// event-transparency on the background area of the rectangle since\r\n\t\t\t\t\t// HTML shapes do not let mouseevents through even when transparent\r\n\t\t\t\t\tthis.guideX.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?\r\n\t\t\t\t\t\tmxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;\r\n\t\t\t\t\tthis.guideX.pointerEvents = false;\r\n\t\t\t\t\tthis.guideX.init(this.graph.getView().getOverlayPane());\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\toverrideX = overrideX || override;\r\n\t\t};\r\n\t\t\r\n\t\t// Snaps the top, middle or bottom to the given y-coordinate\r\n\t\tfunction snapY(y, state)\r\n\t\t{\r\n\t\t\ty += this.graph.panDy;\r\n\t\t\tvar override = false;\r\n\t\t\t\r\n\t\t\tif (Math.abs(y - middle) < ttY)\r\n\t\t\t{\r\n\t\t\t\tdy = y - bounds.getCenterY();\r\n\t\t\t\tttY = Math.abs(y -  middle);\r\n\t\t\t\toverride = true;\r\n\t\t\t}\r\n\t\t\telse if (Math.abs(y - top) < ttY)\r\n\t\t\t{\r\n\t\t\t\tdy = y - bounds.y;\r\n\t\t\t\tttY = Math.abs(y - top);\r\n\t\t\t\toverride = true;\r\n\t\t\t}\r\n\t\t\telse if (Math.abs(y - bottom) < ttY)\r\n\t\t\t{\r\n\t\t\t\tdy = y - bounds.y - bounds.height;\r\n\t\t\t\tttY = Math.abs(y - bottom);\r\n\t\t\t\toverride = true;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (override)\r\n\t\t\t{\r\n\t\t\t\tstateY = state;\r\n\t\t\t\tvalueY = Math.round(y - this.graph.panDy);\r\n\t\t\t\t\r\n\t\t\t\tif (this.guideY == null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.guideY = this.createGuideShape(false);\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Makes sure to use either VML or SVG shapes in order to implement\r\n\t\t\t\t\t// event-transparency on the background area of the rectangle since\r\n\t\t\t\t\t// HTML shapes do not let mouseevents through even when transparent\r\n\t\t\t\t\tthis.guideY.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?\r\n\t\t\t\t\t\tmxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;\r\n\t\t\t\t\tthis.guideY.pointerEvents = false;\r\n\t\t\t\t\tthis.guideY.init(this.graph.getView().getOverlayPane());\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\toverrideY = overrideY || override;\r\n\t\t};\r\n\t\t\r\n\t\tfor (var i = 0; i < this.states.length; i++)\r\n\t\t{\r\n\t\t\tvar state =  this.states[i];\r\n\t\t\t\r\n\t\t\tif (state != null)\r\n\t\t\t{\r\n\t\t\t\t// Align x\r\n\t\t\t\tif (this.horizontal)\r\n\t\t\t\t{\r\n\t\t\t\t\tsnapX.call(this, state.getCenterX(), state);\r\n\t\t\t\t\tsnapX.call(this, state.x, state);\r\n\t\t\t\t\tsnapX.call(this, state.x + state.width, state);\r\n\t\t\t\t}\r\n\t\r\n\t\t\t\t// Align y\r\n\t\t\t\tif (this.vertical)\r\n\t\t\t\t{\r\n\t\t\t\t\tsnapY.call(this, state.getCenterY(), state);\r\n\t\t\t\t\tsnapY.call(this, state.y, state);\r\n\t\t\t\t\tsnapY.call(this, state.y + state.height, state);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Moves cells that are off-grid back to the grid on move\r\n\t\tif (gridEnabled)\r\n\t\t{\r\n\t\t\tif (!overrideX)\r\n\t\t\t{\r\n\t\t\t\tvar tx = bounds.x - (this.graph.snap(bounds.x /\r\n\t\t\t\t\tscale - trx.x) + trx.x) * scale;\r\n\t\t\t\tdx = this.graph.snap(dx / scale) * scale - tx;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (!overrideY)\r\n\t\t\t{\r\n\t\t\t\tvar ty = bounds.y - (this.graph.snap(bounds.y /\r\n\t\t\t\t\tscale - trx.y) + trx.y) * scale;\r\n\t\t\t\tdy = this.graph.snap(dy / scale) * scale - ty;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Redraws the guides\r\n\t\tvar c = this.graph.container;\r\n\t\t\r\n\t\tif (!overrideX && this.guideX != null)\r\n\t\t{\r\n\t\t\tthis.guideX.node.style.visibility = 'hidden';\r\n\t\t}\r\n\t\telse if (this.guideX != null)\r\n\t\t{\r\n\t\t\tif (stateX != null && bounds != null)\r\n\t\t\t{\r\n\t\t\t\tminY = Math.min(bounds.y + dy - this.graph.panDy, stateX.y);\r\n\t\t\t\tmaxY = Math.max(bounds.y + bounds.height + dy - this.graph.panDy, stateX.y + stateX.height);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (minY != null && maxY != null)\r\n\t\t\t{\r\n\t\t\t\tthis.guideX.points = [new mxPoint(valueX, minY), new mxPoint(valueX, maxY)];\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.guideX.points = [new mxPoint(valueX, -this.graph.panDy), new mxPoint(valueX, c.scrollHeight - 3 - this.graph.panDy)];\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.guideX.stroke = this.getGuideColor(stateX, true);\r\n\t\t\tthis.guideX.node.style.visibility = 'visible';\r\n\t\t\tthis.guideX.redraw();\r\n\t\t}\r\n\t\t\r\n\t\tif (!overrideY && this.guideY != null)\r\n\t\t{\r\n\t\t\tthis.guideY.node.style.visibility = 'hidden';\r\n\t\t}\r\n\t\telse if (this.guideY != null)\r\n\t\t{\r\n\t\t\tif (stateY != null && bounds != null)\r\n\t\t\t{\r\n\t\t\t\tminX = Math.min(bounds.x + dx - this.graph.panDx, stateY.x);\r\n\t\t\t\tmaxX = Math.max(bounds.x + bounds.width + dx - this.graph.panDx, stateY.x + stateY.width);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (minX != null && maxX != null)\r\n\t\t\t{\r\n\t\t\t\tthis.guideY.points = [new mxPoint(minX, valueY), new mxPoint(maxX, valueY)];\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.guideY.points = [new mxPoint(-this.graph.panDx, valueY), new mxPoint(c.scrollWidth - 3 - this.graph.panDx, valueY)];\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.guideY.stroke = this.getGuideColor(stateY, false);\r\n\t\t\tthis.guideY.node.style.visibility = 'visible';\r\n\t\t\tthis.guideY.redraw();\r\n\t\t}\r\n\r\n\t\tdelta = this.getDelta(bounds, stateX, dx, stateY, dy)\r\n\t}\r\n\t\r\n\treturn delta;\r\n};\r\n\r\n/**\r\n * Function: hide\r\n * \r\n * Hides all current guides.\r\n */\r\nmxGuide.prototype.getDelta = function(bounds, stateX, dx, stateY, dy)\r\n{\r\n\t// Round to pixels for virtual states (eg. page guides)\r\n\tif (this.rounded || (stateX != null && stateX.cell == null))\r\n\t{\r\n\t\tdx = Math.floor(bounds.x + dx) - bounds.x;\r\n\t}\r\n\r\n\tif (this.rounded || (stateY != null && stateY.cell == null))\r\n\t{\r\n\t\tdy = Math.floor(bounds.y + dy) - bounds.y;\r\n\t}\r\n\t\r\n\treturn new mxPoint(dx, dy);\r\n};\r\n\r\n/**\r\n * Function: hide\r\n * \r\n * Hides all current guides.\r\n */\r\nmxGuide.prototype.getGuideColor = function(state, horizontal)\r\n{\r\n\treturn mxConstants.GUIDE_COLOR;\r\n};\r\n\r\n/**\r\n * Function: hide\r\n * \r\n * Hides all current guides.\r\n */\r\nmxGuide.prototype.hide = function()\r\n{\r\n\tthis.setVisible(false);\r\n};\r\n\r\n/**\r\n * Function: setVisible\r\n * \r\n * Shows or hides the current guides.\r\n */\r\nmxGuide.prototype.setVisible = function(visible)\r\n{\r\n\tif (this.guideX != null)\r\n\t{\r\n\t\tthis.guideX.node.style.visibility = (visible) ? 'visible' : 'hidden';\r\n\t}\r\n\t\r\n\tif (this.guideY != null)\r\n\t{\r\n\t\tthis.guideY.node.style.visibility = (visible) ? 'visible' : 'hidden';\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Destroys all resources that this object uses.\r\n */\r\nmxGuide.prototype.destroy = function()\r\n{\r\n\tif (this.guideX != null)\r\n\t{\r\n\t\tthis.guideX.destroy();\r\n\t\tthis.guideX = null;\r\n\t}\r\n\t\r\n\tif (this.guideY != null)\r\n\t{\r\n\t\tthis.guideY.destroy();\r\n\t\tthis.guideY = null;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxShape\r\n *\r\n * Base class for all shapes. A shape in mxGraph is a\r\n * separate implementation for SVG, VML and HTML. Which\r\n * implementation to use is controlled by the <dialect>\r\n * property which is assigned from within the <mxCellRenderer>\r\n * when the shape is created. The dialect must be assigned\r\n * for a shape, and it does normally depend on the browser and\r\n * the confiuration of the graph (see <mxGraph> rendering hint).\r\n *\r\n * For each supported shape in SVG and VML, a corresponding\r\n * shape exists in mxGraph, namely for text, image, rectangle,\r\n * rhombus, ellipse and polyline. The other shapes are a\r\n * combination of these shapes (eg. label and swimlane)\r\n * or they consist of one or more (filled) path objects\r\n * (eg. actor and cylinder). The HTML implementation is\r\n * optional but may be required for a HTML-only view of\r\n * the graph.\r\n *\r\n * Custom Shapes:\r\n *\r\n * To extend from this class, the basic code looks as follows.\r\n * In the special case where the custom shape consists only of\r\n * one filled region or one filled region and an additional stroke\r\n * the <mxActor> and <mxCylinder> should be subclassed,\r\n * respectively.\r\n *\r\n * (code)\r\n * function CustomShape() { }\r\n * \r\n * CustomShape.prototype = new mxShape();\r\n * CustomShape.prototype.constructor = CustomShape; \r\n * (end)\r\n *\r\n * To register a custom shape in an existing graph instance,\r\n * one must register the shape under a new name in the graph's\r\n * cell renderer as follows:\r\n *\r\n * (code)\r\n * mxCellRenderer.registerShape('customShape', CustomShape);\r\n * (end)\r\n *\r\n * The second argument is the name of the constructor.\r\n *\r\n * In order to use the shape you can refer to the given name above\r\n * in a stylesheet. For example, to change the shape for the default\r\n * vertex style, the following code is used:\r\n *\r\n * (code)\r\n * var style = graph.getStylesheet().getDefaultVertexStyle();\r\n * style[mxConstants.STYLE_SHAPE] = 'customShape';\r\n * (end)\r\n * \r\n * Constructor: mxShape\r\n *\r\n * Constructs a new shape.\r\n */\r\nfunction mxShape(stencil)\r\n{\r\n\tthis.stencil = stencil;\r\n\tthis.initStyles();\r\n};\r\n\r\n/**\r\n * Variable: dialect\r\n *\r\n * Holds the dialect in which the shape is to be painted.\r\n * This can be one of the DIALECT constants in <mxConstants>.\r\n */\r\nmxShape.prototype.dialect = null;\r\n\r\n/**\r\n * Variable: scale\r\n *\r\n * Holds the scale in which the shape is being painted.\r\n */\r\nmxShape.prototype.scale = 1;\r\n\r\n/**\r\n * Variable: antiAlias\r\n * \r\n * Rendering hint for configuring the canvas.\r\n */\r\nmxShape.prototype.antiAlias = true;\r\n\r\n/**\r\n * Variable: minSvgStrokeWidth\r\n * \r\n * Minimum stroke width for SVG output.\r\n */\r\nmxShape.prototype.minSvgStrokeWidth = 1;\r\n\r\n/**\r\n * Variable: bounds\r\n *\r\n * Holds the <mxRectangle> that specifies the bounds of this shape.\r\n */\r\nmxShape.prototype.bounds = null;\r\n\r\n/**\r\n * Variable: points\r\n *\r\n * Holds the array of <mxPoints> that specify the points of this shape.\r\n */\r\nmxShape.prototype.points = null;\r\n\r\n/**\r\n * Variable: node\r\n *\r\n * Holds the outermost DOM node that represents this shape.\r\n */\r\nmxShape.prototype.node = null;\r\n \r\n/**\r\n * Variable: state\r\n * \r\n * Optional reference to the corresponding <mxCellState>.\r\n */\r\nmxShape.prototype.state = null;\r\n\r\n/**\r\n * Variable: style\r\n *\r\n * Optional reference to the style of the corresponding <mxCellState>.\r\n */\r\nmxShape.prototype.style = null;\r\n\r\n/**\r\n * Variable: boundingBox\r\n *\r\n * Contains the bounding box of the shape, that is, the smallest rectangle\r\n * that includes all pixels of the shape.\r\n */\r\nmxShape.prototype.boundingBox = null;\r\n\r\n/**\r\n * Variable: stencil\r\n *\r\n * Holds the <mxStencil> that defines the shape.\r\n */\r\nmxShape.prototype.stencil = null;\r\n\r\n/**\r\n * Variable: svgStrokeTolerance\r\n *\r\n * Event-tolerance for SVG strokes (in px). Default is 8. This is only passed\r\n * to the canvas in <createSvgCanvas> if <pointerEvents> is true.\r\n */\r\nmxShape.prototype.svgStrokeTolerance = 8;\r\n\r\n/**\r\n * Variable: pointerEvents\r\n * \r\n * Specifies if pointer events should be handled. Default is true.\r\n */\r\nmxShape.prototype.pointerEvents = true;\r\n\r\n/**\r\n * Variable: svgPointerEvents\r\n * \r\n * Specifies if pointer events should be handled. Default is true.\r\n */\r\nmxShape.prototype.svgPointerEvents = 'all';\r\n\r\n/**\r\n * Variable: shapePointerEvents\r\n * \r\n * Specifies if pointer events outside of shape should be handled. Default\r\n * is false.\r\n */\r\nmxShape.prototype.shapePointerEvents = false;\r\n\r\n/**\r\n * Variable: stencilPointerEvents\r\n * \r\n * Specifies if pointer events outside of stencils should be handled. Default\r\n * is false. Set this to true for backwards compatibility with the 1.x branch.\r\n */\r\nmxShape.prototype.stencilPointerEvents = false;\r\n\r\n/**\r\n * Variable: vmlScale\r\n * \r\n * Scale for improving the precision of VML rendering. Default is 1.\r\n */\r\nmxShape.prototype.vmlScale = 1;\r\n\r\n/**\r\n * Variable: outline\r\n * \r\n * Specifies if the shape should be drawn as an outline. This disables all\r\n * fill colors and can be used to disable other drawing states that should\r\n * not be painted for outlines. Default is false. This should be set before\r\n * calling <apply>.\r\n */\r\nmxShape.prototype.outline = false;\r\n\r\n/**\r\n * Variable: visible\r\n * \r\n * Specifies if the shape is visible. Default is true.\r\n */\r\nmxShape.prototype.visible = true;\r\n\r\n/**\r\n * Variable: useSvgBoundingBox\r\n * \r\n * Allows to use the SVG bounding box in SVG. Default is false for performance\r\n * reasons.\r\n */\r\nmxShape.prototype.useSvgBoundingBox = false;\r\n\r\n/**\r\n * Function: init\r\n *\r\n * Initializes the shape by creaing the DOM node using <create>\r\n * and adding it into the given container.\r\n *\r\n * Parameters:\r\n *\r\n * container - DOM node that will contain the shape.\r\n */\r\nmxShape.prototype.init = function(container)\r\n{\r\n\tif (this.node == null)\r\n\t{\r\n\t\tthis.node = this.create(container);\r\n\t\t\r\n\t\tif (container != null)\r\n\t\t{\r\n\t\t\tcontainer.appendChild(this.node);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: initStyles\r\n *\r\n * Sets the styles to their default values.\r\n */\r\nmxShape.prototype.initStyles = function(container)\r\n{\r\n\tthis.strokewidth = 1;\r\n\tthis.rotation = 0;\r\n\tthis.opacity = 100;\r\n\tthis.fillOpacity = 100;\r\n\tthis.strokeOpacity = 100;\r\n\tthis.flipH = false;\r\n\tthis.flipV = false;\r\n};\r\n\r\n/**\r\n * Function: isParseVml\r\n * \r\n * Specifies if any VML should be added via insertAdjacentHtml to the DOM. This\r\n * is only needed in IE8 and only if the shape contains VML markup. This method\r\n * returns true.\r\n */\r\nmxShape.prototype.isParseVml = function()\r\n{\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: isHtmlAllowed\r\n * \r\n * Returns true if HTML is allowed for this shape. This implementation always\r\n * returns false.\r\n */\r\nmxShape.prototype.isHtmlAllowed = function()\r\n{\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: getSvgScreenOffset\r\n * \r\n * Returns 0, or 0.5 if <strokewidth> % 2 == 1.\r\n */\r\nmxShape.prototype.getSvgScreenOffset = function()\r\n{\r\n\tvar sw = this.stencil && this.stencil.strokewidth != 'inherit' ? Number(this.stencil.strokewidth) : this.strokewidth;\r\n\t\r\n\treturn (mxUtils.mod(Math.max(1, Math.round(sw * this.scale)), 2) == 1) ? 0.5 : 0;\r\n};\r\n\r\n/**\r\n * Function: create\r\n *\r\n * Creates and returns the DOM node(s) for the shape in\r\n * the given container. This implementation invokes\r\n * <createSvg>, <createHtml> or <createVml> depending\r\n * on the <dialect> and style settings.\r\n *\r\n * Parameters:\r\n *\r\n * container - DOM node that will contain the shape.\r\n */\r\nmxShape.prototype.create = function(container)\r\n{\r\n\tvar node = null;\r\n\t\r\n\tif (container != null && container.ownerSVGElement != null)\r\n\t{\r\n\t\tnode = this.createSvg(container);\r\n\t}\r\n\telse if (document.documentMode == 8 || !mxClient.IS_VML ||\r\n\t\t(this.dialect != mxConstants.DIALECT_VML && this.isHtmlAllowed()))\r\n\t{\r\n\t\tnode = this.createHtml(container);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tnode = this.createVml(container);\r\n\t}\r\n\t\r\n\treturn node;\r\n};\r\n\r\n/**\r\n * Function: createSvg\r\n *\r\n * Creates and returns the SVG node(s) to represent this shape.\r\n */\r\nmxShape.prototype.createSvg = function()\r\n{\r\n\treturn document.createElementNS(mxConstants.NS_SVG, 'g');\r\n};\r\n\r\n/**\r\n * Function: createVml\r\n *\r\n * Creates and returns the VML node to represent this shape.\r\n */\r\nmxShape.prototype.createVml = function()\r\n{\r\n\tvar node = document.createElement(mxClient.VML_PREFIX + ':group');\r\n\tnode.style.position = 'absolute';\r\n\t\r\n\treturn node;\r\n};\r\n\r\n/**\r\n * Function: createHtml\r\n *\r\n * Creates and returns the HTML DOM node(s) to represent\r\n * this shape. This implementation falls back to <createVml>\r\n * so that the HTML creation is optional.\r\n */\r\nmxShape.prototype.createHtml = function()\r\n{\r\n\tvar node = document.createElement('div');\r\n\tnode.style.position = 'absolute';\r\n\t\r\n\treturn node;\r\n};\r\n\r\n/**\r\n * Function: reconfigure\r\n *\r\n * Reconfigures this shape. This will update the colors etc in\r\n * addition to the bounds or points.\r\n */\r\nmxShape.prototype.reconfigure = function()\r\n{\r\n\tthis.redraw();\r\n};\r\n\r\n/**\r\n * Function: redraw\r\n *\r\n * Creates and returns the SVG node(s) to represent this shape.\r\n */\r\nmxShape.prototype.redraw = function()\r\n{\r\n\tthis.updateBoundsFromPoints();\r\n\t\r\n\tif (this.visible && this.checkBounds())\r\n\t{\r\n\t\tthis.node.style.visibility = 'visible';\r\n\t\tthis.clear();\r\n\t\t\r\n\t\tif (this.node.nodeName == 'DIV' && (this.isHtmlAllowed() || !mxClient.IS_VML))\r\n\t\t{\r\n\t\t\tthis.redrawHtmlShape();\r\n\t\t}\r\n\t\telse\r\n\t\t{\t\r\n\t\t\tthis.redrawShape();\r\n\t\t}\r\n\r\n\t\tthis.updateBoundingBox();\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.node.style.visibility = 'hidden';\r\n\t\tthis.boundingBox = null;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: clear\r\n * \r\n * Removes all child nodes and resets all CSS.\r\n */\r\nmxShape.prototype.clear = function()\r\n{\r\n\tif (this.node.ownerSVGElement != null)\r\n\t{\r\n\t\twhile (this.node.lastChild != null)\r\n\t\t{\r\n\t\t\tthis.node.removeChild(this.node.lastChild);\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.node.style.cssText = 'position:absolute;' + ((this.cursor != null) ?\r\n\t\t\t('cursor:' + this.cursor + ';') : '');\r\n\t\tthis.node.innerHTML = '';\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: updateBoundsFromPoints\r\n * \r\n * Updates the bounds based on the points.\r\n */\r\nmxShape.prototype.updateBoundsFromPoints = function()\r\n{\r\n\tvar pts = this.points;\r\n\t\r\n\tif (pts != null && pts.length > 0 && pts[0] != null)\r\n\t{\r\n\t\tthis.bounds = new mxRectangle(Number(pts[0].x), Number(pts[0].y), 1, 1);\r\n\t\t\r\n\t\tfor (var i = 1; i < this.points.length; i++)\r\n\t\t{\r\n\t\t\tif (pts[i] != null)\r\n\t\t\t{\r\n\t\t\t\tthis.bounds.add(new mxRectangle(Number(pts[i].x), Number(pts[i].y), 1, 1));\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getLabelBounds\r\n * \r\n * Returns the <mxRectangle> for the label bounds of this shape, based on the\r\n * given scaled and translated bounds of the shape. This method should not\r\n * change the rectangle in-place. This implementation returns the given rect.\r\n */\r\nmxShape.prototype.getLabelBounds = function(rect)\r\n{\r\n\tvar d = mxUtils.getValue(this.style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST);\r\n\tvar bounds = rect;\r\n\t\r\n\t// Normalizes argument for getLabelMargins hook\r\n\tif (d != mxConstants.DIRECTION_SOUTH && d != mxConstants.DIRECTION_NORTH &&\r\n\t\tthis.state != null && this.state.text != null &&\r\n\t\tthis.state.text.isPaintBoundsInverted())\r\n\t{\r\n\t\tbounds = bounds.clone();\r\n\t\tvar tmp = bounds.width;\r\n\t\tbounds.width = bounds.height;\r\n\t\tbounds.height = tmp;\r\n\t}\r\n\t\t\r\n\tvar m = this.getLabelMargins(bounds);\r\n\t\r\n\tif (m != null)\r\n\t{\r\n\t\tvar flipH = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPH, false) == '1';\r\n\t\tvar flipV = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPV, false) == '1';\r\n\t\t\r\n\t\t// Handles special case for vertical labels\r\n\t\tif (this.state != null && this.state.text != null &&\r\n\t\t\tthis.state.text.isPaintBoundsInverted())\r\n\t\t{\r\n\t\t\tvar tmp = m.x;\r\n\t\t\tm.x = m.height;\r\n\t\t\tm.height = m.width;\r\n\t\t\tm.width = m.y;\r\n\t\t\tm.y = tmp;\r\n\r\n\t\t\ttmp = flipH;\r\n\t\t\tflipH = flipV;\r\n\t\t\tflipV = tmp;\r\n\t\t}\r\n\t\t\r\n\t\treturn mxUtils.getDirectedBounds(rect, m, this.style, flipH, flipV);\r\n\t}\r\n\t\r\n\treturn rect;\r\n};\r\n\r\n/**\r\n * Function: getLabelMargins\r\n * \r\n * Returns the scaled top, left, bottom and right margin to be used for\r\n * computing the label bounds as an <mxRectangle>, where the bottom and right\r\n * margin are defined in the width and height of the rectangle, respectively.\r\n */\r\nmxShape.prototype.getLabelMargins= function(rect)\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: checkBounds\r\n * \r\n * Returns true if the bounds are not null and all of its variables are numeric.\r\n */\r\nmxShape.prototype.checkBounds = function()\r\n{\r\n\treturn (!isNaN(this.scale) && isFinite(this.scale) && this.scale > 0 &&\r\n\t\t\tthis.bounds != null && !isNaN(this.bounds.x) && !isNaN(this.bounds.y) &&\r\n\t\t\t!isNaN(this.bounds.width) && !isNaN(this.bounds.height) &&\r\n\t\t\tthis.bounds.width > 0 && this.bounds.height > 0);\r\n};\r\n\r\n/**\r\n * Function: createVmlGroup\r\n *\r\n * Returns the temporary element used for rendering in IE8 standards mode.\r\n */\r\nmxShape.prototype.createVmlGroup = function()\r\n{\r\n\tvar node = document.createElement(mxClient.VML_PREFIX + ':group');\r\n\tnode.style.position = 'absolute';\r\n\tnode.style.width = this.node.style.width;\r\n\tnode.style.height = this.node.style.height;\r\n\t\r\n\treturn node;\r\n};\r\n\r\n/**\r\n * Function: redrawShape\r\n *\r\n * Updates the SVG or VML shape.\r\n */\r\nmxShape.prototype.redrawShape = function()\r\n{\r\n\tvar canvas = this.createCanvas();\r\n\t\r\n\tif (canvas != null)\r\n\t{\r\n\t\t// Specifies if events should be handled\r\n\t\tcanvas.pointerEvents = this.pointerEvents;\r\n\t\r\n\t\tthis.paint(canvas);\r\n\t\r\n\t\tif (this.node != canvas.root)\r\n\t\t{\r\n\t\t\t// Forces parsing in IE8 standards mode - slow! avoid\r\n\t\t\tthis.node.insertAdjacentHTML('beforeend', canvas.root.outerHTML);\r\n\t\t}\r\n\t\r\n\t\tif (this.node.nodeName == 'DIV' && document.documentMode == 8)\r\n\t\t{\r\n\t\t\t// Makes DIV transparent to events for IE8 in IE8 standards\r\n\t\t\t// mode (Note: Does not work for IE9 in IE8 standards mode\r\n\t\t\t// and not for IE11 in enterprise mode)\r\n\t\t\tthis.node.style.filter = '';\r\n\t\t\t\r\n\t\t\t// Adds event transparency in IE8 standards\r\n\t\t\tmxUtils.addTransparentBackgroundFilter(this.node);\r\n\t\t}\r\n\t\t\r\n\t\tthis.destroyCanvas(canvas);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createCanvas\r\n * \r\n * Creates a new canvas for drawing this shape. May return null.\r\n */\r\nmxShape.prototype.createCanvas = function()\r\n{\r\n\tvar canvas = null;\r\n\t\r\n\t// LATER: Check if reusing existing DOM nodes improves performance\r\n\tif (this.node.ownerSVGElement != null)\r\n\t{\r\n\t\tcanvas = this.createSvgCanvas();\r\n\t}\r\n\telse if (mxClient.IS_VML)\r\n\t{\r\n\t\tthis.updateVmlContainer();\r\n\t\tcanvas = this.createVmlCanvas();\r\n\t}\r\n\t\r\n\tif (canvas != null && this.outline)\r\n\t{\r\n\t\tcanvas.setStrokeWidth(this.strokewidth);\r\n\t\tcanvas.setStrokeColor(this.stroke);\r\n\t\t\r\n\t\tif (this.isDashed != null)\r\n\t\t{\r\n\t\t\tcanvas.setDashed(this.isDashed);\r\n\t\t}\r\n\t\t\r\n\t\tcanvas.setStrokeWidth = function() {};\r\n\t\tcanvas.setStrokeColor = function() {};\r\n\t\tcanvas.setFillColor = function() {};\r\n\t\tcanvas.setGradient = function() {};\r\n\t\tcanvas.setDashed = function() {};\r\n\t\tcanvas.text = function() {};\r\n\t}\r\n\r\n\treturn canvas;\r\n};\r\n\r\n/**\r\n * Function: createSvgCanvas\r\n * \r\n * Creates and returns an <mxSvgCanvas2D> for rendering this shape.\r\n */\r\nmxShape.prototype.createSvgCanvas = function()\r\n{\r\n\tvar canvas = new mxSvgCanvas2D(this.node, false);\r\n\tcanvas.strokeTolerance = (this.pointerEvents) ? this.svgStrokeTolerance : 0;\r\n\tcanvas.pointerEventsValue = this.svgPointerEvents;\r\n\tcanvas.blockImagePointerEvents = mxClient.IS_FF;\r\n\tvar off = this.getSvgScreenOffset();\r\n\r\n\tif (off != 0)\r\n\t{\r\n\t\tthis.node.setAttribute('transform', 'translate(' + off + ',' + off + ')');\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.node.removeAttribute('transform');\r\n\t}\r\n\r\n\tcanvas.minStrokeWidth = this.minSvgStrokeWidth;\r\n\t\r\n\tif (!this.antiAlias)\r\n\t{\r\n\t\t// Rounds all numbers in the SVG output to integers\r\n\t\tcanvas.format = function(value)\r\n\t\t{\r\n\t\t\treturn Math.round(parseFloat(value));\r\n\t\t};\r\n\t}\r\n\t\r\n\treturn canvas;\r\n};\r\n\r\n/**\r\n * Function: createVmlCanvas\r\n * \r\n * Creates and returns an <mxVmlCanvas2D> for rendering this shape.\r\n */\r\nmxShape.prototype.createVmlCanvas = function()\r\n{\r\n\t// Workaround for VML rendering bug in IE8 standards mode\r\n\tvar node = (document.documentMode == 8 && this.isParseVml()) ? this.createVmlGroup() : this.node;\r\n\tvar canvas = new mxVmlCanvas2D(node, false);\r\n\t\r\n\tif (node.tagUrn != '')\r\n\t{\r\n\t\tvar w = Math.max(1, Math.round(this.bounds.width));\r\n\t\tvar h = Math.max(1, Math.round(this.bounds.height));\r\n\t\tnode.coordsize = (w * this.vmlScale) + ',' + (h * this.vmlScale);\r\n\t\tcanvas.scale(this.vmlScale);\r\n\t\tcanvas.vmlScale = this.vmlScale;\r\n\t}\r\n\r\n\t// Painting relative to top, left shape corner\r\n\tvar s = this.scale;\r\n\tcanvas.translate(-Math.round(this.bounds.x / s), -Math.round(this.bounds.y / s));\r\n\t\r\n\treturn canvas;\r\n};\r\n\r\n/**\r\n * Function: updateVmlContainer\r\n * \r\n * Updates the bounds of the VML container.\r\n */\r\nmxShape.prototype.updateVmlContainer = function()\r\n{\r\n\tthis.node.style.left = Math.round(this.bounds.x) + 'px';\r\n\tthis.node.style.top = Math.round(this.bounds.y) + 'px';\r\n\tvar w = Math.max(1, Math.round(this.bounds.width));\r\n\tvar h = Math.max(1, Math.round(this.bounds.height));\r\n\tthis.node.style.width = w + 'px';\r\n\tthis.node.style.height = h + 'px';\r\n\tthis.node.style.overflow = 'visible';\r\n};\r\n\r\n/**\r\n * Function: redrawHtml\r\n *\r\n * Allow optimization by replacing VML with HTML.\r\n */\r\nmxShape.prototype.redrawHtmlShape = function()\r\n{\r\n\t// LATER: Refactor methods\r\n\tthis.updateHtmlBounds(this.node);\r\n\tthis.updateHtmlFilters(this.node);\r\n\tthis.updateHtmlColors(this.node);\r\n};\r\n\r\n/**\r\n * Function: updateHtmlFilters\r\n *\r\n * Allow optimization by replacing VML with HTML.\r\n */\r\nmxShape.prototype.updateHtmlFilters = function(node)\r\n{\r\n\tvar f = '';\r\n\t\r\n\tif (this.opacity < 100)\r\n\t{\r\n\t\tf += 'alpha(opacity=' + (this.opacity) + ')';\r\n\t}\r\n\t\r\n\tif (this.isShadow)\r\n\t{\r\n\t\t// FIXME: Cannot implement shadow transparency with filter\r\n\t\tf += 'progid:DXImageTransform.Microsoft.dropShadow (' +\r\n\t\t\t'OffX=\\'' + Math.round(mxConstants.SHADOW_OFFSET_X * this.scale) + '\\', ' +\r\n\t\t\t'OffY=\\'' + Math.round(mxConstants.SHADOW_OFFSET_Y * this.scale) + '\\', ' +\r\n\t\t\t'Color=\\'' + mxConstants.VML_SHADOWCOLOR + '\\')';\r\n\t}\r\n\t\r\n\tif (this.fill != null && this.fill != mxConstants.NONE && this.gradient && this.gradient != mxConstants.NONE)\r\n\t{\r\n\t\tvar start = this.fill;\r\n\t\tvar end = this.gradient;\r\n\t\tvar type = '0';\r\n\t\t\r\n\t\tvar lookup = {east:0,south:1,west:2,north:3};\r\n\t\tvar dir = (this.direction != null) ? lookup[this.direction] : 0;\r\n\t\t\r\n\t\tif (this.gradientDirection != null)\r\n\t\t{\r\n\t\t\tdir = mxUtils.mod(dir + lookup[this.gradientDirection] - 1, 4);\r\n\t\t}\r\n\r\n\t\tif (dir == 1)\r\n\t\t{\r\n\t\t\ttype = '1';\r\n\t\t\tvar tmp = start;\r\n\t\t\tstart = end;\r\n\t\t\tend = tmp;\r\n\t\t}\r\n\t\telse if (dir == 2)\r\n\t\t{\r\n\t\t\tvar tmp = start;\r\n\t\t\tstart = end;\r\n\t\t\tend = tmp;\r\n\t\t}\r\n\t\telse if (dir == 3)\r\n\t\t{\r\n\t\t\ttype = '1';\r\n\t\t}\r\n\t\t\r\n\t\tf += 'progid:DXImageTransform.Microsoft.gradient(' +\r\n\t\t\t'startColorStr=\\'' + start + '\\', endColorStr=\\'' + end +\r\n\t\t\t'\\', gradientType=\\'' + type + '\\')';\r\n\t}\r\n\r\n\tnode.style.filter = f;\r\n};\r\n\r\n/**\r\n * Function: mixedModeHtml\r\n *\r\n * Allow optimization by replacing VML with HTML.\r\n */\r\nmxShape.prototype.updateHtmlColors = function(node)\r\n{\r\n\tvar color = this.stroke;\r\n\t\r\n\tif (color != null && color != mxConstants.NONE)\r\n\t{\r\n\t\tnode.style.borderColor = color;\r\n\r\n\t\tif (this.isDashed)\r\n\t\t{\r\n\t\t\tnode.style.borderStyle = 'dashed';\r\n\t\t}\r\n\t\telse if (this.strokewidth > 0)\r\n\t\t{\r\n\t\t\tnode.style.borderStyle = 'solid';\r\n\t\t}\r\n\r\n\t\tnode.style.borderWidth = Math.max(1, Math.ceil(this.strokewidth * this.scale)) + 'px';\r\n\t}\r\n\telse\r\n\t{\r\n\t\tnode.style.borderWidth = '0px';\r\n\t}\r\n\r\n\tcolor = (this.outline) ? null : this.fill;\r\n\t\r\n\tif (color != null && color != mxConstants.NONE)\r\n\t{\r\n\t\tnode.style.backgroundColor = color;\r\n\t\tnode.style.backgroundImage = 'none';\r\n\t}\r\n\telse if (this.pointerEvents)\r\n\t{\r\n\t\t node.style.backgroundColor = 'transparent';\r\n\t}\r\n\telse if (document.documentMode == 8)\r\n\t{\r\n\t\tmxUtils.addTransparentBackgroundFilter(node);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.setTransparentBackgroundImage(node);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: mixedModeHtml\r\n *\r\n * Allow optimization by replacing VML with HTML.\r\n */\r\nmxShape.prototype.updateHtmlBounds = function(node)\r\n{\r\n\tvar sw = (document.documentMode >= 9) ? 0 : Math.ceil(this.strokewidth * this.scale);\r\n\tnode.style.borderWidth = Math.max(1, sw) + 'px';\r\n\tnode.style.overflow = 'hidden';\r\n\t\r\n\tnode.style.left = Math.round(this.bounds.x - sw / 2) + 'px';\r\n\tnode.style.top = Math.round(this.bounds.y - sw / 2) + 'px';\r\n\r\n\tif (document.compatMode == 'CSS1Compat')\r\n\t{\r\n\t\tsw = -sw;\r\n\t}\r\n\t\r\n\tnode.style.width = Math.round(Math.max(0, this.bounds.width + sw)) + 'px';\r\n\tnode.style.height = Math.round(Math.max(0, this.bounds.height + sw)) + 'px';\r\n};\r\n\r\n/**\r\n * Function: destroyCanvas\r\n * \r\n * Destroys the given canvas which was used for drawing. This implementation\r\n * increments the reference counts on all shared gradients used in the canvas.\r\n */\r\nmxShape.prototype.destroyCanvas = function(canvas)\r\n{\r\n\t// Manages reference counts\r\n\tif (canvas instanceof mxSvgCanvas2D)\r\n\t{\r\n\t\t// Increments ref counts\r\n\t\tfor (var key in canvas.gradients)\r\n\t\t{\r\n\t\t\tvar gradient = canvas.gradients[key];\r\n\t\t\t\r\n\t\t\tif (gradient != null)\r\n\t\t\t{\r\n\t\t\t\tgradient.mxRefCount = (gradient.mxRefCount || 0) + 1;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tthis.releaseSvgGradients(this.oldGradients);\r\n\t\tthis.oldGradients = canvas.gradients;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: paint\r\n * \r\n * Generic rendering code.\r\n */\r\nmxShape.prototype.paint = function(c)\r\n{\r\n\tvar strokeDrawn = false;\r\n\t\r\n\tif (c != null && this.outline)\r\n\t{\r\n\t\tvar stroke = c.stroke;\r\n\t\t\r\n\t\tc.stroke = function()\r\n\t\t{\r\n\t\t\tstrokeDrawn = true;\r\n\t\t\tstroke.apply(this, arguments);\r\n\t\t};\r\n\r\n\t\tvar fillAndStroke = c.fillAndStroke;\r\n\t\t\r\n\t\tc.fillAndStroke = function()\r\n\t\t{\r\n\t\t\tstrokeDrawn = true;\r\n\t\t\tfillAndStroke.apply(this, arguments);\r\n\t\t};\r\n\t}\r\n\r\n\t// Scale is passed-through to canvas\r\n\tvar s = this.scale;\r\n\tvar x = this.bounds.x / s;\r\n\tvar y = this.bounds.y / s;\r\n\tvar w = this.bounds.width / s;\r\n\tvar h = this.bounds.height / s;\r\n\r\n\tif (this.isPaintBoundsInverted())\r\n\t{\r\n\t\tvar t = (w - h) / 2;\r\n\t\tx += t;\r\n\t\ty -= t;\r\n\t\tvar tmp = w;\r\n\t\tw = h;\r\n\t\th = tmp;\r\n\t}\r\n\t\r\n\tthis.updateTransform(c, x, y, w, h);\r\n\tthis.configureCanvas(c, x, y, w, h);\r\n\r\n\t// Adds background rectangle to capture events\r\n\tvar bg = null;\r\n\t\r\n\tif ((this.stencil == null && this.points == null && this.shapePointerEvents) ||\r\n\t\t(this.stencil != null && this.stencilPointerEvents))\r\n\t{\r\n\t\tvar bb = this.createBoundingBox();\r\n\t\t\r\n\t\tif (this.dialect == mxConstants.DIALECT_SVG)\r\n\t\t{\r\n\t\t\tbg = this.createTransparentSvgRectangle(bb.x, bb.y, bb.width, bb.height);\r\n\t\t\tthis.node.appendChild(bg);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar rect = c.createRect('rect', bb.x / s, bb.y / s, bb.width / s, bb.height / s);\r\n\t\t\trect.appendChild(c.createTransparentFill());\r\n\t\t\trect.stroked = 'false';\r\n\t\t\tc.root.appendChild(rect);\r\n\t\t}\r\n\t}\r\n\r\n\tif (this.stencil != null)\r\n\t{\r\n\t\tthis.stencil.drawShape(c, this, x, y, w, h);\r\n\t}\r\n\telse\r\n\t{\r\n\t\t// Stencils have separate strokewidth\r\n\t\tc.setStrokeWidth(this.strokewidth);\r\n\t\t\r\n\t\tif (this.points != null)\r\n\t\t{\r\n\t\t\t// Paints edge shape\r\n\t\t\tvar pts = [];\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < this.points.length; i++)\r\n\t\t\t{\r\n\t\t\t\tif (this.points[i] != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tpts.push(new mxPoint(this.points[i].x / s, this.points[i].y / s));\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tthis.paintEdgeShape(c, pts);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Paints vertex shape\r\n\t\t\tthis.paintVertexShape(c, x, y, w, h);\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (bg != null && c.state != null && c.state.transform != null)\r\n\t{\r\n\t\tbg.setAttribute('transform', c.state.transform);\r\n\t}\r\n\t\r\n\t// Draws highlight rectangle if no stroke was used\r\n\tif (c != null && this.outline && !strokeDrawn)\r\n\t{\r\n\t\tc.rect(x, y, w, h);\r\n\t\tc.stroke();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: configureCanvas\r\n * \r\n * Sets the state of the canvas for drawing the shape.\r\n */\r\nmxShape.prototype.configureCanvas = function(c, x, y, w, h)\r\n{\r\n\tvar dash = null;\r\n\t\r\n\tif (this.style != null)\r\n\t{\r\n\t\tdash = this.style['dashPattern'];\t\t\r\n\t}\r\n\r\n\tc.setAlpha(this.opacity / 100);\r\n\tc.setFillAlpha(this.fillOpacity / 100);\r\n\tc.setStrokeAlpha(this.strokeOpacity / 100);\r\n\r\n\t// Sets alpha, colors and gradients\r\n\tif (this.isShadow != null)\r\n\t{\r\n\t\tc.setShadow(this.isShadow);\r\n\t}\r\n\t\r\n\t// Dash pattern\r\n\tif (this.isDashed != null)\r\n\t{\r\n\t\tc.setDashed(this.isDashed, (this.style != null) ?\r\n\t\t\tmxUtils.getValue(this.style, mxConstants.STYLE_FIX_DASH, false) == 1 : false);\r\n\t}\r\n\r\n\tif (dash != null)\r\n\t{\r\n\t\tc.setDashPattern(dash);\r\n\t}\r\n\r\n\tif (this.fill != null && this.fill != mxConstants.NONE && this.gradient && this.gradient != mxConstants.NONE)\r\n\t{\r\n\t\tvar b = this.getGradientBounds(c, x, y, w, h);\r\n\t\tc.setGradient(this.fill, this.gradient, b.x, b.y, b.width, b.height, this.gradientDirection);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tc.setFillColor(this.fill);\r\n\t}\r\n\r\n\tc.setStrokeColor(this.stroke);\r\n};\r\n\r\n/**\r\n * Function: getGradientBounds\r\n * \r\n * Returns the bounding box for the gradient box for this shape.\r\n */\r\nmxShape.prototype.getGradientBounds = function(c, x, y, w, h)\r\n{\r\n\treturn new mxRectangle(x, y, w, h);\r\n};\r\n\r\n/**\r\n * Function: updateTransform\r\n * \r\n * Sets the scale and rotation on the given canvas.\r\n */\r\nmxShape.prototype.updateTransform = function(c, x, y, w, h)\r\n{\r\n\t// NOTE: Currently, scale is implemented in state and canvas. This will\r\n\t// move to canvas in a later version, so that the states are unscaled\r\n\t// and untranslated and do not need an update after zooming or panning.\r\n\tc.scale(this.scale);\r\n\tc.rotate(this.getShapeRotation(), this.flipH, this.flipV, x + w / 2, y + h / 2);\r\n};\r\n\r\n/**\r\n * Function: paintVertexShape\r\n * \r\n * Paints the vertex shape.\r\n */\r\nmxShape.prototype.paintVertexShape = function(c, x, y, w, h)\r\n{\r\n\tthis.paintBackground(c, x, y, w, h);\r\n\t\r\n\tif (!this.outline || this.style == null || mxUtils.getValue(\r\n\t\tthis.style, mxConstants.STYLE_BACKGROUND_OUTLINE, 0) == 0)\r\n\t{\r\n\t\tc.setShadow(false);\r\n\t\tthis.paintForeground(c, x, y, w, h);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: paintBackground\r\n * \r\n * Hook for subclassers. This implementation is empty.\r\n */\r\nmxShape.prototype.paintBackground = function(c, x, y, w, h) { };\r\n\r\n/**\r\n * Function: paintForeground\r\n * \r\n * Hook for subclassers. This implementation is empty.\r\n */\r\nmxShape.prototype.paintForeground = function(c, x, y, w, h) { };\r\n\r\n/**\r\n * Function: paintEdgeShape\r\n * \r\n * Hook for subclassers. This implementation is empty.\r\n */\r\nmxShape.prototype.paintEdgeShape = function(c, pts) { };\r\n\r\n/**\r\n * Function: getArcSize\r\n * \r\n * Returns the arc size for the given dimension.\r\n */\r\nmxShape.prototype.getArcSize = function(w, h)\r\n{\r\n\tvar r = 0;\r\n\t\r\n\tif (mxUtils.getValue(this.style, mxConstants.STYLE_ABSOLUTE_ARCSIZE, 0) == '1')\r\n\t{\r\n\t\tr = Math.min(w / 2, Math.min(h / 2, mxUtils.getValue(this.style,\r\n\t\t\tmxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2));\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE,\r\n\t\t\tmxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;\r\n\t\tr = Math.min(w * f, h * f);\r\n\t}\r\n\t\r\n\treturn r;\r\n};\r\n\r\n/**\r\n * Function: paintGlassEffect\r\n * \r\n * Paints the glass gradient effect.\r\n */\r\nmxShape.prototype.paintGlassEffect = function(c, x, y, w, h, arc)\r\n{\r\n\tvar sw = Math.ceil(this.strokewidth / 2);\r\n\tvar size = 0.4;\r\n\t\r\n\tc.setGradient('#ffffff', '#ffffff', x, y, w, h * 0.6, 'south', 0.9, 0.1);\r\n\tc.begin();\r\n\tarc += 2 * sw;\r\n\t\t\r\n\tif (this.isRounded)\r\n\t{\r\n\t\tc.moveTo(x - sw + arc, y - sw);\r\n\t\tc.quadTo(x - sw, y - sw, x - sw, y - sw + arc);\r\n\t\tc.lineTo(x - sw, y + h * size);\r\n\t\tc.quadTo(x + w * 0.5, y + h * 0.7, x + w + sw, y + h * size);\r\n\t\tc.lineTo(x + w + sw, y - sw + arc);\r\n\t\tc.quadTo(x + w + sw, y - sw, x + w + sw - arc, y - sw);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tc.moveTo(x - sw, y - sw);\r\n\t\tc.lineTo(x - sw, y + h * size);\r\n\t\tc.quadTo(x + w * 0.5, y + h * 0.7, x + w + sw, y + h * size);\r\n\t\tc.lineTo(x + w + sw, y - sw);\r\n\t}\r\n\t\r\n\tc.close();\r\n\tc.fill();\r\n};\r\n\r\n/**\r\n * Function: addPoints\r\n * \r\n * Paints the given points with rounded corners.\r\n */\r\nmxShape.prototype.addPoints = function(c, pts, rounded, arcSize, close, exclude, initialMove)\r\n{\r\n\tif (pts != null && pts.length > 0)\r\n\t{\r\n\t\tinitialMove = (initialMove != null) ? initialMove : true;\r\n\t\tvar pe = pts[pts.length - 1];\r\n\t\t\r\n\t\t// Adds virtual waypoint in the center between start and end point\r\n\t\tif (close && rounded)\r\n\t\t{\r\n\t\t\tpts = pts.slice();\r\n\t\t\tvar p0 = pts[0];\r\n\t\t\tvar wp = new mxPoint(pe.x + (p0.x - pe.x) / 2, pe.y + (p0.y - pe.y) / 2);\r\n\t\t\tpts.splice(0, 0, wp);\r\n\t\t}\r\n\t\r\n\t\tvar pt = pts[0];\r\n\t\tvar i = 1;\r\n\t\r\n\t\t// Draws the line segments\r\n\t\tif (initialMove)\r\n\t\t{\r\n\t\t\tc.moveTo(pt.x, pt.y);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tc.lineTo(pt.x, pt.y);\r\n\t\t}\r\n\t\t\r\n\t\twhile (i < ((close) ? pts.length : pts.length - 1))\r\n\t\t{\r\n\t\t\tvar tmp = pts[mxUtils.mod(i, pts.length)];\r\n\t\t\tvar dx = pt.x - tmp.x;\r\n\t\t\tvar dy = pt.y - tmp.y;\r\n\t\r\n\t\t\tif (rounded && (dx != 0 || dy != 0) && (exclude == null || mxUtils.indexOf(exclude, i - 1) < 0))\r\n\t\t\t{\r\n\t\t\t\t// Draws a line from the last point to the current\r\n\t\t\t\t// point with a spacing of size off the current point\r\n\t\t\t\t// into direction of the last point\r\n\t\t\t\tvar dist = Math.sqrt(dx * dx + dy * dy);\r\n\t\t\t\tvar nx1 = dx * Math.min(arcSize, dist / 2) / dist;\r\n\t\t\t\tvar ny1 = dy * Math.min(arcSize, dist / 2) / dist;\r\n\t\r\n\t\t\t\tvar x1 = tmp.x + nx1;\r\n\t\t\t\tvar y1 = tmp.y + ny1;\r\n\t\t\t\tc.lineTo(x1, y1);\r\n\t\r\n\t\t\t\t// Draws a curve from the last point to the current\r\n\t\t\t\t// point with a spacing of size off the current point\r\n\t\t\t\t// into direction of the next point\r\n\t\t\t\tvar next = pts[mxUtils.mod(i + 1, pts.length)];\r\n\t\t\t\t\r\n\t\t\t\t// Uses next non-overlapping point\r\n\t\t\t\twhile (i < pts.length - 2 && Math.round(next.x - tmp.x) == 0 && Math.round(next.y - tmp.y) == 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tnext = pts[mxUtils.mod(i + 2, pts.length)];\r\n\t\t\t\t\ti++;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tdx = next.x - tmp.x;\r\n\t\t\t\tdy = next.y - tmp.y;\r\n\t\r\n\t\t\t\tdist = Math.max(1, Math.sqrt(dx * dx + dy * dy));\r\n\t\t\t\tvar nx2 = dx * Math.min(arcSize, dist / 2) / dist;\r\n\t\t\t\tvar ny2 = dy * Math.min(arcSize, dist / 2) / dist;\r\n\t\r\n\t\t\t\tvar x2 = tmp.x + nx2;\r\n\t\t\t\tvar y2 = tmp.y + ny2;\r\n\t\r\n\t\t\t\tc.quadTo(tmp.x, tmp.y, x2, y2);\r\n\t\t\t\ttmp = new mxPoint(x2, y2);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tc.lineTo(tmp.x, tmp.y);\r\n\t\t\t}\r\n\t\r\n\t\t\tpt = tmp;\r\n\t\t\ti++;\r\n\t\t}\r\n\t\r\n\t\tif (close)\r\n\t\t{\r\n\t\t\tc.close();\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tc.lineTo(pe.x, pe.y);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: resetStyles\r\n * \r\n * Resets all styles.\r\n */\r\nmxShape.prototype.resetStyles = function()\r\n{\r\n\tthis.initStyles();\r\n\r\n\tthis.spacing = 0;\r\n\t\r\n\tdelete this.fill;\r\n\tdelete this.gradient;\r\n\tdelete this.gradientDirection;\r\n\tdelete this.stroke;\r\n\tdelete this.startSize;\r\n\tdelete this.endSize;\r\n\tdelete this.startArrow;\r\n\tdelete this.endArrow;\r\n\tdelete this.direction;\r\n\tdelete this.isShadow;\r\n\tdelete this.isDashed;\r\n\tdelete this.isRounded;\r\n\tdelete this.glass;\r\n};\r\n\r\n/**\r\n * Function: apply\r\n * \r\n * Applies the style of the given <mxCellState> to the shape. This\r\n * implementation assigns the following styles to local fields:\r\n * \r\n * - <mxConstants.STYLE_FILLCOLOR> => fill\r\n * - <mxConstants.STYLE_GRADIENTCOLOR> => gradient\r\n * - <mxConstants.STYLE_GRADIENT_DIRECTION> => gradientDirection\r\n * - <mxConstants.STYLE_OPACITY> => opacity\r\n * - <mxConstants.STYLE_FILL_OPACITY> => fillOpacity\r\n * - <mxConstants.STYLE_STROKE_OPACITY> => strokeOpacity\r\n * - <mxConstants.STYLE_STROKECOLOR> => stroke\r\n * - <mxConstants.STYLE_STROKEWIDTH> => strokewidth\r\n * - <mxConstants.STYLE_SHADOW> => isShadow\r\n * - <mxConstants.STYLE_DASHED> => isDashed\r\n * - <mxConstants.STYLE_SPACING> => spacing\r\n * - <mxConstants.STYLE_STARTSIZE> => startSize\r\n * - <mxConstants.STYLE_ENDSIZE> => endSize\r\n * - <mxConstants.STYLE_ROUNDED> => isRounded\r\n * - <mxConstants.STYLE_STARTARROW> => startArrow\r\n * - <mxConstants.STYLE_ENDARROW> => endArrow\r\n * - <mxConstants.STYLE_ROTATION> => rotation\r\n * - <mxConstants.STYLE_DIRECTION> => direction\r\n * - <mxConstants.STYLE_GLASS> => glass\r\n *\r\n * This keeps a reference to the <style>. If you need to keep a reference to\r\n * the cell, you can override this method and store a local reference to\r\n * state.cell or the <mxCellState> itself. If <outline> should be true, make\r\n * sure to set it before calling this method.\r\n *\r\n * Parameters:\r\n *\r\n * state - <mxCellState> of the corresponding cell.\r\n */\r\nmxShape.prototype.apply = function(state)\r\n{\r\n\tthis.state = state;\r\n\tthis.style = state.style;\r\n\r\n\tif (this.style != null)\r\n\t{\r\n\t\tthis.fill = mxUtils.getValue(this.style, mxConstants.STYLE_FILLCOLOR, this.fill);\r\n\t\tthis.gradient = mxUtils.getValue(this.style, mxConstants.STYLE_GRADIENTCOLOR, this.gradient);\r\n\t\tthis.gradientDirection = mxUtils.getValue(this.style, mxConstants.STYLE_GRADIENT_DIRECTION, this.gradientDirection);\r\n\t\tthis.opacity = mxUtils.getValue(this.style, mxConstants.STYLE_OPACITY, this.opacity);\r\n\t\tthis.fillOpacity = mxUtils.getValue(this.style, mxConstants.STYLE_FILL_OPACITY, this.fillOpacity);\r\n\t\tthis.strokeOpacity = mxUtils.getValue(this.style, mxConstants.STYLE_STROKE_OPACITY, this.strokeOpacity);\r\n\t\tthis.stroke = mxUtils.getValue(this.style, mxConstants.STYLE_STROKECOLOR, this.stroke);\r\n\t\tthis.strokewidth = mxUtils.getNumber(this.style, mxConstants.STYLE_STROKEWIDTH, this.strokewidth);\r\n\t\tthis.spacing = mxUtils.getValue(this.style, mxConstants.STYLE_SPACING, this.spacing);\r\n\t\tthis.startSize = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE, this.startSize);\r\n\t\tthis.endSize = mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE, this.endSize);\r\n\t\tthis.startArrow = mxUtils.getValue(this.style, mxConstants.STYLE_STARTARROW, this.startArrow);\r\n\t\tthis.endArrow = mxUtils.getValue(this.style, mxConstants.STYLE_ENDARROW, this.endArrow);\r\n\t\tthis.rotation = mxUtils.getValue(this.style, mxConstants.STYLE_ROTATION, this.rotation);\r\n\t\tthis.direction = mxUtils.getValue(this.style, mxConstants.STYLE_DIRECTION, this.direction);\r\n\t\tthis.flipH = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPH, 0) == 1;\r\n\t\tthis.flipV = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPV, 0) == 1;\t\r\n\t\t\r\n\t\t// Legacy support for stencilFlipH/V\r\n\t\tif (this.stencil != null)\r\n\t\t{\r\n\t\t\tthis.flipH = mxUtils.getValue(this.style, 'stencilFlipH', 0) == 1 || this.flipH;\r\n\t\t\tthis.flipV = mxUtils.getValue(this.style, 'stencilFlipV', 0) == 1 || this.flipV;\r\n\t\t}\r\n\t\t\r\n\t\tif (this.direction == mxConstants.DIRECTION_NORTH || this.direction == mxConstants.DIRECTION_SOUTH)\r\n\t\t{\r\n\t\t\tvar tmp = this.flipH;\r\n\t\t\tthis.flipH = this.flipV;\r\n\t\t\tthis.flipV = tmp;\r\n\t\t}\r\n\r\n\t\tthis.isShadow = mxUtils.getValue(this.style, mxConstants.STYLE_SHADOW, this.isShadow) == 1;\r\n\t\tthis.isDashed = mxUtils.getValue(this.style, mxConstants.STYLE_DASHED, this.isDashed) == 1;\r\n\t\tthis.isRounded = mxUtils.getValue(this.style, mxConstants.STYLE_ROUNDED, this.isRounded) == 1;\r\n\t\tthis.glass = mxUtils.getValue(this.style, mxConstants.STYLE_GLASS, this.glass) == 1;\r\n\t\t\r\n\t\tif (this.fill == mxConstants.NONE)\r\n\t\t{\r\n\t\t\tthis.fill = null;\r\n\t\t}\r\n\r\n\t\tif (this.gradient == mxConstants.NONE)\r\n\t\t{\r\n\t\t\tthis.gradient = null;\r\n\t\t}\r\n\r\n\t\tif (this.stroke == mxConstants.NONE)\r\n\t\t{\r\n\t\t\tthis.stroke = null;\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setCursor\r\n * \r\n * Sets the cursor on the given shape.\r\n *\r\n * Parameters:\r\n *\r\n * cursor - The cursor to be used.\r\n */\r\nmxShape.prototype.setCursor = function(cursor)\r\n{\r\n\tif (cursor == null)\r\n\t{\r\n\t\tcursor = '';\r\n\t}\r\n\t\r\n\tthis.cursor = cursor;\r\n\r\n\tif (this.node != null)\r\n\t{\r\n\t\tthis.node.style.cursor = cursor;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getCursor\r\n * \r\n * Returns the current cursor.\r\n */\r\nmxShape.prototype.getCursor = function()\r\n{\r\n\treturn this.cursor;\r\n};\r\n\r\n/**\r\n * Function: isRoundable\r\n * \r\n * Hook for subclassers.\r\n */\r\nmxShape.prototype.isRoundable = function()\r\n{\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: updateBoundingBox\r\n *\r\n * Updates the <boundingBox> for this shape using <createBoundingBox> and\r\n * <augmentBoundingBox> and stores the result in <boundingBox>.\r\n */\r\nmxShape.prototype.updateBoundingBox = function()\r\n{\r\n\t// Tries to get bounding box from SVG subsystem\r\n\t// LATER: Use getBoundingClientRect for fallback in VML\r\n\tif (this.useSvgBoundingBox && this.node != null && this.node.ownerSVGElement != null)\r\n\t{\r\n\t\ttry\r\n\t\t{\r\n\t\t\tvar b = this.node.getBBox();\r\n\t\r\n\t\t\tif (b.width > 0 && b.height > 0)\r\n\t\t\t{\r\n\t\t\t\tthis.boundingBox = new mxRectangle(b.x, b.y, b.width, b.height);\r\n\t\t\t\t\r\n\t\t\t\t// Adds strokeWidth\r\n\t\t\t\tthis.boundingBox.grow(this.strokewidth * this.scale / 2);\r\n\t\t\t\t\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t}\r\n\t\tcatch(e)\r\n\t\t{\r\n\t\t\t// fallback to code below\r\n\t\t}\r\n\t}\r\n\r\n\tif (this.bounds != null)\r\n\t{\r\n\t\tvar bbox = this.createBoundingBox();\r\n\t\t\r\n\t\tif (bbox != null)\r\n\t\t{\r\n\t\t\tthis.augmentBoundingBox(bbox);\r\n\t\t\tvar rot = this.getShapeRotation();\r\n\t\t\t\r\n\t\t\tif (rot != 0)\r\n\t\t\t{\r\n\t\t\t\tbbox = mxUtils.getBoundingBox(bbox, rot);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.boundingBox = bbox;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createBoundingBox\r\n *\r\n * Returns a new rectangle that represents the bounding box of the bare shape\r\n * with no shadows or strokewidths.\r\n */\r\nmxShape.prototype.createBoundingBox = function()\r\n{\r\n\tvar bb = this.bounds.clone();\r\n\r\n\tif ((this.stencil != null && (this.direction == mxConstants.DIRECTION_NORTH ||\r\n\t\tthis.direction == mxConstants.DIRECTION_SOUTH)) || this.isPaintBoundsInverted())\r\n\t{\r\n\t\tbb.rotate90();\r\n\t}\r\n\t\r\n\treturn bb;\r\n};\r\n\r\n/**\r\n * Function: augmentBoundingBox\r\n *\r\n * Augments the bounding box with the strokewidth and shadow offsets.\r\n */\r\nmxShape.prototype.augmentBoundingBox = function(bbox)\r\n{\r\n\tif (this.isShadow)\r\n\t{\r\n\t\tbbox.width += Math.ceil(mxConstants.SHADOW_OFFSET_X * this.scale);\r\n\t\tbbox.height += Math.ceil(mxConstants.SHADOW_OFFSET_Y * this.scale);\r\n\t}\r\n\t\r\n\t// Adds strokeWidth\r\n\tbbox.grow(this.strokewidth * this.scale / 2);\r\n};\r\n\r\n/**\r\n * Function: isPaintBoundsInverted\r\n * \r\n * Returns true if the bounds should be inverted.\r\n */\r\nmxShape.prototype.isPaintBoundsInverted = function()\r\n{\r\n\t// Stencil implements inversion via aspect\r\n\treturn this.stencil == null && (this.direction == mxConstants.DIRECTION_NORTH ||\r\n\t\t\tthis.direction == mxConstants.DIRECTION_SOUTH);\r\n};\r\n\r\n/**\r\n * Function: getRotation\r\n * \r\n * Returns the rotation from the style.\r\n */\r\nmxShape.prototype.getRotation = function()\r\n{\r\n\treturn (this.rotation != null) ? this.rotation : 0;\r\n};\r\n\r\n/**\r\n * Function: getTextRotation\r\n * \r\n * Returns the rotation for the text label.\r\n */\r\nmxShape.prototype.getTextRotation = function()\r\n{\r\n\tvar rot = this.getRotation();\r\n\t\r\n\tif (mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, 1) != 1)\r\n\t{\r\n\t\trot += mxText.prototype.verticalTextRotation;\r\n\t}\r\n\t\r\n\treturn rot;\r\n};\r\n\r\n/**\r\n * Function: getShapeRotation\r\n * \r\n * Returns the actual rotation of the shape.\r\n */\r\nmxShape.prototype.getShapeRotation = function()\r\n{\r\n\tvar rot = this.getRotation();\r\n\t\r\n\tif (this.direction != null)\r\n\t{\r\n\t\tif (this.direction == mxConstants.DIRECTION_NORTH)\r\n\t\t{\r\n\t\t\trot += 270;\r\n\t\t}\r\n\t\telse if (this.direction == mxConstants.DIRECTION_WEST)\r\n\t\t{\r\n\t\t\trot += 180;\r\n\t\t}\r\n\t\telse if (this.direction == mxConstants.DIRECTION_SOUTH)\r\n\t\t{\r\n\t\t\trot += 90;\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn rot;\r\n};\r\n\r\n/**\r\n * Function: createTransparentSvgRectangle\r\n * \r\n * Adds a transparent rectangle that catches all events.\r\n */\r\nmxShape.prototype.createTransparentSvgRectangle = function(x, y, w, h)\r\n{\r\n\tvar rect = document.createElementNS(mxConstants.NS_SVG, 'rect');\r\n\trect.setAttribute('x', x);\r\n\trect.setAttribute('y', y);\r\n\trect.setAttribute('width', w);\r\n\trect.setAttribute('height', h);\r\n\trect.setAttribute('fill', 'none');\r\n\trect.setAttribute('stroke', 'none');\r\n\trect.setAttribute('pointer-events', 'all');\r\n\t\r\n\treturn rect;\r\n};\r\n\r\n/**\r\n * Function: setTransparentBackgroundImage\r\n * \r\n * Sets a transparent background CSS style to catch all events.\r\n * \r\n * Paints the line shape.\r\n */\r\nmxShape.prototype.setTransparentBackgroundImage = function(node)\r\n{\r\n\tnode.style.backgroundImage = 'url(\\'' + mxClient.imageBasePath + '/transparent.gif\\')';\r\n};\r\n\r\n/**\r\n * Function: releaseSvgGradients\r\n * \r\n * Paints the line shape.\r\n */\r\nmxShape.prototype.releaseSvgGradients = function(grads)\r\n{\r\n\tif (grads != null)\r\n\t{\r\n\t\tfor (var key in grads)\r\n\t\t{\r\n\t\t\tvar gradient = grads[key];\r\n\t\t\t\r\n\t\t\tif (gradient != null)\r\n\t\t\t{\r\n\t\t\t\tgradient.mxRefCount = (gradient.mxRefCount || 0) - 1;\r\n\t\t\t\t\r\n\t\t\t\tif (gradient.mxRefCount == 0 && gradient.parentNode != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tgradient.parentNode.removeChild(gradient);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n *\r\n * Destroys the shape by removing it from the DOM and releasing the DOM\r\n * node associated with the shape using <mxEvent.release>.\r\n */\r\nmxShape.prototype.destroy = function()\r\n{\r\n\tif (this.node != null)\r\n\t{\r\n\t\tmxEvent.release(this.node);\r\n\t\t\r\n\t\tif (this.node.parentNode != null)\r\n\t\t{\r\n\t\t\tthis.node.parentNode.removeChild(this.node);\r\n\t\t}\r\n\t\t\r\n\t\tthis.node = null;\r\n\t}\r\n\t\r\n\t// Decrements refCount and removes unused\r\n\tthis.releaseSvgGradients(this.oldGradients);\r\n\tthis.oldGradients = null;\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxStencil\r\n *\r\n * Implements a generic shape which is based on a XML node as a description.\r\n * \r\n * shape:\r\n * \r\n * The outer element is *shape*, that has attributes:\r\n * \r\n * - \"name\", string, required. The stencil name that uniquely identifies the shape.\r\n * - \"w\" and \"h\" are optional decimal view bounds. This defines your co-ordinate\r\n * system for the graphics operations in the shape. The default is 100,100.\r\n * - \"aspect\", optional string. Either \"variable\", the default, or \"fixed\". Fixed\r\n * means always render the shape with the aspect ratio defined by the ratio w/h.\r\n * Variable causes the ratio to match that of the geometry of the current vertex.\r\n * - \"strokewidth\", optional string. Either an integer or the string \"inherit\".\r\n * \"inherit\" indicates that the strokeWidth of the cell is only changed on scaling,\r\n * not on resizing. Default is \"1\".\r\n * If numeric values are used, the strokeWidth of the cell is changed on both\r\n * scaling and resizing and the value defines the multiple that is applied to\r\n * the width.\r\n * \r\n * connections:\r\n * \r\n * If you want to define specific fixed connection points on the shape use the\r\n * *connections* element. Each *constraint* element within connections defines\r\n * a fixed connection point on the shape. Constraints have attributes:\r\n * \r\n * - \"perimeter\", required. 1 or 0. 0 sets the connection point where specified\r\n * by x,y. 1 Causes the position of the connection point to be extrapolated from\r\n * the center of the shape, through x,y to the point of intersection with the\r\n * perimeter of the shape.\r\n * - \"x\" and \"y\" are the position of the fixed point relative to the bounds of\r\n * the shape. They can be automatically adjusted if perimeter=1. So, (0,0) is top\r\n * left, (0.5,0.5) the center, (1,0.5) the center of the right hand edge of the\r\n * bounds, etc. Values may be less than 0 or greater than 1 to be positioned\r\n * outside of the shape.\r\n * - \"name\", optional string. A unique identifier for the port on the shape.\r\n * \r\n * background and foreground:\r\n * \r\n * The path of the graphics drawing is split into two elements, *foreground* and\r\n * *background*. The split is to define which part any shadow applied to the shape\r\n * is derived from (the background). This, generally, means the background is the\r\n * line tracing of the outside of the shape, but not always.\r\n * \r\n * Any stroke, fill or fillstroke of a background must be the first element of the\r\n * foreground element, they must not be used within *background*. If the background\r\n * is empty, this is not required.\r\n * \r\n * Because the background cannot have any fill or stroke, it can contain only one\r\n * *path*, *rect*, *roundrect* or *ellipse* element (or none). It can also not\r\n * include *image*, *text* or *include-shape*.\r\n * \r\n * Note that the state, styling and drawing in mxGraph stencils is very close in\r\n * design to that of HTML 5 canvas. Tutorials on this subject, if you're not\r\n * familiar with the topic, will give a good high-level introduction to the\r\n * concepts used.\r\n * \r\n * State:\r\n * \r\n * Rendering within the foreground and background elements has the concept of\r\n * state. There are two types of operations other than state save/load, styling\r\n * and drawing. The styling operations change the current state, so you can save\r\n * the current state with <save/> and pull the last saved state from the state\r\n * stack using <restore/>.\r\n * \r\n * Styling:\r\n * \r\n * The elements that change colors within the current state all take a hash\r\n * prefixed hex color code (\"#FFEA80\").\r\n * \r\n * - *strokecolor*, this sets the color that drawing paths will be rendered in\r\n * when a stroke or fillstroke command is issued.\r\n * - *fillcolor*, this sets the color that the inside of closed paths will be\r\n * rendered in when a fill or fillstroke command is issued.\r\n * - *fontcolor*, this sets the color that fonts are rendered in when text is drawn.\r\n * \r\n * *alpha* defines the degree of transparency used between 1.0 for fully opaque\r\n * and 0.0 for fully transparent.\r\n * \r\n * *strokewidth* defines the integer thickness of drawing elements rendered by\r\n * stroking. Use fixed=\"1\" to apply the value as-is, without scaling.\r\n * \r\n * *dashed* is \"1\" for dashing enabled and \"0\" for disabled.\r\n * \r\n * When *dashed* is enabled the current dash pattern, defined by *dashpattern*,\r\n * is used on strokes. dashpattern is a sequence of space separated \"on, off\"\r\n * lengths that define what distance to paint the stroke for, then what distance\r\n * to paint nothing for, repeat... The default is \"3 3\". You could define a more\r\n * complex pattern with \"5 3 2 6\", for example. Generally, it makes sense to have\r\n * an even number of elements in the dashpattern, but that's not required.\r\n * \r\n * *linejoin*, *linecap* and *miterlimit* are best explained by the Mozilla page\r\n * on Canvas styling (about halfway down). The values are all the same except we\r\n * use \"flat\" for linecap, instead of Canvas' \"butt\".\r\n * \r\n * For font styling there are.\r\n * \r\n * - *fontsize*, an integer,\r\n * - *fontstyle*, an ORed bit pattern of bold (1), italic (2) and underline (4),\r\n * i.e bold underline is \"5\".\r\n * - *fontfamily*, is a string defining the typeface to be used.\r\n * \r\n * Drawing:\r\n * \r\n * Most drawing is contained within a *path* element. Again, the graphic\r\n * primitives are very similar to that of HTML 5 canvas.\r\n * \r\n * - *move* to attributes required decimals (x,y).\r\n * - *line* to attributes required decimals (x,y).\r\n * - *quad* to required decimals (x2,y2) via control point required decimals\r\n * (x1,y1).\r\n * - *curve* to required decimals (x3,y3), via control points required decimals\r\n * (x1,y1) and (x2,y2).\r\n * - *arc*, this doesn't follow the HTML Canvas signatures, instead it's a copy\r\n * of the SVG arc command. The SVG specification documentation gives the best\r\n * description of its behaviors. The attributes are named identically, they are\r\n * decimals and all required.\r\n * - *close* ends the current subpath and causes an automatic straight line to\r\n * be drawn from the current point to the initial point of the current subpath.\r\n * \r\n * Complex drawing:\r\n * \r\n * In addition to the graphics primitive operations there are non-primitive\r\n * operations. These provide an easy method to draw some basic shapes.\r\n * \r\n * - *rect*, attributes \"x\", \"y\", \"w\", \"h\", all required decimals\r\n * - *roundrect*, attributes \"x\", \"y\", \"w\", \"h\", all required decimals. Also\r\n * \"arcsize\" an optional decimal attribute defining how large, the corner curves\r\n * are.\r\n * - *ellipse*, attributes \"x\", \"y\", \"w\", \"h\", all required decimals.\r\n * \r\n * Note that these 3 shapes and all paths must be followed by either a fill,\r\n * stroke, or fillstroke.\r\n * \r\n * Text:\r\n * \r\n * *text* elements have the following attributes.\r\n * \r\n * - \"str\", the text string to display, required.\r\n * - \"x\" and \"y\", the decimal location (x,y) of the text element, required.\r\n * - \"align\", the horizontal alignment of the text element, either \"left\",\r\n * \"center\" or \"right\". Optional, default is \"left\".\r\n * - \"valign\", the vertical alignment of the text element, either \"top\", \"middle\"\r\n * or \"bottom\". Optional, default is \"top\".\r\n * - \"localized\", 0 or 1, if 1 then the \"str\" actually contains a key to use to\r\n * fetch the value out of mxResources. Optional, default is\r\n * <mxStencil.defaultLocalized>.\r\n * - \"vertical\", 0 or 1, if 1 the label is rendered vertically (rotated by 90\r\n * degrees). Optional, default is 0.\r\n * - \"rotation\", angle in degrees (0 to 360). The angle to rotate the text by.\r\n * Optional, default is 0.\r\n * - \"align-shape\", 0 or 1, if 0 ignore the rotation of the shape when setting\r\n * the text rotation. Optional, default is 1.\r\n * \r\n * If <allowEval> is true, then the text content of the this element can define\r\n * a function which is invoked with the shape as the only argument and returns\r\n * the value for the text element (ignored if the str attribute is not null).\r\n * \r\n * Images:\r\n * \r\n * *image* elements can either be external URLs, or data URIs, where supported\r\n * (not in IE 7-). Attributes are:\r\n * \r\n * - \"src\", required string. Either a data URI or URL.\r\n * - \"x\", \"y\", required decimals. The (x,y) position of the image.\r\n * - \"w\", \"h\", required decimals. The width and height of the image.\r\n * - \"flipH\" and \"flipV\", optional 0 or 1. Whether to flip the image along the\r\n * horizontal/vertical axis. Default is 0 for both.\r\n * \r\n * If <allowEval> is true, then the text content of the this element can define\r\n * a function which is invoked with the shape as the only argument and returns\r\n * the value for the image source (ignored if the src attribute is not null).\r\n * \r\n * Sub-shapes:\r\n * \r\n * *include-shape* allow stencils to be rendered within the current stencil by\r\n * referencing the sub-stencil by name. Attributes are:\r\n * \r\n * - \"name\", required string. The unique shape name of the stencil.\r\n * - \"x\", \"y\", \"w\", \"h\", required decimals. The (x,y) position of the sub-shape\r\n * and its width and height.\r\n * \r\n * Constructor: mxStencil\r\n * \r\n * Constructs a new generic shape by setting <desc> to the given XML node and\r\n * invoking <parseDescription> and <parseConstraints>.\r\n * \r\n * Parameters:\r\n * \r\n * desc - XML node that contains the stencil description.\r\n */\r\nfunction mxStencil(desc)\r\n{\r\n\tthis.desc = desc;\r\n\tthis.parseDescription();\r\n\tthis.parseConstraints();\r\n};\r\n\r\n/**\r\n * Extends mxShape.\r\n */\r\nmxUtils.extend(mxStencil, mxShape);\r\n\r\n/**\r\n * Variable: defaultLocalized\r\n * \r\n * Static global variable that specifies the default value for the localized\r\n * attribute of the text element. Default is false.\r\n */\r\nmxStencil.defaultLocalized = false;\r\n\r\n/**\r\n * Function: allowEval\r\n * \r\n * Static global switch that specifies if the use of eval is allowed for\r\n * evaluating text content and images. Default is false. Set this to true\r\n * if stencils can not contain user input.\r\n */\r\nmxStencil.allowEval = false;\r\n\r\n/**\r\n * Variable: desc\r\n *\r\n * Holds the XML node with the stencil description.\r\n */\r\nmxStencil.prototype.desc = null;\r\n\r\n/**\r\n * Variable: constraints\r\n * \r\n * Holds an array of <mxConnectionConstraints> as defined in the shape.\r\n */\r\nmxStencil.prototype.constraints = null;\r\n\r\n/**\r\n * Variable: aspect\r\n *\r\n * Holds the aspect of the shape. Default is 'auto'.\r\n */\r\nmxStencil.prototype.aspect = null;\r\n\r\n/**\r\n * Variable: w0\r\n *\r\n * Holds the width of the shape. Default is 100.\r\n */\r\nmxStencil.prototype.w0 = null;\r\n\r\n/**\r\n * Variable: h0\r\n *\r\n * Holds the height of the shape. Default is 100.\r\n */\r\nmxStencil.prototype.h0 = null;\r\n\r\n/**\r\n * Variable: bgNodes\r\n *\r\n * Holds the XML node with the stencil description.\r\n */\r\nmxStencil.prototype.bgNode = null;\r\n\r\n/**\r\n * Variable: fgNodes\r\n *\r\n * Holds the XML node with the stencil description.\r\n */\r\nmxStencil.prototype.fgNode = null;\r\n\r\n/**\r\n * Variable: strokewidth\r\n *\r\n * Holds the strokewidth direction from the description.\r\n */\r\nmxStencil.prototype.strokewidth = null;\r\n\r\n/**\r\n * Function: parseDescription\r\n *\r\n * Reads <w0>, <h0>, <aspect>, <bgNodes> and <fgNodes> from <desc>.\r\n */\r\nmxStencil.prototype.parseDescription = function()\r\n{\r\n\t// LATER: Preprocess nodes for faster painting\r\n\tthis.fgNode = this.desc.getElementsByTagName('foreground')[0];\r\n\tthis.bgNode = this.desc.getElementsByTagName('background')[0];\r\n\tthis.w0 = Number(this.desc.getAttribute('w') || 100);\r\n\tthis.h0 = Number(this.desc.getAttribute('h') || 100);\r\n\t\r\n\t// Possible values for aspect are: variable and fixed where\r\n\t// variable means fill the available space and fixed means\r\n\t// use w0 and h0 to compute the aspect.\r\n\tvar aspect = this.desc.getAttribute('aspect');\r\n\tthis.aspect = (aspect != null) ? aspect : 'variable';\r\n\t\r\n\t// Possible values for strokewidth are all numbers and \"inherit\"\r\n\t// where the inherit means take the value from the style (ie. the\r\n\t// user-defined stroke-width). Note that the strokewidth is scaled\r\n\t// by the minimum scaling that is used to draw the shape (sx, sy).\r\n\tvar sw = this.desc.getAttribute('strokewidth');\r\n\tthis.strokewidth = (sw != null) ? sw : '1';\r\n};\r\n\r\n/**\r\n * Function: parseConstraints\r\n *\r\n * Reads the constraints from <desc> into <constraints> using\r\n * <parseConstraint>.\r\n */\r\nmxStencil.prototype.parseConstraints = function()\r\n{\r\n\tvar conns = this.desc.getElementsByTagName('connections')[0];\r\n\t\r\n\tif (conns != null)\r\n\t{\r\n\t\tvar tmp = mxUtils.getChildNodes(conns);\r\n\t\t\r\n\t\tif (tmp != null && tmp.length > 0)\r\n\t\t{\r\n\t\t\tthis.constraints = [];\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < tmp.length; i++)\r\n\t\t\t{\r\n\t\t\t\tthis.constraints.push(this.parseConstraint(tmp[i]));\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: parseConstraint\r\n *\r\n * Parses the given XML node and returns its <mxConnectionConstraint>.\r\n */\r\nmxStencil.prototype.parseConstraint = function(node)\r\n{\r\n\tvar x = Number(node.getAttribute('x'));\r\n\tvar y = Number(node.getAttribute('y'));\r\n\tvar perimeter = node.getAttribute('perimeter') == '1';\r\n\tvar name = node.getAttribute('name');\r\n\t\r\n\treturn new mxConnectionConstraint(new mxPoint(x, y), perimeter, name);\r\n};\r\n\r\n/**\r\n * Function: evaluateTextAttribute\r\n * \r\n * Gets the given attribute as a text. The return value from <evaluateAttribute>\r\n * is used as a key to <mxResources.get> if the localized attribute in the text\r\n * node is 1 or if <defaultLocalized> is true.\r\n */\r\nmxStencil.prototype.evaluateTextAttribute = function(node, attribute, shape)\r\n{\r\n\tvar result = this.evaluateAttribute(node, attribute, shape);\r\n\tvar loc = node.getAttribute('localized');\r\n\t\r\n\tif ((mxStencil.defaultLocalized && loc == null) || loc == '1')\r\n\t{\r\n\t\tresult = mxResources.get(result);\r\n\t}\r\n\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: evaluateAttribute\r\n *\r\n * Gets the attribute for the given name from the given node. If the attribute\r\n * does not exist then the text content of the node is evaluated and if it is\r\n * a function it is invoked with <shape> as the only argument and the return\r\n * value is used as the attribute value to be returned.\r\n */\r\nmxStencil.prototype.evaluateAttribute = function(node, attribute, shape)\r\n{\r\n\tvar result = node.getAttribute(attribute);\r\n\t\r\n\tif (result == null)\r\n\t{\r\n\t\tvar text = mxUtils.getTextContent(node);\r\n\t\t\r\n\t\tif (text != null && mxStencil.allowEval)\r\n\t\t{\r\n\t\t\tvar funct = mxUtils.eval(text);\r\n\t\t\t\r\n\t\t\tif (typeof(funct) == 'function')\r\n\t\t\t{\r\n\t\t\t\tresult = funct(shape);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: drawShape\r\n *\r\n * Draws this stencil inside the given bounds.\r\n */\r\nmxStencil.prototype.drawShape = function(canvas, shape, x, y, w, h)\r\n{\r\n\t// TODO: Internal structure (array of special structs?), relative and absolute\r\n\t// coordinates (eg. note shape, process vs star, actor etc.), text rendering\r\n\t// and non-proportional scaling, how to implement pluggable edge shapes\r\n\t// (start, segment, end blocks), pluggable markers, how to implement\r\n\t// swimlanes (title area) with this API, add icon, horizontal/vertical\r\n\t// label, indicator for all shapes, rotation\r\n\tvar direction = mxUtils.getValue(shape.style, mxConstants.STYLE_DIRECTION, null);\r\n\tvar aspect = this.computeAspect(shape.style, x, y, w, h, direction);\r\n\tvar minScale = Math.min(aspect.width, aspect.height);\r\n\tvar sw = (this.strokewidth == 'inherit') ?\r\n\t\t\tNumber(mxUtils.getNumber(shape.style, mxConstants.STYLE_STROKEWIDTH, 1)) :\r\n\t\t\tNumber(this.strokewidth) * minScale;\r\n\tcanvas.setStrokeWidth(sw);\r\n\r\n\t// Draws a transparent rectangle for catching events\r\n\tif (shape.style != null && mxUtils.getValue(shape.style, mxConstants.STYLE_POINTER_EVENTS, '0') == '1')\r\n\t{\r\n\t\tcanvas.setStrokeColor(mxConstants.NONE);\r\n\t\tcanvas.rect(x, y, w, h);\r\n\t\tcanvas.stroke();\r\n\t\tcanvas.setStrokeColor(shape.stroke);\r\n\t}\r\n\r\n\tthis.drawChildren(canvas, shape, x, y, w, h, this.bgNode, aspect, false, true);\r\n\tthis.drawChildren(canvas, shape, x, y, w, h, this.fgNode, aspect, true,\r\n\t\t!shape.outline || shape.style == null || mxUtils.getValue(\r\n\t\tshape.style, mxConstants.STYLE_BACKGROUND_OUTLINE, 0) == 0);\r\n};\r\n\r\n/**\r\n * Function: drawChildren\r\n *\r\n * Draws this stencil inside the given bounds.\r\n */\r\nmxStencil.prototype.drawChildren = function(canvas, shape, x, y, w, h, node, aspect, disableShadow, paint)\r\n{\r\n\tif (node != null && w > 0 && h > 0)\r\n\t{\r\n\t\tvar tmp = node.firstChild;\r\n\t\t\r\n\t\twhile (tmp != null)\r\n\t\t{\r\n\t\t\tif (tmp.nodeType == mxConstants.NODETYPE_ELEMENT)\r\n\t\t\t{\r\n\t\t\t\tthis.drawNode(canvas, shape, tmp, aspect, disableShadow, paint);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\ttmp = tmp.nextSibling;\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: computeAspect\r\n *\r\n * Returns a rectangle that contains the offset in x and y and the horizontal\r\n * and vertical scale in width and height used to draw this shape inside the\r\n * given <mxRectangle>.\r\n * \r\n * Parameters:\r\n * \r\n * shape - <mxShape> to be drawn.\r\n * bounds - <mxRectangle> that should contain the stencil.\r\n * direction - Optional direction of the shape to be darwn.\r\n */\r\nmxStencil.prototype.computeAspect = function(shape, x, y, w, h, direction)\r\n{\r\n\tvar x0 = x;\r\n\tvar y0 = y;\r\n\tvar sx = w / this.w0;\r\n\tvar sy = h / this.h0;\r\n\t\r\n\tvar inverse = (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH);\r\n\r\n\tif (inverse)\r\n\t{\r\n\t\tsy = w / this.h0;\r\n\t\tsx = h / this.w0;\r\n\t\t\r\n\t\tvar delta = (w - h) / 2;\r\n\r\n\t\tx0 += delta;\r\n\t\ty0 -= delta;\r\n\t}\r\n\r\n\tif (this.aspect == 'fixed')\r\n\t{\r\n\t\tsy = Math.min(sx, sy);\r\n\t\tsx = sy;\r\n\t\t\r\n\t\t// Centers the shape inside the available space\r\n\t\tif (inverse)\r\n\t\t{\r\n\t\t\tx0 += (h - this.w0 * sx) / 2;\r\n\t\t\ty0 += (w - this.h0 * sy) / 2;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tx0 += (w - this.w0 * sx) / 2;\r\n\t\t\ty0 += (h - this.h0 * sy) / 2;\r\n\t\t}\r\n\t}\r\n\r\n\treturn new mxRectangle(x0, y0, sx, sy);\r\n};\r\n\r\n/**\r\n * Function: drawNode\r\n *\r\n * Draws this stencil inside the given bounds.\r\n */\r\nmxStencil.prototype.drawNode = function(canvas, shape, node, aspect, disableShadow, paint)\r\n{\r\n\tvar name = node.nodeName;\r\n\tvar x0 = aspect.x;\r\n\tvar y0 = aspect.y;\r\n\tvar sx = aspect.width;\r\n\tvar sy = aspect.height;\r\n\tvar minScale = Math.min(sx, sy);\r\n\t\r\n\tif (name == 'save')\r\n\t{\r\n\t\tcanvas.save();\r\n\t}\r\n\telse if (name == 'restore')\r\n\t{\r\n\t\tcanvas.restore();\r\n\t}\r\n\telse if (paint)\r\n\t{\r\n\t\tif (name == 'path')\r\n\t\t{\r\n\t\t\tcanvas.begin();\r\n\t\t\t\r\n\t\t\tvar parseRegularly = true;\r\n\t\t\t\r\n\t\t\tif (node.getAttribute('rounded') == '1')\r\n\t\t\t{\r\n\t\t\t\tparseRegularly = false;\r\n\t\t\t\t\r\n\t\t\t\tvar arcSize = Number(node.getAttribute('arcSize'));\r\n\t\t\t\tvar pointCount = 0;\r\n\t\t\t\tvar segs = [];\r\n\t\t\t\t\r\n\t\t\t\t// Renders the elements inside the given path\r\n\t\t\t\tvar childNode = node.firstChild;\r\n\t\t\t\t\r\n\t\t\t\twhile (childNode != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (childNode.nodeType == mxConstants.NODETYPE_ELEMENT)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar childName = childNode.nodeName;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (childName == 'move' || childName == 'line')\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tif (childName == 'move' || segs.length == 0)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tsegs.push([]);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tsegs[segs.length - 1].push(new mxPoint(x0 + Number(childNode.getAttribute('x')) * sx,\r\n\t\t\t\t\t\t\t\ty0 + Number(childNode.getAttribute('y')) * sy));\r\n\t\t\t\t\t\t\tpointCount++;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t//We only support move and line for rounded corners\r\n\t\t\t\t\t\t\tparseRegularly = true;\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tchildNode = childNode.nextSibling;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (!parseRegularly && pointCount > 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tfor (var i = 0; i < segs.length; i++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar close = false, ps = segs[i][0], pe = segs[i][segs[i].length - 1];\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (ps.x == pe.x && ps.y == pe.y) \r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tsegs[i].pop();\r\n\t\t\t\t\t\t\tclose = true;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tthis.addPoints(canvas, segs[i], true, arcSize, close);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tparseRegularly = true;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (parseRegularly)\r\n\t\t\t{\r\n\t\t\t\t// Renders the elements inside the given path\r\n\t\t\t\tvar childNode = node.firstChild;\r\n\t\t\t\t\r\n\t\t\t\twhile (childNode != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (childNode.nodeType == mxConstants.NODETYPE_ELEMENT)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.drawNode(canvas, shape, childNode, aspect, disableShadow, paint);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tchildNode = childNode.nextSibling;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (name == 'close')\r\n\t\t{\r\n\t\t\tcanvas.close();\r\n\t\t}\r\n\t\telse if (name == 'move')\r\n\t\t{\r\n\t\t\tcanvas.moveTo(x0 + Number(node.getAttribute('x')) * sx, y0 + Number(node.getAttribute('y')) * sy);\r\n\t\t}\r\n\t\telse if (name == 'line')\r\n\t\t{\r\n\t\t\tcanvas.lineTo(x0 + Number(node.getAttribute('x')) * sx, y0 + Number(node.getAttribute('y')) * sy);\r\n\t\t}\r\n\t\telse if (name == 'quad')\r\n\t\t{\r\n\t\t\tcanvas.quadTo(x0 + Number(node.getAttribute('x1')) * sx,\r\n\t\t\t\t\ty0 + Number(node.getAttribute('y1')) * sy,\r\n\t\t\t\t\tx0 + Number(node.getAttribute('x2')) * sx,\r\n\t\t\t\t\ty0 + Number(node.getAttribute('y2')) * sy);\r\n\t\t}\r\n\t\telse if (name == 'curve')\r\n\t\t{\r\n\t\t\tcanvas.curveTo(x0 + Number(node.getAttribute('x1')) * sx,\r\n\t\t\t\t\ty0 + Number(node.getAttribute('y1')) * sy,\r\n\t\t\t\t\tx0 + Number(node.getAttribute('x2')) * sx,\r\n\t\t\t\t\ty0 + Number(node.getAttribute('y2')) * sy,\r\n\t\t\t\t\tx0 + Number(node.getAttribute('x3')) * sx,\r\n\t\t\t\t\ty0 + Number(node.getAttribute('y3')) * sy);\r\n\t\t}\r\n\t\telse if (name == 'arc')\r\n\t\t{\r\n\t\t\tcanvas.arcTo(Number(node.getAttribute('rx')) * sx,\r\n\t\t\t\t\tNumber(node.getAttribute('ry')) * sy,\r\n\t\t\t\t\tNumber(node.getAttribute('x-axis-rotation')),\r\n\t\t\t\t\tNumber(node.getAttribute('large-arc-flag')),\r\n\t\t\t\t\tNumber(node.getAttribute('sweep-flag')),\r\n\t\t\t\t\tx0 + Number(node.getAttribute('x')) * sx,\r\n\t\t\t\t\ty0 + Number(node.getAttribute('y')) * sy);\r\n\t\t}\r\n\t\telse if (name == 'rect')\r\n\t\t{\r\n\t\t\tcanvas.rect(x0 + Number(node.getAttribute('x')) * sx,\r\n\t\t\t\t\ty0 + Number(node.getAttribute('y')) * sy,\r\n\t\t\t\t\tNumber(node.getAttribute('w')) * sx,\r\n\t\t\t\t\tNumber(node.getAttribute('h')) * sy);\r\n\t\t}\r\n\t\telse if (name == 'roundrect')\r\n\t\t{\r\n\t\t\tvar arcsize = Number(node.getAttribute('arcsize'));\r\n\t\r\n\t\t\tif (arcsize == 0)\r\n\t\t\t{\r\n\t\t\t\tarcsize = mxConstants.RECTANGLE_ROUNDING_FACTOR * 100;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar w = Number(node.getAttribute('w')) * sx;\r\n\t\t\tvar h = Number(node.getAttribute('h')) * sy;\r\n\t\t\tvar factor = Number(arcsize) / 100;\r\n\t\t\tvar r = Math.min(w * factor, h * factor);\r\n\t\t\t\r\n\t\t\tcanvas.roundrect(x0 + Number(node.getAttribute('x')) * sx,\r\n\t\t\t\t\ty0 + Number(node.getAttribute('y')) * sy,\r\n\t\t\t\t\tw, h, r, r);\r\n\t\t}\r\n\t\telse if (name == 'ellipse')\r\n\t\t{\r\n\t\t\tcanvas.ellipse(x0 + Number(node.getAttribute('x')) * sx,\r\n\t\t\t\ty0 + Number(node.getAttribute('y')) * sy,\r\n\t\t\t\tNumber(node.getAttribute('w')) * sx,\r\n\t\t\t\tNumber(node.getAttribute('h')) * sy);\r\n\t\t}\r\n\t\telse if (name == 'image')\r\n\t\t{\r\n\t\t\tif (!shape.outline)\r\n\t\t\t{\r\n\t\t\t\tvar src = this.evaluateAttribute(node, 'src', shape);\r\n\t\t\t\t\r\n\t\t\t\tcanvas.image(x0 + Number(node.getAttribute('x')) * sx,\r\n\t\t\t\t\ty0 + Number(node.getAttribute('y')) * sy,\r\n\t\t\t\t\tNumber(node.getAttribute('w')) * sx,\r\n\t\t\t\t\tNumber(node.getAttribute('h')) * sy,\r\n\t\t\t\t\tsrc, false, node.getAttribute('flipH') == '1',\r\n\t\t\t\t\tnode.getAttribute('flipV') == '1');\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (name == 'text')\r\n\t\t{\r\n\t\t\tif (!shape.outline)\r\n\t\t\t{\r\n\t\t\t\tvar str = this.evaluateTextAttribute(node, 'str', shape);\r\n\t\t\t\tvar rotation = node.getAttribute('vertical') == '1' ? -90 : 0;\r\n\t\t\t\t\r\n\t\t\t\tif (node.getAttribute('align-shape') == '0')\r\n\t\t\t\t{\r\n\t\t\t\t\tvar dr = shape.rotation;\r\n\t\t\r\n\t\t\t\t\t// Depends on flipping\r\n\t\t\t\t\tvar flipH = mxUtils.getValue(shape.style, mxConstants.STYLE_FLIPH, 0) == 1;\r\n\t\t\t\t\tvar flipV = mxUtils.getValue(shape.style, mxConstants.STYLE_FLIPV, 0) == 1;\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (flipH && flipV)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\trotation -= dr;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (flipH || flipV)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\trotation += dr;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\trotation -= dr;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\r\n\t\t\t\trotation -= node.getAttribute('rotation');\r\n\t\t\r\n\t\t\t\tcanvas.text(x0 + Number(node.getAttribute('x')) * sx,\r\n\t\t\t\t\t\ty0 + Number(node.getAttribute('y')) * sy,\r\n\t\t\t\t\t\t0, 0, str, node.getAttribute('align') || 'left',\r\n\t\t\t\t\t\tnode.getAttribute('valign') || 'top', false, '',\r\n\t\t\t\t\t\tnull, false, rotation);\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (name == 'include-shape')\r\n\t\t{\r\n\t\t\tvar stencil = mxStencilRegistry.getStencil(node.getAttribute('name'));\r\n\t\t\t\r\n\t\t\tif (stencil != null)\r\n\t\t\t{\r\n\t\t\t\tvar x = x0 + Number(node.getAttribute('x')) * sx;\r\n\t\t\t\tvar y = y0 + Number(node.getAttribute('y')) * sy;\r\n\t\t\t\tvar w = Number(node.getAttribute('w')) * sx;\r\n\t\t\t\tvar h = Number(node.getAttribute('h')) * sy;\r\n\t\t\t\t\r\n\t\t\t\tstencil.drawShape(canvas, shape, x, y, w, h);\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (name == 'fillstroke')\r\n\t\t{\r\n\t\t\tcanvas.fillAndStroke();\r\n\t\t}\r\n\t\telse if (name == 'fill')\r\n\t\t{\r\n\t\t\tcanvas.fill();\r\n\t\t}\r\n\t\telse if (name == 'stroke')\r\n\t\t{\r\n\t\t\tcanvas.stroke();\r\n\t\t}\r\n\t\telse if (name == 'strokewidth')\r\n\t\t{\r\n\t\t\tvar s = (node.getAttribute('fixed') == '1') ? 1 : minScale;\r\n\t\t\tcanvas.setStrokeWidth(Number(node.getAttribute('width')) * s);\r\n\t\t}\r\n\t\telse if (name == 'dashed')\r\n\t\t{\r\n\t\t\tcanvas.setDashed(node.getAttribute('dashed') == '1');\r\n\t\t}\r\n\t\telse if (name == 'dashpattern')\r\n\t\t{\r\n\t\t\tvar value = node.getAttribute('pattern');\r\n\t\t\t\r\n\t\t\tif (value != null)\r\n\t\t\t{\r\n\t\t\t\tvar tmp = value.split(' ');\r\n\t\t\t\tvar pat = [];\r\n\t\t\t\t\r\n\t\t\t\tfor (var i = 0; i < tmp.length; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (tmp[i].length > 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tpat.push(Number(tmp[i]) * minScale);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tvalue = pat.join(' ');\r\n\t\t\t\tcanvas.setDashPattern(value);\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (name == 'strokecolor')\r\n\t\t{\r\n\t\t\tcanvas.setStrokeColor(node.getAttribute('color'));\r\n\t\t}\r\n\t\telse if (name == 'linecap')\r\n\t\t{\r\n\t\t\tcanvas.setLineCap(node.getAttribute('cap'));\r\n\t\t}\r\n\t\telse if (name == 'linejoin')\r\n\t\t{\r\n\t\t\tcanvas.setLineJoin(node.getAttribute('join'));\r\n\t\t}\r\n\t\telse if (name == 'miterlimit')\r\n\t\t{\r\n\t\t\tcanvas.setMiterLimit(Number(node.getAttribute('limit')));\r\n\t\t}\r\n\t\telse if (name == 'fillcolor')\r\n\t\t{\r\n\t\t\tcanvas.setFillColor(node.getAttribute('color'));\r\n\t\t}\r\n\t\telse if (name == 'alpha')\r\n\t\t{\r\n\t\t\tcanvas.setAlpha(node.getAttribute('alpha'));\r\n\t\t}\r\n\t\telse if (name == 'fontcolor')\r\n\t\t{\r\n\t\t\tcanvas.setFontColor(node.getAttribute('color'));\r\n\t\t}\r\n\t\telse if (name == 'fontstyle')\r\n\t\t{\r\n\t\t\tcanvas.setFontStyle(node.getAttribute('style'));\r\n\t\t}\r\n\t\telse if (name == 'fontfamily')\r\n\t\t{\r\n\t\t\tcanvas.setFontFamily(node.getAttribute('family'));\r\n\t\t}\r\n\t\telse if (name == 'fontsize')\r\n\t\t{\r\n\t\t\tcanvas.setFontSize(Number(node.getAttribute('size')) * minScale);\r\n\t\t}\r\n\t\t\r\n\t\tif (disableShadow && (name == 'fillstroke' || name == 'fill' || name == 'stroke'))\r\n\t\t{\r\n\t\t\tdisableShadow = false;\r\n\t\t\tcanvas.setShadow(false);\r\n\t\t}\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n * \r\n * Code to add stencils.\r\n * \r\n * (code)\r\n * var req = mxUtils.load('test/stencils.xml');\r\n * var root = req.getDocumentElement();\r\n * var shape = root.firstChild;\r\n * \r\n * while (shape != null)\r\n * {\r\n * \t if (shape.nodeType == mxConstants.NODETYPE_ELEMENT)\r\n *   {\r\n *     mxStencilRegistry.addStencil(shape.getAttribute('name'), new mxStencil(shape));\r\n *   }\r\n *   \r\n *   shape = shape.nextSibling;\r\n * }\r\n * (end)\r\n */\r\nvar mxStencilRegistry =\r\n{\r\n\t/**\r\n\t * Class: mxStencilRegistry\r\n\t * \r\n\t * A singleton class that provides a registry for stencils and the methods\r\n\t * for painting those stencils onto a canvas or into a DOM.\r\n\t */\r\n\tstencils: {},\r\n\t\r\n\t/**\r\n\t * Function: addStencil\r\n\t * \r\n\t * Adds the given <mxStencil>.\r\n\t */\r\n\taddStencil: function(name, stencil)\r\n\t{\r\n\t\tmxStencilRegistry.stencils[name] = stencil;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: getStencil\r\n\t * \r\n\t * Returns the <mxStencil> for the given name.\r\n\t */\r\n\tgetStencil: function(name)\r\n\t{\r\n\t\treturn mxStencilRegistry.stencils[name];\r\n\t}\r\n\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nvar mxMarker =\r\n{\r\n\t/**\r\n\t * Class: mxMarker\r\n\t * \r\n\t * A static class that implements all markers for VML and SVG using a\r\n\t * registry. NOTE: The signatures in this class will change.\r\n\t * \r\n\t * Variable: markers\r\n\t * \r\n\t * Maps from markers names to functions to paint the markers.\r\n\t */\r\n\tmarkers: [],\r\n\t\r\n\t/**\r\n\t * Function: addMarker\r\n\t * \r\n\t * Adds a factory method that updates a given endpoint and returns a\r\n\t * function to paint the marker onto the given canvas.\r\n\t */\r\n\taddMarker: function(type, funct)\r\n\t{\r\n\t\tmxMarker.markers[type] = funct;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: createMarker\r\n\t * \r\n\t * Returns a function to paint the given marker.\r\n\t */\r\n\tcreateMarker: function(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)\r\n\t{\r\n\t\tvar funct = mxMarker.markers[type];\r\n\t\t\r\n\t\treturn (funct != null) ? funct(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled) : null;\r\n\t}\r\n\r\n};\r\n\r\n/**\r\n * Adds the classic and block marker factory method.\r\n */\r\n(function()\r\n{\r\n\tfunction createArrow(widthFactor)\r\n\t{\r\n\t\twidthFactor = (widthFactor != null) ? widthFactor : 2;\r\n\t\t\r\n\t\treturn function(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)\r\n\t\t{\r\n\t\t\t// The angle of the forward facing arrow sides against the x axis is\r\n\t\t\t// 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for\r\n\t\t\t// only half the strokewidth is processed ).\r\n\t\t\tvar endOffsetX = unitX * sw * 1.118;\r\n\t\t\tvar endOffsetY = unitY * sw * 1.118;\r\n\t\t\t\r\n\t\t\tunitX = unitX * (size + sw);\r\n\t\t\tunitY = unitY * (size + sw);\r\n\t\r\n\t\t\tvar pt = pe.clone();\r\n\t\t\tpt.x -= endOffsetX;\r\n\t\t\tpt.y -= endOffsetY;\r\n\t\t\t\r\n\t\t\tvar f = (type != mxConstants.ARROW_CLASSIC && type != mxConstants.ARROW_CLASSIC_THIN) ? 1 : 3 / 4;\r\n\t\t\tpe.x += -unitX * f - endOffsetX;\r\n\t\t\tpe.y += -unitY * f - endOffsetY;\r\n\t\t\t\r\n\t\t\treturn function()\r\n\t\t\t{\r\n\t\t\t\tcanvas.begin();\r\n\t\t\t\tcanvas.moveTo(pt.x, pt.y);\r\n\t\t\t\tcanvas.lineTo(pt.x - unitX - unitY / widthFactor, pt.y - unitY + unitX / widthFactor);\r\n\t\t\t\r\n\t\t\t\tif (type == mxConstants.ARROW_CLASSIC || type == mxConstants.ARROW_CLASSIC_THIN)\r\n\t\t\t\t{\r\n\t\t\t\t\tcanvas.lineTo(pt.x - unitX * 3 / 4, pt.y - unitY * 3 / 4);\r\n\t\t\t\t}\r\n\t\t\t\r\n\t\t\t\tcanvas.lineTo(pt.x + unitY / widthFactor - unitX, pt.y - unitY - unitX / widthFactor);\r\n\t\t\t\tcanvas.close();\r\n\t\r\n\t\t\t\tif (filled)\r\n\t\t\t\t{\r\n\t\t\t\t\tcanvas.fillAndStroke();\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tcanvas.stroke();\r\n\t\t\t\t}\r\n\t\t\t};\r\n\t\t}\r\n\t};\r\n\t\r\n\tmxMarker.addMarker('classic', createArrow(2));\r\n\tmxMarker.addMarker('classicThin', createArrow(3));\r\n\tmxMarker.addMarker('block', createArrow(2));\r\n\tmxMarker.addMarker('blockThin', createArrow(3));\r\n\t\r\n\tfunction createOpenArrow(widthFactor)\r\n\t{\r\n\t\twidthFactor = (widthFactor != null) ? widthFactor : 2;\r\n\t\t\r\n\t\treturn function(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)\r\n\t\t{\r\n\t\t\t// The angle of the forward facing arrow sides against the x axis is\r\n\t\t\t// 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for\r\n\t\t\t// only half the strokewidth is processed ).\r\n\t\t\tvar endOffsetX = unitX * sw * 1.118;\r\n\t\t\tvar endOffsetY = unitY * sw * 1.118;\r\n\t\t\t\r\n\t\t\tunitX = unitX * (size + sw);\r\n\t\t\tunitY = unitY * (size + sw);\r\n\t\t\t\r\n\t\t\tvar pt = pe.clone();\r\n\t\t\tpt.x -= endOffsetX;\r\n\t\t\tpt.y -= endOffsetY;\r\n\t\t\t\r\n\t\t\tpe.x += -endOffsetX * 2;\r\n\t\t\tpe.y += -endOffsetY * 2;\r\n\r\n\t\t\treturn function()\r\n\t\t\t{\r\n\t\t\t\tcanvas.begin();\r\n\t\t\t\tcanvas.moveTo(pt.x - unitX - unitY / widthFactor, pt.y - unitY + unitX / widthFactor);\r\n\t\t\t\tcanvas.lineTo(pt.x, pt.y);\r\n\t\t\t\tcanvas.lineTo(pt.x + unitY / widthFactor - unitX, pt.y - unitY - unitX / widthFactor);\r\n\t\t\t\tcanvas.stroke();\r\n\t\t\t};\r\n\t\t}\r\n\t};\r\n\t\r\n\tmxMarker.addMarker('open', createOpenArrow(2));\r\n\tmxMarker.addMarker('openThin', createOpenArrow(3));\r\n\t\r\n\tmxMarker.addMarker('oval', function(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)\r\n\t{\r\n\t\tvar a = size / 2;\r\n\t\t\r\n\t\tvar pt = pe.clone();\r\n\t\tpe.x -= unitX * a;\r\n\t\tpe.y -= unitY * a;\r\n\r\n\t\treturn function()\r\n\t\t{\r\n\t\t\tcanvas.ellipse(pt.x - a, pt.y - a, size, size);\r\n\t\t\t\t\t\t\r\n\t\t\tif (filled)\r\n\t\t\t{\r\n\t\t\t\tcanvas.fillAndStroke();\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tcanvas.stroke();\r\n\t\t\t}\r\n\t\t};\r\n\t});\r\n\r\n\tfunction diamond(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)\r\n\t{\r\n\t\t// The angle of the forward facing arrow sides against the x axis is\r\n\t\t// 45 degrees, 1/sin(45) = 1.4142 / 2 = 0.7071 ( / 2 allows for\r\n\t\t// only half the strokewidth is processed ). Or 0.9862 for thin diamond.\r\n\t\t// Note these values and the tk variable below are dependent, update\r\n\t\t// both together (saves trig hard coding it).\r\n\t\tvar swFactor = (type == mxConstants.ARROW_DIAMOND) ?  0.7071 : 0.9862;\r\n\t\tvar endOffsetX = unitX * sw * swFactor;\r\n\t\tvar endOffsetY = unitY * sw * swFactor;\r\n\t\t\r\n\t\tunitX = unitX * (size + sw);\r\n\t\tunitY = unitY * (size + sw);\r\n\t\t\r\n\t\tvar pt = pe.clone();\r\n\t\tpt.x -= endOffsetX;\r\n\t\tpt.y -= endOffsetY;\r\n\t\t\r\n\t\tpe.x += -unitX - endOffsetX;\r\n\t\tpe.y += -unitY - endOffsetY;\r\n\t\t\r\n\t\t// thickness factor for diamond\r\n\t\tvar tk = ((type == mxConstants.ARROW_DIAMOND) ?  2 : 3.4);\r\n\t\t\r\n\t\treturn function()\r\n\t\t{\r\n\t\t\tcanvas.begin();\r\n\t\t\tcanvas.moveTo(pt.x, pt.y);\r\n\t\t\tcanvas.lineTo(pt.x - unitX / 2 - unitY / tk, pt.y + unitX / tk - unitY / 2);\r\n\t\t\tcanvas.lineTo(pt.x - unitX, pt.y - unitY);\r\n\t\t\tcanvas.lineTo(pt.x - unitX / 2 + unitY / tk, pt.y - unitY / 2 - unitX / tk);\r\n\t\t\tcanvas.close();\r\n\t\t\t\r\n\t\t\tif (filled)\r\n\t\t\t{\r\n\t\t\t\tcanvas.fillAndStroke();\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tcanvas.stroke();\r\n\t\t\t}\r\n\t\t};\r\n\t};\r\n\r\n\tmxMarker.addMarker('diamond', diamond);\r\n\tmxMarker.addMarker('diamondThin', diamond);\r\n})();\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxActor\r\n *\r\n * Extends <mxShape> to implement an actor shape. If a custom shape with one\r\n * filled area is needed, then this shape's <redrawPath> should be overridden.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * function SampleShape() { }\r\n * \r\n * SampleShape.prototype = new mxActor();\r\n * SampleShape.prototype.constructor = vsAseShape;\r\n * \r\n * mxCellRenderer.registerShape('sample', SampleShape);\r\n * SampleShape.prototype.redrawPath = function(path, x, y, w, h)\r\n * {\r\n *   path.moveTo(0, 0);\r\n *   path.lineTo(w, h);\r\n *   // ...\r\n *   path.close();\r\n * }\r\n * (end)\r\n * \r\n * This shape is registered under <mxConstants.SHAPE_ACTOR> in\r\n * <mxCellRenderer>.\r\n * \r\n * Constructor: mxActor\r\n *\r\n * Constructs a new actor shape.\r\n * \r\n * Parameters:\r\n * \r\n * bounds - <mxRectangle> that defines the bounds. This is stored in\r\n * <mxShape.bounds>.\r\n * fill - String that defines the fill color. This is stored in <fill>.\r\n * stroke - String that defines the stroke color. This is stored in <stroke>.\r\n * strokewidth - Optional integer that defines the stroke width. Default is\r\n * 1. This is stored in <strokewidth>.\r\n */\r\nfunction mxActor(bounds, fill, stroke, strokewidth)\r\n{\r\n\tmxShape.call(this);\r\n\tthis.bounds = bounds;\r\n\tthis.fill = fill;\r\n\tthis.stroke = stroke;\r\n\tthis.strokewidth = (strokewidth != null) ? strokewidth : 1;\r\n};\r\n\r\n/**\r\n * Extends mxShape.\r\n */\r\nmxUtils.extend(mxActor, mxShape);\r\n\r\n/**\r\n * Function: paintVertexShape\r\n * \r\n * Redirects to redrawPath for subclasses to work.\r\n */\r\nmxActor.prototype.paintVertexShape = function(c, x, y, w, h)\r\n{\r\n\tc.translate(x, y);\r\n\tc.begin();\r\n\tthis.redrawPath(c, x, y, w, h);\r\n\tc.fillAndStroke();\r\n};\r\n\r\n/**\r\n * Function: redrawPath\r\n *\r\n * Draws the path for this shape.\r\n */\r\nmxActor.prototype.redrawPath = function(c, x, y, w, h)\r\n{\r\n\tvar width = w/3;\r\n\tc.moveTo(0, h);\r\n\tc.curveTo(0, 3 * h / 5, 0, 2 * h / 5, w / 2, 2 * h / 5);\r\n\tc.curveTo(w / 2 - width, 2 * h / 5, w / 2 - width, 0, w / 2, 0);\r\n\tc.curveTo(w / 2 + width, 0, w / 2 + width, 2 * h / 5, w / 2, 2 * h / 5);\r\n\tc.curveTo(w, 2 * h / 5, w, 3 * h / 5, w, h);\r\n\tc.close();\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxCloud\r\n *\r\n * Extends <mxActor> to implement a cloud shape.\r\n * \r\n * This shape is registered under <mxConstants.SHAPE_CLOUD> in\r\n * <mxCellRenderer>.\r\n * \r\n * Constructor: mxCloud\r\n *\r\n * Constructs a new cloud shape.\r\n * \r\n * Parameters:\r\n * \r\n * bounds - <mxRectangle> that defines the bounds. This is stored in\r\n * <mxShape.bounds>.\r\n * fill - String that defines the fill color. This is stored in <fill>.\r\n * stroke - String that defines the stroke color. This is stored in <stroke>.\r\n * strokewidth - Optional integer that defines the stroke width. Default is\r\n * 1. This is stored in <strokewidth>.\r\n */\r\nfunction mxCloud(bounds, fill, stroke, strokewidth)\r\n{\r\n\tmxActor.call(this);\r\n\tthis.bounds = bounds;\r\n\tthis.fill = fill;\r\n\tthis.stroke = stroke;\r\n\tthis.strokewidth = (strokewidth != null) ? strokewidth : 1;\r\n};\r\n\r\n/**\r\n * Extends mxActor.\r\n */\r\nmxUtils.extend(mxCloud, mxActor);\r\n\r\n/**\r\n * Function: redrawPath\r\n *\r\n * Draws the path for this shape.\r\n */\r\nmxCloud.prototype.redrawPath = function(c, x, y, w, h)\r\n{\r\n\tc.moveTo(0.25 * w, 0.25 * h);\r\n\tc.curveTo(0.05 * w, 0.25 * h, 0, 0.5 * h, 0.16 * w, 0.55 * h);\r\n\tc.curveTo(0, 0.66 * h, 0.18 * w, 0.9 * h, 0.31 * w, 0.8 * h);\r\n\tc.curveTo(0.4 * w, h, 0.7 * w, h, 0.8 * w, 0.8 * h);\r\n\tc.curveTo(w, 0.8 * h, w, 0.6 * h, 0.875 * w, 0.5 * h);\r\n\tc.curveTo(w, 0.3 * h, 0.8 * w, 0.1 * h, 0.625 * w, 0.2 * h);\r\n\tc.curveTo(0.5 * w, 0.05 * h, 0.3 * w, 0.05 * h, 0.25 * w, 0.25 * h);\r\n\tc.close();\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxRectangleShape\r\n *\r\n * Extends <mxShape> to implement a rectangle shape.\r\n * This shape is registered under <mxConstants.SHAPE_RECTANGLE>\r\n * in <mxCellRenderer>.\r\n * \r\n * Constructor: mxRectangleShape\r\n *\r\n * Constructs a new rectangle shape.\r\n * \r\n * Parameters:\r\n * \r\n * bounds - <mxRectangle> that defines the bounds. This is stored in\r\n * <mxShape.bounds>.\r\n * fill - String that defines the fill color. This is stored in <fill>.\r\n * stroke - String that defines the stroke color. This is stored in <stroke>.\r\n * strokewidth - Optional integer that defines the stroke width. Default is\r\n * 1. This is stored in <strokewidth>.\r\n */\r\nfunction mxRectangleShape(bounds, fill, stroke, strokewidth)\r\n{\r\n\tmxShape.call(this);\r\n\tthis.bounds = bounds;\r\n\tthis.fill = fill;\r\n\tthis.stroke = stroke;\r\n\tthis.strokewidth = (strokewidth != null) ? strokewidth : 1;\r\n};\r\n\r\n/**\r\n * Extends mxShape.\r\n */\r\nmxUtils.extend(mxRectangleShape, mxShape);\r\n\r\n/**\r\n * Function: isHtmlAllowed\r\n *\r\n * Returns true for non-rounded, non-rotated shapes with no glass gradient.\r\n */\r\nmxRectangleShape.prototype.isHtmlAllowed = function()\r\n{\r\n\tvar events = true;\r\n\t\r\n\tif (this.style != null)\r\n\t{\r\n\t\tevents = mxUtils.getValue(this.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1';\t\t\r\n\t}\r\n\t\r\n\treturn !this.isRounded && !this.glass && this.rotation == 0 && (events ||\r\n\t\t(this.fill != null && this.fill != mxConstants.NONE));\r\n};\r\n\r\n/**\r\n * Function: paintBackground\r\n * \r\n * Generic background painting implementation.\r\n */\r\nmxRectangleShape.prototype.paintBackground = function(c, x, y, w, h)\r\n{\r\n\tvar events = true;\r\n\t\r\n\tif (this.style != null)\r\n\t{\r\n\t\tevents = mxUtils.getValue(this.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1';\r\n\t}\r\n\t\r\n\tif (events || (this.fill != null && this.fill != mxConstants.NONE) ||\r\n\t\t(this.stroke != null && this.stroke != mxConstants.NONE))\r\n\t{\r\n\t\tif (!events && (this.fill == null || this.fill == mxConstants.NONE))\r\n\t\t{\r\n\t\t\tc.pointerEvents = false;\r\n\t\t}\r\n\t\t\r\n\t\tif (this.isRounded)\r\n\t\t{\r\n\t\t\tvar r = 0;\r\n\t\t\t\r\n\t\t\tif (mxUtils.getValue(this.style, mxConstants.STYLE_ABSOLUTE_ARCSIZE, 0) == '1')\r\n\t\t\t{\r\n\t\t\t\tr = Math.min(w / 2, Math.min(h / 2, mxUtils.getValue(this.style,\r\n\t\t\t\t\tmxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2));\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tvar f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE,\r\n\t\t\t\t\tmxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;\r\n\t\t\t\tr = Math.min(w * f, h * f);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tc.roundrect(x, y, w, h, r, r);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tc.rect(x, y, w, h);\r\n\t\t}\r\n\t\t\t\r\n\t\tc.fillAndStroke();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isRoundable\r\n * \r\n * Adds roundable support.\r\n */\r\nmxRectangleShape.prototype.isRoundable = function(c, x, y, w, h)\r\n{\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: paintForeground\r\n * \r\n * Generic background painting implementation.\r\n */\r\nmxRectangleShape.prototype.paintForeground = function(c, x, y, w, h)\r\n{\r\n\tif (this.glass && !this.outline && this.fill != null && this.fill != mxConstants.NONE)\r\n\t{\r\n\t\tthis.paintGlassEffect(c, x, y, w, h, this.getArcSize(w + this.strokewidth, h + this.strokewidth));\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxEllipse\r\n *\r\n * Extends <mxShape> to implement an ellipse shape.\r\n * This shape is registered under <mxConstants.SHAPE_ELLIPSE>\r\n * in <mxCellRenderer>.\r\n * \r\n * Constructor: mxEllipse\r\n *\r\n * Constructs a new ellipse shape.\r\n * \r\n * Parameters:\r\n * \r\n * bounds - <mxRectangle> that defines the bounds. This is stored in\r\n * <mxShape.bounds>.\r\n * fill - String that defines the fill color. This is stored in <fill>.\r\n * stroke - String that defines the stroke color. This is stored in <stroke>.\r\n * strokewidth - Optional integer that defines the stroke width. Default is\r\n * 1. This is stored in <strokewidth>.\r\n */\r\nfunction mxEllipse(bounds, fill, stroke, strokewidth)\r\n{\r\n\tmxShape.call(this);\r\n\tthis.bounds = bounds;\r\n\tthis.fill = fill;\r\n\tthis.stroke = stroke;\r\n\tthis.strokewidth = (strokewidth != null) ? strokewidth : 1;\r\n};\r\n\r\n/**\r\n * Extends mxShape.\r\n */\r\nmxUtils.extend(mxEllipse, mxShape);\r\n\r\n/**\r\n * Function: paintVertexShape\r\n * \r\n * Paints the ellipse shape.\r\n */\r\nmxEllipse.prototype.paintVertexShape = function(c, x, y, w, h)\r\n{\r\n\tc.ellipse(x, y, w, h);\r\n\tc.fillAndStroke();\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxDoubleEllipse\r\n *\r\n * Extends <mxShape> to implement a double ellipse shape. This shape is\r\n * registered under <mxConstants.SHAPE_DOUBLE_ELLIPSE> in <mxCellRenderer>.\r\n * Use the following override to only fill the inner ellipse in this shape:\r\n * \r\n * (code)\r\n * mxDoubleEllipse.prototype.paintVertexShape = function(c, x, y, w, h)\r\n * {\r\n *   c.ellipse(x, y, w, h);\r\n *   c.stroke();\r\n *   \r\n *   var inset = mxUtils.getValue(this.style, mxConstants.STYLE_MARGIN, Math.min(3 + this.strokewidth, Math.min(w / 5, h / 5)));\r\n *   x += inset;\r\n *   y += inset;\r\n *   w -= 2 * inset;\r\n *   h -= 2 * inset;\r\n *   \r\n *   if (w > 0 && h > 0)\r\n *   {\r\n *     c.ellipse(x, y, w, h);\r\n *   }\r\n *   \r\n *   c.fillAndStroke();\r\n * };\r\n * (end)\r\n * \r\n * Constructor: mxDoubleEllipse\r\n *\r\n * Constructs a new ellipse shape.\r\n *\r\n * Parameters:\r\n *\r\n * bounds - <mxRectangle> that defines the bounds. This is stored in\r\n * <mxShape.bounds>.\r\n * fill - String that defines the fill color. This is stored in <fill>.\r\n * stroke - String that defines the stroke color. This is stored in <stroke>.\r\n * strokewidth - Optional integer that defines the stroke width. Default is\r\n * 1. This is stored in <strokewidth>.\r\n */\r\nfunction mxDoubleEllipse(bounds, fill, stroke, strokewidth)\r\n{\r\n\tmxShape.call(this);\r\n\tthis.bounds = bounds;\r\n\tthis.fill = fill;\r\n\tthis.stroke = stroke;\r\n\tthis.strokewidth = (strokewidth != null) ? strokewidth : 1;\r\n};\r\n\r\n/**\r\n * Extends mxShape.\r\n */\r\nmxUtils.extend(mxDoubleEllipse, mxShape);\r\n\r\n/**\r\n * Variable: vmlScale\r\n * \r\n * Scale for improving the precision of VML rendering. Default is 10.\r\n */\r\nmxDoubleEllipse.prototype.vmlScale = 10;\r\n\r\n/**\r\n * Function: paintBackground\r\n * \r\n * Paints the background.\r\n */\r\nmxDoubleEllipse.prototype.paintBackground = function(c, x, y, w, h)\r\n{\r\n\tc.ellipse(x, y, w, h);\r\n\tc.fillAndStroke();\r\n};\r\n\r\n/**\r\n * Function: paintForeground\r\n * \r\n * Paints the foreground.\r\n */\r\nmxDoubleEllipse.prototype.paintForeground = function(c, x, y, w, h)\r\n{\r\n\tif (!this.outline)\r\n\t{\r\n\t\tvar margin = mxUtils.getValue(this.style, mxConstants.STYLE_MARGIN, Math.min(3 + this.strokewidth, Math.min(w / 5, h / 5)));\r\n\t\tx += margin;\r\n\t\ty += margin;\r\n\t\tw -= 2 * margin;\r\n\t\th -= 2 * margin;\r\n\t\t\r\n\t\t// FIXME: Rounding issues in IE8 standards mode (not in 1.x)\r\n\t\tif (w > 0 && h > 0)\r\n\t\t{\r\n\t\t\tc.ellipse(x, y, w, h);\r\n\t\t}\r\n\t\t\r\n\t\tc.stroke();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getLabelBounds\r\n * \r\n * Returns the bounds for the label.\r\n */\r\nmxDoubleEllipse.prototype.getLabelBounds = function(rect)\r\n{\r\n\tvar margin = (mxUtils.getValue(this.style, mxConstants.STYLE_MARGIN, Math.min(3 + this.strokewidth,\r\n\t\t\tMath.min(rect.width / 5 / this.scale, rect.height / 5 / this.scale)))) * this.scale;\r\n\r\n\treturn new mxRectangle(rect.x + margin, rect.y + margin, rect.width - 2 * margin, rect.height - 2 * margin);\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxRhombus\r\n *\r\n * Extends <mxShape> to implement a rhombus (aka diamond) shape.\r\n * This shape is registered under <mxConstants.SHAPE_RHOMBUS>\r\n * in <mxCellRenderer>.\r\n * \r\n * Constructor: mxRhombus\r\n *\r\n * Constructs a new rhombus shape.\r\n * \r\n * Parameters:\r\n * \r\n * bounds - <mxRectangle> that defines the bounds. This is stored in\r\n * <mxShape.bounds>.\r\n * fill - String that defines the fill color. This is stored in <fill>.\r\n * stroke - String that defines the stroke color. This is stored in <stroke>.\r\n * strokewidth - Optional integer that defines the stroke width. Default is\r\n * 1. This is stored in <strokewidth>.\r\n */\r\nfunction mxRhombus(bounds, fill, stroke, strokewidth)\r\n{\r\n\tmxShape.call(this);\r\n\tthis.bounds = bounds;\r\n\tthis.fill = fill;\r\n\tthis.stroke = stroke;\r\n\tthis.strokewidth = (strokewidth != null) ? strokewidth : 1;\r\n};\r\n\r\n/**\r\n * Extends mxShape.\r\n */\r\nmxUtils.extend(mxRhombus, mxShape);\r\n\r\n/**\r\n * Function: isRoundable\r\n * \r\n * Adds roundable support.\r\n */\r\nmxRhombus.prototype.isRoundable = function()\r\n{\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: paintVertexShape\r\n * \r\n * Generic painting implementation.\r\n */\r\nmxRhombus.prototype.paintVertexShape = function(c, x, y, w, h)\r\n{\r\n\tvar hw = w / 2;\r\n\tvar hh = h / 2;\r\n\t\r\n\tvar arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;\r\n\tc.begin();\r\n\tthis.addPoints(c, [new mxPoint(x + hw, y), new mxPoint(x + w, y + hh), new mxPoint(x + hw, y + h),\r\n\t     new mxPoint(x, y + hh)], this.isRounded, arcSize, true);\r\n\tc.fillAndStroke();\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxPolyline\r\n *\r\n * Extends <mxShape> to implement a polyline (a line with multiple points).\r\n * This shape is registered under <mxConstants.SHAPE_POLYLINE> in\r\n * <mxCellRenderer>.\r\n * \r\n * Constructor: mxPolyline\r\n *\r\n * Constructs a new polyline shape.\r\n * \r\n * Parameters:\r\n * \r\n * points - Array of <mxPoints> that define the points. This is stored in\r\n * <mxShape.points>.\r\n * stroke - String that defines the stroke color. Default is 'black'. This is\r\n * stored in <stroke>.\r\n * strokewidth - Optional integer that defines the stroke width. Default is\r\n * 1. This is stored in <strokewidth>.\r\n */\r\nfunction mxPolyline(points, stroke, strokewidth)\r\n{\r\n\tmxShape.call(this);\r\n\tthis.points = points;\r\n\tthis.stroke = stroke;\r\n\tthis.strokewidth = (strokewidth != null) ? strokewidth : 1;\r\n};\r\n\r\n/**\r\n * Extends mxShape.\r\n */\r\nmxUtils.extend(mxPolyline, mxShape);\r\n\r\n/**\r\n * Function: getRotation\r\n * \r\n * Returns 0.\r\n */\r\nmxPolyline.prototype.getRotation = function()\r\n{\r\n\treturn 0;\r\n};\r\n\r\n/**\r\n * Function: getShapeRotation\r\n * \r\n * Returns 0.\r\n */\r\nmxPolyline.prototype.getShapeRotation = function()\r\n{\r\n\treturn 0;\r\n};\r\n\r\n/**\r\n * Function: isPaintBoundsInverted\r\n * \r\n * Returns false.\r\n */\r\nmxPolyline.prototype.isPaintBoundsInverted = function()\r\n{\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: paintEdgeShape\r\n * \r\n * Paints the line shape.\r\n */\r\nmxPolyline.prototype.paintEdgeShape = function(c, pts)\r\n{\r\n\tvar prev = c.pointerEventsValue;\r\n\tc.pointerEventsValue = 'stroke';\r\n\t\r\n\tif (this.style == null || this.style[mxConstants.STYLE_CURVED] != 1)\r\n\t{\r\n\t\tthis.paintLine(c, pts, this.isRounded);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.paintCurvedLine(c, pts);\r\n\t}\r\n\t\r\n\tc.pointerEventsValue = prev;\r\n};\r\n\r\n/**\r\n * Function: paintLine\r\n * \r\n * Paints the line shape.\r\n */\r\nmxPolyline.prototype.paintLine = function(c, pts, rounded)\r\n{\r\n\tvar arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;\r\n\tc.begin();\r\n\tthis.addPoints(c, pts, rounded, arcSize, false);\r\n\tc.stroke();\r\n};\r\n\r\n/**\r\n * Function: paintLine\r\n * \r\n * Paints the line shape.\r\n */\r\nmxPolyline.prototype.paintCurvedLine = function(c, pts)\r\n{\r\n\tc.begin();\r\n\t\r\n\tvar pt = pts[0];\r\n\tvar n = pts.length;\r\n\t\r\n\tc.moveTo(pt.x, pt.y);\r\n\t\r\n\tfor (var i = 1; i < n - 2; i++)\r\n\t{\r\n\t\tvar p0 = pts[i];\r\n\t\tvar p1 = pts[i + 1];\r\n\t\tvar ix = (p0.x + p1.x) / 2;\r\n\t\tvar iy = (p0.y + p1.y) / 2;\r\n\t\t\r\n\t\tc.quadTo(p0.x, p0.y, ix, iy);\r\n\t}\r\n\t\r\n\tvar p0 = pts[n - 2];\r\n\tvar p1 = pts[n - 1];\r\n\t\r\n\tc.quadTo(p0.x, p0.y, p1.x, p1.y);\r\n\tc.stroke();\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxArrow\r\n *\r\n * Extends <mxShape> to implement an arrow shape. (The shape\r\n * is used to represent edges, not vertices.)\r\n * This shape is registered under <mxConstants.SHAPE_ARROW>\r\n * in <mxCellRenderer>.\r\n * \r\n * Constructor: mxArrow\r\n *\r\n * Constructs a new arrow shape.\r\n * \r\n * Parameters:\r\n * \r\n * points - Array of <mxPoints> that define the points. This is stored in\r\n * <mxShape.points>.\r\n * fill - String that defines the fill color. This is stored in <fill>.\r\n * stroke - String that defines the stroke color. This is stored in <stroke>.\r\n * strokewidth - Optional integer that defines the stroke width. Default is\r\n * 1. This is stored in <strokewidth>.\r\n * arrowWidth - Optional integer that defines the arrow width. Default is\r\n * <mxConstants.ARROW_WIDTH>. This is stored in <arrowWidth>.\r\n * spacing - Optional integer that defines the spacing between the arrow shape\r\n * and its endpoints. Default is <mxConstants.ARROW_SPACING>. This is stored in\r\n * <spacing>.\r\n * endSize - Optional integer that defines the size of the arrowhead. Default\r\n * is <mxConstants.ARROW_SIZE>. This is stored in <endSize>.\r\n */\r\nfunction mxArrow(points, fill, stroke, strokewidth, arrowWidth, spacing, endSize)\r\n{\r\n\tmxShape.call(this);\r\n\tthis.points = points;\r\n\tthis.fill = fill;\r\n\tthis.stroke = stroke;\r\n\tthis.strokewidth = (strokewidth != null) ? strokewidth : 1;\r\n\tthis.arrowWidth = (arrowWidth != null) ? arrowWidth : mxConstants.ARROW_WIDTH;\r\n\tthis.spacing = (spacing != null) ? spacing : mxConstants.ARROW_SPACING;\r\n\tthis.endSize = (endSize != null) ? endSize : mxConstants.ARROW_SIZE;\r\n};\r\n\r\n/**\r\n * Extends mxShape.\r\n */\r\nmxUtils.extend(mxArrow, mxShape);\r\n\r\n/**\r\n * Function: augmentBoundingBox\r\n *\r\n * Augments the bounding box with the edge width and markers.\r\n */\r\nmxArrow.prototype.augmentBoundingBox = function(bbox)\r\n{\r\n\tmxShape.prototype.augmentBoundingBox.apply(this, arguments);\r\n\t\r\n\tvar w = Math.max(this.arrowWidth, this.endSize);\r\n\tbbox.grow((w / 2 + this.strokewidth) * this.scale);\r\n};\r\n\r\n/**\r\n * Function: paintEdgeShape\r\n * \r\n * Paints the line shape.\r\n */\r\nmxArrow.prototype.paintEdgeShape = function(c, pts)\r\n{\r\n\t// Geometry of arrow\r\n\tvar spacing =  mxConstants.ARROW_SPACING;\r\n\tvar width = mxConstants.ARROW_WIDTH;\r\n\tvar arrow = mxConstants.ARROW_SIZE;\r\n\r\n\t// Base vector (between end points)\r\n\tvar p0 = pts[0];\r\n\tvar pe = pts[pts.length - 1];\r\n\tvar dx = pe.x - p0.x;\r\n\tvar dy = pe.y - p0.y;\r\n\tvar dist = Math.sqrt(dx * dx + dy * dy);\r\n\tvar length = dist - 2 * spacing - arrow;\r\n\t\r\n\t// Computes the norm and the inverse norm\r\n\tvar nx = dx / dist;\r\n\tvar ny = dy / dist;\r\n\tvar basex = length * nx;\r\n\tvar basey = length * ny;\r\n\tvar floorx = width * ny/3;\r\n\tvar floory = -width * nx/3;\r\n\t\r\n\t// Computes points\r\n\tvar p0x = p0.x - floorx / 2 + spacing * nx;\r\n\tvar p0y = p0.y - floory / 2 + spacing * ny;\r\n\tvar p1x = p0x + floorx;\r\n\tvar p1y = p0y + floory;\r\n\tvar p2x = p1x + basex;\r\n\tvar p2y = p1y + basey;\r\n\tvar p3x = p2x + floorx;\r\n\tvar p3y = p2y + floory;\r\n\t// p4 not necessary\r\n\tvar p5x = p3x - 3 * floorx;\r\n\tvar p5y = p3y - 3 * floory;\r\n\t\r\n\tc.begin();\r\n\tc.moveTo(p0x, p0y);\r\n\tc.lineTo(p1x, p1y);\r\n\tc.lineTo(p2x, p2y);\r\n\tc.lineTo(p3x, p3y);\r\n\tc.lineTo(pe.x - spacing * nx, pe.y - spacing * ny);\r\n\tc.lineTo(p5x, p5y);\r\n\tc.lineTo(p5x + floorx, p5y + floory);\r\n\tc.close();\r\n\r\n\tc.fillAndStroke();\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxArrowConnector\r\n *\r\n * Extends <mxShape> to implement an new rounded arrow shape with support for\r\n * waypoints and double arrows. (The shape is used to represent edges, not\r\n * vertices.) This shape is registered under <mxConstants.SHAPE_ARROW_CONNECTOR>\r\n * in <mxCellRenderer>.\r\n * \r\n * Constructor: mxArrowConnector\r\n *\r\n * Constructs a new arrow shape.\r\n * \r\n * Parameters:\r\n * \r\n * points - Array of <mxPoints> that define the points. This is stored in\r\n * <mxShape.points>.\r\n * fill - String that defines the fill color. This is stored in <fill>.\r\n * stroke - String that defines the stroke color. This is stored in <stroke>.\r\n * strokewidth - Optional integer that defines the stroke width. Default is\r\n * 1. This is stored in <strokewidth>.\r\n * arrowWidth - Optional integer that defines the arrow width. Default is\r\n * <mxConstants.ARROW_WIDTH>. This is stored in <arrowWidth>.\r\n * spacing - Optional integer that defines the spacing between the arrow shape\r\n * and its endpoints. Default is <mxConstants.ARROW_SPACING>. This is stored in\r\n * <spacing>.\r\n * endSize - Optional integer that defines the size of the arrowhead. Default\r\n * is <mxConstants.ARROW_SIZE>. This is stored in <endSize>.\r\n */\r\nfunction mxArrowConnector(points, fill, stroke, strokewidth, arrowWidth, spacing, endSize)\r\n{\r\n\tmxShape.call(this);\r\n\tthis.points = points;\r\n\tthis.fill = fill;\r\n\tthis.stroke = stroke;\r\n\tthis.strokewidth = (strokewidth != null) ? strokewidth : 1;\r\n\tthis.arrowWidth = (arrowWidth != null) ? arrowWidth : mxConstants.ARROW_WIDTH;\r\n\tthis.arrowSpacing = (spacing != null) ? spacing : mxConstants.ARROW_SPACING;\r\n\tthis.startSize = mxConstants.ARROW_SIZE / 5;\r\n\tthis.endSize = mxConstants.ARROW_SIZE / 5;\r\n};\r\n\r\n/**\r\n * Extends mxShape.\r\n */\r\nmxUtils.extend(mxArrowConnector, mxShape);\r\n\r\n/**\r\n * Variable: useSvgBoundingBox\r\n * \r\n * Allows to use the SVG bounding box in SVG. Default is false for performance\r\n * reasons.\r\n */\r\nmxArrowConnector.prototype.useSvgBoundingBox = true;\r\n\r\n/**\r\n * Variable: resetStyles\r\n * \r\n * Overrides mxShape to reset spacing.\r\n */\r\nmxArrowConnector.prototype.resetStyles = function()\r\n{\r\n\tmxShape.prototype.resetStyles.apply(this, arguments);\r\n\t\r\n\tthis.arrowSpacing = mxConstants.ARROW_SPACING;\r\n};\r\n\r\n/**\r\n * Overrides apply to get smooth transition from default start- and endsize.\r\n */\r\nmxArrowConnector.prototype.apply = function(state)\r\n{\r\n\tmxShape.prototype.apply.apply(this, arguments);\r\n\r\n\tif (this.style != null)\r\n\t{\r\n\t\tthis.startSize = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE, mxConstants.ARROW_SIZE / 5) * 3;\r\n\t\tthis.endSize = mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE, mxConstants.ARROW_SIZE / 5) * 3;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: augmentBoundingBox\r\n *\r\n * Augments the bounding box with the edge width and markers.\r\n */\r\nmxArrowConnector.prototype.augmentBoundingBox = function(bbox)\r\n{\r\n\tmxShape.prototype.augmentBoundingBox.apply(this, arguments);\r\n\t\r\n\tvar w = this.getEdgeWidth();\r\n\t\r\n\tif (this.isMarkerStart())\r\n\t{\r\n\t\tw = Math.max(w, this.getStartArrowWidth());\r\n\t}\r\n\t\r\n\tif (this.isMarkerEnd())\r\n\t{\r\n\t\tw = Math.max(w, this.getEndArrowWidth());\r\n\t}\r\n\t\r\n\tbbox.grow((w / 2 + this.strokewidth) * this.scale);\r\n};\r\n\r\n/**\r\n * Function: paintEdgeShape\r\n * \r\n * Paints the line shape.\r\n */\r\nmxArrowConnector.prototype.paintEdgeShape = function(c, pts)\r\n{\r\n\t// Geometry of arrow\r\n\tvar strokeWidth = this.strokewidth;\r\n\t\r\n\tif (this.outline)\r\n\t{\r\n\t\tstrokeWidth = Math.max(1, mxUtils.getNumber(this.style, mxConstants.STYLE_STROKEWIDTH, this.strokewidth));\r\n\t}\r\n\t\r\n\tvar startWidth = this.getStartArrowWidth() + strokeWidth;\r\n\tvar endWidth = this.getEndArrowWidth() + strokeWidth;\r\n\tvar edgeWidth = this.outline ? this.getEdgeWidth() + strokeWidth : this.getEdgeWidth();\r\n\tvar openEnded = this.isOpenEnded();\r\n\tvar markerStart = this.isMarkerStart();\r\n\tvar markerEnd = this.isMarkerEnd();\r\n\tvar spacing = (openEnded) ? 0 : this.arrowSpacing + strokeWidth / 2;\r\n\tvar startSize = this.startSize + strokeWidth;\r\n\tvar endSize = this.endSize + strokeWidth;\r\n\tvar isRounded = this.isArrowRounded();\r\n\t\r\n\t// Base vector (between first points)\r\n\tvar pe = pts[pts.length - 1];\r\n\r\n\t// Finds first non-overlapping point\r\n\tvar i0 = 1;\r\n\t\r\n\twhile (i0 < pts.length - 1 && pts[i0].x == pts[0].x && pts[i0].y == pts[0].y)\r\n\t{\r\n\t\ti0++;\r\n\t}\r\n\t\r\n\tvar dx = pts[i0].x - pts[0].x;\r\n\tvar dy = pts[i0].y - pts[0].y;\r\n\tvar dist = Math.sqrt(dx * dx + dy * dy);\r\n\t\r\n\tif (dist == 0)\r\n\t{\r\n\t\treturn;\r\n\t}\r\n\t\r\n\t// Computes the norm and the inverse norm\r\n\tvar nx = dx / dist;\r\n\tvar nx2, nx1 = nx;\r\n\tvar ny = dy / dist;\r\n\tvar ny2, ny1 = ny;\r\n\tvar orthx = edgeWidth * ny;\r\n\tvar orthy = -edgeWidth * nx;\r\n\t\r\n\t// Stores the inbound function calls in reverse order in fns\r\n\tvar fns = [];\r\n\t\r\n\tif (isRounded)\r\n\t{\r\n\t\tc.setLineJoin('round');\r\n\t}\r\n\telse if (pts.length > 2)\r\n\t{\r\n\t\t// Only mitre if there are waypoints\r\n\t\tc.setMiterLimit(1.42);\r\n\t}\r\n\r\n\tc.begin();\r\n\r\n\tvar startNx = nx;\r\n\tvar startNy = ny;\r\n\r\n\tif (markerStart && !openEnded)\r\n\t{\r\n\t\tthis.paintMarker(c, pts[0].x, pts[0].y, nx, ny, startSize, startWidth, edgeWidth, spacing, true);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar outStartX = pts[0].x + orthx / 2 + spacing * nx;\r\n\t\tvar outStartY = pts[0].y + orthy / 2 + spacing * ny;\r\n\t\tvar inEndX = pts[0].x - orthx / 2 + spacing * nx;\r\n\t\tvar inEndY = pts[0].y - orthy / 2 + spacing * ny;\r\n\t\t\r\n\t\tif (openEnded)\r\n\t\t{\r\n\t\t\tc.moveTo(outStartX, outStartY);\r\n\t\t\t\r\n\t\t\tfns.push(function()\r\n\t\t\t{\r\n\t\t\t\tc.lineTo(inEndX, inEndY);\r\n\t\t\t});\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tc.moveTo(inEndX, inEndY);\r\n\t\t\tc.lineTo(outStartX, outStartY);\r\n\t\t}\r\n\t}\r\n\t\r\n\tvar dx1 = 0;\r\n\tvar dy1 = 0;\r\n\tvar dist1 = 0;\r\n\r\n\tfor (var i = 0; i < pts.length - 2; i++)\r\n\t{\r\n\t\t// Work out in which direction the line is bending\r\n\t\tvar pos = mxUtils.relativeCcw(pts[i].x, pts[i].y, pts[i+1].x, pts[i+1].y, pts[i+2].x, pts[i+2].y);\r\n\r\n\t\tdx1 = pts[i+2].x - pts[i+1].x;\r\n\t\tdy1 = pts[i+2].y - pts[i+1].y;\r\n\r\n\t\tdist1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);\r\n\t\t\r\n\t\tif (dist1 != 0)\r\n\t\t{\r\n\t\t\tnx1 = dx1 / dist1;\r\n\t\t\tny1 = dy1 / dist1;\r\n\t\t\t\r\n\t\t\tvar tmp1 = nx * nx1 + ny * ny1;\r\n\t\t\ttmp = Math.max(Math.sqrt((tmp1 + 1) / 2), 0.04);\r\n\t\t\t\r\n\t\t\t// Work out the normal orthogonal to the line through the control point and the edge sides intersection\r\n\t\t\tnx2 = (nx + nx1);\r\n\t\t\tny2 = (ny + ny1);\r\n\t\r\n\t\t\tvar dist2 = Math.sqrt(nx2 * nx2 + ny2 * ny2);\r\n\t\t\t\r\n\t\t\tif (dist2 != 0)\r\n\t\t\t{\r\n\t\t\t\tnx2 = nx2 / dist2;\r\n\t\t\t\tny2 = ny2 / dist2;\r\n\t\t\t\t\r\n\t\t\t\t// Higher strokewidths require a larger minimum bend, 0.35 covers all but the most extreme cases\r\n\t\t\t\tvar strokeWidthFactor = Math.max(tmp, Math.min(this.strokewidth / 200 + 0.04, 0.35));\r\n\t\t\t\tvar angleFactor = (pos != 0 && isRounded) ? Math.max(0.1, strokeWidthFactor) : Math.max(tmp, 0.06);\r\n\r\n\t\t\t\tvar outX = pts[i+1].x + ny2 * edgeWidth / 2 / angleFactor;\r\n\t\t\t\tvar outY = pts[i+1].y - nx2 * edgeWidth / 2 / angleFactor;\r\n\t\t\t\tvar inX = pts[i+1].x - ny2 * edgeWidth / 2 / angleFactor;\r\n\t\t\t\tvar inY = pts[i+1].y + nx2 * edgeWidth / 2 / angleFactor;\r\n\t\t\t\t\r\n\t\t\t\tif (pos == 0 || !isRounded)\r\n\t\t\t\t{\r\n\t\t\t\t\t// If the two segments are aligned, or if we're not drawing curved sections between segments\r\n\t\t\t\t\t// just draw straight to the intersection point\r\n\t\t\t\t\tc.lineTo(outX, outY);\r\n\t\t\t\t\t\r\n\t\t\t\t\t(function(x, y)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tfns.push(function()\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tc.lineTo(x, y);\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t})(inX, inY);\r\n\t\t\t\t}\r\n\t\t\t\telse if (pos == -1)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar c1x = inX + ny * edgeWidth;\r\n\t\t\t\t\tvar c1y = inY - nx * edgeWidth;\r\n\t\t\t\t\tvar c2x = inX + ny1 * edgeWidth;\r\n\t\t\t\t\tvar c2y = inY - nx1 * edgeWidth;\r\n\t\t\t\t\tc.lineTo(c1x, c1y);\r\n\t\t\t\t\tc.quadTo(outX, outY, c2x, c2y);\r\n\t\t\t\t\t\r\n\t\t\t\t\t(function(x, y)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tfns.push(function()\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tc.lineTo(x, y);\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t})(inX, inY);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tc.lineTo(outX, outY);\r\n\t\t\t\t\t\r\n\t\t\t\t\t(function(x, y)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar c1x = outX - ny * edgeWidth;\r\n\t\t\t\t\t\tvar c1y = outY + nx * edgeWidth;\r\n\t\t\t\t\t\tvar c2x = outX - ny1 * edgeWidth;\r\n\t\t\t\t\t\tvar c2y = outY + nx1 * edgeWidth;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tfns.push(function()\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tc.quadTo(x, y, c1x, c1y);\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tfns.push(function()\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tc.lineTo(c2x, c2y);\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t})(inX, inY);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tnx = nx1;\r\n\t\t\t\tny = ny1;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\torthx = edgeWidth * ny1;\r\n\torthy = - edgeWidth * nx1;\r\n\r\n\tif (markerEnd && !openEnded)\r\n\t{\r\n\t\tthis.paintMarker(c, pe.x, pe.y, -nx, -ny, endSize, endWidth, edgeWidth, spacing, false);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tc.lineTo(pe.x - spacing * nx1 + orthx / 2, pe.y - spacing * ny1 + orthy / 2);\r\n\t\t\r\n\t\tvar inStartX = pe.x - spacing * nx1 - orthx / 2;\r\n\t\tvar inStartY = pe.y - spacing * ny1 - orthy / 2;\r\n\r\n\t\tif (!openEnded)\r\n\t\t{\r\n\t\t\tc.lineTo(inStartX, inStartY);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tc.moveTo(inStartX, inStartY);\r\n\t\t\t\r\n\t\t\tfns.splice(0, 0, function()\r\n\t\t\t{\r\n\t\t\t\tc.moveTo(inStartX, inStartY);\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n\t\r\n\tfor (var i = fns.length - 1; i >= 0; i--)\r\n\t{\r\n\t\tfns[i]();\r\n\t}\r\n\r\n\tif (openEnded)\r\n\t{\r\n\t\tc.end();\r\n\t\tc.stroke();\r\n\t}\r\n\telse\r\n\t{\r\n\t\tc.close();\r\n\t\tc.fillAndStroke();\r\n\t}\r\n\t\r\n\t// Workaround for shadow on top of base arrow\r\n\tc.setShadow(false);\r\n\t\r\n\t// Need to redraw the markers without the low miter limit\r\n\tc.setMiterLimit(4);\r\n\t\r\n\tif (isRounded)\r\n\t{\r\n\t\tc.setLineJoin('flat');\r\n\t}\r\n\r\n\tif (pts.length > 2)\r\n\t{\r\n\t\t// Only to repaint markers if no waypoints\r\n\t\t// Need to redraw the markers without the low miter limit\r\n\t\tc.setMiterLimit(4);\r\n\t\tif (markerStart && !openEnded)\r\n\t\t{\r\n\t\t\tc.begin();\r\n\t\t\tthis.paintMarker(c, pts[0].x, pts[0].y, startNx, startNy, startSize, startWidth, edgeWidth, spacing, true);\r\n\t\t\tc.stroke();\r\n\t\t\tc.end();\r\n\t\t}\r\n\t\t\r\n\t\tif (markerEnd && !openEnded)\r\n\t\t{\r\n\t\t\tc.begin();\r\n\t\t\tthis.paintMarker(c, pe.x, pe.y, -nx, -ny, endSize, endWidth, edgeWidth, spacing, true);\r\n\t\t\tc.stroke();\r\n\t\t\tc.end();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: paintEdgeShape\r\n * \r\n * Paints the line shape.\r\n */\r\nmxArrowConnector.prototype.paintMarker = function(c, ptX, ptY, nx, ny, size, arrowWidth, edgeWidth, spacing, initialMove)\r\n{\r\n\tvar widthArrowRatio = edgeWidth / arrowWidth;\r\n\tvar orthx = edgeWidth * ny / 2;\r\n\tvar orthy = -edgeWidth * nx / 2;\r\n\r\n\tvar spaceX = (spacing + size) * nx;\r\n\tvar spaceY = (spacing + size) * ny;\r\n\r\n\tif (initialMove)\r\n\t{\r\n\t\tc.moveTo(ptX - orthx + spaceX, ptY - orthy + spaceY);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tc.lineTo(ptX - orthx + spaceX, ptY - orthy + spaceY);\r\n\t}\r\n\r\n\tc.lineTo(ptX - orthx / widthArrowRatio + spaceX, ptY - orthy / widthArrowRatio + spaceY);\r\n\tc.lineTo(ptX + spacing * nx, ptY + spacing * ny);\r\n\tc.lineTo(ptX + orthx / widthArrowRatio + spaceX, ptY + orthy / widthArrowRatio + spaceY);\r\n\tc.lineTo(ptX + orthx + spaceX, ptY + orthy + spaceY);\r\n}\r\n\r\n/**\r\n * Function: isArrowRounded\r\n * \r\n * Returns wether the arrow is rounded\r\n */\r\nmxArrowConnector.prototype.isArrowRounded = function()\r\n{\r\n\treturn this.isRounded;\r\n};\r\n\r\n/**\r\n * Function: getStartArrowWidth\r\n * \r\n * Returns the width of the start arrow\r\n */\r\nmxArrowConnector.prototype.getStartArrowWidth = function()\r\n{\r\n\treturn mxConstants.ARROW_WIDTH;\r\n};\r\n\r\n/**\r\n * Function: getEndArrowWidth\r\n * \r\n * Returns the width of the end arrow\r\n */\r\nmxArrowConnector.prototype.getEndArrowWidth = function()\r\n{\r\n\treturn mxConstants.ARROW_WIDTH;\r\n};\r\n\r\n/**\r\n * Function: getEdgeWidth\r\n * \r\n * Returns the width of the body of the edge\r\n */\r\nmxArrowConnector.prototype.getEdgeWidth = function()\r\n{\r\n\treturn mxConstants.ARROW_WIDTH / 3;\r\n};\r\n\r\n/**\r\n * Function: isOpenEnded\r\n * \r\n * Returns whether the ends of the shape are drawn\r\n */\r\nmxArrowConnector.prototype.isOpenEnded = function()\r\n{\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: isMarkerStart\r\n * \r\n * Returns whether the start marker is drawn\r\n */\r\nmxArrowConnector.prototype.isMarkerStart = function()\r\n{\r\n\treturn (mxUtils.getValue(this.style, mxConstants.STYLE_STARTARROW, mxConstants.NONE) != mxConstants.NONE);\r\n};\r\n\r\n/**\r\n * Function: isMarkerEnd\r\n * \r\n * Returns whether the end marker is drawn\r\n */\r\nmxArrowConnector.prototype.isMarkerEnd = function()\r\n{\r\n\treturn (mxUtils.getValue(this.style, mxConstants.STYLE_ENDARROW, mxConstants.NONE) != mxConstants.NONE);\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxText\r\n *\r\n * Extends <mxShape> to implement a text shape. To change vertical text from\r\n * bottom to top to top to bottom, the following code can be used:\r\n * \r\n * (code)\r\n * mxText.prototype.verticalTextRotation = 90;\r\n * (end)\r\n * \r\n * Constructor: mxText\r\n *\r\n * Constructs a new text shape.\r\n * \r\n * Parameters:\r\n * \r\n * value - String that represents the text to be displayed. This is stored in\r\n * <value>.\r\n * bounds - <mxRectangle> that defines the bounds. This is stored in\r\n * <mxShape.bounds>.\r\n * align - Specifies the horizontal alignment. Default is ''. This is stored in\r\n * <align>.\r\n * valign - Specifies the vertical alignment. Default is ''. This is stored in\r\n * <valign>.\r\n * color - String that specifies the text color. Default is 'black'. This is\r\n * stored in <color>.\r\n * family - String that specifies the font family. Default is\r\n * <mxConstants.DEFAULT_FONTFAMILY>. This is stored in <family>.\r\n * size - Integer that specifies the font size. Default is\r\n * <mxConstants.DEFAULT_FONTSIZE>. This is stored in <size>.\r\n * fontStyle - Specifies the font style. Default is 0. This is stored in\r\n * <fontStyle>.\r\n * spacing - Integer that specifies the global spacing. Default is 2. This is\r\n * stored in <spacing>.\r\n * spacingTop - Integer that specifies the top spacing. Default is 0. The\r\n * sum of the spacing and this is stored in <spacingTop>.\r\n * spacingRight - Integer that specifies the right spacing. Default is 0. The\r\n * sum of the spacing and this is stored in <spacingRight>.\r\n * spacingBottom - Integer that specifies the bottom spacing. Default is 0.The\r\n * sum of the spacing and this is stored in <spacingBottom>.\r\n * spacingLeft - Integer that specifies the left spacing. Default is 0. The\r\n * sum of the spacing and this is stored in <spacingLeft>.\r\n * horizontal - Boolean that specifies if the label is horizontal. Default is\r\n * true. This is stored in <horizontal>.\r\n * background - String that specifies the background color. Default is null.\r\n * This is stored in <background>.\r\n * border - String that specifies the label border color. Default is null.\r\n * This is stored in <border>.\r\n * wrap - Specifies if word-wrapping should be enabled. Default is false.\r\n * This is stored in <wrap>.\r\n * clipped - Specifies if the label should be clipped. Default is false.\r\n * This is stored in <clipped>.\r\n * overflow - Value of the overflow style. Default is 'visible'.\r\n */\r\nfunction mxText(value, bounds, align, valign, color,\r\n\tfamily,\tsize, fontStyle, spacing, spacingTop, spacingRight,\r\n\tspacingBottom, spacingLeft, horizontal, background, border,\r\n\twrap, clipped, overflow, labelPadding, textDirection)\r\n{\r\n\tmxShape.call(this);\r\n\tthis.value = value;\r\n\tthis.bounds = bounds;\r\n\tthis.color = (color != null) ? color : 'black';\r\n\tthis.align = (align != null) ? align : mxConstants.ALIGN_CENTER;\r\n\tthis.valign = (valign != null) ? valign : mxConstants.ALIGN_MIDDLE;\r\n\tthis.family = (family != null) ? family : mxConstants.DEFAULT_FONTFAMILY;\r\n\tthis.size = (size != null) ? size : mxConstants.DEFAULT_FONTSIZE;\r\n\tthis.fontStyle = (fontStyle != null) ? fontStyle : mxConstants.DEFAULT_FONTSTYLE;\r\n\tthis.spacing = parseInt(spacing || 2);\r\n\tthis.spacingTop = this.spacing + parseInt(spacingTop || 0);\r\n\tthis.spacingRight = this.spacing + parseInt(spacingRight || 0);\r\n\tthis.spacingBottom = this.spacing + parseInt(spacingBottom || 0);\r\n\tthis.spacingLeft = this.spacing + parseInt(spacingLeft || 0);\r\n\tthis.horizontal = (horizontal != null) ? horizontal : true;\r\n\tthis.background = background;\r\n\tthis.border = border;\r\n\tthis.wrap = (wrap != null) ? wrap : false;\r\n\tthis.clipped = (clipped != null) ? clipped : false;\r\n\tthis.overflow = (overflow != null) ? overflow : 'visible';\r\n\tthis.labelPadding = (labelPadding != null) ? labelPadding : 0;\r\n\tthis.textDirection = textDirection;\r\n\tthis.rotation = 0;\r\n\tthis.updateMargin();\r\n};\r\n\r\n/**\r\n * Extends mxShape.\r\n */\r\nmxUtils.extend(mxText, mxShape);\r\n\r\n/**\r\n * Variable: baseSpacingTop\r\n * \r\n * Specifies the spacing to be added to the top spacing. Default is 0. Use the\r\n * value 5 here to get the same label positions as in mxGraph 1.x.\r\n */\r\nmxText.prototype.baseSpacingTop = 0;\r\n\r\n/**\r\n * Variable: baseSpacingBottom\r\n * \r\n * Specifies the spacing to be added to the bottom spacing. Default is 0. Use the\r\n * value 1 here to get the same label positions as in mxGraph 1.x.\r\n */\r\nmxText.prototype.baseSpacingBottom = 0;\r\n\r\n/**\r\n * Variable: baseSpacingLeft\r\n * \r\n * Specifies the spacing to be added to the left spacing. Default is 0.\r\n */\r\nmxText.prototype.baseSpacingLeft = 0;\r\n\r\n/**\r\n * Variable: baseSpacingRight\r\n * \r\n * Specifies the spacing to be added to the right spacing. Default is 0.\r\n */\r\nmxText.prototype.baseSpacingRight = 0;\r\n\r\n/**\r\n * Variable: replaceLinefeeds\r\n * \r\n * Specifies if linefeeds in HTML labels should be replaced with BR tags.\r\n * Default is true.\r\n */\r\nmxText.prototype.replaceLinefeeds = true;\r\n\r\n/**\r\n * Variable: verticalTextRotation\r\n * \r\n * Rotation for vertical text. Default is -90 (bottom to top).\r\n */\r\nmxText.prototype.verticalTextRotation = -90;\r\n\r\n/**\r\n * Variable: ignoreClippedStringSize\r\n * \r\n * Specifies if the string size should be measured in <updateBoundingBox> if\r\n * the label is clipped and the label position is center and middle. If this is\r\n * true, then the bounding box will be set to <bounds>. Default is true.\r\n * <ignoreStringSize> has precedence over this switch.\r\n */\r\nmxText.prototype.ignoreClippedStringSize = true;\r\n\r\n/**\r\n * Variable: ignoreStringSize\r\n * \r\n * Specifies if the actual string size should be measured. If disabled the\r\n * boundingBox will not ignore the actual size of the string, otherwise\r\n * <bounds> will be used instead. Default is false.\r\n */\r\nmxText.prototype.ignoreStringSize = false;\r\n\r\n/**\r\n * Variable: textWidthPadding\r\n * \r\n * Specifies the padding to be added to the text width for the bounding box.\r\n * This is needed to make sure no clipping is applied to borders. Default is 4\r\n * for IE 8 standards mode and 3 for all others.\r\n */\r\nmxText.prototype.textWidthPadding = (document.documentMode == 8 && !mxClient.IS_EM) ? 4 : 3;\r\n\r\n/**\r\n * Variable: lastValue\r\n * \r\n * Contains the last rendered text value. Used for caching.\r\n */\r\nmxText.prototype.lastValue = null;\r\n\r\n/**\r\n * Variable: cacheEnabled\r\n * \r\n * Specifies if caching for HTML labels should be enabled. Default is true.\r\n */\r\nmxText.prototype.cacheEnabled = true;\r\n\r\n/**\r\n * Function: isParseVml\r\n * \r\n * Text shapes do not contain VML markup and do not need to be parsed. This\r\n * method returns false to speed up rendering in IE8.\r\n */\r\nmxText.prototype.isParseVml = function()\r\n{\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: isHtmlAllowed\r\n * \r\n * Returns true if HTML is allowed for this shape. This implementation returns\r\n * true if the browser is not in IE8 standards mode.\r\n */\r\nmxText.prototype.isHtmlAllowed = function()\r\n{\r\n\treturn document.documentMode != 8 || mxClient.IS_EM;\r\n};\r\n\r\n/**\r\n * Function: getSvgScreenOffset\r\n * \r\n * Disables offset in IE9 for crisper image output.\r\n */\r\nmxText.prototype.getSvgScreenOffset = function()\r\n{\r\n\treturn 0;\r\n};\r\n\r\n/**\r\n * Function: checkBounds\r\n * \r\n * Returns true if the bounds are not null and all of its variables are numeric.\r\n */\r\nmxText.prototype.checkBounds = function()\r\n{\r\n\treturn (!isNaN(this.scale) && isFinite(this.scale) && this.scale > 0 &&\r\n\t\t\tthis.bounds != null && !isNaN(this.bounds.x) && !isNaN(this.bounds.y) &&\r\n\t\t\t!isNaN(this.bounds.width) && !isNaN(this.bounds.height));\r\n};\r\n\r\n/**\r\n * Function: paint\r\n * \r\n * Generic rendering code.\r\n */\r\nmxText.prototype.paint = function(c, update)\r\n{\r\n\t// Scale is passed-through to canvas\r\n\tvar s = this.scale;\r\n\tvar x = this.bounds.x / s;\r\n\tvar y = this.bounds.y / s;\r\n\tvar w = this.bounds.width / s;\r\n\tvar h = this.bounds.height / s;\r\n\t\r\n\tthis.updateTransform(c, x, y, w, h);\r\n\tthis.configureCanvas(c, x, y, w, h);\r\n\r\n\tvar unscaledWidth = (this.state != null) ? this.state.unscaledWidth : null;\r\n\r\n\tif (update)\r\n\t{\r\n\t\tif (this.node.firstChild != null && (unscaledWidth == null ||\r\n\t\t\tthis.lastUnscaledWidth != unscaledWidth))\r\n\t\t{\r\n\t\t\tc.invalidateCachedOffsetSize(this.node);\r\n\t\t}\r\n\r\n\t\tc.updateText(x, y, w, h, this.align, this.valign, this.wrap, this.overflow,\r\n\t\t\t\tthis.clipped, this.getTextRotation(), this.node);\r\n\t}\r\n\telse\r\n\t{\r\n\t\t// Checks if text contains HTML markup\r\n\t\tvar realHtml = mxUtils.isNode(this.value) || this.dialect == mxConstants.DIALECT_STRICTHTML;\r\n\t\t\r\n\t\t// Always renders labels as HTML in VML\r\n\t\tvar fmt = (realHtml || c instanceof mxVmlCanvas2D) ? 'html' : '';\r\n\t\tvar val = this.value;\r\n\t\t\r\n\t\tif (!realHtml && fmt == 'html')\r\n\t\t{\r\n\t\t\tval =  mxUtils.htmlEntities(val, false);\r\n\t\t}\r\n\t\t\r\n\t\tif (fmt == 'html' && !mxUtils.isNode(this.value))\r\n\t\t{\r\n\t\t\tval = mxUtils.replaceTrailingNewlines(val, '<div><br></div>');\t\t\t\r\n\t\t}\r\n\t\t\r\n\t\t// Handles trailing newlines to make sure they are visible in rendering output\r\n\t\tval = (!mxUtils.isNode(this.value) && this.replaceLinefeeds && fmt == 'html') ?\r\n\t\t\tval.replace(/\\n/g, '<br/>') : val;\r\n\t\t\t\r\n\t\tvar dir = this.textDirection;\r\n\t\r\n\t\tif (dir == mxConstants.TEXT_DIRECTION_AUTO && !realHtml)\r\n\t\t{\r\n\t\t\tdir = this.getAutoDirection();\r\n\t\t}\r\n\t\t\r\n\t\tif (dir != mxConstants.TEXT_DIRECTION_LTR && dir != mxConstants.TEXT_DIRECTION_RTL)\r\n\t\t{\r\n\t\t\tdir = null;\r\n\t\t}\r\n\t\r\n\t\tc.text(x, y, w, h, val, this.align, this.valign, this.wrap, fmt, this.overflow,\r\n\t\t\tthis.clipped, this.getTextRotation(), dir);\r\n\t}\r\n\t\r\n\t// Needs to invalidate the cached offset widths if the geometry changes\r\n\tthis.lastUnscaledWidth = unscaledWidth;\r\n};\r\n\r\n/**\r\n * Function: redraw\r\n * \r\n * Renders the text using the given DOM nodes.\r\n */\r\nmxText.prototype.redraw = function()\r\n{\r\n\tif (this.visible && this.checkBounds() && this.cacheEnabled && this.lastValue == this.value &&\r\n\t\t(mxUtils.isNode(this.value) || this.dialect == mxConstants.DIALECT_STRICTHTML))\r\n\t{\r\n\t\tif (this.node.nodeName == 'DIV' && (this.isHtmlAllowed() || !mxClient.IS_VML))\r\n\t\t{\r\n\t\t\tthis.updateSize(this.node, (this.state == null || this.state.view.textDiv == null));\r\n\r\n\t\t\tif (mxClient.IS_IE && (document.documentMode == null || document.documentMode <= 8))\r\n\t\t\t{\r\n\t\t\t\tthis.updateHtmlFilter();\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.updateHtmlTransform();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.updateBoundingBox();\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar canvas = this.createCanvas();\r\n\r\n\t\t\tif (canvas != null && canvas.updateText != null &&\r\n\t\t\t\tcanvas.invalidateCachedOffsetSize != null)\r\n\t\t\t{\r\n\t\t\t\tthis.paint(canvas, true);\r\n\t\t\t\tthis.destroyCanvas(canvas);\r\n\t\t\t\tthis.updateBoundingBox();\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\t// Fallback if canvas does not support updateText (VML)\r\n\t\t\t\tmxShape.prototype.redraw.apply(this, arguments);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tmxShape.prototype.redraw.apply(this, arguments);\r\n\t\t\r\n\t\tif (mxUtils.isNode(this.value) || this.dialect == mxConstants.DIALECT_STRICTHTML)\r\n\t\t{\r\n\t\t\tthis.lastValue = this.value;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.lastValue = null;\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: resetStyles\r\n * \r\n * Resets all styles.\r\n */\r\nmxText.prototype.resetStyles = function()\r\n{\r\n\tmxShape.prototype.resetStyles.apply(this, arguments);\r\n\t\r\n\tthis.color = 'black';\r\n\tthis.align = mxConstants.ALIGN_CENTER;\r\n\tthis.valign = mxConstants.ALIGN_MIDDLE;\r\n\tthis.family = mxConstants.DEFAULT_FONTFAMILY;\r\n\tthis.size = mxConstants.DEFAULT_FONTSIZE;\r\n\tthis.fontStyle = mxConstants.DEFAULT_FONTSTYLE;\r\n\tthis.spacing = 2;\r\n\tthis.spacingTop = 2;\r\n\tthis.spacingRight = 2;\r\n\tthis.spacingBottom = 2;\r\n\tthis.spacingLeft = 2;\r\n\tthis.horizontal = true;\r\n\tdelete this.background;\r\n\tdelete this.border;\r\n\tthis.textDirection = mxConstants.DEFAULT_TEXT_DIRECTION;\r\n\tdelete this.margin;\r\n};\r\n\r\n/**\r\n * Function: apply\r\n * \r\n * Extends mxShape to update the text styles.\r\n *\r\n * Parameters:\r\n *\r\n * state - <mxCellState> of the corresponding cell.\r\n */\r\nmxText.prototype.apply = function(state)\r\n{\r\n\tvar old = this.spacing;\r\n\tmxShape.prototype.apply.apply(this, arguments);\r\n\t\r\n\tif (this.style != null)\r\n\t{\r\n\t\tthis.fontStyle = mxUtils.getValue(this.style, mxConstants.STYLE_FONTSTYLE, this.fontStyle);\r\n\t\tthis.family = mxUtils.getValue(this.style, mxConstants.STYLE_FONTFAMILY, this.family);\r\n\t\tthis.size = mxUtils.getValue(this.style, mxConstants.STYLE_FONTSIZE, this.size);\r\n\t\tthis.color = mxUtils.getValue(this.style, mxConstants.STYLE_FONTCOLOR, this.color);\r\n\t\tthis.align = mxUtils.getValue(this.style, mxConstants.STYLE_ALIGN, this.align);\r\n\t\tthis.valign = mxUtils.getValue(this.style, mxConstants.STYLE_VERTICAL_ALIGN, this.valign);\r\n\t\tthis.spacing = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING, this.spacing));\r\n\t\tthis.spacingTop = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_TOP, this.spacingTop - old)) + this.spacing;\r\n\t\tthis.spacingRight = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_RIGHT, this.spacingRight - old)) + this.spacing;\r\n\t\tthis.spacingBottom = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_BOTTOM, this.spacingBottom - old)) + this.spacing;\r\n\t\tthis.spacingLeft = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_LEFT, this.spacingLeft - old)) + this.spacing;\r\n\t\tthis.horizontal = mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, this.horizontal);\r\n\t\tthis.background = mxUtils.getValue(this.style, mxConstants.STYLE_LABEL_BACKGROUNDCOLOR, this.background);\r\n\t\tthis.border = mxUtils.getValue(this.style, mxConstants.STYLE_LABEL_BORDERCOLOR, this.border);\r\n\t\tthis.textDirection = mxUtils.getValue(this.style, mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION);\r\n\t\tthis.opacity = mxUtils.getValue(this.style, mxConstants.STYLE_TEXT_OPACITY, 100);\r\n\t\tthis.updateMargin();\r\n\t}\r\n\t\r\n\tthis.flipV = null;\r\n\tthis.flipH = null;\r\n};\r\n\r\n/**\r\n * Function: getAutoDirection\r\n * \r\n * Used to determine the automatic text direction. Returns\r\n * <mxConstants.TEXT_DIRECTION_LTR> or <mxConstants.TEXT_DIRECTION_RTL>\r\n * depending on the contents of <value>. This is not invoked for HTML, wrapped\r\n * content or if <value> is a DOM node.\r\n */\r\nmxText.prototype.getAutoDirection = function()\r\n{\r\n\t// Looks for strong (directional) characters\r\n\tvar tmp = /[A-Za-z\\u05d0-\\u065f\\u066a-\\u06ef\\u06fa-\\u07ff\\ufb1d-\\ufdff\\ufe70-\\ufefc]/.exec(this.value);\r\n\t\r\n\t// Returns the direction defined by the character\r\n\treturn (tmp != null && tmp.length > 0 && tmp[0] > 'z') ?\r\n\t\tmxConstants.TEXT_DIRECTION_RTL : mxConstants.TEXT_DIRECTION_LTR;\r\n};\r\n\r\n/**\r\n * Function: updateBoundingBox\r\n *\r\n * Updates the <boundingBox> for this shape using the given node and position.\r\n */\r\nmxText.prototype.updateBoundingBox = function()\r\n{\r\n\tvar node = this.node;\r\n\tthis.boundingBox = this.bounds.clone();\r\n\tvar rot = this.getTextRotation();\r\n\t\r\n\tvar h = (this.style != null) ? mxUtils.getValue(this.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER) : null;\r\n\tvar v = (this.style != null) ? mxUtils.getValue(this.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE) : null;\r\n\r\n\tif (!this.ignoreStringSize && node != null && this.overflow != 'fill' && (!this.clipped ||\r\n\t\t!this.ignoreClippedStringSize || h != mxConstants.ALIGN_CENTER || v != mxConstants.ALIGN_MIDDLE))\r\n\t{\r\n\t\tvar ow = null;\r\n\t\tvar oh = null;\r\n\t\t\r\n\t\tif (node.ownerSVGElement != null)\r\n\t\t{\r\n\t\t\tif (node.firstChild != null && node.firstChild.firstChild != null &&\r\n\t\t\t\tnode.firstChild.firstChild.nodeName == 'foreignObject')\r\n\t\t\t{\r\n\t\t\t\tnode = node.firstChild.firstChild;\r\n\t\t\t\tow = parseInt(node.getAttribute('width')) * this.scale;\r\n\t\t\t\toh = parseInt(node.getAttribute('height')) * this.scale;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\ttry\r\n\t\t\t\t{\r\n\t\t\t\t\tvar b = node.getBBox();\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Workaround for bounding box of empty string\r\n\t\t\t\t\tif (typeof(this.value) == 'string' && mxUtils.trim(this.value) == 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.boundingBox = null;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (b.width == 0 && b.height == 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.boundingBox = null;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.boundingBox = new mxRectangle(b.x, b.y, b.width, b.height);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t\tcatch (e)\r\n\t\t\t\t{\r\n\t\t\t\t\t// Ignores NS_ERROR_FAILURE in FF if container display is none.\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar td = (this.state != null) ? this.state.view.textDiv : null;\r\n\r\n\t\t\t// Use cached offset size\r\n\t\t\tif (this.offsetWidth != null && this.offsetHeight != null)\r\n\t\t\t{\r\n\t\t\t\tow = this.offsetWidth * this.scale;\r\n\t\t\t\toh = this.offsetHeight * this.scale;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\t// Cannot get node size while container hidden so a\r\n\t\t\t\t// shared temporary DIV is used for text measuring\r\n\t\t\t\tif (td != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.updateFont(td);\r\n\t\t\t\t\tthis.updateSize(td, false);\r\n\t\t\t\t\tthis.updateInnerHtml(td);\r\n\r\n\t\t\t\t\tnode = td;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tvar sizeDiv = node;\r\n\r\n\t\t\t\tif (document.documentMode == 8 && !mxClient.IS_EM)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar w = Math.round(this.bounds.width / this.scale);\r\n\t\r\n\t\t\t\t\tif (this.wrap && w > 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tnode.style.wordWrap = mxConstants.WORD_WRAP;\r\n\t\t\t\t\t\tnode.style.whiteSpace = 'normal';\r\n\r\n\t\t\t\t\t\tif (node.style.wordWrap != 'break-word')\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t// Innermost DIV is used for measuring text\r\n\t\t\t\t\t\t\tvar divs = sizeDiv.getElementsByTagName('div');\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tif (divs.length > 0)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tsizeDiv = divs[divs.length - 1];\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tow = sizeDiv.offsetWidth + 2;\r\n\t\t\t\t\t\t\tdivs = this.node.getElementsByTagName('div');\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tif (this.clipped)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tow = Math.min(w, ow);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t// Second last DIV width must be updated in DOM tree\r\n\t\t\t\t\t\t\tif (divs.length > 1)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tdivs[divs.length - 2].style.width = ow + 'px';\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tnode.style.whiteSpace = 'nowrap';\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')\r\n\t\t\t\t{\r\n\t\t\t\t\tsizeDiv = sizeDiv.firstChild;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tthis.offsetWidth = sizeDiv.offsetWidth + this.textWidthPadding;\r\n\t\t\t\tthis.offsetHeight = sizeDiv.offsetHeight;\r\n\t\t\t\t\r\n\t\t\t\tow = this.offsetWidth * this.scale;\r\n\t\t\t\toh = this.offsetHeight * this.scale;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (ow != null && oh != null)\r\n\t\t{\t\r\n\t\t\tthis.boundingBox = new mxRectangle(this.bounds.x,\r\n\t\t\t\tthis.bounds.y, ow, oh);\r\n\t\t}\r\n\t}\r\n\r\n\tif (this.boundingBox != null)\r\n\t{\r\n\t\tif (rot != 0)\r\n\t\t{\r\n\t\t\t// Accounts for pre-rotated x and y\r\n\t\t\tvar bbox = mxUtils.getBoundingBox(new mxRectangle(\r\n\t\t\t\tthis.margin.x * this.boundingBox.width,\r\n\t\t\t\tthis.margin.y * this.boundingBox.height,\r\n\t\t\t\tthis.boundingBox.width, this.boundingBox.height),\r\n\t\t\t\trot, new mxPoint(0, 0));\r\n\t\t\t\r\n\t\t\tthis.unrotatedBoundingBox = mxRectangle.fromRectangle(this.boundingBox);\r\n\t\t\tthis.unrotatedBoundingBox.x += this.margin.x * this.unrotatedBoundingBox.width;\r\n\t\t\tthis.unrotatedBoundingBox.y += this.margin.y * this.unrotatedBoundingBox.height;\r\n\t\t\t\r\n\t\t\tthis.boundingBox.x += bbox.x;\r\n\t\t\tthis.boundingBox.y += bbox.y;\r\n\t\t\tthis.boundingBox.width = bbox.width;\r\n\t\t\tthis.boundingBox.height = bbox.height;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.boundingBox.x += this.margin.x * this.boundingBox.width;\r\n\t\t\tthis.boundingBox.y += this.margin.y * this.boundingBox.height;\r\n\t\t\tthis.unrotatedBoundingBox = null;\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getShapeRotation\r\n * \r\n * Returns 0 to avoid using rotation in the canvas via updateTransform.\r\n */\r\nmxText.prototype.getShapeRotation = function()\r\n{\r\n\treturn 0;\r\n};\r\n\r\n/**\r\n * Function: getTextRotation\r\n * \r\n * Returns the rotation for the text label of the corresponding shape.\r\n */\r\nmxText.prototype.getTextRotation = function()\r\n{\r\n\treturn (this.state != null && this.state.shape != null) ? this.state.shape.getTextRotation() : 0;\r\n};\r\n\r\n/**\r\n * Function: isPaintBoundsInverted\r\n * \r\n * Inverts the bounds if <mxShape.isBoundsInverted> returns true or if the\r\n * horizontal style is false.\r\n */\r\nmxText.prototype.isPaintBoundsInverted = function()\r\n{\r\n\treturn !this.horizontal && this.state != null && this.state.view.graph.model.isVertex(this.state.cell);\r\n};\r\n\r\n/**\r\n * Function: configureCanvas\r\n * \r\n * Sets the state of the canvas for drawing the shape.\r\n */\r\nmxText.prototype.configureCanvas = function(c, x, y, w, h)\r\n{\r\n\tmxShape.prototype.configureCanvas.apply(this, arguments);\r\n\t\r\n\tc.setFontColor(this.color);\r\n\tc.setFontBackgroundColor(this.background);\r\n\tc.setFontBorderColor(this.border);\r\n\tc.setFontFamily(this.family);\r\n\tc.setFontSize(this.size);\r\n\tc.setFontStyle(this.fontStyle);\r\n};\r\n\r\n/**\r\n * Function: updateVmlContainer\r\n * \r\n * Sets the width and height of the container to 1px.\r\n */\r\nmxText.prototype.updateVmlContainer = function()\r\n{\r\n\tthis.node.style.left = Math.round(this.bounds.x) + 'px';\r\n\tthis.node.style.top = Math.round(this.bounds.y) + 'px';\r\n\tthis.node.style.width = '1px';\r\n\tthis.node.style.height = '1px';\r\n\tthis.node.style.overflow = 'visible';\r\n};\r\n\r\n/**\r\n * Function: redrawHtmlShape\r\n *\r\n * Updates the HTML node(s) to reflect the latest bounds and scale.\r\n */\r\nmxText.prototype.redrawHtmlShape = function()\r\n{\r\n\tvar style = this.node.style;\r\n\r\n\t// Resets CSS styles\r\n\tstyle.whiteSpace = 'normal';\r\n\tstyle.overflow = '';\r\n\tstyle.width = '';\r\n\tstyle.height = '';\r\n\t\r\n\tthis.updateValue();\r\n\tthis.updateFont(this.node);\r\n\tthis.updateSize(this.node, (this.state == null || this.state.view.textDiv == null));\r\n\t\r\n\tthis.offsetWidth = null;\r\n\tthis.offsetHeight = null;\r\n\r\n\tif (mxClient.IS_IE && (document.documentMode == null || document.documentMode <= 8))\r\n\t{\r\n\t\tthis.updateHtmlFilter();\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.updateHtmlTransform();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: updateHtmlTransform\r\n *\r\n * Returns the spacing as an <mxPoint>.\r\n */\r\nmxText.prototype.updateHtmlTransform = function()\r\n{\r\n\tvar theta = this.getTextRotation();\r\n\tvar style = this.node.style;\r\n\tvar dx = this.margin.x;\r\n\tvar dy = this.margin.y;\r\n\t\r\n\tif (theta != 0)\r\n\t{\r\n\t\tmxUtils.setPrefixedStyle(style, 'transformOrigin', (-dx * 100) + '%' + ' ' + (-dy * 100) + '%');\r\n\t\tmxUtils.setPrefixedStyle(style, 'transform', 'translate(' + (dx * 100) + '%' + ',' + (dy * 100) + '%)' +\r\n\t\t\t'scale(' + this.scale + ') rotate(' + theta + 'deg)');\r\n\t}\r\n\telse\r\n\t{\r\n\t\tmxUtils.setPrefixedStyle(style, 'transformOrigin', '0% 0%');\r\n\t\tmxUtils.setPrefixedStyle(style, 'transform', 'scale(' + this.scale + ')' +\r\n\t\t\t'translate(' + (dx * 100) + '%' + ',' + (dy * 100) + '%)');\r\n\t}\r\n\r\n\tstyle.left = Math.round(this.bounds.x - Math.ceil(dx * ((this.overflow != 'fill' &&\r\n\t\tthis.overflow != 'width') ? 3 : 1))) + 'px';\r\n\tstyle.top = Math.round(this.bounds.y - dy * ((this.overflow != 'fill') ? 3 : 1)) + 'px';\r\n\t\r\n\tif (this.opacity < 100)\r\n\t{\r\n\t\tstyle.opacity = this.opacity / 100;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tstyle.opacity = '';\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setInnerHtml\r\n * \r\n * Sets the inner HTML of the given element to the <value>.\r\n */\r\nmxText.prototype.updateInnerHtml = function(elt)\r\n{\r\n\tif (mxUtils.isNode(this.value))\r\n\t{\r\n\t\telt.innerHTML = this.value.outerHTML;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar val = this.value;\r\n\t\t\r\n\t\tif (this.dialect != mxConstants.DIALECT_STRICTHTML)\r\n\t\t{\r\n\t\t\t// LATER: Can be cached in updateValue\r\n\t\t\tval = mxUtils.htmlEntities(val, false);\r\n\t\t}\r\n\t\t\r\n\t\t// Handles trailing newlines to make sure they are visible in rendering output\r\n\t\tval = mxUtils.replaceTrailingNewlines(val, '<div>&nbsp;</div>');\r\n\t\tval = (this.replaceLinefeeds) ? val.replace(/\\n/g, '<br/>') : val;\r\n\t\tval = '<div style=\"display:inline-block;_display:inline;\">' + val + '</div>';\r\n\t\t\r\n\t\telt.innerHTML = val;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: updateHtmlFilter\r\n *\r\n * Rotated text rendering quality is bad for IE9 quirks/IE8 standards\r\n */\r\nmxText.prototype.updateHtmlFilter = function()\r\n{\r\n\tvar style = this.node.style;\r\n\tvar dx = this.margin.x;\r\n\tvar dy = this.margin.y;\r\n\tvar s = this.scale;\r\n\t\r\n\t// Resets filter before getting offsetWidth\r\n\tmxUtils.setOpacity(this.node, this.opacity);\r\n\t\r\n\t// Adds 1 to match table height in 1.x\r\n\tvar ow = 0;\r\n\tvar oh = 0;\r\n\tvar td = (this.state != null) ? this.state.view.textDiv : null;\r\n\tvar sizeDiv = this.node;\r\n\t\r\n\t// Fallback for hidden text rendering in IE quirks mode\r\n\tif (td != null)\r\n\t{\r\n\t\ttd.style.overflow = '';\r\n\t\ttd.style.height = '';\r\n\t\ttd.style.width = '';\r\n\t\t\r\n\t\tthis.updateFont(td);\r\n\t\tthis.updateSize(td, false);\r\n\t\tthis.updateInnerHtml(td);\r\n\t\t\r\n\t\tvar w = Math.round(this.bounds.width / this.scale);\r\n\r\n\t\tif (this.wrap && w > 0)\r\n\t\t{\r\n\t\t\ttd.style.whiteSpace = 'normal';\r\n\t\t\ttd.style.wordWrap = mxConstants.WORD_WRAP;\r\n\t\t\tow = w;\r\n\t\t\t\r\n\t\t\tif (this.clipped)\r\n\t\t\t{\r\n\t\t\t\tow = Math.min(ow, this.bounds.width);\r\n\t\t\t}\r\n\r\n\t\t\ttd.style.width = ow + 'px';\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\ttd.style.whiteSpace = 'nowrap';\r\n\t\t}\r\n\t\t\r\n\t\tsizeDiv = td;\r\n\t\t\r\n\t\tif (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')\r\n\t\t{\r\n\t\t\tsizeDiv = sizeDiv.firstChild;\r\n\t\t\t\r\n\t\t\tif (this.wrap && td.style.wordWrap == 'break-word')\r\n\t\t\t{\r\n\t\t\t\tsizeDiv.style.width = '100%';\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Required to update the height of the text box after wrapping width is known \r\n\t\tif (!this.clipped && this.wrap && w > 0)\r\n\t\t{\r\n\t\t\tow = sizeDiv.offsetWidth + this.textWidthPadding;\r\n\t\t\ttd.style.width = ow + 'px';\r\n\t\t}\r\n\t\t\r\n\t\toh = sizeDiv.offsetHeight + 2;\r\n\t\t\r\n\t\tif (mxClient.IS_QUIRKS && this.border != null && this.border != mxConstants.NONE)\r\n\t\t{\r\n\t\t\toh += 3;\r\n\t\t}\r\n\t}\r\n\telse if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')\r\n\t{\r\n\t\tsizeDiv = sizeDiv.firstChild;\r\n\t\toh = sizeDiv.offsetHeight;\r\n\t}\r\n\r\n\tow = sizeDiv.offsetWidth + this.textWidthPadding;\r\n\t\r\n\tif (this.clipped)\r\n\t{\r\n\t\toh = Math.min(oh, this.bounds.height);\r\n\t}\r\n\r\n\tvar w = this.bounds.width / s;\r\n\tvar h = this.bounds.height / s;\r\n\r\n\t// Handles special case for live preview with no wrapper DIV and no textDiv\r\n\tif (this.overflow == 'fill')\r\n\t{\r\n\t\toh = h;\r\n\t\tow = w;\r\n\t}\r\n\telse if (this.overflow == 'width')\r\n\t{\r\n\t\toh = sizeDiv.scrollHeight;\r\n\t\tow = w;\r\n\t}\r\n\t\r\n\t// Stores for later use\r\n\tthis.offsetWidth = ow;\r\n\tthis.offsetHeight = oh;\r\n\t\r\n\t// Simulates max-height CSS in quirks mode\r\n\tif (mxClient.IS_QUIRKS && (this.clipped || (this.overflow == 'width' && h > 0)))\r\n\t{\r\n\t\th = Math.min(h, oh);\r\n\t\tstyle.height = Math.round(h) + 'px';\r\n\t}\r\n\telse\r\n\t{\r\n\t\th = oh;\r\n\t}\r\n\r\n\tif (this.overflow != 'fill' && this.overflow != 'width')\r\n\t{\r\n\t\tif (this.clipped)\r\n\t\t{\r\n\t\t\tow = Math.min(w, ow);\r\n\t\t}\r\n\t\t\r\n\t\tw = ow;\r\n\r\n\t\t// Simulates max-width CSS in quirks mode\r\n\t\tif ((mxClient.IS_QUIRKS && this.clipped) || this.wrap)\r\n\t\t{\r\n\t\t\tstyle.width = Math.round(w) + 'px';\r\n\t\t}\r\n\t}\r\n\r\n\th *= s;\r\n\tw *= s;\r\n\t\r\n\t// Rotation case is handled via VML canvas\r\n\tvar rad = this.getTextRotation() * (Math.PI / 180);\r\n\t\r\n\t// Precalculate cos and sin for the rotation\r\n\tvar real_cos = parseFloat(parseFloat(Math.cos(rad)).toFixed(8));\r\n\tvar real_sin = parseFloat(parseFloat(Math.sin(-rad)).toFixed(8));\r\n\r\n\trad %= 2 * Math.PI;\r\n\t\r\n\tif (rad < 0)\r\n\t{\r\n\t\trad += 2 * Math.PI;\r\n\t}\r\n\t\r\n\trad %= Math.PI;\r\n\t\r\n\tif (rad > Math.PI / 2)\r\n\t{\r\n\t\trad = Math.PI - rad;\r\n\t}\r\n\t\r\n\tvar cos = Math.cos(rad);\r\n\tvar sin = Math.sin(-rad);\r\n\r\n\tvar tx = w * -(dx + 0.5);\r\n\tvar ty = h * -(dy + 0.5);\r\n\r\n\tvar top_fix = (h - h * cos + w * sin) / 2 + real_sin * tx - real_cos * ty;\r\n\tvar left_fix = (w - w * cos + h * sin) / 2 - real_cos * tx - real_sin * ty;\r\n\t\r\n\tif (rad != 0)\r\n\t{\r\n\t\tvar f = 'progid:DXImageTransform.Microsoft.Matrix(M11=' + real_cos + ', M12='+\r\n\t\t\treal_sin + ', M21=' + (-real_sin) + ', M22=' + real_cos + ', sizingMethod=\\'auto expand\\')';\r\n\t\t\r\n\t\tif (style.filter != null && style.filter.length > 0)\r\n\t\t{\r\n\t\t\tstyle.filter += ' ' + f;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tstyle.filter = f;\r\n\t\t}\r\n\t}\r\n\t\r\n\t// Workaround for rendering offsets\r\n\tvar dy = 0;\r\n\t\r\n\tif (this.overflow != 'fill' && mxClient.IS_QUIRKS)\r\n\t{\r\n\t\tif (this.valign == mxConstants.ALIGN_TOP)\r\n\t\t{\r\n\t\t\tdy -= 1;\r\n\t\t}\r\n\t\telse if (this.valign == mxConstants.ALIGN_BOTTOM)\r\n\t\t{\r\n\t\t\tdy += 2;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tdy += 1;\r\n\t\t}\r\n\t}\r\n\r\n\tstyle.zoom = s;\r\n\tstyle.left = Math.round(this.bounds.x + left_fix - w / 2) + 'px';\r\n\tstyle.top = Math.round(this.bounds.y + top_fix - h / 2 + dy) + 'px';\r\n};\r\n\r\n/**\r\n * Function: updateValue\r\n *\r\n * Updates the HTML node(s) to reflect the latest bounds and scale.\r\n */\r\nmxText.prototype.updateValue = function()\r\n{\r\n\tif (mxUtils.isNode(this.value))\r\n\t{\r\n\t\tthis.node.innerHTML = '';\r\n\t\tthis.node.appendChild(this.value);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar val = this.value;\r\n\t\t\r\n\t\tif (this.dialect != mxConstants.DIALECT_STRICTHTML)\r\n\t\t{\r\n\t\t\tval = mxUtils.htmlEntities(val, false);\r\n\t\t}\r\n\t\t\r\n\t\t// Handles trailing newlines to make sure they are visible in rendering output\r\n\t\tval = mxUtils.replaceTrailingNewlines(val, '<div><br></div>');\r\n\t\tval = (this.replaceLinefeeds) ? val.replace(/\\n/g, '<br/>') : val;\r\n\t\tvar bg = (this.background != null && this.background != mxConstants.NONE) ? this.background : null;\r\n\t\tvar bd = (this.border != null && this.border != mxConstants.NONE) ? this.border : null;\r\n\r\n\t\tif (this.overflow == 'fill' || this.overflow == 'width')\r\n\t\t{\r\n\t\t\tif (bg != null)\r\n\t\t\t{\r\n\t\t\t\tthis.node.style.backgroundColor = bg;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (bd != null)\r\n\t\t\t{\r\n\t\t\t\tthis.node.style.border = '1px solid ' + bd;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar css = '';\r\n\t\t\t\r\n\t\t\tif (bg != null)\r\n\t\t\t{\r\n\t\t\t\tcss += 'background-color:' + bg + ';';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (bd != null)\r\n\t\t\t{\r\n\t\t\t\tcss += 'border:1px solid ' + bd + ';';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Wrapper DIV for background, zoom needed for inline in quirks\r\n\t\t\t// and to measure wrapped font sizes in all browsers\r\n\t\t\t// FIXME: Background size in quirks mode for wrapped text\r\n\t\t\tvar lh = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (this.size * mxConstants.LINE_HEIGHT) + 'px' :\r\n\t\t\t\tmxConstants.LINE_HEIGHT;\r\n\t\t\tval = '<div style=\"zoom:1;' + css + 'display:inline-block;_display:inline;text-decoration:inherit;' +\r\n\t\t\t\t'padding-bottom:1px;padding-right:1px;line-height:' + lh + '\">' + val + '</div>';\r\n\t\t}\r\n\r\n\t\tthis.node.innerHTML = val;\r\n\t\t\r\n\t\t// Sets text direction\r\n\t\tvar divs = this.node.getElementsByTagName('div');\r\n\t\t\r\n\t\tif (divs.length > 0)\r\n\t\t{\r\n\t\t\tvar dir = this.textDirection;\r\n\r\n\t\t\tif (dir == mxConstants.TEXT_DIRECTION_AUTO && this.dialect != mxConstants.DIALECT_STRICTHTML)\r\n\t\t\t{\r\n\t\t\t\tdir = this.getAutoDirection();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (dir == mxConstants.TEXT_DIRECTION_LTR || dir == mxConstants.TEXT_DIRECTION_RTL)\r\n\t\t\t{\r\n\t\t\t\tdivs[divs.length - 1].setAttribute('dir', dir);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tdivs[divs.length - 1].removeAttribute('dir');\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: updateFont\r\n *\r\n * Updates the HTML node(s) to reflect the latest bounds and scale.\r\n */\r\nmxText.prototype.updateFont = function(node)\r\n{\r\n\tvar style = node.style;\r\n\t\r\n\tstyle.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (this.size * mxConstants.LINE_HEIGHT) + 'px' : mxConstants.LINE_HEIGHT;\r\n\tstyle.fontSize = this.size + 'px';\r\n\t// Quotes are workaround for font name \"m+\"\r\n\tstyle.fontFamily = '\"' + this.family + '\"';\r\n\tstyle.verticalAlign = 'top';\r\n\tstyle.color = this.color;\r\n\t\r\n\tif ((this.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)\r\n\t{\r\n\t\tstyle.fontWeight = 'bold';\r\n\t}\r\n\telse\r\n\t{\r\n\t\tstyle.fontWeight = '';\r\n\t}\r\n\r\n\tif ((this.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)\r\n\t{\r\n\t\tstyle.fontStyle = 'italic';\r\n\t}\r\n\telse\r\n\t{\r\n\t\tstyle.fontStyle = '';\r\n\t}\r\n\t\r\n\tif ((this.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)\r\n\t{\r\n\t\tstyle.textDecoration = 'underline';\r\n\t}\r\n\telse\r\n\t{\r\n\t\tstyle.textDecoration = '';\r\n\t}\r\n\t\r\n\tif (this.align == mxConstants.ALIGN_CENTER)\r\n\t{\r\n\t\tstyle.textAlign = 'center';\r\n\t}\r\n\telse if (this.align == mxConstants.ALIGN_RIGHT)\r\n\t{\r\n\t\tstyle.textAlign = 'right';\r\n\t}\r\n\telse\r\n\t{\r\n\t\tstyle.textAlign = 'left';\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: updateSize\r\n *\r\n * Updates the HTML node(s) to reflect the latest bounds and scale.\r\n */\r\nmxText.prototype.updateSize = function(node, enableWrap)\r\n{\r\n\tvar w = Math.max(0, Math.round(this.bounds.width / this.scale));\r\n\tvar h = Math.max(0, Math.round(this.bounds.height / this.scale));\r\n\tvar style = node.style;\r\n\t\r\n\t// NOTE: Do not use maxWidth here because wrapping will\r\n\t// go wrong if the cell is outside of the viewable area\r\n\tif (this.clipped)\r\n\t{\r\n\t\tstyle.overflow = 'hidden';\r\n\t\t\r\n\t\tif (!mxClient.IS_QUIRKS)\r\n\t\t{\r\n\t\t\tstyle.maxHeight = h + 'px';\r\n\t\t\tstyle.maxWidth = w + 'px';\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tstyle.width = w + 'px';\r\n\t\t}\r\n\t}\r\n\telse if (this.overflow == 'fill')\r\n\t{\r\n\t\tstyle.width = (w + 1) + 'px';\r\n\t\tstyle.height = (h + 1) + 'px';\r\n\t\tstyle.overflow = 'hidden';\r\n\t}\r\n\telse if (this.overflow == 'width')\r\n\t{\r\n\t\tstyle.width = (w + 1) + 'px';\r\n\t\tstyle.maxHeight = (h + 1) + 'px';\r\n\t\tstyle.overflow = 'hidden';\r\n\t}\r\n\t\r\n\tif (this.wrap && w > 0)\r\n\t{\r\n\t\tstyle.wordWrap = mxConstants.WORD_WRAP;\r\n\t\tstyle.whiteSpace = 'normal';\r\n\t\tstyle.width = w + 'px';\r\n\r\n\t\tif (enableWrap && this.overflow != 'fill' && this.overflow != 'width')\r\n\t\t{\r\n\t\t\tvar sizeDiv = node;\r\n\t\t\t\r\n\t\t\tif (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')\r\n\t\t\t{\r\n\t\t\t\tsizeDiv = sizeDiv.firstChild;\r\n\t\t\t\t\r\n\t\t\t\tif (node.style.wordWrap == 'break-word')\r\n\t\t\t\t{\r\n\t\t\t\t\tsizeDiv.style.width = '100%';\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar tmp = sizeDiv.offsetWidth;\r\n\t\t\t\r\n\t\t\t// Workaround for text measuring in hidden containers\r\n\t\t\tif (tmp == 0)\r\n\t\t\t{\r\n\t\t\t\tvar prev = node.parentNode;\r\n\t\t\t\tnode.style.visibility = 'hidden';\r\n\t\t\t\tdocument.body.appendChild(node);\r\n\t\t\t\ttmp = sizeDiv.offsetWidth;\r\n\t\t\t\tnode.style.visibility = '';\r\n\t\t\t\tprev.appendChild(node);\r\n\t\t\t}\r\n\r\n\t\t\ttmp += 3;\r\n\t\t\t\r\n\t\t\tif (this.clipped)\r\n\t\t\t{\r\n\t\t\t\ttmp = Math.min(tmp, w);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tstyle.width = tmp + 'px';\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tstyle.whiteSpace = 'nowrap';\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getMargin\r\n *\r\n * Returns the spacing as an <mxPoint>.\r\n */\r\nmxText.prototype.updateMargin = function()\r\n{\r\n\tthis.margin = mxUtils.getAlignmentAsPoint(this.align, this.valign);\r\n};\r\n\r\n/**\r\n * Function: getSpacing\r\n *\r\n * Returns the spacing as an <mxPoint>.\r\n */\r\nmxText.prototype.getSpacing = function()\r\n{\r\n\tvar dx = 0;\r\n\tvar dy = 0;\r\n\r\n\tif (this.align == mxConstants.ALIGN_CENTER)\r\n\t{\r\n\t\tdx = (this.spacingLeft - this.spacingRight) / 2;\r\n\t}\r\n\telse if (this.align == mxConstants.ALIGN_RIGHT)\r\n\t{\r\n\t\tdx = -this.spacingRight - this.baseSpacingRight;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tdx = this.spacingLeft + this.baseSpacingLeft;\r\n\t}\r\n\r\n\tif (this.valign == mxConstants.ALIGN_MIDDLE)\r\n\t{\r\n\t\tdy = (this.spacingTop - this.spacingBottom) / 2;\r\n\t}\r\n\telse if (this.valign == mxConstants.ALIGN_BOTTOM)\r\n\t{\r\n\t\tdy = -this.spacingBottom - this.baseSpacingBottom;;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tdy = this.spacingTop + this.baseSpacingTop;\r\n\t}\r\n\t\r\n\treturn new mxPoint(dx, dy);\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxTriangle\r\n * \r\n * Implementation of the triangle shape.\r\n * \r\n * Constructor: mxTriangle\r\n *\r\n * Constructs a new triangle shape.\r\n */\r\nfunction mxTriangle()\r\n{\r\n\tmxActor.call(this);\r\n};\r\n\r\n/**\r\n * Extends mxActor.\r\n */\r\nmxUtils.extend(mxTriangle, mxActor);\r\n\r\n/**\r\n * Function: isRoundable\r\n * \r\n * Adds roundable support.\r\n */\r\nmxTriangle.prototype.isRoundable = function()\r\n{\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: redrawPath\r\n *\r\n * Draws the path for this shape.\r\n */\r\nmxTriangle.prototype.redrawPath = function(c, x, y, w, h)\r\n{\r\n\tvar arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;\r\n\tthis.addPoints(c, [new mxPoint(0, 0), new mxPoint(w, 0.5 * h), new mxPoint(0, h)], this.isRounded, arcSize, true);\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxHexagon\r\n * \r\n * Implementation of the hexagon shape.\r\n * \r\n * Constructor: mxHexagon\r\n *\r\n * Constructs a new hexagon shape.\r\n */\r\nfunction mxHexagon()\r\n{\r\n\tmxActor.call(this);\r\n};\r\n\r\n/**\r\n * Extends mxActor.\r\n */\r\nmxUtils.extend(mxHexagon, mxActor);\r\n\r\n/**\r\n * Function: redrawPath\r\n *\r\n * Draws the path for this shape.\r\n */\r\nmxHexagon.prototype.redrawPath = function(c, x, y, w, h)\r\n{\r\n\tvar arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;\r\n\tthis.addPoints(c, [new mxPoint(0.25 * w, 0), new mxPoint(0.75 * w, 0), new mxPoint(w, 0.5 * h), new mxPoint(0.75 * w, h),\r\n\t                   new mxPoint(0.25 * w, h), new mxPoint(0, 0.5 * h)], this.isRounded, arcSize, true);\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxLine\r\n *\r\n * Extends <mxShape> to implement a horizontal line shape.\r\n * This shape is registered under <mxConstants.SHAPE_LINE> in\r\n * <mxCellRenderer>.\r\n * \r\n * Constructor: mxLine\r\n *\r\n * Constructs a new line shape.\r\n * \r\n * Parameters:\r\n * \r\n * bounds - <mxRectangle> that defines the bounds. This is stored in\r\n * <mxShape.bounds>.\r\n * stroke - String that defines the stroke color. Default is 'black'. This is\r\n * stored in <stroke>.\r\n * strokewidth - Optional integer that defines the stroke width. Default is\r\n * 1. This is stored in <strokewidth>.\r\n */\r\nfunction mxLine(bounds, stroke, strokewidth)\r\n{\r\n\tmxShape.call(this);\r\n\tthis.bounds = bounds;\r\n\tthis.stroke = stroke;\r\n\tthis.strokewidth = (strokewidth != null) ? strokewidth : 1;\r\n};\r\n\r\n/**\r\n * Extends mxShape.\r\n */\r\nmxUtils.extend(mxLine, mxShape);\r\n\r\n/**\r\n * Function: paintVertexShape\r\n * \r\n * Redirects to redrawPath for subclasses to work.\r\n */\r\nmxLine.prototype.paintVertexShape = function(c, x, y, w, h)\r\n{\r\n\tvar mid = y + h / 2;\r\n\r\n\tc.begin();\r\n\tc.moveTo(x, mid);\r\n\tc.lineTo(x + w, mid);\r\n\tc.stroke();\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxImageShape\r\n *\r\n * Extends <mxShape> to implement an image shape. This shape is registered\r\n * under <mxConstants.SHAPE_IMAGE> in <mxCellRenderer>.\r\n * \r\n * Constructor: mxImageShape\r\n * \r\n * Constructs a new image shape.\r\n * \r\n * Parameters:\r\n * \r\n * bounds - <mxRectangle> that defines the bounds. This is stored in\r\n * <mxShape.bounds>.\r\n * image - String that specifies the URL of the image. This is stored in\r\n * <image>.\r\n * fill - String that defines the fill color. This is stored in <fill>.\r\n * stroke - String that defines the stroke color. This is stored in <stroke>.\r\n * strokewidth - Optional integer that defines the stroke width. Default is\r\n * 0. This is stored in <strokewidth>.\r\n */\r\nfunction mxImageShape(bounds, image, fill, stroke, strokewidth)\r\n{\r\n\tmxShape.call(this);\r\n\tthis.bounds = bounds;\r\n\tthis.image = image;\r\n\tthis.fill = fill;\r\n\tthis.stroke = stroke;\r\n\tthis.strokewidth = (strokewidth != null) ? strokewidth : 1;\r\n\tthis.shadow = false;\r\n};\r\n\r\n/**\r\n * Extends mxShape.\r\n */\r\nmxUtils.extend(mxImageShape, mxRectangleShape);\r\n\r\n/**\r\n * Variable: preserveImageAspect\r\n *\r\n * Switch to preserve image aspect. Default is true.\r\n */\r\nmxImageShape.prototype.preserveImageAspect = true;\r\n\r\n/**\r\n * Function: getSvgScreenOffset\r\n * \r\n * Disables offset in IE9 for crisper image output.\r\n */\r\nmxImageShape.prototype.getSvgScreenOffset = function()\r\n{\r\n\treturn 0;\r\n};\r\n\r\n/**\r\n * Function: apply\r\n * \r\n * Overrides <mxShape.apply> to replace the fill and stroke colors with the\r\n * respective values from <mxConstants.STYLE_IMAGE_BACKGROUND> and\r\n * <mxConstants.STYLE_IMAGE_BORDER>.\r\n * \r\n * Applies the style of the given <mxCellState> to the shape. This\r\n * implementation assigns the following styles to local fields:\r\n * \r\n * - <mxConstants.STYLE_IMAGE_BACKGROUND> => fill\r\n * - <mxConstants.STYLE_IMAGE_BORDER> => stroke\r\n *\r\n * Parameters:\r\n *\r\n * state - <mxCellState> of the corresponding cell.\r\n */\r\nmxImageShape.prototype.apply = function(state)\r\n{\r\n\tmxShape.prototype.apply.apply(this, arguments);\r\n\t\r\n\tthis.fill = null;\r\n\tthis.stroke = null;\r\n\tthis.gradient = null;\r\n\t\r\n\tif (this.style != null)\r\n\t{\r\n\t\tthis.preserveImageAspect = mxUtils.getNumber(this.style, mxConstants.STYLE_IMAGE_ASPECT, 1) == 1;\r\n\t\t\r\n\t\t// Legacy support for imageFlipH/V\r\n\t\tthis.flipH = this.flipH || mxUtils.getValue(this.style, 'imageFlipH', 0) == 1;\r\n\t\tthis.flipV = this.flipV || mxUtils.getValue(this.style, 'imageFlipV', 0) == 1;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isHtmlAllowed\r\n * \r\n * Returns true if HTML is allowed for this shape. This implementation always\r\n * returns false.\r\n */\r\nmxImageShape.prototype.isHtmlAllowed = function()\r\n{\r\n\treturn !this.preserveImageAspect;\r\n};\r\n\r\n/**\r\n * Function: createHtml\r\n *\r\n * Creates and returns the HTML DOM node(s) to represent\r\n * this shape. This implementation falls back to <createVml>\r\n * so that the HTML creation is optional.\r\n */\r\nmxImageShape.prototype.createHtml = function()\r\n{\r\n\tvar node = document.createElement('div');\r\n\tnode.style.position = 'absolute';\r\n\r\n\treturn node;\r\n};\r\n\r\n/**\r\n * Function: isRoundable\r\n * \r\n * Disables inherited roundable support.\r\n */\r\nmxImageShape.prototype.isRoundable = function(c, x, y, w, h)\r\n{\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: paintVertexShape\r\n * \r\n * Generic background painting implementation.\r\n */\r\nmxImageShape.prototype.paintVertexShape = function(c, x, y, w, h)\r\n{\r\n\tif (this.image != null)\r\n\t{\r\n\t\tvar fill = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BACKGROUND, null);\r\n\t\tvar stroke = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BORDER, null);\r\n\t\t\r\n\t\tif (fill != null)\r\n\t\t{\r\n\t\t\t// Stroke rendering required for shadow\r\n\t\t\tc.setFillColor(fill);\r\n\t\t\tc.setStrokeColor(stroke);\r\n\t\t\tc.rect(x, y, w, h);\r\n\t\t\tc.fillAndStroke();\r\n\t\t}\r\n\r\n\t\t// FlipH/V are implicit via mxShape.updateTransform\r\n\t\tc.image(x, y, w, h, this.image, this.preserveImageAspect, false, false);\r\n\t\t\r\n\t\tvar stroke = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BORDER, null);\r\n\t\t\r\n\t\tif (stroke != null)\r\n\t\t{\r\n\t\t\tc.setShadow(false);\r\n\t\t\tc.setStrokeColor(stroke);\r\n\t\t\tc.rect(x, y, w, h);\r\n\t\t\tc.stroke();\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tmxRectangleShape.prototype.paintBackground.apply(this, arguments);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: redraw\r\n * \r\n * Overrides <mxShape.redraw> to preserve the aspect ratio of images.\r\n */\r\nmxImageShape.prototype.redrawHtmlShape = function()\r\n{\r\n\tthis.node.style.left = Math.round(this.bounds.x) + 'px';\r\n\tthis.node.style.top = Math.round(this.bounds.y) + 'px';\r\n\tthis.node.style.width = Math.max(0, Math.round(this.bounds.width)) + 'px';\r\n\tthis.node.style.height = Math.max(0, Math.round(this.bounds.height)) + 'px';\r\n\tthis.node.innerHTML = '';\r\n\r\n\tif (this.image != null)\r\n\t{\r\n\t\tvar fill = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BACKGROUND, '');\r\n\t\tvar stroke = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BORDER, '');\r\n\t\tthis.node.style.backgroundColor = fill;\r\n\t\tthis.node.style.borderColor = stroke;\r\n\t\t\r\n\t\t// VML image supports PNG in IE6\r\n\t\tvar useVml = mxClient.IS_IE6 || ((document.documentMode == null || document.documentMode <= 8) && this.rotation != 0);\r\n\t\tvar img = document.createElement((useVml) ? mxClient.VML_PREFIX + ':image' : 'img');\r\n\t\timg.setAttribute('border', '0');\r\n\t\timg.style.position = 'absolute';\r\n\t\timg.src = this.image;\r\n\r\n\t\tvar filter = (this.opacity < 100) ? 'alpha(opacity=' + this.opacity + ')' : '';\r\n\t\tthis.node.style.filter = filter;\r\n\t\t\r\n\t\tif (this.flipH && this.flipV)\r\n\t\t{\r\n\t\t\tfilter += 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2)';\r\n\t\t}\r\n\t\telse if (this.flipH)\r\n\t\t{\r\n\t\t\tfilter += 'progid:DXImageTransform.Microsoft.BasicImage(mirror=1)';\r\n\t\t}\r\n\t\telse if (this.flipV)\r\n\t\t{\r\n\t\t\tfilter += 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)';\r\n\t\t}\r\n\r\n\t\tif (img.style.filter != filter)\r\n\t\t{\r\n\t\t\timg.style.filter = filter;\r\n\t\t}\r\n\r\n\t\tif (img.nodeName == 'image')\r\n\t\t{\r\n\t\t\timg.style.rotation = this.rotation;\r\n\t\t}\r\n\t\telse if (this.rotation != 0)\r\n\t\t{\r\n\t\t\t// LATER: Add flipV/H support\r\n\t\t\tmxUtils.setPrefixedStyle(img.style, 'transform', 'rotate(' + this.rotation + 'deg)');\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tmxUtils.setPrefixedStyle(img.style, 'transform', '');\r\n\t\t}\r\n\r\n\t\t// Known problem: IE clips top line of image for certain angles\r\n\t\timg.style.width = this.node.style.width;\r\n\t\timg.style.height = this.node.style.height;\r\n\t\t\r\n\t\tthis.node.style.backgroundImage = '';\r\n\t\tthis.node.appendChild(img);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.setTransparentBackgroundImage(this.node);\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxLabel\r\n *\r\n * Extends <mxShape> to implement an image shape with a label.\r\n * This shape is registered under <mxConstants.SHAPE_LABEL> in\r\n * <mxCellRenderer>.\r\n * \r\n * Constructor: mxLabel\r\n *\r\n * Constructs a new label shape.\r\n * \r\n * Parameters:\r\n * \r\n * bounds - <mxRectangle> that defines the bounds. This is stored in\r\n * <mxShape.bounds>.\r\n * fill - String that defines the fill color. This is stored in <fill>.\r\n * stroke - String that defines the stroke color. This is stored in <stroke>.\r\n * strokewidth - Optional integer that defines the stroke width. Default is\r\n * 1. This is stored in <strokewidth>.\r\n */\r\nfunction mxLabel(bounds, fill, stroke, strokewidth)\r\n{\r\n\tmxRectangleShape.call(this, bounds, fill, stroke, strokewidth);\r\n};\r\n\r\n/**\r\n * Extends mxShape.\r\n */\r\nmxUtils.extend(mxLabel, mxRectangleShape);\r\n\r\n/**\r\n * Variable: imageSize\r\n *\r\n * Default width and height for the image. Default is\r\n * <mxConstants.DEFAULT_IMAGESIZE>.\r\n */\r\nmxLabel.prototype.imageSize = mxConstants.DEFAULT_IMAGESIZE;\r\n\r\n/**\r\n * Variable: spacing\r\n *\r\n * Default value for image spacing. Default is 2.\r\n */\r\nmxLabel.prototype.spacing = 2;\r\n\r\n/**\r\n * Variable: indicatorSize\r\n *\r\n * Default width and height for the indicicator. Default is 10.\r\n */\r\nmxLabel.prototype.indicatorSize = 10;\r\n\r\n/**\r\n * Variable: indicatorSpacing\r\n *\r\n * Default spacing between image and indicator. Default is 2.\r\n */\r\nmxLabel.prototype.indicatorSpacing = 2;\r\n\r\n/**\r\n * Function: init\r\n *\r\n * Initializes the shape and the <indicator>.\r\n */\r\nmxLabel.prototype.init = function(container)\r\n{\r\n\tmxShape.prototype.init.apply(this, arguments);\r\n\r\n\tif (this.indicatorShape != null)\r\n\t{\r\n\t\tthis.indicator = new this.indicatorShape();\r\n\t\tthis.indicator.dialect = this.dialect;\r\n\t\tthis.indicator.init(this.node);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: redraw\r\n *\r\n * Reconfigures this shape. This will update the colors of the indicator\r\n * and reconfigure it if required.\r\n */\r\nmxLabel.prototype.redraw = function()\r\n{\r\n\tif (this.indicator != null)\r\n\t{\r\n\t\tthis.indicator.fill = this.indicatorColor;\r\n\t\tthis.indicator.stroke = this.indicatorStrokeColor;\r\n\t\tthis.indicator.gradient = this.indicatorGradientColor;\r\n\t\tthis.indicator.direction = this.indicatorDirection;\r\n\t}\r\n\t\r\n\tmxShape.prototype.redraw.apply(this, arguments);\r\n};\r\n\r\n/**\r\n * Function: isHtmlAllowed\r\n *\r\n * Returns true for non-rounded, non-rotated shapes with no glass gradient and\r\n * no indicator shape.\r\n */\r\nmxLabel.prototype.isHtmlAllowed = function()\r\n{\r\n\treturn mxRectangleShape.prototype.isHtmlAllowed.apply(this, arguments) &&\r\n\t\tthis.indicatorColor == null && this.indicatorShape == null;\r\n};\r\n\r\n/**\r\n * Function: paintForeground\r\n * \r\n * Generic background painting implementation.\r\n */\r\nmxLabel.prototype.paintForeground = function(c, x, y, w, h)\r\n{\r\n\tthis.paintImage(c, x, y, w, h);\r\n\tthis.paintIndicator(c, x, y, w, h);\r\n\t\r\n\tmxRectangleShape.prototype.paintForeground.apply(this, arguments);\r\n};\r\n\r\n/**\r\n * Function: paintImage\r\n * \r\n * Generic background painting implementation.\r\n */\r\nmxLabel.prototype.paintImage = function(c, x, y, w, h)\r\n{\r\n\tif (this.image != null)\r\n\t{\r\n\t\tvar bounds = this.getImageBounds(x, y, w, h);\r\n\t\tc.image(bounds.x, bounds.y, bounds.width, bounds.height, this.image, false, false, false);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getImageBounds\r\n * \r\n * Generic background painting implementation.\r\n */\r\nmxLabel.prototype.getImageBounds = function(x, y, w, h)\r\n{\r\n\tvar align = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_ALIGN, mxConstants.ALIGN_LEFT);\r\n\tvar valign = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE);\r\n\tvar width = mxUtils.getNumber(this.style, mxConstants.STYLE_IMAGE_WIDTH, mxConstants.DEFAULT_IMAGESIZE);\r\n\tvar height = mxUtils.getNumber(this.style, mxConstants.STYLE_IMAGE_HEIGHT, mxConstants.DEFAULT_IMAGESIZE);\r\n\tvar spacing = mxUtils.getNumber(this.style, mxConstants.STYLE_SPACING, this.spacing) + 5;\r\n\r\n\tif (align == mxConstants.ALIGN_CENTER)\r\n\t{\r\n\t\tx += (w - width) / 2;\r\n\t}\r\n\telse if (align == mxConstants.ALIGN_RIGHT)\r\n\t{\r\n\t\tx += w - width - spacing;\r\n\t}\r\n\telse // default is left\r\n\t{\r\n\t\tx += spacing;\r\n\t}\r\n\r\n\tif (valign == mxConstants.ALIGN_TOP)\r\n\t{\r\n\t\ty += spacing;\r\n\t}\r\n\telse if (valign == mxConstants.ALIGN_BOTTOM)\r\n\t{\r\n\t\ty += h - height - spacing;\r\n\t}\r\n\telse // default is middle\r\n\t{\r\n\t\ty += (h - height) / 2;\r\n\t}\r\n\t\r\n\treturn new mxRectangle(x, y, width, height);\r\n};\r\n\r\n/**\r\n * Function: paintIndicator\r\n * \r\n * Generic background painting implementation.\r\n */\r\nmxLabel.prototype.paintIndicator = function(c, x, y, w, h)\r\n{\r\n\tif (this.indicator != null)\r\n\t{\r\n\t\tthis.indicator.bounds = this.getIndicatorBounds(x, y, w, h);\r\n\t\tthis.indicator.paint(c);\r\n\t}\r\n\telse if (this.indicatorImage != null)\r\n\t{\r\n\t\tvar bounds = this.getIndicatorBounds(x, y, w, h);\r\n\t\tc.image(bounds.x, bounds.y, bounds.width, bounds.height, this.indicatorImage, false, false, false);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getIndicatorBounds\r\n * \r\n * Generic background painting implementation.\r\n */\r\nmxLabel.prototype.getIndicatorBounds = function(x, y, w, h)\r\n{\r\n\tvar align = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_ALIGN, mxConstants.ALIGN_LEFT);\r\n\tvar valign = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE);\r\n\tvar width = mxUtils.getNumber(this.style, mxConstants.STYLE_INDICATOR_WIDTH, this.indicatorSize);\r\n\tvar height = mxUtils.getNumber(this.style, mxConstants.STYLE_INDICATOR_HEIGHT, this.indicatorSize);\r\n\tvar spacing = this.spacing + 5;\t\t\r\n\t\r\n\tif (align == mxConstants.ALIGN_RIGHT)\r\n\t{\r\n\t\tx += w - width - spacing;\r\n\t}\r\n\telse if (align == mxConstants.ALIGN_CENTER)\r\n\t{\r\n\t\tx += (w - width) / 2;\r\n\t}\r\n\telse // default is left\r\n\t{\r\n\t\tx += spacing;\r\n\t}\r\n\t\r\n\tif (valign == mxConstants.ALIGN_BOTTOM)\r\n\t{\r\n\t\ty += h - height - spacing;\r\n\t}\r\n\telse if (valign == mxConstants.ALIGN_TOP)\r\n\t{\r\n\t\ty += spacing;\r\n\t}\r\n\telse // default is middle\r\n\t{\r\n\t\ty += (h - height) / 2;\r\n\t}\r\n\t\r\n\treturn new mxRectangle(x, y, width, height);\r\n};\r\n/**\r\n * Function: redrawHtmlShape\r\n * \r\n * Generic background painting implementation.\r\n */\r\nmxLabel.prototype.redrawHtmlShape = function()\r\n{\r\n\tmxRectangleShape.prototype.redrawHtmlShape.apply(this, arguments);\r\n\t\r\n\t// Removes all children\r\n\twhile(this.node.hasChildNodes())\r\n\t{\r\n\t\tthis.node.removeChild(this.node.lastChild);\r\n\t}\r\n\t\r\n\tif (this.image != null)\r\n\t{\r\n\t\tvar node = document.createElement('img');\r\n\t\tnode.style.position = 'relative';\r\n\t\tnode.setAttribute('border', '0');\r\n\t\t\r\n\t\tvar bounds = this.getImageBounds(this.bounds.x, this.bounds.y, this.bounds.width, this.bounds.height);\r\n\t\tbounds.x -= this.bounds.x;\r\n\t\tbounds.y -= this.bounds.y;\r\n\r\n\t\tnode.style.left = Math.round(bounds.x) + 'px';\r\n\t\tnode.style.top = Math.round(bounds.y) + 'px';\r\n\t\tnode.style.width = Math.round(bounds.width) + 'px';\r\n\t\tnode.style.height = Math.round(bounds.height) + 'px';\r\n\t\t\r\n\t\tnode.src = this.image;\r\n\t\t\r\n\t\tthis.node.appendChild(node);\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxCylinder\r\n *\r\n * Extends <mxShape> to implement an cylinder shape. If a\r\n * custom shape with one filled area and an overlay path is\r\n * needed, then this shape's <redrawPath> should be overridden.\r\n * This shape is registered under <mxConstants.SHAPE_CYLINDER>\r\n * in <mxCellRenderer>.\r\n * \r\n * Constructor: mxCylinder\r\n *\r\n * Constructs a new cylinder shape.\r\n * \r\n * Parameters:\r\n * \r\n * bounds - <mxRectangle> that defines the bounds. This is stored in\r\n * <mxShape.bounds>.\r\n * fill - String that defines the fill color. This is stored in <fill>.\r\n * stroke - String that defines the stroke color. This is stored in <stroke>.\r\n * strokewidth - Optional integer that defines the stroke width. Default is\r\n * 1. This is stored in <strokewidth>.\r\n */\r\nfunction mxCylinder(bounds, fill, stroke, strokewidth)\r\n{\r\n\tmxShape.call(this);\r\n\tthis.bounds = bounds;\r\n\tthis.fill = fill;\r\n\tthis.stroke = stroke;\r\n\tthis.strokewidth = (strokewidth != null) ? strokewidth : 1;\r\n};\r\n\r\n/**\r\n * Extends mxShape.\r\n */\r\nmxUtils.extend(mxCylinder, mxShape);\r\n\r\n/**\r\n * Variable: maxHeight\r\n *\r\n * Defines the maximum height of the top and bottom part\r\n * of the cylinder shape.\r\n */\r\nmxCylinder.prototype.maxHeight = 40;\r\n\r\n/**\r\n * Variable: svgStrokeTolerance\r\n *\r\n * Sets stroke tolerance to 0 for SVG.\r\n */\r\nmxCylinder.prototype.svgStrokeTolerance = 0;\r\n\r\n/**\r\n * Function: paintVertexShape\r\n * \r\n * Redirects to redrawPath for subclasses to work.\r\n */\r\nmxCylinder.prototype.paintVertexShape = function(c, x, y, w, h)\r\n{\r\n\tc.translate(x, y);\r\n\tc.begin();\r\n\tthis.redrawPath(c, x, y, w, h, false);\r\n\tc.fillAndStroke();\r\n\t\r\n\tif (!this.outline || this.style == null || mxUtils.getValue(\r\n\t\tthis.style, mxConstants.STYLE_BACKGROUND_OUTLINE, 0) == 0)\r\n\t{\r\n\t\tc.setShadow(false);\r\n\t\tc.begin();\r\n\t\tthis.redrawPath(c, x, y, w, h, true);\r\n\t\tc.stroke();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: redrawPath\r\n *\r\n * Draws the path for this shape.\r\n */\r\nmxCylinder.prototype.getCylinderSize = function(x, y, w, h)\r\n{\r\n\treturn Math.min(this.maxHeight, Math.round(h / 5));\r\n};\r\n\r\n/**\r\n * Function: redrawPath\r\n *\r\n * Draws the path for this shape.\r\n */\r\nmxCylinder.prototype.redrawPath = function(c, x, y, w, h, isForeground)\r\n{\r\n\tvar dy = this.getCylinderSize(x, y, w, h);\r\n\t\r\n\tif ((isForeground && this.fill != null) || (!isForeground && this.fill == null))\r\n\t{\r\n\t\tc.moveTo(0, dy);\r\n\t\tc.curveTo(0, 2 * dy, w, 2 * dy, w, dy);\r\n\t\t\r\n\t\t// Needs separate shapes for correct hit-detection\r\n\t\tif (!isForeground)\r\n\t\t{\r\n\t\t\tc.stroke();\r\n\t\t\tc.begin();\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (!isForeground)\r\n\t{\r\n\t\tc.moveTo(0, dy);\r\n\t\tc.curveTo(0, -dy / 3, w, -dy / 3, w, dy);\r\n\t\tc.lineTo(w, h - dy);\r\n\t\tc.curveTo(w, h + dy / 3, 0, h + dy / 3, 0, h - dy);\r\n\t\tc.close();\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxConnector\r\n * \r\n * Extends <mxShape> to implement a connector shape. The connector\r\n * shape allows for arrow heads on either side.\r\n * \r\n * This shape is registered under <mxConstants.SHAPE_CONNECTOR> in\r\n * <mxCellRenderer>.\r\n * \r\n * Constructor: mxConnector\r\n * \r\n * Constructs a new connector shape.\r\n * \r\n * Parameters:\r\n * \r\n * points - Array of <mxPoints> that define the points. This is stored in\r\n * <mxShape.points>.\r\n * stroke - String that defines the stroke color. This is stored in <stroke>.\r\n * Default is 'black'.\r\n * strokewidth - Optional integer that defines the stroke width. Default is\r\n * 1. This is stored in <strokewidth>.\r\n */\r\nfunction mxConnector(points, stroke, strokewidth)\r\n{\r\n\tmxPolyline.call(this, points, stroke, strokewidth);\r\n};\r\n\r\n/**\r\n * Extends mxPolyline.\r\n */\r\nmxUtils.extend(mxConnector, mxPolyline);\r\n\r\n/**\r\n * Function: updateBoundingBox\r\n *\r\n * Updates the <boundingBox> for this shape using <createBoundingBox> and\r\n * <augmentBoundingBox> and stores the result in <boundingBox>.\r\n */\r\nmxConnector.prototype.updateBoundingBox = function()\r\n{\r\n\tthis.useSvgBoundingBox = this.style != null && this.style[mxConstants.STYLE_CURVED] == 1;\r\n\tmxShape.prototype.updateBoundingBox.apply(this, arguments);\r\n};\r\n\r\n/**\r\n * Function: paintEdgeShape\r\n * \r\n * Paints the line shape.\r\n */\r\nmxConnector.prototype.paintEdgeShape = function(c, pts)\r\n{\r\n\t// The indirection via functions for markers is needed in\r\n\t// order to apply the offsets before painting the line and\r\n\t// paint the markers after painting the line.\r\n\tvar sourceMarker = this.createMarker(c, pts, true);\r\n\tvar targetMarker = this.createMarker(c, pts, false);\r\n\r\n\tmxPolyline.prototype.paintEdgeShape.apply(this, arguments);\r\n\t\r\n\t// Disables shadows, dashed styles and fixes fill color for markers\r\n\tc.setFillColor(this.stroke);\r\n\tc.setShadow(false);\r\n\tc.setDashed(false);\r\n\t\r\n\tif (sourceMarker != null)\r\n\t{\r\n\t\tsourceMarker();\r\n\t}\r\n\t\r\n\tif (targetMarker != null)\r\n\t{\r\n\t\ttargetMarker();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createMarker\r\n * \r\n * Prepares the marker by adding offsets in pts and returning a function to\r\n * paint the marker.\r\n */\r\nmxConnector.prototype.createMarker = function(c, pts, source)\r\n{\r\n\tvar result = null;\r\n\tvar n = pts.length;\r\n\tvar type = mxUtils.getValue(this.style, (source) ? mxConstants.STYLE_STARTARROW : mxConstants.STYLE_ENDARROW);\r\n\tvar p0 = (source) ? pts[1] : pts[n - 2];\r\n\tvar pe = (source) ? pts[0] : pts[n - 1];\r\n\t\r\n\tif (type != null && p0 != null && pe != null)\r\n\t{\r\n\t\tvar count = 1;\r\n\t\t\r\n\t\t// Uses next non-overlapping point\r\n\t\twhile (count < n - 1 && Math.round(p0.x - pe.x) == 0 && Math.round(p0.y - pe.y) == 0)\r\n\t\t{\r\n\t\t\tp0 = (source) ? pts[1 + count] : pts[n - 2 - count];\r\n\t\t\tcount++;\r\n\t\t}\r\n\t\r\n\t\t// Computes the norm and the inverse norm\r\n\t\tvar dx = pe.x - p0.x;\r\n\t\tvar dy = pe.y - p0.y;\r\n\t\r\n\t\tvar dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));\r\n\t\t\r\n\t\tvar unitX = dx / dist;\r\n\t\tvar unitY = dy / dist;\r\n\t\r\n\t\tvar size = mxUtils.getNumber(this.style, (source) ? mxConstants.STYLE_STARTSIZE : mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE);\r\n\t\t\r\n\t\t// Allow for stroke width in the end point used and the \r\n\t\t// orthogonal vectors describing the direction of the marker\r\n\t\tvar filled = this.style[(source) ? mxConstants.STYLE_STARTFILL : mxConstants.STYLE_ENDFILL] != 0;\r\n\t\t\r\n\t\tresult = mxMarker.createMarker(c, this, type, pe, unitX, unitY, size, source, this.strokewidth, filled);\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: augmentBoundingBox\r\n *\r\n * Augments the bounding box with the strokewidth and shadow offsets.\r\n */\r\nmxConnector.prototype.augmentBoundingBox = function(bbox)\r\n{\r\n\tmxShape.prototype.augmentBoundingBox.apply(this, arguments);\r\n\t\r\n\t// Adds marker sizes\r\n\tvar size = 0;\r\n\t\r\n\tif (mxUtils.getValue(this.style, mxConstants.STYLE_STARTARROW, mxConstants.NONE) != mxConstants.NONE)\r\n\t{\r\n\t\tsize = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_MARKERSIZE) + 1;\r\n\t}\r\n\t\r\n\tif (mxUtils.getValue(this.style, mxConstants.STYLE_ENDARROW, mxConstants.NONE) != mxConstants.NONE)\r\n\t{\r\n\t\tsize = Math.max(size, mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE)) + 1;\r\n\t}\r\n\t\r\n\tbbox.grow(size * this.scale);\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxSwimlane\r\n *\r\n * Extends <mxShape> to implement a swimlane shape. This shape is registered\r\n * under <mxConstants.SHAPE_SWIMLANE> in <mxCellRenderer>. Use the\r\n * <mxConstants.STYLE_STYLE_STARTSIZE> to define the size of the title\r\n * region, <mxConstants.STYLE_SWIMLANE_FILLCOLOR> for the content area fill,\r\n * <mxConstants.STYLE_SEPARATORCOLOR> to draw an additional vertical separator\r\n * and <mxConstants.STYLE_SWIMLANE_LINE> to hide the line between the title\r\n * region and the content area. The <mxConstants.STYLE_HORIZONTAL> affects\r\n * the orientation of this shape, not only its label.\r\n * \r\n * Constructor: mxSwimlane\r\n *\r\n * Constructs a new swimlane shape.\r\n * \r\n * Parameters:\r\n * \r\n * bounds - <mxRectangle> that defines the bounds. This is stored in\r\n * <mxShape.bounds>.\r\n * fill - String that defines the fill color. This is stored in <fill>.\r\n * stroke - String that defines the stroke color. This is stored in <stroke>.\r\n * strokewidth - Optional integer that defines the stroke width. Default is\r\n * 1. This is stored in <strokewidth>.\r\n */\r\nfunction mxSwimlane(bounds, fill, stroke, strokewidth)\r\n{\r\n\tmxShape.call(this);\r\n\tthis.bounds = bounds;\r\n\tthis.fill = fill;\r\n\tthis.stroke = stroke;\r\n\tthis.strokewidth = (strokewidth != null) ? strokewidth : 1;\r\n};\r\n\r\n/**\r\n * Extends mxShape.\r\n */\r\nmxUtils.extend(mxSwimlane, mxShape);\r\n\r\n/**\r\n * Variable: imageSize\r\n *\r\n * Default imagewidth and imageheight if an image but no imagewidth\r\n * and imageheight are defined in the style. Value is 16.\r\n */\r\nmxSwimlane.prototype.imageSize = 16;\r\n\r\n/**\r\n * Function: isRoundable\r\n * \r\n * Adds roundable support.\r\n */\r\nmxSwimlane.prototype.isRoundable = function(c, x, y, w, h)\r\n{\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: getGradientBounds\r\n * \r\n * Returns the bounding box for the gradient box for this shape.\r\n */\r\nmxSwimlane.prototype.getTitleSize = function()\r\n{\r\n\treturn Math.max(0, mxUtils.getValue(this.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));\r\n};\r\n\r\n/**\r\n * Function: getGradientBounds\r\n * \r\n * Returns the bounding box for the gradient box for this shape.\r\n */\r\nmxSwimlane.prototype.getLabelBounds = function(rect)\r\n{\r\n\tvar start = this.getTitleSize();\r\n\tvar bounds = new mxRectangle(rect.x, rect.y, rect.width, rect.height);\r\n\tvar horizontal = this.isHorizontal();\r\n\t\r\n\tvar flipH = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPH, 0) == 1;\r\n\tvar flipV = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPV, 0) == 1;\t\r\n\t\r\n\t// East is default\r\n\tvar shapeVertical = (this.direction == mxConstants.DIRECTION_NORTH ||\r\n\t\t\tthis.direction == mxConstants.DIRECTION_SOUTH);\r\n\tvar realHorizontal = horizontal == !shapeVertical;\r\n\t\r\n\tvar realFlipH = !realHorizontal && flipH != (this.direction == mxConstants.DIRECTION_SOUTH ||\r\n\t\t\tthis.direction == mxConstants.DIRECTION_WEST);\r\n\tvar realFlipV = realHorizontal && flipV != (this.direction == mxConstants.DIRECTION_SOUTH ||\r\n\t\t\tthis.direction == mxConstants.DIRECTION_WEST);\r\n\r\n\t// Shape is horizontal\r\n\tif (!shapeVertical)\r\n\t{\r\n\t\tvar tmp = Math.min(bounds.height, start * this.scale);\r\n\r\n\t\tif (realFlipH || realFlipV)\r\n\t\t{\r\n\t\t\tbounds.y += bounds.height - tmp;\r\n\t\t}\r\n\r\n\t\tbounds.height = tmp;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar tmp = Math.min(bounds.width, start * this.scale);\r\n\t\t\r\n\t\tif (realFlipH || realFlipV)\r\n\t\t{\r\n\t\t\tbounds.x += bounds.width - tmp;\t\r\n\t\t}\r\n\r\n\t\tbounds.width = tmp;\r\n\t}\r\n\t\r\n\treturn bounds;\r\n};\r\n\r\n/**\r\n * Function: getGradientBounds\r\n * \r\n * Returns the bounding box for the gradient box for this shape.\r\n */\r\nmxSwimlane.prototype.getGradientBounds = function(c, x, y, w, h)\r\n{\r\n\tvar start = this.getTitleSize();\r\n\t\r\n\tif (this.isHorizontal())\r\n\t{\r\n\t\tstart = Math.min(start, h);\r\n\t\treturn new mxRectangle(x, y, w, start);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tstart = Math.min(start, w);\r\n\t\treturn new mxRectangle(x, y, start, h);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getArcSize\r\n * \r\n * Returns the arcsize for the swimlane.\r\n */\r\nmxSwimlane.prototype.getArcSize = function(w, h, start)\r\n{\r\n\tvar f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;\r\n\r\n\treturn start * f * 3; \r\n};\r\n\r\n/**\r\n * Function: paintVertexShape\r\n *\r\n * Paints the swimlane vertex shape.\r\n */\r\nmxSwimlane.prototype.isHorizontal = function()\r\n{\r\n\treturn mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, 1) == 1;\r\n};\r\n\r\n/**\r\n * Function: paintVertexShape\r\n *\r\n * Paints the swimlane vertex shape.\r\n */\r\nmxSwimlane.prototype.paintVertexShape = function(c, x, y, w, h)\r\n{\r\n\tvar start = this.getTitleSize();\r\n\tvar fill = mxUtils.getValue(this.style, mxConstants.STYLE_SWIMLANE_FILLCOLOR, mxConstants.NONE);\r\n\tvar swimlaneLine = mxUtils.getValue(this.style, mxConstants.STYLE_SWIMLANE_LINE, 1) == 1;\r\n\tvar r = 0;\r\n\t\r\n\tif (this.isHorizontal())\r\n\t{\r\n\t\tstart = Math.min(start, h);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tstart = Math.min(start, w);\r\n\t}\r\n\t\r\n\tc.translate(x, y);\r\n\t\r\n\tif (!this.isRounded)\r\n\t{\r\n\t\tthis.paintSwimlane(c, x, y, w, h, start, fill, swimlaneLine);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tr = this.getArcSize(w, h, start);\r\n\t\tr = Math.min(((this.isHorizontal()) ? h : w) - start, Math.min(start, r));\r\n\t\tthis.paintRoundedSwimlane(c, x, y, w, h, start, r, fill, swimlaneLine);\r\n\t}\r\n\t\r\n\tvar sep = mxUtils.getValue(this.style, mxConstants.STYLE_SEPARATORCOLOR, mxConstants.NONE);\r\n\tthis.paintSeparator(c, x, y, w, h, start, sep);\r\n\r\n\tif (this.image != null)\r\n\t{\r\n\t\tvar bounds = this.getImageBounds(x, y, w, h);\r\n\t\tc.image(bounds.x - x, bounds.y - y, bounds.width, bounds.height,\r\n\t\t\t\tthis.image, false, false, false);\r\n\t}\r\n\t\r\n\tif (this.glass)\r\n\t{\r\n\t\tc.setShadow(false);\r\n\t\tthis.paintGlassEffect(c, 0, 0, w, start, r);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: paintSwimlane\r\n *\r\n * Paints the swimlane vertex shape.\r\n */\r\nmxSwimlane.prototype.paintSwimlane = function(c, x, y, w, h, start, fill, swimlaneLine)\r\n{\r\n\tc.begin();\r\n\t\r\n\tif (this.isHorizontal())\r\n\t{\r\n\t\tc.moveTo(0, start);\r\n\t\tc.lineTo(0, 0);\r\n\t\tc.lineTo(w, 0);\r\n\t\tc.lineTo(w, start);\r\n\t\tc.fillAndStroke();\r\n\r\n\t\tif (start < h)\r\n\t\t{\r\n\t\t\tif (fill == mxConstants.NONE)\r\n\t\t\t{\r\n\t\t\t\tc.pointerEvents = false;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tc.setFillColor(fill);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tc.begin();\r\n\t\t\tc.moveTo(0, start);\r\n\t\t\tc.lineTo(0, h);\r\n\t\t\tc.lineTo(w, h);\r\n\t\t\tc.lineTo(w, start);\r\n\t\t\t\r\n\t\t\tif (fill == mxConstants.NONE)\r\n\t\t\t{\r\n\t\t\t\tc.stroke();\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tc.fillAndStroke();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tc.moveTo(start, 0);\r\n\t\tc.lineTo(0, 0);\r\n\t\tc.lineTo(0, h);\r\n\t\tc.lineTo(start, h);\r\n\t\tc.fillAndStroke();\r\n\t\t\r\n\t\tif (start < w)\r\n\t\t{\r\n\t\t\tif (fill == mxConstants.NONE)\r\n\t\t\t{\r\n\t\t\t\tc.pointerEvents = false;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tc.setFillColor(fill);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tc.begin();\r\n\t\t\tc.moveTo(start, 0);\r\n\t\t\tc.lineTo(w, 0);\r\n\t\t\tc.lineTo(w, h);\r\n\t\t\tc.lineTo(start, h);\r\n\t\t\t\r\n\t\t\tif (fill == mxConstants.NONE)\r\n\t\t\t{\r\n\t\t\t\tc.stroke();\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tc.fillAndStroke();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (swimlaneLine)\r\n\t{\r\n\t\tthis.paintDivider(c, x, y, w, h, start, fill == mxConstants.NONE);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: paintRoundedSwimlane\r\n *\r\n * Paints the swimlane vertex shape.\r\n */\r\nmxSwimlane.prototype.paintRoundedSwimlane = function(c, x, y, w, h, start, r, fill, swimlaneLine)\r\n{\r\n\tc.begin();\r\n\r\n\tif (this.isHorizontal())\r\n\t{\r\n\t\tc.moveTo(w, start);\r\n\t\tc.lineTo(w, r);\r\n\t\tc.quadTo(w, 0, w - Math.min(w / 2, r), 0);\r\n\t\tc.lineTo(Math.min(w / 2, r), 0);\r\n\t\tc.quadTo(0, 0, 0, r);\r\n\t\tc.lineTo(0, start);\r\n\t\tc.fillAndStroke();\r\n\t\t\r\n\t\tif (start < h)\r\n\t\t{\r\n\t\t\tif (fill == mxConstants.NONE)\r\n\t\t\t{\r\n\t\t\t\tc.pointerEvents = false;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tc.setFillColor(fill);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tc.begin();\r\n\t\t\tc.moveTo(0, start);\r\n\t\t\tc.lineTo(0, h - r);\r\n\t\t\tc.quadTo(0, h, Math.min(w / 2, r), h);\r\n\t\t\tc.lineTo(w - Math.min(w / 2, r), h);\r\n\t\t\tc.quadTo(w, h, w, h - r);\r\n\t\t\tc.lineTo(w, start);\r\n\t\t\t\r\n\t\t\tif (fill == mxConstants.NONE)\r\n\t\t\t{\r\n\t\t\t\tc.stroke();\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tc.fillAndStroke();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tc.moveTo(start, 0);\r\n\t\tc.lineTo(r, 0);\r\n\t\tc.quadTo(0, 0, 0, Math.min(h / 2, r));\r\n\t\tc.lineTo(0, h - Math.min(h / 2, r));\r\n\t\tc.quadTo(0, h, r, h);\r\n\t\tc.lineTo(start, h);\r\n\t\tc.fillAndStroke();\r\n\r\n\t\tif (start < w)\r\n\t\t{\r\n\t\t\tif (fill == mxConstants.NONE)\r\n\t\t\t{\r\n\t\t\t\tc.pointerEvents = false;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tc.setFillColor(fill);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tc.begin();\r\n\t\t\tc.moveTo(start, h);\r\n\t\t\tc.lineTo(w - r, h);\r\n\t\t\tc.quadTo(w, h, w, h - Math.min(h / 2, r));\r\n\t\t\tc.lineTo(w, Math.min(h / 2, r));\r\n\t\t\tc.quadTo(w, 0, w - r, 0);\r\n\t\t\tc.lineTo(start, 0);\r\n\t\t\t\r\n\t\t\tif (fill == mxConstants.NONE)\r\n\t\t\t{\r\n\t\t\t\tc.stroke();\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tc.fillAndStroke();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tif (swimlaneLine)\r\n\t{\r\n\t\tthis.paintDivider(c, x, y, w, h, start, fill == mxConstants.NONE);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: paintDivider\r\n *\r\n * Paints the divider between swimlane title and content area.\r\n */\r\nmxSwimlane.prototype.paintDivider = function(c, x, y, w, h, start, shadow)\r\n{\r\n\tif (!shadow)\r\n\t{\r\n\t\tc.setShadow(false);\r\n\t}\r\n\r\n\tc.begin();\r\n\t\r\n\tif (this.isHorizontal())\r\n\t{\r\n\t\tc.moveTo(0, start);\r\n\t\tc.lineTo(w, start);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tc.moveTo(start, 0);\r\n\t\tc.lineTo(start, h);\r\n\t}\r\n\r\n\tc.stroke();\r\n};\r\n\r\n/**\r\n * Function: paintSeparator\r\n *\r\n * Paints the vertical or horizontal separator line between swimlanes.\r\n */\r\nmxSwimlane.prototype.paintSeparator = function(c, x, y, w, h, start, color)\r\n{\r\n\tif (color != mxConstants.NONE)\r\n\t{\r\n\t\tc.setStrokeColor(color);\r\n\t\tc.setDashed(true);\r\n\t\tc.begin();\r\n\t\t\r\n\t\tif (this.isHorizontal())\r\n\t\t{\r\n\t\t\tc.moveTo(w, start);\r\n\t\t\tc.lineTo(w, h);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tc.moveTo(start, 0);\r\n\t\t\tc.lineTo(w, 0);\r\n\t\t}\r\n\t\t\r\n\t\tc.stroke();\r\n\t\tc.setDashed(false);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getImageBounds\r\n *\r\n * Paints the swimlane vertex shape.\r\n */\r\nmxSwimlane.prototype.getImageBounds = function(x, y, w, h)\r\n{\r\n\tif (this.isHorizontal())\r\n\t{\r\n\t\treturn new mxRectangle(x + w - this.imageSize, y, this.imageSize, this.imageSize);\r\n\t}\r\n\telse\r\n\t{\r\n\t\treturn new mxRectangle(x, y, this.imageSize, this.imageSize);\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2018, JGraph Ltd\r\n * Copyright (c) 2006-2018, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxGraphLayout\r\n * \r\n * Base class for all layout algorithms in mxGraph. Main public functions are\r\n * <move> for handling a moved cell within a layouted parent, and <execute> for\r\n * running the layout on a given parent cell.\r\n *\r\n * Known Subclasses:\r\n *\r\n * <mxCircleLayout>, <mxCompactTreeLayout>, <mxCompositeLayout>,\r\n * <mxFastOrganicLayout>, <mxParallelEdgeLayout>, <mxPartitionLayout>,\r\n * <mxStackLayout>\r\n * \r\n * Constructor: mxGraphLayout\r\n *\r\n * Constructs a new layout using the given layouts.\r\n *\r\n * Arguments:\r\n * \r\n * graph - Enclosing \r\n */\r\nfunction mxGraphLayout(graph)\r\n{\r\n\tthis.graph = graph;\r\n};\r\n\r\n/**\r\n * Variable: graph\r\n * \r\n * Reference to the enclosing <mxGraph>.\r\n */\r\nmxGraphLayout.prototype.graph = null;\r\n\r\n/**\r\n * Variable: useBoundingBox\r\n *\r\n * Boolean indicating if the bounding box of the label should be used if\r\n * its available. Default is true.\r\n */\r\nmxGraphLayout.prototype.useBoundingBox = true;\r\n\r\n/**\r\n * Variable: parent\r\n *\r\n * The parent cell of the layout, if any\r\n */\r\nmxGraphLayout.prototype.parent = null;\r\n\r\n/**\r\n * Function: moveCell\r\n * \r\n * Notified when a cell is being moved in a parent that has automatic\r\n * layout to update the cell state (eg. index) so that the outcome of the\r\n * layout will position the vertex as close to the point (x, y) as\r\n * possible.\r\n * \r\n * Empty implementation.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> which has been moved.\r\n * x - X-coordinate of the new cell location.\r\n * y - Y-coordinate of the new cell location.\r\n */\r\nmxGraphLayout.prototype.moveCell = function(cell, x, y) { };\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Executes the layout algorithm for the children of the given parent.\r\n * \r\n * Parameters:\r\n * \r\n * parent - <mxCell> whose children should be layed out.\r\n */\r\nmxGraphLayout.prototype.execute = function(parent) { };\r\n\r\n/**\r\n * Function: getGraph\r\n * \r\n * Returns the graph that this layout operates on.\r\n */\r\nmxGraphLayout.prototype.getGraph = function()\r\n{\r\n\treturn this.graph;\r\n};\r\n\r\n/**\r\n * Function: getConstraint\r\n * \r\n * Returns the constraint for the given key and cell. The optional edge and\r\n * source arguments are used to return inbound and outgoing routing-\r\n * constraints for the given edge and vertex. This implementation always\r\n * returns the value for the given key in the style of the given cell.\r\n * \r\n * Parameters:\r\n * \r\n * key - Key of the constraint to be returned.\r\n * cell - <mxCell> whose constraint should be returned.\r\n * edge - Optional <mxCell> that represents the connection whose constraint\r\n * should be returned. Default is null.\r\n * source - Optional boolean that specifies if the connection is incoming\r\n * or outgoing. Default is null.\r\n */\r\nmxGraphLayout.prototype.getConstraint = function(key, cell, edge, source)\r\n{\r\n\tvar state = this.graph.view.getState(cell);\r\n\tvar style = (state != null) ? state.style : this.graph.getCellStyle(cell);\r\n\t\r\n\treturn (style != null) ? style[key] : null;\r\n};\r\n\r\n/**\r\n * Function: traverse\r\n * \r\n * Traverses the (directed) graph invoking the given function for each\r\n * visited vertex and edge. The function is invoked with the current vertex\r\n * and the incoming edge as a parameter. This implementation makes sure\r\n * each vertex is only visited once. The function may return false if the\r\n * traversal should stop at the given vertex.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * mxLog.show();\r\n * var cell = graph.getSelectionCell();\r\n * graph.traverse(cell, false, function(vertex, edge)\r\n * {\r\n *   mxLog.debug(graph.getLabel(vertex));\r\n * });\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * vertex - <mxCell> that represents the vertex where the traversal starts.\r\n * directed - Optional boolean indicating if edges should only be traversed\r\n * from source to target. Default is true.\r\n * func - Visitor function that takes the current vertex and the incoming\r\n * edge as arguments. The traversal stops if the function returns false.\r\n * edge - Optional <mxCell> that represents the incoming edge. This is\r\n * null for the first step of the traversal.\r\n * visited - Optional <mxDictionary> of cell paths for the visited cells.\r\n */\r\nmxGraphLayout.traverse = function(vertex, directed, func, edge, visited)\r\n{\r\n\tif (func != null && vertex != null)\r\n\t{\r\n\t\tdirected = (directed != null) ? directed : true;\r\n\t\tvisited = visited || new mxDictionary();\r\n\t\t\r\n\t\tif (!visited.get(vertex))\r\n\t\t{\r\n\t\t\tvisited.put(vertex, true);\r\n\t\t\tvar result = func(vertex, edge);\r\n\t\t\t\r\n\t\t\tif (result == null || result)\r\n\t\t\t{\r\n\t\t\t\tvar edgeCount = this.graph.model.getEdgeCount(vertex);\r\n\t\t\t\t\r\n\t\t\t\tif (edgeCount > 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tfor (var i = 0; i < edgeCount; i++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar e = this.graph.model.getEdgeAt(vertex, i);\r\n\t\t\t\t\t\tvar isSource = this.graph.model.getTerminal(e, true) == vertex;\r\n\t\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (!directed || isSource)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar next = this.graph.view.getVisibleTerminal(e, !isSource);\r\n\t\t\t\t\t\t\tthis.traverse(next, directed, func, e, visited);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isAncestor\r\n * \r\n * Returns true if the given parent is an ancestor of the given child.\r\n *\r\n * Parameters:\r\n * \r\n * parent - <mxCell> that specifies the parent.\r\n * child - <mxCell> that specifies the child.\r\n * traverseAncestors - boolean whether to \r\n */\r\nmxGraphLayout.prototype.isAncestor = function(parent, child, traverseAncestors)\r\n{\r\n\tif (!traverseAncestors)\r\n\t{\r\n\t\treturn (this.graph.model.getParent(child) == parent);\r\n\t}\t\r\n\t\r\n\tif (child == parent)\r\n\t{\r\n\t\treturn false;\r\n\t}\r\n\r\n\twhile (child != null && child != parent)\r\n\t{\r\n\t\tchild = this.graph.model.getParent(child);\r\n\t}\r\n\t\r\n\treturn child == parent;\r\n};\r\n\r\n/**\r\n * Function: isVertexMovable\r\n * \r\n * Returns a boolean indicating if the given <mxCell> is movable or\r\n * bendable by the algorithm. This implementation returns true if the given\r\n * cell is movable in the graph.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose movable state should be returned.\r\n */\r\nmxGraphLayout.prototype.isVertexMovable = function(cell)\r\n{\r\n\treturn this.graph.isCellMovable(cell);\r\n};\r\n\r\n/**\r\n * Function: isVertexIgnored\r\n * \r\n * Returns a boolean indicating if the given <mxCell> should be ignored by\r\n * the algorithm. This implementation returns false for all vertices.\r\n * \r\n * Parameters:\r\n * \r\n * vertex - <mxCell> whose ignored state should be returned.\r\n */\r\nmxGraphLayout.prototype.isVertexIgnored = function(vertex)\r\n{\r\n\treturn !this.graph.getModel().isVertex(vertex) ||\r\n\t\t!this.graph.isCellVisible(vertex);\r\n};\r\n\r\n/**\r\n * Function: isEdgeIgnored\r\n * \r\n * Returns a boolean indicating if the given <mxCell> should be ignored by\r\n * the algorithm. This implementation returns false for all vertices.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose ignored state should be returned.\r\n */\r\nmxGraphLayout.prototype.isEdgeIgnored = function(edge)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\t\r\n\treturn !model.isEdge(edge) ||\r\n\t\t!this.graph.isCellVisible(edge) ||\r\n\t\tmodel.getTerminal(edge, true) == null ||\r\n\t\tmodel.getTerminal(edge, false) == null;\r\n};\r\n\r\n/**\r\n * Function: setEdgeStyleEnabled\r\n * \r\n * Disables or enables the edge style of the given edge.\r\n */\r\nmxGraphLayout.prototype.setEdgeStyleEnabled = function(edge, value)\r\n{\r\n\tthis.graph.setCellStyles(mxConstants.STYLE_NOEDGESTYLE,\r\n\t\t\t(value) ? '0' : '1', [edge]);\r\n};\r\n\r\n/**\r\n * Function: setOrthogonalEdge\r\n * \r\n * Disables or enables orthogonal end segments of the given edge.\r\n */\r\nmxGraphLayout.prototype.setOrthogonalEdge = function(edge, value)\r\n{\r\n\tthis.graph.setCellStyles(mxConstants.STYLE_ORTHOGONAL,\r\n\t\t\t(value) ? '1' : '0', [edge]);\r\n};\r\n\r\n/**\r\n * Function: getParentOffset\r\n * \r\n * Determines the offset of the given parent to the parent\r\n * of the layout\r\n */\r\nmxGraphLayout.prototype.getParentOffset = function(parent)\r\n{\r\n\tvar result = new mxPoint();\r\n\r\n\tif (parent != null && parent != this.parent)\r\n\t{\r\n\t\tvar model = this.graph.getModel();\r\n\r\n\t\tif (model.isAncestor(this.parent, parent))\r\n\t\t{\r\n\t\t\tvar parentGeo = model.getGeometry(parent);\r\n\r\n\t\t\twhile (parent != this.parent)\r\n\t\t\t{\r\n\t\t\t\tresult.x = result.x + parentGeo.x;\r\n\t\t\t\tresult.y = result.y + parentGeo.y;\r\n\r\n\t\t\t\tparent = model.getParent(parent);;\r\n\t\t\t\tparentGeo = model.getGeometry(parent);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: setEdgePoints\r\n * \r\n * Replaces the array of mxPoints in the geometry of the given edge\r\n * with the given array of mxPoints.\r\n */\r\nmxGraphLayout.prototype.setEdgePoints = function(edge, points)\r\n{\r\n\tif (edge != null)\r\n\t{\r\n\t\tvar model = this.graph.model;\r\n\t\tvar geometry = model.getGeometry(edge);\r\n\r\n\t\tif (geometry == null)\r\n\t\t{\r\n\t\t\tgeometry = new mxGeometry();\r\n\t\t\tgeometry.setRelative(true);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tgeometry = geometry.clone();\r\n\t\t}\r\n\r\n\t\tif (this.parent != null && points != null)\r\n\t\t{\r\n\t\t\tvar parent = model.getParent(edge);\r\n\r\n\t\t\tvar parentOffset = this.getParentOffset(parent);\r\n\r\n\t\t\tfor (var i = 0; i < points.length; i++)\r\n\t\t\t{\r\n\t\t\t\tpoints[i].x = points[i].x - parentOffset.x;\r\n\t\t\t\tpoints[i].y = points[i].y - parentOffset.y;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tgeometry.points = points;\r\n\t\tmodel.setGeometry(edge, geometry);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setVertexLocation\r\n * \r\n * Sets the new position of the given cell taking into account the size of\r\n * the bounding box if <useBoundingBox> is true. The change is only carried\r\n * out if the new location is not equal to the existing location, otherwise\r\n * the geometry is not replaced with an updated instance. The new or old\r\n * bounds are returned (including overlapping labels).\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose geometry is to be set.\r\n * x - Integer that defines the x-coordinate of the new location.\r\n * y - Integer that defines the y-coordinate of the new location.\r\n */\r\nmxGraphLayout.prototype.setVertexLocation = function(cell, x, y)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\tvar geometry = model.getGeometry(cell);\r\n\tvar result = null;\r\n\t\r\n\tif (geometry != null)\r\n\t{\r\n\t\tresult = new mxRectangle(x, y, geometry.width, geometry.height);\r\n\t\t\r\n\t\t// Checks for oversize labels and shifts the result\r\n\t\t// TODO: Use mxUtils.getStringSize for label bounds\r\n\t\tif (this.useBoundingBox)\r\n\t\t{\r\n\t\t\tvar state = this.graph.getView().getState(cell);\r\n\t\t\t\r\n\t\t\tif (state != null && state.text != null && state.text.boundingBox != null)\r\n\t\t\t{\r\n\t\t\t\tvar scale = this.graph.getView().scale;\r\n\t\t\t\tvar box = state.text.boundingBox;\r\n\t\t\t\t\r\n\t\t\t\tif (state.text.boundingBox.x < state.x)\r\n\t\t\t\t{\r\n\t\t\t\t\tx += (state.x - box.x) / scale;\r\n\t\t\t\t\tresult.width = box.width;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (state.text.boundingBox.y < state.y)\r\n\t\t\t\t{\r\n\t\t\t\t\ty += (state.y - box.y) / scale;\r\n\t\t\t\t\tresult.height = box.height;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (this.parent != null)\r\n\t\t{\r\n\t\t\tvar parent = model.getParent(cell);\r\n\r\n\t\t\tif (parent != null && parent != this.parent)\r\n\t\t\t{\r\n\t\t\t\tvar parentOffset = this.getParentOffset(parent);\r\n\r\n\t\t\t\tx = x - parentOffset.x;\r\n\t\t\t\ty = y - parentOffset.y;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (geometry.x != x || geometry.y != y)\r\n\t\t{\r\n\t\t\tgeometry = geometry.clone();\r\n\t\t\tgeometry.x = x;\r\n\t\t\tgeometry.y = y;\r\n\t\t\t\r\n\t\t\tmodel.setGeometry(cell, geometry);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: getVertexBounds\r\n * \r\n * Returns an <mxRectangle> that defines the bounds of the given cell or\r\n * the bounding box if <useBoundingBox> is true.\r\n */\r\nmxGraphLayout.prototype.getVertexBounds = function(cell)\r\n{\r\n\tvar geo = this.graph.getModel().getGeometry(cell);\r\n\r\n\t// Checks for oversize label bounding box and corrects\r\n\t// the return value accordingly\r\n\t// TODO: Use mxUtils.getStringSize for label bounds\r\n\tif (this.useBoundingBox)\r\n\t{\r\n\t\tvar state = this.graph.getView().getState(cell);\r\n\r\n\t\tif (state != null && state.text != null && state.text.boundingBox != null)\r\n\t\t{\r\n\t\t\tvar scale = this.graph.getView().scale;\r\n\t\t\tvar tmp = state.text.boundingBox;\r\n\r\n\t\t\tvar dx0 = Math.max(state.x - tmp.x, 0) / scale;\r\n\t\t\tvar dy0 = Math.max(state.y - tmp.y, 0) / scale;\r\n\t\t\tvar dx1 = Math.max((tmp.x + tmp.width) - (state.x + state.width), 0) / scale;\r\n  \t\t\tvar dy1 = Math.max((tmp.y + tmp.height) - (state.y + state.height), 0) / scale;\r\n\r\n\t\t\tgeo = new mxRectangle(geo.x - dx0, geo.y - dy0, geo.width + dx0 + dx1, geo.height + dy0 + dy1);\r\n\t\t}\r\n\t}\r\n\r\n\tif (this.parent != null)\r\n\t{\r\n\t\tvar parent = this.graph.getModel().getParent(cell);\r\n\t\tgeo = geo.clone();\r\n\r\n\t\tif (parent != null && parent != this.parent)\r\n\t\t{\r\n\t\t\tvar parentOffset = this.getParentOffset(parent);\r\n\t\t\tgeo.x = geo.x + parentOffset.x;\r\n\t\t\tgeo.y = geo.y + parentOffset.y;\r\n\t\t}\r\n\t}\r\n\r\n\treturn new mxRectangle(geo.x, geo.y, geo.width, geo.height);\r\n};\r\n\r\n/**\r\n * Function: arrangeGroups\r\n * \r\n * Shortcut to <mxGraph.updateGroupBounds> with moveGroup set to true.\r\n */\r\nmxGraphLayout.prototype.arrangeGroups = function(cells, border, topBorder, rightBorder, bottomBorder, leftBorder)\r\n{\r\n\treturn this.graph.updateGroupBounds(cells, border, true, topBorder, rightBorder, bottomBorder, leftBorder);\r\n};\r\n\r\n/**\r\n * Class: WeightedCellSorter\r\n * \r\n * A utility class used to track cells whilst sorting occurs on the weighted\r\n * sum of their connected edges. Does not violate (x.compareTo(y)==0) ==\r\n * (x.equals(y))\r\n *\r\n * Constructor: WeightedCellSorter\r\n * \r\n * Constructs a new weighted cell sorted for the given cell and weight.\r\n */\r\nfunction WeightedCellSorter(cell, weightedValue)\r\n{\r\n\tthis.cell = cell;\r\n\tthis.weightedValue = weightedValue;\r\n};\r\n\r\n/**\r\n * Variable: weightedValue\r\n * \r\n * The weighted value of the cell stored.\r\n */\r\nWeightedCellSorter.prototype.weightedValue = 0;\r\n\r\n/**\r\n * Variable: nudge\r\n * \r\n * Whether or not to flip equal weight values.\r\n */\r\nWeightedCellSorter.prototype.nudge = false;\r\n\r\n/**\r\n * Variable: visited\r\n * \r\n * Whether or not this cell has been visited in the current assignment.\r\n */\r\nWeightedCellSorter.prototype.visited = false;\r\n\r\n/**\r\n * Variable: rankIndex\r\n * \r\n * The index this cell is in the model rank.\r\n */\r\nWeightedCellSorter.prototype.rankIndex = null;\r\n\r\n/**\r\n * Variable: cell\r\n * \r\n * The cell whose median value is being calculated.\r\n */\r\nWeightedCellSorter.prototype.cell = null;\r\n\r\n/**\r\n * Function: compare\r\n * \r\n * Compares two WeightedCellSorters.\r\n */\r\nWeightedCellSorter.prototype.compare = function(a, b)\r\n{\r\n\tif (a != null && b != null)\r\n\t{\r\n\t\tif (b.weightedValue > a.weightedValue)\r\n\t\t{\r\n\t\t\treturn -1;\r\n\t\t}\r\n\t\telse if (b.weightedValue < a.weightedValue)\r\n\t\t{\r\n\t\t\treturn 1;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tif (b.nudge)\r\n\t\t\t{\r\n\t\t\t\treturn -1;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\treturn 1;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\treturn 0;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxStackLayout\r\n * \r\n * Extends <mxGraphLayout> to create a horizontal or vertical stack of the\r\n * child vertices. The children do not need to be connected for this layout\r\n * to work.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * var layout = new mxStackLayout(graph, true);\r\n * layout.execute(graph.getDefaultParent());\r\n * (end)\r\n * \r\n * Constructor: mxStackLayout\r\n * \r\n * Constructs a new stack layout layout for the specified graph,\r\n * spacing, orientation and offset.\r\n */\r\nfunction mxStackLayout(graph, horizontal, spacing, x0, y0, border)\r\n{\r\n\tmxGraphLayout.call(this, graph);\r\n\tthis.horizontal = (horizontal != null) ? horizontal : true;\r\n\tthis.spacing = (spacing != null) ? spacing : 0;\r\n\tthis.x0 = (x0 != null) ? x0 : 0;\r\n\tthis.y0 = (y0 != null) ? y0 : 0;\r\n\tthis.border = (border != null) ? border : 0;\r\n};\r\n\r\n/**\r\n * Extends mxGraphLayout.\r\n */\r\nmxStackLayout.prototype = new mxGraphLayout();\r\nmxStackLayout.prototype.constructor = mxStackLayout;\r\n\r\n/**\r\n * Variable: horizontal\r\n *\r\n * Specifies the orientation of the layout. Default is true.\r\n */\r\nmxStackLayout.prototype.horizontal = null;\r\n\r\n/**\r\n * Variable: spacing\r\n *\r\n * Specifies the spacing between the cells. Default is 0.\r\n */\r\nmxStackLayout.prototype.spacing = null;\r\n\r\n/**\r\n * Variable: x0\r\n *\r\n * Specifies the horizontal origin of the layout. Default is 0.\r\n */\r\nmxStackLayout.prototype.x0 = null;\r\n\r\n/**\r\n * Variable: y0\r\n *\r\n * Specifies the vertical origin of the layout. Default is 0.\r\n */\r\nmxStackLayout.prototype.y0 = null;\r\n\r\n/**\r\n * Variable: border\r\n *\r\n * Border to be added if fill is true. Default is 0.\r\n */\r\nmxStackLayout.prototype.border = 0;\r\n\r\n/**\r\n * Variable: marginTop\r\n * \r\n * Top margin for the child area. Default is 0.\r\n */\r\nmxStackLayout.prototype.marginTop = 0;\r\n\r\n/**\r\n * Variable: marginLeft\r\n * \r\n * Top margin for the child area. Default is 0.\r\n */\r\nmxStackLayout.prototype.marginLeft = 0;\r\n\r\n/**\r\n * Variable: marginRight\r\n * \r\n * Top margin for the child area. Default is 0.\r\n */\r\nmxStackLayout.prototype.marginRight = 0;\r\n\r\n/**\r\n * Variable: marginBottom\r\n * \r\n * Top margin for the child area. Default is 0.\r\n */\r\nmxStackLayout.prototype.marginBottom = 0;\r\n\r\n/**\r\n * Variable: keepFirstLocation\r\n * \r\n * Boolean indicating if the location of the first cell should be\r\n * kept, that is, it will not be moved to x0 or y0.\r\n */\r\nmxStackLayout.prototype.keepFirstLocation = false;\r\n\r\n/**\r\n * Variable: fill\r\n * \r\n * Boolean indicating if dimension should be changed to fill out the parent\r\n * cell. Default is false.\r\n */\r\nmxStackLayout.prototype.fill = false;\r\n\t\r\n/**\r\n * Variable: resizeParent\r\n * \r\n * If the parent should be resized to match the width/height of the\r\n * stack. Default is false.\r\n */\r\nmxStackLayout.prototype.resizeParent = false;\r\n\r\n/**\r\n * Variable: resizeParentMax\r\n * \r\n * Use maximum of existing value and new value for resize of parent.\r\n * Default is false.\r\n */\r\nmxStackLayout.prototype.resizeParentMax = false;\r\n\r\n/**\r\n * Variable: resizeLast\r\n * \r\n * If the last element should be resized to fill out the parent. Default is\r\n * false. If <resizeParent> is true then this is ignored.\r\n */\r\nmxStackLayout.prototype.resizeLast = false;\r\n\r\n/**\r\n * Variable: wrap\r\n * \r\n * Value at which a new column or row should be created. Default is null.\r\n */\r\nmxStackLayout.prototype.wrap = null;\r\n\r\n/**\r\n * Variable: borderCollapse\r\n * \r\n * If the strokeWidth should be ignored. Default is true.\r\n */\r\nmxStackLayout.prototype.borderCollapse = true;\r\n\r\n/**\r\n * Variable: allowGaps\r\n * \r\n * If gaps should be allowed in the stack. Default is false.\r\n */\r\nmxStackLayout.prototype.allowGaps = false;\r\n\r\n/**\r\n * Variable: gridSize\r\n * \r\n * Grid size for alignment of position and size. Default is 0.\r\n */\r\nmxStackLayout.prototype.gridSize = 0;\r\n\r\n/**\r\n * Function: isHorizontal\r\n * \r\n * Returns <horizontal>.\r\n */\r\nmxStackLayout.prototype.isHorizontal = function()\r\n{\r\n\treturn this.horizontal;\r\n};\r\n\r\n/**\r\n * Function: moveCell\r\n * \r\n * Implements <mxGraphLayout.moveCell>.\r\n */\r\nmxStackLayout.prototype.moveCell = function(cell, x, y)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\tvar parent = model.getParent(cell);\r\n\tvar horizontal = this.isHorizontal();\r\n\t\r\n\tif (cell != null && parent != null)\r\n\t{\r\n\t\tvar i = 0;\r\n\t\tvar last = 0;\r\n\t\tvar childCount = model.getChildCount(parent);\r\n\t\tvar value = (horizontal) ? x : y;\r\n\t\tvar pstate = this.graph.getView().getState(parent);\r\n\r\n\t\tif (pstate != null)\r\n\t\t{\r\n\t\t\tvalue -= (horizontal) ? pstate.x : pstate.y;\r\n\t\t}\r\n\t\t\r\n\t\tvalue /= this.graph.view.scale;\r\n\t\t\r\n\t\tfor (i = 0; i < childCount; i++)\r\n\t\t{\r\n\t\t\tvar child = model.getChildAt(parent, i);\r\n\t\t\t\r\n\t\t\tif (child != cell)\r\n\t\t\t{\r\n\t\t\t\tvar bounds = model.getGeometry(child);\r\n\t\t\t\t\r\n\t\t\t\tif (bounds != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar tmp = (horizontal) ?\r\n\t\t\t\t\t\tbounds.x + bounds.width / 2 :\r\n\t\t\t\t\t\tbounds.y + bounds.height / 2;\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (last <= value && tmp > value)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tlast = tmp;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Changes child order in parent\r\n\t\tvar idx = parent.getIndex(cell);\r\n\t\tidx = Math.max(0, i - ((i > idx) ? 1 : 0));\r\n\r\n\t\tmodel.add(parent, cell, idx);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getParentSize\r\n * \r\n * Returns the size for the parent container or the size of the graph\r\n * container if the parent is a layer or the root of the model.\r\n */\r\nmxStackLayout.prototype.getParentSize = function(parent)\r\n{\r\n\tvar model = this.graph.getModel();\t\t\t\r\n\tvar pgeo = model.getGeometry(parent);\r\n\t\r\n\t// Handles special case where the parent is either a layer with no\r\n\t// geometry or the current root of the view in which case the size\r\n\t// of the graph's container will be used.\r\n\tif (this.graph.container != null && ((pgeo == null &&\r\n\t\tmodel.isLayer(parent)) || parent == this.graph.getView().currentRoot))\r\n\t{\r\n\t\tvar width = this.graph.container.offsetWidth - 1;\r\n\t\tvar height = this.graph.container.offsetHeight - 1;\r\n\t\tpgeo = new mxRectangle(0, 0, width, height);\r\n\t}\r\n\t\r\n\treturn pgeo;\r\n};\r\n\r\n/**\r\n * Function: getLayoutCells\r\n * \r\n * Returns the cells to be layouted.\r\n */\r\nmxStackLayout.prototype.getLayoutCells = function(parent)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\tvar childCount = model.getChildCount(parent);\r\n\tvar cells = [];\r\n\t\r\n\tfor (var i = 0; i < childCount; i++)\r\n\t{\r\n\t\tvar child = model.getChildAt(parent, i);\r\n\t\t\r\n\t\tif (!this.isVertexIgnored(child) && this.isVertexMovable(child))\r\n\t\t{\r\n\t\t\tcells.push(child);\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (this.allowGaps)\r\n\t{\r\n\t\tcells.sort(mxUtils.bind(this, function(c1, c2)\r\n\t\t{\r\n\t\t\tvar geo1 = this.graph.getCellGeometry(c1);\r\n\t\t\tvar geo2 = this.graph.getCellGeometry(c2);\r\n\t\t\t\r\n\t\t\treturn (geo1.y == geo2.y) ? 0 : ((geo1.y > geo2.y > 0) ? 1 : -1);\r\n\t\t}));\r\n\t}\r\n\t\r\n\treturn cells;\r\n};\r\n\r\n/**\r\n * Function: snap\r\n * \r\n * Snaps the given value to the grid size.\r\n */\r\nmxStackLayout.prototype.snap = function(value)\r\n{\r\n\tif (this.gridSize != null && this.gridSize > 0)\r\n\t{\r\n\t\tvalue = Math.max(value, this.gridSize);\r\n\t\t\r\n\t\tif (value / this.gridSize > 1)\r\n\t\t{\r\n\t\t\tvar mod = value % this.gridSize;\r\n\t\t\tvalue += mod > this.gridSize / 2 ? (this.gridSize - mod) : -mod;\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn value;\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Implements <mxGraphLayout.execute>.\r\n * \r\n * Only children where <isVertexIgnored> returns false are taken into\r\n * account.\r\n */\r\nmxStackLayout.prototype.execute = function(parent)\r\n{\r\n\tif (parent != null)\r\n\t{\r\n\t\tvar pgeo = this.getParentSize(parent);\r\n\t\tvar horizontal = this.isHorizontal();\r\n\t\tvar model = this.graph.getModel();\t\r\n\t\tvar fillValue = null;\r\n\t\t\r\n\t\tif (pgeo != null)\r\n\t\t{\r\n\t\t\tfillValue = (horizontal) ? pgeo.height - this.marginTop - this.marginBottom :\r\n\t\t\t\tpgeo.width - this.marginLeft - this.marginRight;\r\n\t\t}\r\n\t\t\r\n\t\tfillValue -= 2 * this.border;\r\n\t\tvar x0 = this.x0 + this.border + this.marginLeft;\r\n\t\tvar y0 = this.y0 + this.border + this.marginTop;\r\n\t\t\r\n\t\t// Handles swimlane start size\r\n\t\tif (this.graph.isSwimlane(parent))\r\n\t\t{\r\n\t\t\t// Uses computed style to get latest \r\n\t\t\tvar style = this.graph.getCellStyle(parent);\r\n\t\t\tvar start = mxUtils.getNumber(style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE);\r\n\t\t\tvar horz = mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true) == 1;\r\n\r\n\t\t\tif (pgeo != null)\r\n\t\t\t{\r\n\t\t\t\tif (horz)\r\n\t\t\t\t{\r\n\t\t\t\t\tstart = Math.min(start, pgeo.height);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tstart = Math.min(start, pgeo.width);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (horizontal == horz)\r\n\t\t\t{\r\n\t\t\t\tfillValue -= start;\r\n\t\t\t}\r\n\r\n\t\t\tif (horz)\r\n\t\t\t{\r\n\t\t\t\ty0 += start;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tx0 += start;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tmodel.beginUpdate();\r\n\t\ttry\r\n\t\t{\r\n\t\t\tvar tmp = 0;\r\n\t\t\tvar last = null;\r\n\t\t\tvar lastValue = 0;\r\n\t\t\tvar lastChild = null;\r\n\t\t\tvar cells = this.getLayoutCells(parent);\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar child = cells[i];\r\n\t\t\t\tvar geo = model.getGeometry(child);\r\n\t\t\t\t\r\n\t\t\t\tif (geo != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tgeo = geo.clone();\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (this.wrap != null && last != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif ((horizontal && last.x + last.width +\r\n\t\t\t\t\t\t\tgeo.width + 2 * this.spacing > this.wrap) ||\r\n\t\t\t\t\t\t\t(!horizontal && last.y + last.height +\r\n\t\t\t\t\t\t\tgeo.height + 2 * this.spacing > this.wrap))\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tlast = null;\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tif (horizontal)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\ty0 += tmp + this.spacing;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tx0 += tmp + this.spacing;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\ttmp = 0;\r\n\t\t\t\t\t\t}\t\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\ttmp = Math.max(tmp, (horizontal) ? geo.height : geo.width);\r\n\t\t\t\t\tvar sw = 0;\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (!this.borderCollapse)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar childStyle = this.graph.getCellStyle(child);\r\n\t\t\t\t\t\tsw = mxUtils.getNumber(childStyle, mxConstants.STYLE_STROKEWIDTH, 1);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (last != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar temp = lastValue + this.spacing + Math.floor(sw / 2);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (horizontal)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tgeo.x = this.snap(((this.allowGaps) ? Math.max(temp, geo.x) :\r\n\t\t\t\t\t\t\t\ttemp) - this.marginLeft) + this.marginLeft;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tgeo.y = this.snap(((this.allowGaps) ? Math.max(temp, geo.y) :\r\n\t\t\t\t\t\t\t\ttemp) - this.marginTop) + this.marginTop;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (!this.keepFirstLocation)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (horizontal)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tgeo.x = (this.allowGaps && geo.x > x0) ? Math.max(this.snap(geo.x -\r\n\t\t\t\t\t\t\t\tthis.marginLeft) + this.marginLeft, x0) : x0;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tgeo.y = (this.allowGaps && geo.y > y0) ? Math.max(this.snap(geo.y -\r\n\t\t\t\t\t\t\t\tthis.marginTop) + this.marginTop, y0) : y0;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (horizontal)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tgeo.y = y0;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tgeo.x = x0;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (this.fill && fillValue != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (horizontal)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tgeo.height = fillValue;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tgeo.width = fillValue;\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (horizontal)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tgeo.width = this.snap(geo.width);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tgeo.height = this.snap(geo.height);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tthis.setChildGeometry(child, geo);\r\n\t\t\t\t\tlastChild = child;\r\n\t\t\t\t\tlast = geo;\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (horizontal)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tlastValue = last.x + last.width + Math.floor(sw / 2);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tlastValue = last.y + last.height + Math.floor(sw / 2);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (this.resizeParent && pgeo != null && last != null && !this.graph.isCellCollapsed(parent))\r\n\t\t\t{\r\n\t\t\t\tthis.updateParentGeometry(parent, pgeo, last);\r\n\t\t\t}\r\n\t\t\telse if (this.resizeLast && pgeo != null && last != null && lastChild != null)\r\n\t\t\t{\r\n\t\t\t\tif (horizontal)\r\n\t\t\t\t{\r\n\t\t\t\t\tlast.width = pgeo.width - last.x - this.spacing - this.marginRight - this.marginLeft;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tlast.height = pgeo.height - last.y - this.spacing - this.marginBottom;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.setChildGeometry(lastChild, last);\r\n\t\t\t}\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tmodel.endUpdate();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Implements <mxGraphLayout.execute>.\r\n * \r\n * Only children where <isVertexIgnored> returns false are taken into\r\n * account.\r\n */\r\nmxStackLayout.prototype.setChildGeometry = function(child, geo)\r\n{\r\n\tvar geo2 = this.graph.getCellGeometry(child);\r\n\t\r\n\tif (geo2 == null || geo.x != geo2.x || geo.y != geo2.y ||\r\n\t\tgeo.width != geo2.width || geo.height != geo2.height)\r\n\t{\r\n\t\tthis.graph.getModel().setGeometry(child, geo);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Implements <mxGraphLayout.execute>.\r\n * \r\n * Only children where <isVertexIgnored> returns false are taken into\r\n * account.\r\n */\r\nmxStackLayout.prototype.updateParentGeometry = function(parent, pgeo, last)\r\n{\r\n\tvar horizontal = this.isHorizontal();\r\n\tvar model = this.graph.getModel();\t\r\n\r\n\tvar pgeo2 = pgeo.clone();\r\n\t\r\n\tif (horizontal)\r\n\t{\r\n\t\tvar tmp = last.x + last.width + this.marginRight + this.border;\r\n\t\t\r\n\t\tif (this.resizeParentMax)\r\n\t\t{\r\n\t\t\tpgeo2.width = Math.max(pgeo2.width, tmp);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tpgeo2.width = tmp;\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar tmp = last.y + last.height + this.marginBottom + this.border;\r\n\t\t\r\n\t\tif (this.resizeParentMax)\r\n\t\t{\r\n\t\t\tpgeo2.height = Math.max(pgeo2.height, tmp);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tpgeo2.height = tmp;\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (pgeo.x != pgeo2.x || pgeo.y != pgeo2.y ||\r\n\t\tpgeo.width != pgeo2.width || pgeo.height != pgeo2.height)\r\n\t{\r\n\t\tmodel.setGeometry(parent, pgeo2);\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxPartitionLayout\r\n * \r\n * Extends <mxGraphLayout> for partitioning the parent cell vertically or\r\n * horizontally by filling the complete area with the child cells. A horizontal\r\n * layout partitions the height of the given parent whereas a a non-horizontal\r\n * layout partitions the width. If the parent is a layer (that is, a child of\r\n * the root node), then the current graph size is partitioned. The children do\r\n * not need to be connected for this layout to work.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * var layout = new mxPartitionLayout(graph, true, 10, 20);\r\n * layout.execute(graph.getDefaultParent());\r\n * (end)\r\n * \r\n * Constructor: mxPartitionLayout\r\n * \r\n * Constructs a new stack layout layout for the specified graph,\r\n * spacing, orientation and offset.\r\n */\r\nfunction mxPartitionLayout(graph, horizontal, spacing, border)\r\n{\r\n\tmxGraphLayout.call(this, graph);\r\n\tthis.horizontal = (horizontal != null) ? horizontal : true;\r\n\tthis.spacing = spacing || 0;\r\n\tthis.border = border || 0;\r\n};\r\n\r\n/**\r\n * Extends mxGraphLayout.\r\n */\r\nmxPartitionLayout.prototype = new mxGraphLayout();\r\nmxPartitionLayout.prototype.constructor = mxPartitionLayout;\r\n\r\n/**\r\n * Variable: horizontal\r\n * \r\n * Boolean indicating the direction in which the space is partitioned.\r\n * Default is true.\r\n */\r\nmxPartitionLayout.prototype.horizontal = null;\r\n\r\n/**\r\n * Variable: spacing\r\n * \r\n * Integer that specifies the absolute spacing in pixels between the\r\n * children. Default is 0.\r\n */\r\nmxPartitionLayout.prototype.spacing = null;\r\n\r\n/**\r\n * Variable: border\r\n * \r\n * Integer that specifies the absolute inset in pixels for the parent that\r\n * contains the children. Default is 0.\r\n */\r\nmxPartitionLayout.prototype.border = null;\r\n\r\n/**\r\n * Variable: resizeVertices\r\n * \r\n * Boolean that specifies if vertices should be resized. Default is true.\r\n */\r\nmxPartitionLayout.prototype.resizeVertices = true;\r\n\r\n/**\r\n * Function: isHorizontal\r\n * \r\n * Returns <horizontal>.\r\n */\r\nmxPartitionLayout.prototype.isHorizontal = function()\r\n{\r\n\treturn this.horizontal;\r\n};\r\n\r\n/**\r\n * Function: moveCell\r\n * \r\n * Implements <mxGraphLayout.moveCell>.\r\n */\r\nmxPartitionLayout.prototype.moveCell = function(cell, x, y)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\tvar parent = model.getParent(cell);\r\n\t\r\n\tif (cell != null &&\r\n\t\tparent != null)\r\n\t{\r\n\t\tvar i = 0;\r\n\t\tvar last = 0;\r\n\t\tvar childCount = model.getChildCount(parent);\r\n\t\t\r\n\t\t// Finds index of the closest swimlane\r\n\t\t// TODO: Take into account the orientation\r\n\t\tfor (i = 0; i < childCount; i++)\r\n\t\t{\r\n\t\t\tvar child = model.getChildAt(parent, i);\r\n\t\t\tvar bounds = this.getVertexBounds(child);\r\n\t\t\t\r\n\t\t\tif (bounds != null)\r\n\t\t\t{\r\n\t\t\t\tvar tmp = bounds.x + bounds.width / 2;\r\n\t\t\t\t\r\n\t\t\t\tif (last < x && tmp > x)\r\n\t\t\t\t{\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tlast = tmp;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Changes child order in parent\r\n\t\tvar idx = parent.getIndex(cell);\r\n\t\tidx = Math.max(0, i - ((i > idx) ? 1 : 0));\r\n\t\t\r\n\t\tmodel.add(parent, cell, idx);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Implements <mxGraphLayout.execute>. All children where <isVertexIgnored>\r\n * returns false and <isVertexMovable> returns true are modified.\r\n */\r\nmxPartitionLayout.prototype.execute = function(parent)\r\n{\r\n\tvar horizontal = this.isHorizontal();\r\n\tvar model = this.graph.getModel();\r\n\tvar pgeo = model.getGeometry(parent);\r\n\t\r\n\t// Handles special case where the parent is either a layer with no\r\n\t// geometry or the current root of the view in which case the size\r\n\t// of the graph's container will be used.\r\n\tif (this.graph.container != null &&\r\n\t\t((pgeo == null &&\r\n\t\tmodel.isLayer(parent)) ||\r\n\t\tparent == this.graph.getView().currentRoot))\r\n\t{\r\n\t\tvar width = this.graph.container.offsetWidth - 1;\r\n\t\tvar height = this.graph.container.offsetHeight - 1;\r\n\t\tpgeo = new mxRectangle(0, 0, width, height);\r\n\t}\r\n\r\n\tif (pgeo != null)\r\n\t{\r\n\t\tvar children = [];\r\n\t\tvar childCount = model.getChildCount(parent);\r\n\t\t\r\n\t\tfor (var i = 0; i < childCount; i++)\r\n\t\t{\r\n\t\t\tvar child = model.getChildAt(parent, i);\r\n\t\t\t\r\n\t\t\tif (!this.isVertexIgnored(child) &&\r\n\t\t\t\tthis.isVertexMovable(child))\r\n\t\t\t{\r\n\t\t\t\tchildren.push(child);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tvar n = children.length;\r\n\r\n\t\tif (n > 0)\r\n\t\t{\r\n\t\t\tvar x0 = this.border;\r\n\t\t\tvar y0 = this.border;\r\n\t\t\tvar other = (horizontal) ? pgeo.height : pgeo.width;\r\n\t\t\tother -= 2 * this.border;\r\n\r\n\t\t\tvar size = (this.graph.isSwimlane(parent)) ?\r\n\t\t\t\tthis.graph.getStartSize(parent) :\r\n\t\t\t\tnew mxRectangle();\r\n\r\n\t\t\tother -= (horizontal) ? size.height : size.width;\r\n\t\t\tx0 = x0 + size.width;\r\n\t\t\ty0 = y0 + size.height;\r\n\r\n\t\t\tvar tmp = this.border + (n - 1) * this.spacing;\r\n\t\t\tvar value = (horizontal) ?\r\n\t\t\t\t((pgeo.width - x0 - tmp) / n) :\r\n\t\t\t\t((pgeo.height - y0 - tmp) / n);\r\n\t\t\t\r\n\t\t\t// Avoids negative values, that is values where the sum of the\r\n\t\t\t// spacing plus the border is larger then the available space\r\n\t\t\tif (value > 0)\r\n\t\t\t{\r\n\t\t\t\tmodel.beginUpdate();\r\n\t\t\t\ttry\r\n\t\t\t\t{\r\n\t\t\t\t\tfor (var i = 0; i < n; i++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar child = children[i];\r\n\t\t\t\t\t\tvar geo = model.getGeometry(child);\r\n\t\t\t\t\t\r\n\t\t\t\t\t\tif (geo != null)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tgeo = geo.clone();\r\n\t\t\t\t\t\t\tgeo.x = x0;\r\n\t\t\t\t\t\t\tgeo.y = y0;\r\n\r\n\t\t\t\t\t\t\tif (horizontal)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tif (this.resizeVertices)\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\tgeo.width = value;\r\n\t\t\t\t\t\t\t\t\tgeo.height = other;\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\tx0 += value + this.spacing;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tif (this.resizeVertices)\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\tgeo.height = value;\r\n\t\t\t\t\t\t\t\t\tgeo.width = other;\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\ty0 += value + this.spacing;\r\n\t\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t\tmodel.setGeometry(child, geo);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tfinally\r\n\t\t\t\t{\r\n\t\t\t\t\tmodel.endUpdate();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2018, JGraph Ltd\r\n * Copyright (c) 2006-2018, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxCompactTreeLayout\r\n * \r\n * Extends <mxGraphLayout> to implement a compact tree (Moen) algorithm. This\r\n * layout is suitable for graphs that have no cycles (trees). Vertices that are\r\n * not connected to the tree will be ignored by this layout.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * var layout = new mxCompactTreeLayout(graph);\r\n * layout.execute(graph.getDefaultParent());\r\n * (end)\r\n * \r\n * Constructor: mxCompactTreeLayout\r\n * \r\n * Constructs a new compact tree layout for the specified graph\r\n * and orientation.\r\n */\r\nfunction mxCompactTreeLayout(graph, horizontal, invert)\r\n{\r\n\tmxGraphLayout.call(this, graph);\r\n\tthis.horizontal = (horizontal != null) ? horizontal : true;\r\n\tthis.invert = (invert != null) ? invert : false;\r\n};\r\n\r\n/**\r\n * Extends mxGraphLayout.\r\n */\r\nmxCompactTreeLayout.prototype = new mxGraphLayout();\r\nmxCompactTreeLayout.prototype.constructor = mxCompactTreeLayout;\r\n\r\n/**\r\n * Variable: horizontal\r\n *\r\n * Specifies the orientation of the layout. Default is true.\r\n */\r\nmxCompactTreeLayout.prototype.horizontal = null;\t \r\n\r\n/**\r\n * Variable: invert\r\n *\r\n * Specifies if edge directions should be inverted. Default is false.\r\n */\r\nmxCompactTreeLayout.prototype.invert = null;\t \r\n\r\n/**\r\n * Variable: resizeParent\r\n * \r\n * If the parents should be resized to match the width/height of the\r\n * children. Default is true.\r\n */\r\nmxCompactTreeLayout.prototype.resizeParent = true;\r\n\r\n/**\r\n * Variable: maintainParentLocation\r\n * \r\n * Specifies if the parent location should be maintained, so that the\r\n * top, left corner stays the same before and after execution of\r\n * the layout. Default is false for backwards compatibility.\r\n */\r\nmxCompactTreeLayout.prototype.maintainParentLocation = false;\r\n\r\n/**\r\n * Variable: groupPadding\r\n * \r\n * Padding added to resized parents. Default is 10.\r\n */\r\nmxCompactTreeLayout.prototype.groupPadding = 10;\r\n\r\n/**\r\n * Variable: groupPaddingTop\r\n * \r\n * Top padding added to resized parents. Default is 0.\r\n */\r\nmxCompactTreeLayout.prototype.groupPaddingTop = 0;\r\n\r\n/**\r\n * Variable: groupPaddingRight\r\n * \r\n * Right padding added to resized parents. Default is 0.\r\n */\r\nmxCompactTreeLayout.prototype.groupPaddingRight = 0;\r\n\r\n/**\r\n * Variable: groupPaddingBottom\r\n * \r\n * Bottom padding added to resized parents. Default is 0.\r\n */\r\nmxCompactTreeLayout.prototype.groupPaddingBottom = 0;\r\n\r\n/**\r\n * Variable: groupPaddingLeft\r\n * \r\n * Left padding added to resized parents. Default is 0.\r\n */\r\nmxCompactTreeLayout.prototype.groupPaddingLeft = 0;\r\n\r\n/**\r\n * Variable: parentsChanged\r\n *\r\n * A set of the parents that need updating based on children\r\n * process as part of the layout.\r\n */\r\nmxCompactTreeLayout.prototype.parentsChanged = null;\r\n\r\n/**\r\n * Variable: moveTree\r\n * \r\n * Specifies if the tree should be moved to the top, left corner\r\n * if it is inside a top-level layer. Default is false.\r\n */\r\nmxCompactTreeLayout.prototype.moveTree = false;\r\n\r\n/**\r\n * Variable: visited\r\n * \r\n * Specifies if the tree should be moved to the top, left corner\r\n * if it is inside a top-level layer. Default is false.\r\n */\r\nmxCompactTreeLayout.prototype.visited = null;\r\n\r\n/**\r\n * Variable: levelDistance\r\n *\r\n * Holds the levelDistance. Default is 10.\r\n */\r\nmxCompactTreeLayout.prototype.levelDistance = 10;\r\n\r\n/**\r\n * Variable: nodeDistance\r\n *\r\n * Holds the nodeDistance. Default is 20.\r\n */\r\nmxCompactTreeLayout.prototype.nodeDistance = 20;\r\n\r\n/**\r\n * Variable: resetEdges\r\n * \r\n * Specifies if all edge points of traversed edges should be removed.\r\n * Default is true.\r\n */\r\nmxCompactTreeLayout.prototype.resetEdges = true;\r\n\r\n/**\r\n * Variable: prefHozEdgeSep\r\n * \r\n * The preferred horizontal distance between edges exiting a vertex.\r\n */\r\nmxCompactTreeLayout.prototype.prefHozEdgeSep = 5;\r\n\r\n/**\r\n * Variable: prefVertEdgeOff\r\n * \r\n * The preferred vertical offset between edges exiting a vertex.\r\n */\r\nmxCompactTreeLayout.prototype.prefVertEdgeOff = 4;\r\n\r\n/**\r\n * Variable: minEdgeJetty\r\n * \r\n * The minimum distance for an edge jetty from a vertex.\r\n */\r\nmxCompactTreeLayout.prototype.minEdgeJetty = 8;\r\n\r\n/**\r\n * Variable: channelBuffer\r\n * \r\n * The size of the vertical buffer in the center of inter-rank channels\r\n * where edge control points should not be placed.\r\n */\r\nmxCompactTreeLayout.prototype.channelBuffer = 4;\r\n\r\n/**\r\n * Variable: edgeRouting\r\n * \r\n * Whether or not to apply the internal tree edge routing.\r\n */\r\nmxCompactTreeLayout.prototype.edgeRouting = true;\r\n\r\n/**\r\n * Variable: sortEdges\r\n * \r\n * Specifies if edges should be sorted according to the order of their\r\n * opposite terminal cell in the model.\r\n */\r\nmxCompactTreeLayout.prototype.sortEdges = false;\r\n\r\n/**\r\n * Variable: alignRanks\r\n * \r\n * Whether or not the tops of cells in each rank should be aligned\r\n * across the rank\r\n */\r\nmxCompactTreeLayout.prototype.alignRanks = false;\r\n\r\n/**\r\n * Variable: maxRankHeight\r\n * \r\n * An array of the maximum height of cells (relative to the layout direction)\r\n * per rank\r\n */\r\nmxCompactTreeLayout.prototype.maxRankHeight = null;\r\n\r\n/**\r\n * Variable: root\r\n * \r\n * The cell to use as the root of the tree\r\n */\r\nmxCompactTreeLayout.prototype.root = null;\r\n\r\n/**\r\n * Variable: node\r\n * \r\n * The internal node representation of the root cell. Do not set directly\r\n * , this value is only exposed to assist with post-processing functionality\r\n */\r\nmxCompactTreeLayout.prototype.node = null;\r\n\r\n/**\r\n * Function: isVertexIgnored\r\n * \r\n * Returns a boolean indicating if the given <mxCell> should be ignored as a\r\n * vertex. This returns true if the cell has no connections.\r\n * \r\n * Parameters:\r\n * \r\n * vertex - <mxCell> whose ignored state should be returned.\r\n */\r\nmxCompactTreeLayout.prototype.isVertexIgnored = function(vertex)\r\n{\r\n\treturn mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) ||\r\n\t\tthis.graph.getConnections(vertex).length == 0;\r\n};\r\n\r\n/**\r\n * Function: isHorizontal\r\n * \r\n * Returns <horizontal>.\r\n */\r\nmxCompactTreeLayout.prototype.isHorizontal = function()\r\n{\r\n\treturn this.horizontal;\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Implements <mxGraphLayout.execute>.\r\n * \r\n * If the parent has any connected edges, then it is used as the root of\r\n * the tree. Else, <mxGraph.findTreeRoots> will be used to find a suitable\r\n * root node within the set of children of the given parent.\r\n * \r\n * Parameters:\r\n * \r\n * parent - <mxCell> whose children should be laid out.\r\n * root - Optional <mxCell> that will be used as the root of the tree.\r\n * Overrides <root> if specified.\r\n */\r\nmxCompactTreeLayout.prototype.execute = function(parent, root)\r\n{\r\n\tthis.parent = parent;\r\n\tvar model = this.graph.getModel();\r\n\r\n\tif (root == null)\r\n\t{\r\n\t\t// Takes the parent as the root if it has outgoing edges\r\n\t\tif (this.graph.getEdges(parent, model.getParent(parent),\r\n\t\t\tthis.invert, !this.invert, false).length > 0)\r\n\t\t{\r\n\t\t\tthis.root = parent;\r\n\t\t}\r\n\t\t\r\n\t\t// Tries to find a suitable root in the parent's\r\n\t\t// children\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar roots = this.graph.findTreeRoots(parent, true, this.invert);\r\n\t\t\t\r\n\t\t\tif (roots.length > 0)\r\n\t\t\t{\r\n\t\t\t\tfor (var i = 0; i < roots.length; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (!this.isVertexIgnored(roots[i]) &&\r\n\t\t\t\t\t\tthis.graph.getEdges(roots[i], null,\r\n\t\t\t\t\t\t\tthis.invert, !this.invert, false).length > 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.root = roots[i];\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.root = root;\r\n\t}\r\n\t\r\n\tif (this.root != null)\r\n\t{\r\n\t\tif (this.resizeParent)\r\n\t\t{\r\n\t\t\tthis.parentsChanged = new Object();\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.parentsChanged = null;\r\n\t\t}\r\n\r\n\t\t//  Maintaining parent location\r\n\t\tthis.parentX = null;\r\n\t\tthis.parentY = null;\r\n\t\t\r\n\t\tif (parent != this.root && model.isVertex(parent) != null && this.maintainParentLocation)\r\n\t\t{\r\n\t\t\tvar geo = this.graph.getCellGeometry(parent);\r\n\t\t\t\r\n\t\t\tif (geo != null)\r\n\t\t\t{\r\n\t\t\t\tthis.parentX = geo.x;\r\n\t\t\t\tthis.parentY = geo.y;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tmodel.beginUpdate();\r\n\t\t\r\n\t\ttry\r\n\t\t{\r\n\t\t\tthis.visited = new Object();\r\n\t\t\tthis.node = this.dfs(this.root, parent);\r\n\t\t\t\r\n\t\t\tif (this.alignRanks)\r\n\t\t\t{\r\n\t\t\t\tthis.maxRankHeight = [];\r\n\t\t\t\tthis.findRankHeights(this.node, 0);\r\n\t\t\t\tthis.setCellHeights(this.node, 0);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (this.node != null)\r\n\t\t\t{\r\n\t\t\t\tthis.layout(this.node);\r\n\t\t\t\tvar x0 = this.graph.gridSize;\r\n\t\t\t\tvar y0 = x0;\r\n\t\t\t\t\r\n\t\t\t\tif (!this.moveTree)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar g = this.getVertexBounds(this.root);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (g != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tx0 = g.x;\r\n\t\t\t\t\t\ty0 = g.y;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tvar bounds = null;\r\n\t\t\t\t\r\n\t\t\t\tif (this.isHorizontal())\r\n\t\t\t\t{\r\n\t\t\t\t\tbounds = this.horizontalLayout(this.node, x0, y0);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tbounds = this.verticalLayout(this.node, null, x0, y0);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (bounds != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar dx = 0;\r\n\t\t\t\t\tvar dy = 0;\r\n\r\n\t\t\t\t\tif (bounds.x < 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tdx = Math.abs(x0 - bounds.x);\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif (bounds.y < 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tdy = Math.abs(y0 - bounds.y);\t\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif (dx != 0 || dy != 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.moveNode(this.node, dx, dy);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (this.resizeParent)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.adjustParents();\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif (this.edgeRouting)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t// Iterate through all edges setting their positions\r\n\t\t\t\t\t\tthis.localEdgeProcessing(this.node);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Maintaining parent location\r\n\t\t\t\tif (this.parentX != null && this.parentY != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar geo = this.graph.getCellGeometry(parent);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (geo != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tgeo = geo.clone();\r\n\t\t\t\t\t\tgeo.x = this.parentX;\r\n\t\t\t\t\t\tgeo.y = this.parentY;\r\n\t\t\t\t\t\tmodel.setGeometry(parent, geo);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tmodel.endUpdate();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: moveNode\r\n * \r\n * Moves the specified node and all of its children by the given amount.\r\n */\r\nmxCompactTreeLayout.prototype.moveNode = function(node, dx, dy)\r\n{\r\n\tnode.x += dx;\r\n\tnode.y += dy;\r\n\tthis.apply(node);\r\n\t\r\n\tvar child = node.child;\r\n\t\r\n\twhile (child != null)\r\n\t{\r\n\t\tthis.moveNode(child, dx, dy);\r\n\t\tchild = child.next;\r\n\t}\r\n};\r\n\r\n\r\n/**\r\n * Function: sortOutgoingEdges\r\n * \r\n * Called if <sortEdges> is true to sort the array of outgoing edges in place.\r\n */\r\nmxCompactTreeLayout.prototype.sortOutgoingEdges = function(source, edges)\r\n{\r\n\tvar lookup = new mxDictionary();\r\n\t\r\n\tedges.sort(function(e1, e2)\r\n\t{\r\n\t\tvar end1 = e1.getTerminal(e1.getTerminal(false) == source);\r\n\t\tvar p1 = lookup.get(end1);\r\n\t\t\r\n\t\tif (p1 == null)\r\n\t\t{\r\n\t\t\tp1 = mxCellPath.create(end1).split(mxCellPath.PATH_SEPARATOR);\r\n\t\t\tlookup.put(end1, p1);\r\n\t\t}\r\n\r\n\t\tvar end2 = e2.getTerminal(e2.getTerminal(false) == source);\r\n\t\tvar p2 = lookup.get(end2);\r\n\t\t\r\n\t\tif (p2 == null)\r\n\t\t{\r\n\t\t\tp2 = mxCellPath.create(end2).split(mxCellPath.PATH_SEPARATOR);\r\n\t\t\tlookup.put(end2, p2);\r\n\t\t}\r\n\r\n\t\treturn mxCellPath.compare(p1, p2);\r\n\t});\r\n};\r\n\r\n/**\r\n * Function: findRankHeights\r\n * \r\n * Stores the maximum height (relative to the layout\r\n * direction) of cells in each rank\r\n */\r\nmxCompactTreeLayout.prototype.findRankHeights = function(node, rank)\r\n{\r\n\tif (this.maxRankHeight[rank] == null || this.maxRankHeight[rank] < node.height)\r\n\t{\r\n\t\tthis.maxRankHeight[rank] = node.height;\r\n\t}\r\n\r\n\tvar child = node.child;\r\n\t\r\n\twhile (child != null)\r\n\t{\r\n\t\tthis.findRankHeights(child, rank + 1);\r\n\t\tchild = child.next;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setCellHeights\r\n * \r\n * Set the cells heights (relative to the layout\r\n * direction) when the tops of each rank are to be aligned\r\n */\r\nmxCompactTreeLayout.prototype.setCellHeights = function(node, rank)\r\n{\r\n\tif (this.maxRankHeight[rank] != null && this.maxRankHeight[rank] > node.height)\r\n\t{\r\n\t\tnode.height = this.maxRankHeight[rank];\r\n\t}\r\n\r\n\tvar child = node.child;\r\n\t\r\n\twhile (child != null)\r\n\t{\r\n\t\tthis.setCellHeights(child, rank + 1);\r\n\t\tchild = child.next;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: dfs\r\n * \r\n * Does a depth first search starting at the specified cell.\r\n * Makes sure the specified parent is never left by the\r\n * algorithm.\r\n */\r\nmxCompactTreeLayout.prototype.dfs = function(cell, parent)\r\n{\r\n\tvar id = mxCellPath.create(cell);\r\n\tvar node = null;\r\n\t\r\n\tif (cell != null && this.visited[id] == null && !this.isVertexIgnored(cell))\r\n\t{\r\n\t\tthis.visited[id] = cell;\r\n\t\tnode = this.createNode(cell);\r\n\r\n\t\tvar model = this.graph.getModel();\r\n\t\tvar prev = null;\r\n\t\tvar out = this.graph.getEdges(cell, parent, this.invert, !this.invert, false, true);\r\n\t\tvar view = this.graph.getView();\r\n\t\t\r\n\t\tif (this.sortEdges)\r\n\t\t{\r\n\t\t\tthis.sortOutgoingEdges(cell, out);\r\n\t\t}\r\n\r\n\t\tfor (var i = 0; i < out.length; i++)\r\n\t\t{\r\n\t\t\tvar edge = out[i];\r\n\t\t\t\r\n\t\t\tif (!this.isEdgeIgnored(edge))\r\n\t\t\t{\r\n\t\t\t\t// Resets the points on the traversed edge\r\n\t\t\t\tif (this.resetEdges)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.setEdgePoints(edge, null);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (this.edgeRouting)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.setEdgeStyleEnabled(edge, false);\r\n\t\t\t\t\tthis.setEdgePoints(edge, null);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Checks if terminal in same swimlane\r\n\t\t\t\tvar state = view.getState(edge);\r\n\t\t\t\tvar target = (state != null) ? state.getVisibleTerminal(this.invert) : view.getVisibleTerminal(edge, this.invert);\r\n\t\t\t\tvar tmp = this.dfs(target, parent);\r\n\t\t\t\t\r\n\t\t\t\tif (tmp != null && model.getGeometry(target) != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (prev == null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tnode.child = tmp;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tprev.next = tmp;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tprev = tmp;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn node;\r\n};\r\n\r\n/**\r\n * Function: layout\r\n * \r\n * Starts the actual compact tree layout algorithm\r\n * at the given node.\r\n */\r\nmxCompactTreeLayout.prototype.layout = function(node)\r\n{\r\n\tif (node != null)\r\n\t{\r\n\t\tvar child = node.child;\r\n\t\t\r\n\t\twhile (child != null)\r\n\t\t{\r\n\t\t\tthis.layout(child);\r\n\t\t\tchild = child.next;\r\n\t\t}\r\n\t\t\r\n\t\tif (node.child != null)\r\n\t\t{\r\n\t\t\tthis.attachParent(node, this.join(node));\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.layoutLeaf(node);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: horizontalLayout\r\n */\r\nmxCompactTreeLayout.prototype.horizontalLayout = function(node, x0, y0, bounds)\r\n{\r\n\tnode.x += x0 + node.offsetX;\r\n\tnode.y += y0 + node.offsetY;\r\n\tbounds = this.apply(node, bounds);\r\n\tvar child = node.child;\r\n\t\r\n\tif (child != null)\r\n\t{\r\n\t\tbounds = this.horizontalLayout(child, node.x, node.y, bounds);\r\n\t\tvar siblingOffset = node.y + child.offsetY;\r\n\t\tvar s = child.next;\r\n\t\t\r\n\t\twhile (s != null)\r\n\t\t{\r\n\t\t\tbounds = this.horizontalLayout(s, node.x + child.offsetX, siblingOffset, bounds);\r\n\t\t\tsiblingOffset += s.offsetY;\r\n\t\t\ts = s.next;\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn bounds;\r\n};\r\n\t\r\n/**\r\n * Function: verticalLayout\r\n */\r\nmxCompactTreeLayout.prototype.verticalLayout = function(node, parent, x0, y0, bounds)\r\n{\r\n\tnode.x += x0 + node.offsetY;\r\n\tnode.y += y0 + node.offsetX;\r\n\tbounds = this.apply(node, bounds);\r\n\tvar child = node.child;\r\n\t\r\n\tif (child != null)\r\n\t{\r\n\t\tbounds = this.verticalLayout(child, node, node.x, node.y, bounds);\r\n\t\tvar siblingOffset = node.x + child.offsetY;\r\n\t\tvar s = child.next;\r\n\t\t\r\n\t\twhile (s != null)\r\n\t\t{\r\n\t\t\tbounds = this.verticalLayout(s, node, siblingOffset, node.y + child.offsetX, bounds);\r\n\t\t\tsiblingOffset += s.offsetY;\r\n\t\t\ts = s.next;\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn bounds;\r\n};\r\n\r\n/**\r\n * Function: attachParent\r\n */\r\nmxCompactTreeLayout.prototype.attachParent = function(node, height)\r\n{\r\n\tvar x = this.nodeDistance + this.levelDistance;\r\n\tvar y2 = (height - node.width) / 2 - this.nodeDistance;\r\n\tvar y1 = y2 + node.width + 2 * this.nodeDistance - height;\r\n\t\r\n\tnode.child.offsetX = x + node.height;\r\n\tnode.child.offsetY = y1;\r\n\t\r\n\tnode.contour.upperHead = this.createLine(node.height, 0,\r\n\t\tthis.createLine(x, y1, node.contour.upperHead));\r\n\tnode.contour.lowerHead = this.createLine(node.height, 0,\r\n\t\tthis.createLine(x, y2, node.contour.lowerHead));\r\n};\r\n\r\n/**\r\n * Function: layoutLeaf\r\n */\r\nmxCompactTreeLayout.prototype.layoutLeaf = function(node)\r\n{\r\n\tvar dist = 2 * this.nodeDistance;\r\n\t\r\n\tnode.contour.upperTail = this.createLine(\r\n\t\tnode.height + dist, 0);\r\n\tnode.contour.upperHead = node.contour.upperTail;\r\n\tnode.contour.lowerTail = this.createLine(\r\n\t\t0, -node.width - dist);\r\n\tnode.contour.lowerHead = this.createLine(\r\n\t\tnode.height + dist, 0, node.contour.lowerTail);\r\n};\r\n\r\n/**\r\n * Function: join\r\n */\r\nmxCompactTreeLayout.prototype.join = function(node)\r\n{\r\n\tvar dist = 2 * this.nodeDistance;\r\n\t\r\n\tvar child = node.child;\r\n\tnode.contour = child.contour;\r\n\tvar h = child.width + dist;\r\n\tvar sum = h;\r\n\tchild = child.next;\r\n\t\r\n\twhile (child != null)\r\n\t{\r\n\t\tvar d = this.merge(node.contour, child.contour);\r\n\t\tchild.offsetY = d + h;\r\n\t\tchild.offsetX = 0;\r\n\t\th = child.width + dist;\r\n\t\tsum += d + h;\r\n\t\tchild = child.next;\r\n\t}\r\n\t\r\n\treturn sum;\r\n};\r\n\r\n/**\r\n * Function: merge\r\n */\r\nmxCompactTreeLayout.prototype.merge = function(p1, p2)\r\n{\r\n\tvar x = 0;\r\n\tvar y = 0;\r\n\tvar total = 0;\r\n\t\r\n\tvar upper = p1.lowerHead;\r\n\tvar lower = p2.upperHead;\r\n\t\r\n\twhile (lower != null && upper != null)\r\n\t{\r\n\t\tvar d = this.offset(x, y, lower.dx, lower.dy,\r\n\t\t\tupper.dx, upper.dy);\r\n\t\ty += d;\r\n\t\ttotal += d;\r\n\t\t\r\n\t\tif (x + lower.dx <= upper.dx)\r\n\t\t{\r\n\t\t\tx += lower.dx;\r\n\t\t\ty += lower.dy;\r\n\t\t\tlower = lower.next;\r\n\t\t}\r\n\t\telse\r\n\t\t{\t\t\t\t\r\n\t\t\tx -= upper.dx;\r\n\t\t\ty -= upper.dy;\r\n\t\t\tupper = upper.next;\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (lower != null)\r\n\t{\r\n\t\tvar b = this.bridge(p1.upperTail, 0, 0, lower, x, y);\r\n\t\tp1.upperTail = (b.next != null) ? p2.upperTail : b;\r\n\t\tp1.lowerTail = p2.lowerTail;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar b = this.bridge(p2.lowerTail, x, y, upper, 0, 0);\r\n\t\t\r\n\t\tif (b.next == null)\r\n\t\t{\r\n\t\t\tp1.lowerTail = b;\r\n\t\t}\r\n\t}\r\n\t\r\n\tp1.lowerHead = p2.lowerHead;\r\n\t\r\n\treturn total;\r\n};\r\n\r\n/**\r\n * Function: offset\r\n */\r\nmxCompactTreeLayout.prototype.offset = function(p1, p2, a1, a2, b1, b2)\r\n{\r\n\tvar d = 0;\r\n\t\r\n\tif (b1 <= p1 || p1 + a1 <= 0)\r\n\t{\r\n\t\treturn 0;\r\n\t}\r\n\r\n\tvar t = b1 * a2 - a1 * b2;\r\n\t\r\n\tif (t > 0)\r\n\t{\r\n\t\tif (p1 < 0)\r\n\t\t{\r\n\t\t\tvar s = p1 * a2;\r\n\t\t\td = s / a1 - p2;\r\n\t\t}\r\n\t\telse if (p1 > 0)\r\n\t\t{\r\n\t\t\tvar s = p1 * b2;\r\n\t\t\td = s / b1 - p2;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\td = -p2;\r\n\t\t}\r\n\t}\r\n\telse if (b1 < p1 + a1)\r\n\t{\r\n\t\tvar s = (b1 - p1) * a2;\r\n\t\td = b2 - (p2 + s / a1);\r\n\t}\r\n\telse if (b1 > p1 + a1)\r\n\t{\r\n\t\tvar s = (a1 + p1) * b2;\r\n\t\td = s / b1 - (p2 + a2);\r\n\t}\r\n\telse\r\n\t{\r\n\t\td = b2 - (p2 + a2);\r\n\t}\r\n\r\n\tif (d > 0)\r\n\t{\r\n\t\treturn d;\r\n\t}\r\n\telse\r\n\t{\r\n\t\treturn 0;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: bridge\r\n */\r\nmxCompactTreeLayout.prototype.bridge = function(line1, x1, y1, line2, x2, y2)\r\n{\r\n\tvar dx = x2 + line2.dx - x1;\r\n\tvar dy = 0;\r\n\tvar s = 0;\r\n\t\r\n\tif (line2.dx == 0)\r\n\t{\r\n\t\tdy = line2.dy;\r\n\t}\r\n\telse\r\n\t{\r\n\t\ts = dx * line2.dy;\r\n\t\tdy = s / line2.dx;\r\n\t}\r\n\t\r\n\tvar r = this.createLine(dx, dy, line2.next);\r\n\tline1.next = this.createLine(0, y2 + line2.dy - dy - y1, r);\r\n\t\r\n\treturn r;\r\n};\r\n\r\n/**\r\n * Function: createNode\r\n */\r\nmxCompactTreeLayout.prototype.createNode = function(cell)\r\n{\r\n\tvar node = new Object();\r\n\tnode.cell = cell;\r\n\tnode.x = 0;\r\n\tnode.y = 0;\r\n\tnode.width = 0;\r\n\tnode.height = 0;\r\n\t\r\n\tvar geo = this.getVertexBounds(cell);\r\n\t\r\n\tif (geo != null)\r\n\t{\r\n\t\tif (this.isHorizontal())\r\n\t\t{\r\n\t\t\tnode.width = geo.height;\r\n\t\t\tnode.height = geo.width;\t\t\t\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tnode.width = geo.width;\r\n\t\t\tnode.height = geo.height;\r\n\t\t}\r\n\t}\r\n\t\r\n\tnode.offsetX = 0;\r\n\tnode.offsetY = 0;\r\n\tnode.contour = new Object();\r\n\t\r\n\treturn node;\r\n};\r\n\r\n/**\r\n * Function: apply\r\n */\r\nmxCompactTreeLayout.prototype.apply = function(node, bounds)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\tvar cell = node.cell;\r\n\tvar g = model.getGeometry(cell);\r\n\r\n\tif (cell != null && g != null)\r\n\t{\r\n\t\tif (this.isVertexMovable(cell))\r\n\t\t{\r\n\t\t\tg = this.setVertexLocation(cell, node.x, node.y);\r\n\t\t\t\r\n\t\t\tif (this.resizeParent)\r\n\t\t\t{\r\n\t\t\t\tvar parent = model.getParent(cell);\r\n\t\t\t\tvar id = mxCellPath.create(parent);\r\n\t\t\t\t\r\n\t\t\t\t// Implements set semantic\r\n\t\t\t\tif (this.parentsChanged[id] == null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.parentsChanged[id] = parent;\t\t\t\t\t\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (bounds == null)\r\n\t\t{\r\n\t\t\tbounds = new mxRectangle(g.x, g.y, g.width, g.height);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tbounds = new mxRectangle(Math.min(bounds.x, g.x),\r\n\t\t\t\tMath.min(bounds.y, g.y),\r\n\t\t\t\tMath.max(bounds.x + bounds.width, g.x + g.width),\r\n\t\t\t\tMath.max(bounds.y + bounds.height, g.y + g.height));\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn bounds;\r\n};\r\n\r\n/**\r\n * Function: createLine\r\n */\r\nmxCompactTreeLayout.prototype.createLine = function(dx, dy, next)\r\n{\r\n\tvar line = new Object();\r\n\tline.dx = dx;\r\n\tline.dy = dy;\r\n\tline.next = next;\r\n\t\r\n\treturn line;\r\n};\r\n\r\n/**\r\n * Function: adjustParents\r\n * \r\n * Adjust parent cells whose child geometries have changed. The default \r\n * implementation adjusts the group to just fit around the children with \r\n * a padding.\r\n */\r\nmxCompactTreeLayout.prototype.adjustParents = function()\r\n{\r\n\tvar tmp = [];\r\n\t\r\n\tfor (var id in this.parentsChanged)\r\n\t{\r\n\t\ttmp.push(this.parentsChanged[id]);\r\n\t}\r\n\t\r\n\tthis.arrangeGroups(mxUtils.sortCells(tmp, true), this.groupPadding, this.groupPaddingTop,\r\n\t\tthis.groupPaddingRight, this.groupPaddingBottom, this.groupPaddingLeft);\r\n};\r\n\r\n/**\r\n * Function: localEdgeProcessing\r\n *\r\n * Moves the specified node and all of its children by the given amount.\r\n */\r\nmxCompactTreeLayout.prototype.localEdgeProcessing = function(node)\r\n{\r\n\tthis.processNodeOutgoing(node);\r\n\tvar child = node.child;\r\n\r\n\twhile (child != null)\r\n\t{\r\n\t\tthis.localEdgeProcessing(child);\r\n\t\tchild = child.next;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: localEdgeProcessing\r\n *\r\n * Separates the x position of edges as they connect to vertices\r\n */\r\nmxCompactTreeLayout.prototype.processNodeOutgoing = function(node)\r\n{\r\n\tvar child = node.child;\r\n\tvar parentCell = node.cell;\r\n\r\n\tvar childCount = 0;\r\n\tvar sortedCells = [];\r\n\r\n\twhile (child != null)\r\n\t{\r\n\t\tchildCount++;\r\n\r\n\t\tvar sortingCriterion = child.x;\r\n\r\n\t\tif (this.horizontal)\r\n\t\t{\r\n\t\t\tsortingCriterion = child.y;\r\n\t\t}\r\n\r\n\t\tsortedCells.push(new WeightedCellSorter(child, sortingCriterion));\r\n\t\tchild = child.next;\r\n\t}\r\n\r\n\tsortedCells.sort(WeightedCellSorter.prototype.compare);\r\n\r\n\tvar availableWidth = node.width;\r\n\r\n\tvar requiredWidth = (childCount + 1) * this.prefHozEdgeSep;\r\n\r\n\t// Add a buffer on the edges of the vertex if the edge count allows\r\n\tif (availableWidth > requiredWidth + (2 * this.prefHozEdgeSep))\r\n\t{\r\n\t\tavailableWidth -= 2 * this.prefHozEdgeSep;\r\n\t}\r\n\r\n\tvar edgeSpacing = availableWidth / childCount;\r\n\r\n\tvar currentXOffset = edgeSpacing / 2.0;\r\n\r\n\tif (availableWidth > requiredWidth + (2 * this.prefHozEdgeSep))\r\n\t{\r\n\t\tcurrentXOffset += this.prefHozEdgeSep;\r\n\t}\r\n\r\n\tvar currentYOffset = this.minEdgeJetty - this.prefVertEdgeOff;\r\n\tvar maxYOffset = 0;\r\n\r\n\tvar parentBounds = this.getVertexBounds(parentCell);\r\n\tchild = node.child;\r\n\r\n\tfor (var j = 0; j < sortedCells.length; j++)\r\n\t{\r\n\t\tvar childCell = sortedCells[j].cell.cell;\r\n\t\tvar childBounds = this.getVertexBounds(childCell);\r\n\r\n\t\tvar edges = this.graph.getEdgesBetween(parentCell,\r\n\t\t\t\tchildCell, false);\r\n\t\t\r\n\t\tvar newPoints = [];\r\n\t\tvar x = 0;\r\n\t\tvar y = 0;\r\n\r\n\t\tfor (var i = 0; i < edges.length; i++)\r\n\t\t{\r\n\t\t\tif (this.horizontal)\r\n\t\t\t{\r\n\t\t\t\t// Use opposite co-ords, calculation was done for \r\n\t\t\t\t// \r\n\t\t\t\tx = parentBounds.x + parentBounds.width;\r\n\t\t\t\ty = parentBounds.y + currentXOffset;\r\n\t\t\t\tnewPoints.push(new mxPoint(x, y));\r\n\t\t\t\tx = parentBounds.x + parentBounds.width\r\n\t\t\t\t\t\t+ currentYOffset;\r\n\t\t\t\tnewPoints.push(new mxPoint(x, y));\r\n\t\t\t\ty = childBounds.y + childBounds.height / 2.0;\r\n\t\t\t\tnewPoints.push(new mxPoint(x, y));\r\n\t\t\t\tthis.setEdgePoints(edges[i], newPoints);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tx = parentBounds.x + currentXOffset;\r\n\t\t\t\ty = parentBounds.y + parentBounds.height;\r\n\t\t\t\tnewPoints.push(new mxPoint(x, y));\r\n\t\t\t\ty = parentBounds.y + parentBounds.height\r\n\t\t\t\t\t\t+ currentYOffset;\r\n\t\t\t\tnewPoints.push(new mxPoint(x, y));\r\n\t\t\t\tx = childBounds.x + childBounds.width / 2.0;\r\n\t\t\t\tnewPoints.push(new mxPoint(x, y));\r\n\t\t\t\tthis.setEdgePoints(edges[i], newPoints);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (j < childCount / 2)\r\n\t\t{\r\n\t\t\tcurrentYOffset += this.prefVertEdgeOff;\r\n\t\t}\r\n\t\telse if (j > childCount / 2)\r\n\t\t{\r\n\t\t\tcurrentYOffset -= this.prefVertEdgeOff;\r\n\t\t}\r\n\t\t// Ignore the case if equals, this means the second of 2\r\n\t\t// jettys with the same y (even number of edges)\r\n\r\n\t\t//\t\t\t\t\t\t\t\tpos[k * 2] = currentX;\r\n\t\tcurrentXOffset += edgeSpacing;\r\n\t\t//\t\t\t\t\t\t\t\tpos[k * 2 + 1] = currentYOffset;\r\n\r\n\t\tmaxYOffset = Math.max(maxYOffset, currentYOffset);\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxRadialTreeLayout\r\n * \r\n * Extends <mxGraphLayout> to implement a radial tree algorithm. This\r\n * layout is suitable for graphs that have no cycles (trees). Vertices that are\r\n * not connected to the tree will be ignored by this layout.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * var layout = new mxRadialTreeLayout(graph);\r\n * layout.execute(graph.getDefaultParent());\r\n * (end)\r\n * \r\n * Constructor: mxRadialTreeLayout\r\n * \r\n * Constructs a new radial tree layout for the specified graph\r\n */\r\nfunction mxRadialTreeLayout(graph)\r\n{\r\n\tmxCompactTreeLayout.call(this, graph , false);\r\n};\r\n\r\n/**\r\n * Extends mxGraphLayout.\r\n */\r\nmxUtils.extend(mxRadialTreeLayout, mxCompactTreeLayout);\r\n\r\n/**\r\n * Variable: angleOffset\r\n *\r\n * The initial offset to compute the angle position.\r\n */\r\nmxRadialTreeLayout.prototype.angleOffset = 0.5;\r\n\r\n/**\r\n * Variable: rootx\r\n *\r\n * The X co-ordinate of the root cell\r\n */\r\nmxRadialTreeLayout.prototype.rootx = 0;\r\n\r\n/**\r\n * Variable: rooty\r\n *\r\n * The Y co-ordinate of the root cell\r\n */\r\nmxRadialTreeLayout.prototype.rooty = 0;\r\n\r\n/**\r\n * Variable: levelDistance\r\n *\r\n * Holds the levelDistance. Default is 120.\r\n */\r\nmxRadialTreeLayout.prototype.levelDistance = 120;\r\n\r\n/**\r\n * Variable: nodeDistance\r\n *\r\n * Holds the nodeDistance. Default is 10.\r\n */\r\nmxRadialTreeLayout.prototype.nodeDistance = 10;\r\n\r\n/**\r\n * Variable: autoRadius\r\n * \r\n * Specifies if the radios should be computed automatically\r\n */\r\nmxRadialTreeLayout.prototype.autoRadius = false;\r\n\r\n/**\r\n * Variable: sortEdges\r\n * \r\n * Specifies if edges should be sorted according to the order of their\r\n * opposite terminal cell in the model.\r\n */\r\nmxRadialTreeLayout.prototype.sortEdges = false;\r\n\r\n/**\r\n * Variable: rowMinX\r\n * \r\n * Array of leftmost x coordinate of each row\r\n */\r\nmxRadialTreeLayout.prototype.rowMinX = [];\r\n\r\n/**\r\n * Variable: rowMaxX\r\n * \r\n * Array of rightmost x coordinate of each row\r\n */\r\nmxRadialTreeLayout.prototype.rowMaxX = [];\r\n\r\n/**\r\n * Variable: rowMinCenX\r\n * \r\n * Array of x coordinate of leftmost vertex of each row\r\n */\r\nmxRadialTreeLayout.prototype.rowMinCenX = [];\r\n\r\n/**\r\n * Variable: rowMaxCenX\r\n * \r\n * Array of x coordinate of rightmost vertex of each row\r\n */\r\nmxRadialTreeLayout.prototype.rowMaxCenX = [];\r\n\r\n/**\r\n * Variable: rowRadi\r\n * \r\n * Array of y deltas of each row behind root vertex, also the radius in the tree\r\n */\r\nmxRadialTreeLayout.prototype.rowRadi = [];\r\n\r\n/**\r\n * Variable: row\r\n * \r\n * Array of vertices on each row\r\n */\r\nmxRadialTreeLayout.prototype.row = [];\r\n\r\n/**\r\n * Function: isVertexIgnored\r\n * \r\n * Returns a boolean indicating if the given <mxCell> should be ignored as a\r\n * vertex. This returns true if the cell has no connections.\r\n * \r\n * Parameters:\r\n * \r\n * vertex - <mxCell> whose ignored state should be returned.\r\n */\r\nmxRadialTreeLayout.prototype.isVertexIgnored = function(vertex)\r\n{\r\n\treturn mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) ||\r\n\t\tthis.graph.getConnections(vertex).length == 0;\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Implements <mxGraphLayout.execute>.\r\n * \r\n * If the parent has any connected edges, then it is used as the root of\r\n * the tree. Else, <mxGraph.findTreeRoots> will be used to find a suitable\r\n * root node within the set of children of the given parent.\r\n * \r\n * Parameters:\r\n * \r\n * parent - <mxCell> whose children should be laid out.\r\n * root - Optional <mxCell> that will be used as the root of the tree.\r\n */\r\nmxRadialTreeLayout.prototype.execute = function(parent, root)\r\n{\r\n\tthis.parent = parent;\r\n\t\r\n\tthis.useBoundingBox = false;\r\n\tthis.edgeRouting = false;\r\n\t//this.horizontal = false;\r\n\r\n\tmxCompactTreeLayout.prototype.execute.apply(this, arguments);\r\n\t\r\n\tvar bounds = null;\r\n\tvar rootBounds = this.getVertexBounds(this.root);\r\n\tthis.centerX = rootBounds.x + rootBounds.width / 2;\r\n\tthis.centerY = rootBounds.y + rootBounds.height / 2;\r\n\r\n\t// Calculate the bounds of the involved vertices directly from the values set in the compact tree\r\n\tfor (var vertex in this.visited)\r\n\t{\r\n\t\tvar vertexBounds = this.getVertexBounds(this.visited[vertex]);\r\n\t\tbounds = (bounds != null) ? bounds : vertexBounds.clone();\r\n\t\tbounds.add(vertexBounds);\r\n\t}\r\n\t\r\n\tthis.calcRowDims([this.node], 0);\r\n\t\r\n\tvar maxLeftGrad = 0;\r\n\tvar maxRightGrad = 0;\r\n\r\n\t// Find the steepest left and right gradients\r\n\tfor (var i = 0; i < this.row.length; i++)\r\n\t{\r\n\t\tvar leftGrad = (this.centerX - this.rowMinX[i] - this.nodeDistance) / this.rowRadi[i];\r\n\t\tvar rightGrad = (this.rowMaxX[i] - this.centerX - this.nodeDistance) / this.rowRadi[i];\r\n\t\t\r\n\t\tmaxLeftGrad = Math.max (maxLeftGrad, leftGrad);\r\n\t\tmaxRightGrad = Math.max (maxRightGrad, rightGrad);\r\n\t}\r\n\t\r\n\t// Extend out row so they meet the maximum gradient and convert to polar co-ords\r\n\tfor (var i = 0; i < this.row.length; i++)\r\n\t{\r\n\t\tvar xLeftLimit = this.centerX - this.nodeDistance - maxLeftGrad * this.rowRadi[i];\r\n\t\tvar xRightLimit = this.centerX + this.nodeDistance + maxRightGrad * this.rowRadi[i];\r\n\t\tvar fullWidth = xRightLimit - xLeftLimit;\r\n\t\t\r\n\t\tfor (var j = 0; j < this.row[i].length; j ++)\r\n\t\t{\r\n\t\t\tvar row = this.row[i];\r\n\t\t\tvar node = row[j];\r\n\t\t\tvar vertexBounds = this.getVertexBounds(node.cell);\r\n\t\t\tvar xProportion = (vertexBounds.x + vertexBounds.width / 2 - xLeftLimit) / (fullWidth);\r\n\t\t\tvar theta =  2 * Math.PI * xProportion;\r\n\t\t\tnode.theta = theta;\r\n\t\t}\r\n\t}\r\n\r\n\t// Post-process from outside inwards to try to align parents with children\r\n\tfor (var i = this.row.length - 2; i >= 0; i--)\r\n\t{\r\n\t\tvar row = this.row[i];\r\n\t\t\r\n\t\tfor (var j = 0; j < row.length; j++)\r\n\t\t{\r\n\t\t\tvar node = row[j];\r\n\t\t\tvar child = node.child;\r\n\t\t\tvar counter = 0;\r\n\t\t\tvar totalTheta = 0;\r\n\t\t\t\r\n\t\t\twhile (child != null)\r\n\t\t\t{\r\n\t\t\t\ttotalTheta += child.theta;\r\n\t\t\t\tcounter++;\r\n\t\t\t\tchild = child.next;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (counter > 0)\r\n\t\t\t{\r\n\t\t\t\tvar averTheta = totalTheta / counter;\r\n\t\t\t\t\r\n\t\t\t\tif (averTheta > node.theta && j < row.length - 1)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar nextTheta = row[j+1].theta;\r\n\t\t\t\t\tnode.theta = Math.min (averTheta, nextTheta - Math.PI/10);\r\n\t\t\t\t}\r\n\t\t\t\telse if (averTheta < node.theta && j > 0 )\r\n\t\t\t\t{\r\n\t\t\t\t\tvar lastTheta = row[j-1].theta;\r\n\t\t\t\t\tnode.theta = Math.max (averTheta, lastTheta + Math.PI/10);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\t// Set locations\r\n\tfor (var i = 0; i < this.row.length; i++)\r\n\t{\r\n\t\tfor (var j = 0; j < this.row[i].length; j ++)\r\n\t\t{\r\n\t\t\tvar row = this.row[i];\r\n\t\t\tvar node = row[j];\r\n\t\t\tvar vertexBounds = this.getVertexBounds(node.cell);\r\n\t\t\tthis.setVertexLocation(node.cell,\r\n\t\t\t\t\t\t\t\t\tthis.centerX - vertexBounds.width / 2 + this.rowRadi[i] * Math.cos(node.theta),\r\n\t\t\t\t\t\t\t\t\tthis.centerY - vertexBounds.height / 2 + this.rowRadi[i] * Math.sin(node.theta));\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: calcRowDims\r\n * \r\n * Recursive function to calculate the dimensions of each row\r\n * \r\n * Parameters:\r\n * \r\n * row - Array of internal nodes, the children of which are to be processed.\r\n * rowNum - Integer indicating which row is being processed.\r\n */\r\nmxRadialTreeLayout.prototype.calcRowDims = function(row, rowNum)\r\n{\r\n\tif (row == null || row.length == 0)\r\n\t{\r\n\t\treturn;\r\n\t}\r\n\r\n\t// Place root's children proportionally around the first level\r\n\tthis.rowMinX[rowNum] = this.centerX;\r\n\tthis.rowMaxX[rowNum] = this.centerX;\r\n\tthis.rowMinCenX[rowNum] = this.centerX;\r\n\tthis.rowMaxCenX[rowNum] = this.centerX;\r\n\tthis.row[rowNum] = [];\r\n\r\n\tvar rowHasChildren = false;\r\n\r\n\tfor (var i = 0; i < row.length; i++)\r\n\t{\r\n\t\tvar child = row[i] != null ? row[i].child : null;\r\n\r\n\t\twhile (child != null)\r\n\t\t{\r\n\t\t\tvar cell = child.cell;\r\n\t\t\tvar vertexBounds = this.getVertexBounds(cell);\r\n\t\t\t\r\n\t\t\tthis.rowMinX[rowNum] = Math.min(vertexBounds.x, this.rowMinX[rowNum]);\r\n\t\t\tthis.rowMaxX[rowNum] = Math.max(vertexBounds.x + vertexBounds.width, this.rowMaxX[rowNum]);\r\n\t\t\tthis.rowMinCenX[rowNum] = Math.min(vertexBounds.x + vertexBounds.width / 2, this.rowMinCenX[rowNum]);\r\n\t\t\tthis.rowMaxCenX[rowNum] = Math.max(vertexBounds.x + vertexBounds.width / 2, this.rowMaxCenX[rowNum]);\r\n\t\t\tthis.rowRadi[rowNum] = vertexBounds.y - this.getVertexBounds(this.root).y;\r\n\t\r\n\t\t\tif (child.child != null)\r\n\t\t\t{\r\n\t\t\t\trowHasChildren = true;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.row[rowNum].push(child);\r\n\t\t\tchild = child.next;\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (rowHasChildren)\r\n\t{\r\n\t\tthis.calcRowDims(this.row[rowNum], rowNum + 1);\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxFastOrganicLayout\r\n * \r\n * Extends <mxGraphLayout> to implement a fast organic layout algorithm.\r\n * The vertices need to be connected for this layout to work, vertices\r\n * with no connections are ignored.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * var layout = new mxFastOrganicLayout(graph);\r\n * layout.execute(graph.getDefaultParent());\r\n * (end)\r\n * \r\n * Constructor: mxCompactTreeLayout\r\n * \r\n * Constructs a new fast organic layout for the specified graph.\r\n */\r\nfunction mxFastOrganicLayout(graph)\r\n{\r\n\tmxGraphLayout.call(this, graph);\r\n};\r\n\r\n/**\r\n * Extends mxGraphLayout.\r\n */\r\nmxFastOrganicLayout.prototype = new mxGraphLayout();\r\nmxFastOrganicLayout.prototype.constructor = mxFastOrganicLayout;\r\n\r\n/**\r\n * Variable: useInputOrigin\r\n * \r\n * Specifies if the top left corner of the input cells should be the origin\r\n * of the layout result. Default is true.\r\n */\r\nmxFastOrganicLayout.prototype.useInputOrigin = true;\r\n\r\n/**\r\n * Variable: resetEdges\r\n * \r\n * Specifies if all edge points of traversed edges should be removed.\r\n * Default is true.\r\n */\r\nmxFastOrganicLayout.prototype.resetEdges = true;\r\n\r\n/**\r\n * Variable: disableEdgeStyle\r\n * \r\n * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are\r\n * modified by the result. Default is true.\r\n */\r\nmxFastOrganicLayout.prototype.disableEdgeStyle = true;\r\n\r\n/**\r\n * Variable: forceConstant\r\n * \r\n * The force constant by which the attractive forces are divided and the\r\n * replusive forces are multiple by the square of. The value equates to the\r\n * average radius there is of free space around each node. Default is 50.\r\n */\r\nmxFastOrganicLayout.prototype.forceConstant = 50;\r\n\r\n/**\r\n * Variable: forceConstantSquared\r\n * \r\n * Cache of <forceConstant>^2 for performance.\r\n */\r\nmxFastOrganicLayout.prototype.forceConstantSquared = 0;\r\n\r\n/**\r\n * Variable: minDistanceLimit\r\n * \r\n * Minimal distance limit. Default is 2. Prevents of\r\n * dividing by zero.\r\n */\r\nmxFastOrganicLayout.prototype.minDistanceLimit = 2;\r\n\r\n/**\r\n * Variable: minDistanceLimit\r\n * \r\n * Minimal distance limit. Default is 2. Prevents of\r\n * dividing by zero.\r\n */\r\nmxFastOrganicLayout.prototype.maxDistanceLimit = 500;\r\n\r\n/**\r\n * Variable: minDistanceLimitSquared\r\n * \r\n * Cached version of <minDistanceLimit> squared.\r\n */\r\nmxFastOrganicLayout.prototype.minDistanceLimitSquared = 4;\r\n\r\n/**\r\n * Variable: initialTemp\r\n * \r\n * Start value of temperature. Default is 200.\r\n */\r\nmxFastOrganicLayout.prototype.initialTemp = 200;\r\n\r\n/**\r\n * Variable: temperature\r\n * \r\n * Temperature to limit displacement at later stages of layout.\r\n */\r\nmxFastOrganicLayout.prototype.temperature = 0;\r\n\r\n/**\r\n * Variable: maxIterations\r\n * \r\n * Total number of iterations to run the layout though.\r\n */\r\nmxFastOrganicLayout.prototype.maxIterations = 0;\r\n\r\n/**\r\n * Variable: iteration\r\n * \r\n * Current iteration count.\r\n */\r\nmxFastOrganicLayout.prototype.iteration = 0;\r\n\r\n/**\r\n * Variable: vertexArray\r\n * \r\n * An array of all vertices to be laid out.\r\n */\r\nmxFastOrganicLayout.prototype.vertexArray;\r\n\r\n/**\r\n * Variable: dispX\r\n * \r\n * An array of locally stored X co-ordinate displacements for the vertices.\r\n */\r\nmxFastOrganicLayout.prototype.dispX;\r\n\r\n/**\r\n * Variable: dispY\r\n * \r\n * An array of locally stored Y co-ordinate displacements for the vertices.\r\n */\r\nmxFastOrganicLayout.prototype.dispY;\r\n\r\n/**\r\n * Variable: cellLocation\r\n * \r\n * An array of locally stored co-ordinate positions for the vertices.\r\n */\r\nmxFastOrganicLayout.prototype.cellLocation;\r\n\r\n/**\r\n * Variable: radius\r\n * \r\n * The approximate radius of each cell, nodes only.\r\n */\r\nmxFastOrganicLayout.prototype.radius;\r\n\r\n/**\r\n * Variable: radiusSquared\r\n * \r\n * The approximate radius squared of each cell, nodes only.\r\n */\r\nmxFastOrganicLayout.prototype.radiusSquared;\r\n\r\n/**\r\n * Variable: isMoveable\r\n * \r\n * Array of booleans representing the movable states of the vertices.\r\n */\r\nmxFastOrganicLayout.prototype.isMoveable;\r\n\r\n/**\r\n * Variable: neighbours\r\n * \r\n * Local copy of cell neighbours.\r\n */\r\nmxFastOrganicLayout.prototype.neighbours;\r\n\r\n/**\r\n * Variable: indices\r\n * \r\n * Hashtable from cells to local indices.\r\n */\r\nmxFastOrganicLayout.prototype.indices;\r\n\r\n/**\r\n * Variable: allowedToRun\r\n * \r\n * Boolean flag that specifies if the layout is allowed to run. If this is\r\n * set to false, then the layout exits in the following iteration.\r\n */\r\nmxFastOrganicLayout.prototype.allowedToRun = true;\r\n\r\n/**\r\n * Function: isVertexIgnored\r\n * \r\n * Returns a boolean indicating if the given <mxCell> should be ignored as a\r\n * vertex. This returns true if the cell has no connections.\r\n * \r\n * Parameters:\r\n * \r\n * vertex - <mxCell> whose ignored state should be returned.\r\n */\r\nmxFastOrganicLayout.prototype.isVertexIgnored = function(vertex)\r\n{\r\n\treturn mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) ||\r\n\t\tthis.graph.getConnections(vertex).length == 0;\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Implements <mxGraphLayout.execute>. This operates on all children of the\r\n * given parent where <isVertexIgnored> returns false.\r\n */\r\nmxFastOrganicLayout.prototype.execute = function(parent)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\tthis.vertexArray = [];\r\n\tvar cells = this.graph.getChildVertices(parent);\r\n\t\r\n\tfor (var i = 0; i < cells.length; i++)\r\n\t{\r\n\t\tif (!this.isVertexIgnored(cells[i]))\r\n\t\t{\r\n\t\t\tthis.vertexArray.push(cells[i]);\r\n\t\t}\r\n\t}\r\n\t\r\n\tvar initialBounds = (this.useInputOrigin) ?\r\n\t\t\tthis.graph.getBoundingBoxFromGeometry(this.vertexArray) :\r\n\t\t\t\tnull;\r\n\tvar n = this.vertexArray.length;\r\n\r\n\tthis.indices = [];\r\n\tthis.dispX = [];\r\n\tthis.dispY = [];\r\n\tthis.cellLocation = [];\r\n\tthis.isMoveable = [];\r\n\tthis.neighbours = [];\r\n\tthis.radius = [];\r\n\tthis.radiusSquared = [];\r\n\r\n\tif (this.forceConstant < 0.001)\r\n\t{\r\n\t\tthis.forceConstant = 0.001;\r\n\t}\r\n\r\n\tthis.forceConstantSquared = this.forceConstant * this.forceConstant;\r\n\r\n\t// Create a map of vertices first. This is required for the array of\r\n\t// arrays called neighbours which holds, for each vertex, a list of\r\n\t// ints which represents the neighbours cells to that vertex as\r\n\t// the indices into vertexArray\r\n\tfor (var i = 0; i < this.vertexArray.length; i++)\r\n\t{\r\n\t\tvar vertex = this.vertexArray[i];\r\n\t\tthis.cellLocation[i] = [];\r\n\t\t\r\n\t\t// Set up the mapping from array indices to cells\r\n\t\tvar id = mxObjectIdentity.get(vertex);\r\n\t\tthis.indices[id] = i;\r\n\t\tvar bounds = this.getVertexBounds(vertex);\r\n\r\n\t\t// Set the X,Y value of the internal version of the cell to\r\n\t\t// the center point of the vertex for better positioning\r\n\t\tvar width = bounds.width;\r\n\t\tvar height = bounds.height;\r\n\t\t\r\n\t\t// Randomize (0, 0) locations\r\n\t\tvar x = bounds.x;\r\n\t\tvar y = bounds.y;\r\n\t\t\r\n\t\tthis.cellLocation[i][0] = x + width / 2.0;\r\n\t\tthis.cellLocation[i][1] = y + height / 2.0;\r\n\t\tthis.radius[i] = Math.min(width, height);\r\n\t\tthis.radiusSquared[i] = this.radius[i] * this.radius[i];\r\n\t}\r\n\r\n\t// Moves cell location back to top-left from center locations used in\r\n\t// algorithm, resetting the edge points is part of the transaction\r\n\tmodel.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tfor (var i = 0; i < n; i++)\r\n\t\t{\r\n\t\t\tthis.dispX[i] = 0;\r\n\t\t\tthis.dispY[i] = 0;\r\n\t\t\tthis.isMoveable[i] = this.isVertexMovable(this.vertexArray[i]);\r\n\r\n\t\t\t// Get lists of neighbours to all vertices, translate the cells\r\n\t\t\t// obtained in indices into vertexArray and store as an array\r\n\t\t\t// against the orginial cell index\r\n\t\t\tvar edges = this.graph.getConnections(this.vertexArray[i], parent);\r\n\t\t\tvar cells = this.graph.getOpposites(edges, this.vertexArray[i]);\r\n\t\t\tthis.neighbours[i] = [];\r\n\r\n\t\t\tfor (var j = 0; j < cells.length; j++)\r\n\t\t\t{\r\n\t\t\t\t// Resets the points on the traversed edge\r\n\t\t\t\tif (this.resetEdges)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.graph.resetEdge(edges[j]);\r\n\t\t\t\t}\r\n\r\n\t\t\t    if (this.disableEdgeStyle)\r\n\t\t\t    {\r\n\t\t\t    \tthis.setEdgeStyleEnabled(edges[j], false);\r\n\t\t\t    }\r\n\r\n\t\t\t\t// Looks the cell up in the indices dictionary\r\n\t\t\t\tvar id = mxObjectIdentity.get(cells[j]);\r\n\t\t\t\tvar index = this.indices[id];\r\n\r\n\t\t\t\t// Check the connected cell in part of the vertex list to be\r\n\t\t\t\t// acted on by this layout\r\n\t\t\t\tif (index != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.neighbours[i][j] = index;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Else if index of the other cell doesn't correspond to\r\n\t\t\t\t// any cell listed to be acted upon in this layout. Set\r\n\t\t\t\t// the index to the value of this vertex (a dummy self-loop)\r\n\t\t\t\t// so the attraction force of the edge is not calculated\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.neighbours[i][j] = i;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tthis.temperature = this.initialTemp;\r\n\r\n\t\t// If max number of iterations has not been set, guess it\r\n\t\tif (this.maxIterations == 0)\r\n\t\t{\r\n\t\t\tthis.maxIterations = 20 * Math.sqrt(n);\r\n\t\t}\r\n\t\t\r\n\t\t// Main iteration loop\r\n\t\tfor (this.iteration = 0; this.iteration < this.maxIterations; this.iteration++)\r\n\t\t{\r\n\t\t\tif (!this.allowedToRun)\r\n\t\t\t{\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Calculate repulsive forces on all vertices\r\n\t\t\tthis.calcRepulsion();\r\n\r\n\t\t\t// Calculate attractive forces through edges\r\n\t\t\tthis.calcAttraction();\r\n\r\n\t\t\tthis.calcPositions();\r\n\t\t\tthis.reduceTemperature();\r\n\t\t}\r\n\r\n\t\tvar minx = null;\r\n\t\tvar miny = null;\r\n\t\t\r\n\t\tfor (var i = 0; i < this.vertexArray.length; i++)\r\n\t\t{\r\n\t\t\tvar vertex = this.vertexArray[i];\r\n\t\t\t\r\n\t\t\tif (this.isVertexMovable(vertex))\r\n\t\t\t{\r\n\t\t\t\tvar bounds = this.getVertexBounds(vertex);\r\n\t\t\t\t\r\n\t\t\t\tif (bounds != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.cellLocation[i][0] -= bounds.width / 2.0;\r\n\t\t\t\t\tthis.cellLocation[i][1] -= bounds.height / 2.0;\r\n\t\t\t\t\t\r\n\t\t\t\t\tvar x = this.graph.snap(Math.round(this.cellLocation[i][0]));\r\n\t\t\t\t\tvar y = this.graph.snap(Math.round(this.cellLocation[i][1]));\r\n\t\t\t\t\t\r\n\t\t\t\t\tthis.setVertexLocation(vertex, x, y);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (minx == null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tminx = x;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tminx = Math.min(minx, x);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (miny == null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tminy = y;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tminy = Math.min(miny, y);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Modifies the cloned geometries in-place. Not needed\r\n\t\t// to clone the geometries again as we're in the same\r\n\t\t// undoable change.\r\n\t\tvar dx = -(minx || 0) + 1;\r\n\t\tvar dy = -(miny || 0) + 1;\r\n\t\t\r\n\t\tif (initialBounds != null)\r\n\t\t{\r\n\t\t\tdx += initialBounds.x;\r\n\t\t\tdy += initialBounds.y;\r\n\t\t}\r\n\t\t\r\n\t\tthis.graph.moveCells(this.vertexArray, dx, dy);\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tmodel.endUpdate();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: calcPositions\r\n * \r\n * Takes the displacements calculated for each cell and applies them to the\r\n * local cache of cell positions. Limits the displacement to the current\r\n * temperature.\r\n */\r\nmxFastOrganicLayout.prototype.calcPositions = function()\r\n{\r\n\tfor (var index = 0; index < this.vertexArray.length; index++)\r\n\t{\r\n\t\tif (this.isMoveable[index])\r\n\t\t{\r\n\t\t\t// Get the distance of displacement for this node for this\r\n\t\t\t// iteration\r\n\t\t\tvar deltaLength = Math.sqrt(this.dispX[index] * this.dispX[index] +\r\n\t\t\t\tthis.dispY[index] * this.dispY[index]);\r\n\r\n\t\t\tif (deltaLength < 0.001)\r\n\t\t\t{\r\n\t\t\t\tdeltaLength = 0.001;\r\n\t\t\t}\r\n\r\n\t\t\t// Scale down by the current temperature if less than the\r\n\t\t\t// displacement distance\r\n\t\t\tvar newXDisp = this.dispX[index] / deltaLength\r\n\t\t\t\t* Math.min(deltaLength, this.temperature);\r\n\r\n\t\t\tvar newYDisp = this.dispY[index] / deltaLength\r\n\t\t\t\t* Math.min(deltaLength, this.temperature);\r\n\r\n\t\t\t// reset displacements\r\n\t\t\tthis.dispX[index] = 0;\r\n\t\t\tthis.dispY[index] = 0;\r\n\r\n\t\t\t// Update the cached cell locations\r\n\t\t\tthis.cellLocation[index][0] += newXDisp;\r\n\t\t\tthis.cellLocation[index][1] += newYDisp;\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: calcAttraction\r\n * \r\n * Calculates the attractive forces between all laid out nodes linked by\r\n * edges\r\n */\r\nmxFastOrganicLayout.prototype.calcAttraction = function()\r\n{\r\n\t// Check the neighbours of each vertex and calculate the attractive\r\n\t// force of the edge connecting them\r\n\tfor (var i = 0; i < this.vertexArray.length; i++)\r\n\t{\r\n\t\tfor (var k = 0; k < this.neighbours[i].length; k++)\r\n\t\t{\r\n\t\t\t// Get the index of the othe cell in the vertex array\r\n\t\t\tvar j = this.neighbours[i][k];\r\n\t\t\t\r\n\t\t\t// Do not proceed self-loops\r\n\t\t\tif (i != j &&\r\n\t\t\t\tthis.isMoveable[i] &&\r\n\t\t\t\tthis.isMoveable[j])\r\n\t\t\t{\r\n\t\t\t\tvar xDelta = this.cellLocation[i][0] - this.cellLocation[j][0];\r\n\t\t\t\tvar yDelta = this.cellLocation[i][1] - this.cellLocation[j][1];\r\n\r\n\t\t\t\t// The distance between the nodes\r\n\t\t\t\tvar deltaLengthSquared = xDelta * xDelta + yDelta\r\n\t\t\t\t\t\t* yDelta - this.radiusSquared[i] - this.radiusSquared[j];\r\n\r\n\t\t\t\tif (deltaLengthSquared < this.minDistanceLimitSquared)\r\n\t\t\t\t{\r\n\t\t\t\t\tdeltaLengthSquared = this.minDistanceLimitSquared;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tvar deltaLength = Math.sqrt(deltaLengthSquared);\r\n\t\t\t\tvar force = (deltaLengthSquared) / this.forceConstant;\r\n\r\n\t\t\t\tvar displacementX = (xDelta / deltaLength) * force;\r\n\t\t\t\tvar displacementY = (yDelta / deltaLength) * force;\r\n\t\t\t\t\r\n\t\t\t\tthis.dispX[i] -= displacementX;\r\n\t\t\t\tthis.dispY[i] -= displacementY;\r\n\t\t\t\t\r\n\t\t\t\tthis.dispX[j] += displacementX;\r\n\t\t\t\tthis.dispY[j] += displacementY;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: calcRepulsion\r\n * \r\n * Calculates the repulsive forces between all laid out nodes\r\n */\r\nmxFastOrganicLayout.prototype.calcRepulsion = function()\r\n{\r\n\tvar vertexCount = this.vertexArray.length;\r\n\r\n\tfor (var i = 0; i < vertexCount; i++)\r\n\t{\r\n\t\tfor (var j = i; j < vertexCount; j++)\r\n\t\t{\r\n\t\t\t// Exits if the layout is no longer allowed to run\r\n\t\t\tif (!this.allowedToRun)\r\n\t\t\t{\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tif (j != i &&\r\n\t\t\t\tthis.isMoveable[i] &&\r\n\t\t\t\tthis.isMoveable[j])\r\n\t\t\t{\r\n\t\t\t\tvar xDelta = this.cellLocation[i][0] - this.cellLocation[j][0];\r\n\t\t\t\tvar yDelta = this.cellLocation[i][1] - this.cellLocation[j][1];\r\n\r\n\t\t\t\tif (xDelta == 0)\r\n\t\t\t\t{\r\n\t\t\t\t\txDelta = 0.01 + Math.random();\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (yDelta == 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tyDelta = 0.01 + Math.random();\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Distance between nodes\r\n\t\t\t\tvar deltaLength = Math.sqrt((xDelta * xDelta)\r\n\t\t\t\t\t\t+ (yDelta * yDelta));\r\n\t\t\t\tvar deltaLengthWithRadius = deltaLength - this.radius[i]\r\n\t\t\t\t\t\t- this.radius[j];\r\n\r\n\t\t\t\tif (deltaLengthWithRadius > this.maxDistanceLimit)\r\n\t\t\t\t{\r\n\t\t\t\t\t// Ignore vertices too far apart\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (deltaLengthWithRadius < this.minDistanceLimit)\r\n\t\t\t\t{\r\n\t\t\t\t\tdeltaLengthWithRadius = this.minDistanceLimit;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tvar force = this.forceConstantSquared / deltaLengthWithRadius;\r\n\r\n\t\t\t\tvar displacementX = (xDelta / deltaLength) * force;\r\n\t\t\t\tvar displacementY = (yDelta / deltaLength) * force;\r\n\t\t\t\t\r\n\t\t\t\tthis.dispX[i] += displacementX;\r\n\t\t\t\tthis.dispY[i] += displacementY;\r\n\r\n\t\t\t\tthis.dispX[j] -= displacementX;\r\n\t\t\t\tthis.dispY[j] -= displacementY;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: reduceTemperature\r\n * \r\n * Reduces the temperature of the layout from an initial setting in a linear\r\n * fashion to zero.\r\n */\r\nmxFastOrganicLayout.prototype.reduceTemperature = function()\r\n{\r\n\tthis.temperature = this.initialTemp * (1.0 - this.iteration / this.maxIterations);\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxCircleLayout\r\n * \r\n * Extends <mxGraphLayout> to implement a circluar layout for a given radius.\r\n * The vertices do not need to be connected for this layout to work and all\r\n * connections between vertices are not taken into account.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * var layout = new mxCircleLayout(graph);\r\n * layout.execute(graph.getDefaultParent());\r\n * (end)\r\n * \r\n * Constructor: mxCircleLayout\r\n *\r\n * Constructs a new circular layout for the specified radius.\r\n *\r\n * Arguments:\r\n * \r\n * graph - <mxGraph> that contains the cells.\r\n * radius - Optional radius as an int. Default is 100.\r\n */\r\nfunction mxCircleLayout(graph, radius)\r\n{\r\n\tmxGraphLayout.call(this, graph);\r\n\tthis.radius = (radius != null) ? radius : 100;\r\n};\r\n\r\n/**\r\n * Extends mxGraphLayout.\r\n */\r\nmxCircleLayout.prototype = new mxGraphLayout();\r\nmxCircleLayout.prototype.constructor = mxCircleLayout;\r\n\r\n/**\r\n * Variable: radius\r\n * \r\n * Integer specifying the size of the radius. Default is 100.\r\n */\r\nmxCircleLayout.prototype.radius = null;\r\n\r\n/**\r\n * Variable: moveCircle\r\n * \r\n * Boolean specifying if the circle should be moved to the top,\r\n * left corner specified by <x0> and <y0>. Default is false.\r\n */\r\nmxCircleLayout.prototype.moveCircle = false;\r\n\r\n/**\r\n * Variable: x0\r\n * \r\n * Integer specifying the left coordinate of the circle.\r\n * Default is 0.\r\n */\r\nmxCircleLayout.prototype.x0 = 0;\r\n\r\n/**\r\n * Variable: y0\r\n * \r\n * Integer specifying the top coordinate of the circle.\r\n * Default is 0.\r\n */\r\nmxCircleLayout.prototype.y0 = 0;\r\n\r\n/**\r\n * Variable: resetEdges\r\n * \r\n * Specifies if all edge points of traversed edges should be removed.\r\n * Default is true.\r\n */\r\nmxCircleLayout.prototype.resetEdges = true;\r\n\r\n/**\r\n * Variable: disableEdgeStyle\r\n * \r\n * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are\r\n * modified by the result. Default is true.\r\n */\r\nmxCircleLayout.prototype.disableEdgeStyle = true;\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Implements <mxGraphLayout.execute>.\r\n */\r\nmxCircleLayout.prototype.execute = function(parent)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\r\n\t// Moves the vertices to build a circle. Makes sure the\r\n\t// radius is large enough for the vertices to not\r\n\t// overlap\r\n\tmodel.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\t// Gets all vertices inside the parent and finds\r\n\t\t// the maximum dimension of the largest vertex\r\n\t\tvar max = 0;\r\n\t\tvar top = null;\r\n\t\tvar left = null;\r\n\t\tvar vertices = [];\r\n\t\tvar childCount = model.getChildCount(parent);\r\n\t\t\r\n\t\tfor (var i = 0; i < childCount; i++)\r\n\t\t{\r\n\t\t\tvar cell = model.getChildAt(parent, i);\r\n\t\t\t\r\n\t\t\tif (!this.isVertexIgnored(cell))\r\n\t\t\t{\r\n\t\t\t\tvertices.push(cell);\r\n\t\t\t\tvar bounds = this.getVertexBounds(cell);\r\n\t\t\t\t\r\n\t\t\t\tif (top == null)\r\n\t\t\t\t{\r\n\t\t\t\t\ttop = bounds.y;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\ttop = Math.min(top, bounds.y);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (left == null)\r\n\t\t\t\t{\r\n\t\t\t\t\tleft = bounds.x;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tleft = Math.min(left, bounds.x);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tmax = Math.max(max, Math.max(bounds.width, bounds.height));\r\n\t\t\t}\r\n\t\t\telse if (!this.isEdgeIgnored(cell))\r\n\t\t\t{\r\n\t\t\t\t// Resets the points on the traversed edge\r\n\t\t\t\tif (this.resetEdges)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.graph.resetEdge(cell);\r\n\t\t\t\t}\r\n\r\n\t\t\t    if (this.disableEdgeStyle)\r\n\t\t\t    {\r\n\t\t\t    \t\tthis.setEdgeStyleEnabled(cell, false);\r\n\t\t\t    }\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tvar r = this.getRadius(vertices.length, max);\r\n\r\n\t\t// Moves the circle to the specified origin\r\n\t\tif (this.moveCircle)\r\n\t\t{\r\n\t\t\tleft = this.x0;\r\n\t\t\ttop = this.y0;\r\n\t\t}\r\n\t\t\r\n\t\tthis.circle(vertices, r, left, top);\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tmodel.endUpdate();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getRadius\r\n * \r\n * Returns the radius to be used for the given vertex count. Max is the maximum\r\n * width or height of all vertices in the layout.\r\n */\r\nmxCircleLayout.prototype.getRadius = function(count, max)\r\n{\r\n\treturn Math.max(count * max / Math.PI, this.radius);\r\n};\r\n\r\n/**\r\n * Function: circle\r\n * \r\n * Executes the circular layout for the specified array\r\n * of vertices and the given radius. This is called from\r\n * <execute>.\r\n */\r\nmxCircleLayout.prototype.circle = function(vertices, r, left, top)\r\n{\r\n\tvar vertexCount = vertices.length;\r\n\tvar phi = 2 * Math.PI / vertexCount;\r\n\t\r\n\tfor (var i = 0; i < vertexCount; i++)\r\n\t{\r\n\t\tif (this.isVertexMovable(vertices[i]))\r\n\t\t{\r\n\t\t\tthis.setVertexLocation(vertices[i],\r\n\t\t\t\tMath.round(left + r + r * Math.sin(i * phi)),\r\n\t\t\t\tMath.round(top + r + r * Math.cos(i * phi)));\r\n\t\t}\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxParallelEdgeLayout\r\n * \r\n * Extends <mxGraphLayout> for arranging parallel edges. This layout works\r\n * on edges for all pairs of vertices where there is more than one edge\r\n * connecting the latter.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * var layout = new mxParallelEdgeLayout(graph);\r\n * layout.execute(graph.getDefaultParent());\r\n * (end)\r\n * \r\n * To run the layout for the parallel edges of a changed edge only, the\r\n * following code can be used.\r\n * \r\n * (code)\r\n * var layout = new mxParallelEdgeLayout(graph);\r\n * \r\n * graph.addListener(mxEvent.CELL_CONNECTED, function(sender, evt)\r\n * {\r\n *   var model = graph.getModel();\r\n *   var edge = evt.getProperty('edge');\r\n *   var src = model.getTerminal(edge, true);\r\n *   var trg = model.getTerminal(edge, false);\r\n *   \r\n *   layout.isEdgeIgnored = function(edge2)\r\n *   {\r\n *     var src2 = model.getTerminal(edge2, true);\r\n *     var trg2 = model.getTerminal(edge2, false);\r\n *     \r\n *     return !(model.isEdge(edge2) && ((src == src2 && trg == trg2) || (src == trg2 && trg == src2)));\r\n *   };\r\n *   \r\n *   layout.execute(graph.getDefaultParent());\r\n * });\r\n * (end)\r\n * \r\n * Constructor: mxCompactTreeLayout\r\n * \r\n * Constructs a new fast organic layout for the specified graph.\r\n */\r\nfunction mxParallelEdgeLayout(graph)\r\n{\r\n\tmxGraphLayout.call(this, graph);\r\n};\r\n\r\n/**\r\n * Extends mxGraphLayout.\r\n */\r\nmxParallelEdgeLayout.prototype = new mxGraphLayout();\r\nmxParallelEdgeLayout.prototype.constructor = mxParallelEdgeLayout;\r\n\r\n/**\r\n * Variable: spacing\r\n * \r\n * Defines the spacing between the parallels. Default is 20.\r\n */\r\nmxParallelEdgeLayout.prototype.spacing = 20;\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Implements <mxGraphLayout.execute>.\r\n */\r\nmxParallelEdgeLayout.prototype.execute = function(parent)\r\n{\r\n\tvar lookup = this.findParallels(parent);\r\n\t\r\n\tthis.graph.model.beginUpdate();\t\r\n\ttry\r\n\t{\r\n\t\tfor (var i in lookup)\r\n\t\t{\r\n\t\t\tvar parallels = lookup[i];\r\n\r\n\t\t\tif (parallels.length > 1)\r\n\t\t\t{\r\n\t\t\t\tthis.layout(parallels);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tthis.graph.model.endUpdate();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: findParallels\r\n * \r\n * Finds the parallel edges in the given parent.\r\n */\r\nmxParallelEdgeLayout.prototype.findParallels = function(parent)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\tvar lookup = [];\r\n\tvar childCount = model.getChildCount(parent);\r\n\t\r\n\tfor (var i = 0; i < childCount; i++)\r\n\t{\r\n\t\tvar child = model.getChildAt(parent, i);\r\n\t\t\r\n\t\tif (!this.isEdgeIgnored(child))\r\n\t\t{\r\n\t\t\tvar id = this.getEdgeId(child);\r\n\t\t\t\r\n\t\t\tif (id != null)\r\n\t\t\t{\r\n\t\t\t\tif (lookup[id] == null)\r\n\t\t\t\t{\r\n\t\t\t\t\tlookup[id] = [];\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tlookup[id].push(child);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn lookup;\r\n};\r\n\r\n/**\r\n * Function: getEdgeId\r\n * \r\n * Returns a unique ID for the given edge. The id is independent of the\r\n * edge direction and is built using the visible terminal of the given\r\n * edge.\r\n */\r\nmxParallelEdgeLayout.prototype.getEdgeId = function(edge)\r\n{\r\n\tvar view = this.graph.getView();\r\n\t\r\n\t// Cannot used cached visible terminal because this could be triggered in BEFORE_UNDO\r\n\tvar src = view.getVisibleTerminal(edge, true);\r\n\tvar trg = view.getVisibleTerminal(edge, false);\r\n\r\n\tif (src != null && trg != null)\r\n\t{\r\n\t\tsrc = mxObjectIdentity.get(src);\r\n\t\ttrg = mxObjectIdentity.get(trg);\r\n\t\t\r\n\t\treturn (src > trg) ? trg + '-' + src : src + '-' + trg;\r\n\t}\r\n\t\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: layout\r\n * \r\n * Lays out the parallel edges in the given array.\r\n */\r\nmxParallelEdgeLayout.prototype.layout = function(parallels)\r\n{\r\n\tvar edge = parallels[0];\r\n\tvar view = this.graph.getView();\r\n\tvar model = this.graph.getModel();\r\n\tvar src = model.getGeometry(view.getVisibleTerminal(edge, true));\r\n\tvar trg = model.getGeometry(view.getVisibleTerminal(edge, false));\r\n\t\r\n\t// Routes multiple loops\r\n\tif (src == trg)\r\n\t{\r\n\t\tvar x0 = src.x + src.width + this.spacing;\r\n\t\tvar y0 = src.y + src.height / 2;\r\n\r\n\t\tfor (var i = 0; i < parallels.length; i++)\r\n\t\t{\r\n\t\t\tthis.route(parallels[i], x0, y0);\r\n\t\t\tx0 += this.spacing;\r\n\t\t}\r\n\t}\r\n\telse if (src != null && trg != null)\r\n\t{\r\n\t\t// Routes parallel edges\r\n\t\tvar scx = src.x + src.width / 2;\r\n\t\tvar scy = src.y + src.height / 2;\r\n\t\t\r\n\t\tvar tcx = trg.x + trg.width / 2;\r\n\t\tvar tcy = trg.y + trg.height / 2;\r\n\t\t\r\n\t\tvar dx = tcx - scx;\r\n\t\tvar dy = tcy - scy;\r\n\r\n\t\tvar len = Math.sqrt(dx * dx + dy * dy);\r\n\t\t\r\n\t\tif (len > 0)\r\n\t\t{\r\n\t\t\tvar x0 = scx + dx / 2;\r\n\t\t\tvar y0 = scy + dy / 2;\r\n\t\t\t\r\n\t\t\tvar nx = dy * this.spacing / len;\r\n\t\t\tvar ny = dx * this.spacing / len;\r\n\t\t\t\r\n\t\t\tx0 += nx * (parallels.length - 1) / 2;\r\n\t\t\ty0 -= ny * (parallels.length - 1) / 2;\r\n\t\r\n\t\t\tfor (var i = 0; i < parallels.length; i++)\r\n\t\t\t{\r\n\t\t\t\tthis.route(parallels[i], x0, y0);\r\n\t\t\t\tx0 -= nx;\r\n\t\t\t\ty0 += ny;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: route\r\n * \r\n * Routes the given edge via the given point.\r\n */\r\nmxParallelEdgeLayout.prototype.route = function(edge, x, y)\r\n{\r\n\tif (this.graph.isCellMovable(edge))\r\n\t{\r\n\t\tthis.setEdgePoints(edge, [new mxPoint(x, y)]);\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxCompositeLayout\r\n * \r\n * Allows to compose multiple layouts into a single layout. The master layout\r\n * is the layout that handles move operations if another layout than the first\r\n * element in <layouts> should be used. The <master> layout is not executed as\r\n * the code assumes that it is part of <layouts>.\r\n * \r\n * Example:\r\n * (code)\r\n * var first = new mxFastOrganicLayout(graph);\r\n * var second = new mxParallelEdgeLayout(graph);\r\n * var layout = new mxCompositeLayout(graph, [first, second], first);\r\n * layout.execute(graph.getDefaultParent());\r\n * (end)\r\n * \r\n * Constructor: mxCompositeLayout\r\n *\r\n * Constructs a new layout using the given layouts. The graph instance is\r\n * required for creating the transaction that contains all layouts.\r\n *\r\n * Arguments:\r\n * \r\n * graph - Reference to the enclosing <mxGraph>.\r\n * layouts - Array of <mxGraphLayouts>.\r\n * master - Optional layout that handles moves. If no layout is given then\r\n * the first layout of the above array is used to handle moves.\r\n */\r\nfunction mxCompositeLayout(graph, layouts, master)\r\n{\r\n\tmxGraphLayout.call(this, graph);\r\n\tthis.layouts = layouts;\r\n\tthis.master = master;\r\n};\r\n\r\n/**\r\n * Extends mxGraphLayout.\r\n */\r\nmxCompositeLayout.prototype = new mxGraphLayout();\r\nmxCompositeLayout.prototype.constructor = mxCompositeLayout;\r\n\t\r\n/**\r\n * Variable: layouts\r\n * \r\n * Holds the array of <mxGraphLayouts> that this layout contains.\r\n */\r\nmxCompositeLayout.prototype.layouts = null;\r\n\r\n/**\r\n * Variable: layouts\r\n * \r\n * Reference to the <mxGraphLayouts> that handles moves. If this is null\r\n * then the first layout in <layouts> is used.\r\n */\r\nmxCompositeLayout.prototype.master = null;\r\n\r\n/**\r\n * Function: moveCell\r\n * \r\n * Implements <mxGraphLayout.moveCell> by calling move on <master> or the first\r\n * layout in <layouts>.\r\n */\r\nmxCompositeLayout.prototype.moveCell = function(cell, x, y)\r\n{\r\n\tif (this.master != null)\r\n\t{\r\n\t\tthis.master.moveCell.apply(this.master, arguments);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.layouts[0].moveCell.apply(this.layouts[0], arguments);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Implements <mxGraphLayout.execute> by executing all <layouts> in a\r\n * single transaction.\r\n */\r\nmxCompositeLayout.prototype.execute = function(parent)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\t\r\n\tmodel.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tfor (var i = 0; i < this.layouts.length; i++)\r\n\t\t{\r\n\t\t\tthis.layouts[i].execute.apply(this.layouts[i], arguments);\r\n\t\t}\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tmodel.endUpdate();\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxEdgeLabelLayout\r\n * \r\n * Extends <mxGraphLayout> to implement an edge label layout. This layout\r\n * makes use of cell states, which means the graph must be validated in\r\n * a graph view (so that the label bounds are available) before this layout\r\n * can be executed.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * var layout = new mxEdgeLabelLayout(graph);\r\n * layout.execute(graph.getDefaultParent());\r\n * (end)\r\n * \r\n * Constructor: mxEdgeLabelLayout\r\n *\r\n * Constructs a new edge label layout.\r\n *\r\n * Arguments:\r\n * \r\n * graph - <mxGraph> that contains the cells.\r\n */\r\nfunction mxEdgeLabelLayout(graph, radius)\r\n{\r\n\tmxGraphLayout.call(this, graph);\r\n};\r\n\r\n/**\r\n * Extends mxGraphLayout.\r\n */\r\nmxEdgeLabelLayout.prototype = new mxGraphLayout();\r\nmxEdgeLabelLayout.prototype.constructor = mxEdgeLabelLayout;\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Implements <mxGraphLayout.execute>.\r\n */\r\nmxEdgeLabelLayout.prototype.execute = function(parent)\r\n{\r\n\tvar view = this.graph.view;\r\n\tvar model = this.graph.getModel();\r\n\t\r\n\t// Gets all vertices and edges inside the parent\r\n\tvar edges = [];\r\n\tvar vertices = [];\r\n\tvar childCount = model.getChildCount(parent);\r\n\t\r\n\tfor (var i = 0; i < childCount; i++)\r\n\t{\r\n\t\tvar cell = model.getChildAt(parent, i);\r\n\t\tvar state = view.getState(cell);\r\n\t\t\r\n\t\tif (state != null)\r\n\t\t{\r\n\t\t\tif (!this.isVertexIgnored(cell))\r\n\t\t\t{\r\n\t\t\t\tvertices.push(state);\r\n\t\t\t}\r\n\t\t\telse if (!this.isEdgeIgnored(cell))\r\n\t\t\t{\r\n\t\t\t\tedges.push(state);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\tthis.placeLabels(vertices, edges);\r\n};\r\n\r\n/**\r\n * Function: placeLabels\r\n * \r\n * Places the labels of the given edges.\r\n */\r\nmxEdgeLabelLayout.prototype.placeLabels = function(v, e)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\t\r\n\t// Moves the vertices to build a circle. Makes sure the\r\n\t// radius is large enough for the vertices to not\r\n\t// overlap\r\n\tmodel.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tfor (var i = 0; i < e.length; i++)\r\n\t\t{\r\n\t\t\tvar edge = e[i];\r\n\t\t\t\r\n\t\t\tif (edge != null && edge.text != null &&\r\n\t\t\t\tedge.text.boundingBox != null)\r\n\t\t\t{\r\n\t\t\t\tfor (var j = 0; j < v.length; j++)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar vertex = v[j];\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (vertex != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.avoid(edge, vertex);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tmodel.endUpdate();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: avoid\r\n * \r\n * Places the labels of the given edges.\r\n */\r\nmxEdgeLabelLayout.prototype.avoid = function(edge, vertex)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\tvar labRect = edge.text.boundingBox;\r\n\t\r\n\tif (mxUtils.intersects(labRect, vertex))\r\n\t{\r\n\t\tvar dy1 = -labRect.y - labRect.height + vertex.y;\r\n\t\tvar dy2 = -labRect.y + vertex.y + vertex.height;\r\n\t\t\r\n\t\tvar dy = (Math.abs(dy1) < Math.abs(dy2)) ? dy1 : dy2;\r\n\t\t\r\n\t\tvar dx1 = -labRect.x - labRect.width + vertex.x;\r\n\t\tvar dx2 = -labRect.x + vertex.x + vertex.width;\r\n\t\r\n\t\tvar dx = (Math.abs(dx1) < Math.abs(dx2)) ? dx1 : dx2;\r\n\t\t\r\n\t\tif (Math.abs(dx) < Math.abs(dy))\r\n\t\t{\r\n\t\t\tdy = 0;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tdx = 0;\r\n\t\t}\r\n\t\r\n\t\tvar g = model.getGeometry(edge.cell);\r\n\t\t\r\n\t\tif (g != null)\r\n\t\t{\r\n\t\t\tg = g.clone();\r\n\t\t\t\r\n\t\t\tif (g.offset != null)\r\n\t\t\t{\r\n\t\t\t\tg.offset.x += dx;\r\n\t\t\t\tg.offset.y += dy;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tg.offset = new mxPoint(dx, dy);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tmodel.setGeometry(edge.cell, g);\r\n\t\t}\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxGraphAbstractHierarchyCell\r\n * \r\n * An abstraction of an internal hierarchy node or edge\r\n * \r\n * Constructor: mxGraphAbstractHierarchyCell\r\n *\r\n * Constructs a new hierarchical layout algorithm.\r\n *\r\n * Arguments:\r\n * \r\n * graph - Reference to the enclosing <mxGraph>.\r\n * deterministic - Optional boolean that specifies if this layout should be\r\n * deterministic. Default is true.\r\n */\r\nfunction mxGraphAbstractHierarchyCell()\r\n{\r\n\tthis.x = [];\r\n\tthis.y = [];\r\n\tthis.temp = [];\r\n};\r\n\r\n/**\r\n * Variable: maxRank\r\n * \r\n * The maximum rank this cell occupies. Default is -1.\r\n */\r\nmxGraphAbstractHierarchyCell.prototype.maxRank = -1;\r\n\r\n/**\r\n * Variable: minRank\r\n * \r\n * The minimum rank this cell occupies. Default is -1.\r\n */\r\nmxGraphAbstractHierarchyCell.prototype.minRank = -1;\r\n\r\n/**\r\n * Variable: x\r\n * \r\n * The x position of this cell for each layer it occupies\r\n */\r\nmxGraphAbstractHierarchyCell.prototype.x = null;\r\n\r\n/**\r\n * Variable: y\r\n * \r\n * The y position of this cell for each layer it occupies\r\n */\r\nmxGraphAbstractHierarchyCell.prototype.y = null;\r\n\r\n/**\r\n * Variable: width\r\n * \r\n * The width of this cell\r\n */\r\nmxGraphAbstractHierarchyCell.prototype.width = 0;\r\n\r\n/**\r\n * Variable: height\r\n * \r\n * The height of this cell\r\n */\r\nmxGraphAbstractHierarchyCell.prototype.height = 0;\r\n\r\n/**\r\n * Variable: nextLayerConnectedCells\r\n * \r\n * A cached version of the cells this cell connects to on the next layer up\r\n */\r\nmxGraphAbstractHierarchyCell.prototype.nextLayerConnectedCells = null;\r\n\r\n/**\r\n * Variable: previousLayerConnectedCells\r\n * \r\n * A cached version of the cells this cell connects to on the next layer down\r\n */\r\nmxGraphAbstractHierarchyCell.prototype.previousLayerConnectedCells = null;\r\n\r\n/**\r\n * Variable: temp\r\n * \r\n * Temporary variable for general use. Generally, try to avoid\r\n * carrying information between stages. Currently, the longest\r\n * path layering sets temp to the rank position in fixRanks()\r\n * and the crossing reduction uses this. This meant temp couldn't\r\n * be used for hashing the nodes in the model dfs and so hashCode\r\n * was created\r\n */\r\nmxGraphAbstractHierarchyCell.prototype.temp = null;\r\n\r\n/**\r\n * Function: getNextLayerConnectedCells\r\n * \r\n * Returns the cells this cell connects to on the next layer up\r\n */\r\nmxGraphAbstractHierarchyCell.prototype.getNextLayerConnectedCells = function(layer)\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: getPreviousLayerConnectedCells\r\n * \r\n * Returns the cells this cell connects to on the next layer down\r\n */\r\nmxGraphAbstractHierarchyCell.prototype.getPreviousLayerConnectedCells = function(layer)\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: isEdge\r\n * \r\n * Returns whether or not this cell is an edge\r\n */\r\nmxGraphAbstractHierarchyCell.prototype.isEdge = function()\r\n{\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: isVertex\r\n * \r\n * Returns whether or not this cell is a node\r\n */\r\nmxGraphAbstractHierarchyCell.prototype.isVertex = function()\r\n{\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: getGeneralPurposeVariable\r\n * \r\n * Gets the value of temp for the specified layer\r\n */\r\nmxGraphAbstractHierarchyCell.prototype.getGeneralPurposeVariable = function(layer)\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: setGeneralPurposeVariable\r\n * \r\n * Set the value of temp for the specified layer\r\n */\r\nmxGraphAbstractHierarchyCell.prototype.setGeneralPurposeVariable = function(layer, value)\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: setX\r\n * \r\n * Set the value of x for the specified layer\r\n */\r\nmxGraphAbstractHierarchyCell.prototype.setX = function(layer, value)\r\n{\r\n\tif (this.isVertex())\r\n\t{\r\n\t\tthis.x[0] = value;\r\n\t}\r\n\telse if (this.isEdge())\r\n\t{\r\n\t\tthis.x[layer - this.minRank - 1] = value;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getX\r\n * \r\n * Gets the value of x on the specified layer\r\n */\r\nmxGraphAbstractHierarchyCell.prototype.getX = function(layer)\r\n{\r\n\tif (this.isVertex())\r\n\t{\r\n\t\treturn this.x[0];\r\n\t}\r\n\telse if (this.isEdge())\r\n\t{\r\n\t\treturn this.x[layer - this.minRank - 1];\r\n\t}\r\n\r\n\treturn 0.0;\r\n};\r\n\r\n/**\r\n * Function: setY\r\n * \r\n * Set the value of y for the specified layer\r\n */\r\nmxGraphAbstractHierarchyCell.prototype.setY = function(layer, value)\r\n{\r\n\tif (this.isVertex())\r\n\t{\r\n\t\tthis.y[0] = value;\r\n\t}\r\n\telse if (this.isEdge())\r\n\t{\r\n\t\tthis.y[layer -this. minRank - 1] = value;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxGraphHierarchyNode\r\n * \r\n * An abstraction of a hierarchical edge for the hierarchy layout\r\n * \r\n * Constructor: mxGraphHierarchyNode\r\n *\r\n * Constructs an internal node to represent the specified real graph cell\r\n *\r\n * Arguments:\r\n * \r\n * cell - the real graph cell this node represents\r\n */\r\nfunction mxGraphHierarchyNode(cell)\r\n{\r\n\tmxGraphAbstractHierarchyCell.apply(this, arguments);\r\n\tthis.cell = cell;\r\n\tthis.id = mxObjectIdentity.get(cell);\r\n\tthis.connectsAsTarget = [];\r\n\tthis.connectsAsSource = [];\r\n};\r\n\r\n/**\r\n * Extends mxGraphAbstractHierarchyCell.\r\n */\r\nmxGraphHierarchyNode.prototype = new mxGraphAbstractHierarchyCell();\r\nmxGraphHierarchyNode.prototype.constructor = mxGraphHierarchyNode;\r\n\r\n/**\r\n * Variable: cell\r\n * \r\n * The graph cell this object represents.\r\n */\r\nmxGraphHierarchyNode.prototype.cell = null;\r\n\r\n/**\r\n * Variable: id\r\n * \r\n * The object identity of the wrapped cell\r\n */\r\nmxGraphHierarchyNode.prototype.id = null;\r\n\r\n/**\r\n * Variable: connectsAsTarget\r\n * \r\n * Collection of hierarchy edges that have this node as a target\r\n */\r\nmxGraphHierarchyNode.prototype.connectsAsTarget = null;\r\n\r\n/**\r\n * Variable: connectsAsSource\r\n * \r\n * Collection of hierarchy edges that have this node as a source\r\n */\r\nmxGraphHierarchyNode.prototype.connectsAsSource = null;\r\n\r\n/**\r\n * Variable: hashCode\r\n * \r\n * Assigns a unique hashcode for each node. Used by the model dfs instead\r\n * of copying HashSets\r\n */\r\nmxGraphHierarchyNode.prototype.hashCode = false;\r\n\r\n/**\r\n * Function: getRankValue\r\n * \r\n * Returns the integer value of the layer that this node resides in\r\n */\r\nmxGraphHierarchyNode.prototype.getRankValue = function(layer)\r\n{\r\n\treturn this.maxRank;\r\n};\r\n\r\n/**\r\n * Function: getNextLayerConnectedCells\r\n * \r\n * Returns the cells this cell connects to on the next layer up\r\n */\r\nmxGraphHierarchyNode.prototype.getNextLayerConnectedCells = function(layer)\r\n{\r\n\tif (this.nextLayerConnectedCells == null)\r\n\t{\r\n\t\tthis.nextLayerConnectedCells = [];\r\n\t\tthis.nextLayerConnectedCells[0] = [];\r\n\t\t\r\n\t\tfor (var i = 0; i < this.connectsAsTarget.length; i++)\r\n\t\t{\r\n\t\t\tvar edge = this.connectsAsTarget[i];\r\n\r\n\t\t\tif (edge.maxRank == -1 || edge.maxRank == layer + 1)\r\n\t\t\t{\r\n\t\t\t\t// Either edge is not in any rank or\r\n\t\t\t\t// no dummy nodes in edge, add node of other side of edge\r\n\t\t\t\tthis.nextLayerConnectedCells[0].push(edge.source);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\t// Edge spans at least two layers, add edge\r\n\t\t\t\tthis.nextLayerConnectedCells[0].push(edge);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn this.nextLayerConnectedCells[0];\r\n};\r\n\r\n/**\r\n * Function: getPreviousLayerConnectedCells\r\n * \r\n * Returns the cells this cell connects to on the next layer down\r\n */\r\nmxGraphHierarchyNode.prototype.getPreviousLayerConnectedCells = function(layer)\r\n{\r\n\tif (this.previousLayerConnectedCells == null)\r\n\t{\r\n\t\tthis.previousLayerConnectedCells = [];\r\n\t\tthis.previousLayerConnectedCells[0] = [];\r\n\t\t\r\n\t\tfor (var i = 0; i < this.connectsAsSource.length; i++)\r\n\t\t{\r\n\t\t\tvar edge = this.connectsAsSource[i];\r\n\r\n\t\t\tif (edge.minRank == -1 || edge.minRank == layer - 1)\r\n\t\t\t{\r\n\t\t\t\t// No dummy nodes in edge, add node of other side of edge\r\n\t\t\t\tthis.previousLayerConnectedCells[0].push(edge.target);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\t// Edge spans at least two layers, add edge\r\n\t\t\t\tthis.previousLayerConnectedCells[0].push(edge);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn this.previousLayerConnectedCells[0];\r\n};\r\n\r\n/**\r\n * Function: isVertex\r\n * \r\n * Returns true.\r\n */\r\nmxGraphHierarchyNode.prototype.isVertex = function()\r\n{\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: getGeneralPurposeVariable\r\n * \r\n * Gets the value of temp for the specified layer\r\n */\r\nmxGraphHierarchyNode.prototype.getGeneralPurposeVariable = function(layer)\r\n{\r\n\treturn this.temp[0];\r\n};\r\n\r\n/**\r\n * Function: setGeneralPurposeVariable\r\n * \r\n * Set the value of temp for the specified layer\r\n */\r\nmxGraphHierarchyNode.prototype.setGeneralPurposeVariable = function(layer, value)\r\n{\r\n\tthis.temp[0] = value;\r\n};\r\n\r\n/**\r\n * Function: isAncestor\r\n */\r\nmxGraphHierarchyNode.prototype.isAncestor = function(otherNode)\r\n{\r\n\t// Firstly, the hash code of this node needs to be shorter than the\r\n\t// other node\r\n\tif (otherNode != null && this.hashCode != null && otherNode.hashCode != null\r\n\t\t\t&& this.hashCode.length < otherNode.hashCode.length)\r\n\t{\r\n\t\tif (this.hashCode == otherNode.hashCode)\r\n\t\t{\r\n\t\t\treturn true;\r\n\t\t}\r\n\t\t\r\n\t\tif (this.hashCode == null || this.hashCode == null)\r\n\t\t{\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\t\r\n\t\t// Secondly, this hash code must match the start of the other\r\n\t\t// node's hash code. Arrays.equals cannot be used here since\r\n\t\t// the arrays are different length, and we do not want to\r\n\t\t// perform another array copy.\r\n\t\tfor (var i = 0; i < this.hashCode.length; i++)\r\n\t\t{\r\n\t\t\tif (this.hashCode[i] != otherNode.hashCode[i])\r\n\t\t\t{\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn true;\r\n\t}\r\n\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: getCoreCell\r\n * \r\n * Gets the core vertex associated with this wrapper\r\n */\r\nmxGraphHierarchyNode.prototype.getCoreCell = function()\r\n{\r\n\treturn this.cell;\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxGraphHierarchyEdge\r\n * \r\n * An abstraction of a hierarchical edge for the hierarchy layout\r\n * \r\n * Constructor: mxGraphHierarchyEdge\r\n *\r\n * Constructs a hierarchy edge\r\n *\r\n * Arguments:\r\n * \r\n * edges - a list of real graph edges this abstraction represents\r\n */\r\nfunction mxGraphHierarchyEdge(edges)\r\n{\r\n\tmxGraphAbstractHierarchyCell.apply(this, arguments);\r\n\tthis.edges = edges;\r\n\tthis.ids = [];\r\n\t\r\n\tfor (var i = 0; i < edges.length; i++)\r\n\t{\r\n\t\tthis.ids.push(mxObjectIdentity.get(edges[i]));\r\n\t}\r\n};\r\n\r\n/**\r\n * Extends mxGraphAbstractHierarchyCell.\r\n */\r\nmxGraphHierarchyEdge.prototype = new mxGraphAbstractHierarchyCell();\r\nmxGraphHierarchyEdge.prototype.constructor = mxGraphHierarchyEdge;\r\n\r\n/**\r\n * Variable: edges\r\n * \r\n * The graph edge(s) this object represents. Parallel edges are all grouped\r\n * together within one hierarchy edge.\r\n */\r\nmxGraphHierarchyEdge.prototype.edges = null;\r\n\r\n/**\r\n * Variable: ids\r\n * \r\n * The object identities of the wrapped cells\r\n */\r\nmxGraphHierarchyEdge.prototype.ids = null;\r\n\r\n/**\r\n * Variable: source\r\n * \r\n * The node this edge is sourced at\r\n */\r\nmxGraphHierarchyEdge.prototype.source = null;\r\n\r\n/**\r\n * Variable: target\r\n * \r\n * The node this edge targets\r\n */\r\nmxGraphHierarchyEdge.prototype.target = null;\r\n\r\n/**\r\n * Variable: isReversed\r\n * \r\n * Whether or not the direction of this edge has been reversed\r\n * internally to create a DAG for the hierarchical layout\r\n */\r\nmxGraphHierarchyEdge.prototype.isReversed = false;\r\n\r\n/**\r\n * Function: invert\r\n * \r\n * Inverts the direction of this internal edge(s)\r\n */\r\nmxGraphHierarchyEdge.prototype.invert = function(layer)\r\n{\r\n\tvar temp = this.source;\r\n\tthis.source = this.target;\r\n\tthis.target = temp;\r\n\tthis.isReversed = !this.isReversed;\r\n};\r\n\r\n/**\r\n * Function: getNextLayerConnectedCells\r\n * \r\n * Returns the cells this cell connects to on the next layer up\r\n */\r\nmxGraphHierarchyEdge.prototype.getNextLayerConnectedCells = function(layer)\r\n{\r\n\tif (this.nextLayerConnectedCells == null)\r\n\t{\r\n\t\tthis.nextLayerConnectedCells = [];\r\n\t\t\r\n\t\tfor (var i = 0; i < this.temp.length; i++)\r\n\t\t{\r\n\t\t\tthis.nextLayerConnectedCells[i] = [];\r\n\t\t\t\r\n\t\t\tif (i == this.temp.length - 1)\r\n\t\t\t{\r\n\t\t\t\tthis.nextLayerConnectedCells[i].push(this.source);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.nextLayerConnectedCells[i].push(this);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn this.nextLayerConnectedCells[layer - this.minRank - 1];\r\n};\r\n\r\n/**\r\n * Function: getPreviousLayerConnectedCells\r\n * \r\n * Returns the cells this cell connects to on the next layer down\r\n */\r\nmxGraphHierarchyEdge.prototype.getPreviousLayerConnectedCells = function(layer)\r\n{\r\n\tif (this.previousLayerConnectedCells == null)\r\n\t{\r\n\t\tthis.previousLayerConnectedCells = [];\r\n\r\n\t\tfor (var i = 0; i < this.temp.length; i++)\r\n\t\t{\r\n\t\t\tthis.previousLayerConnectedCells[i] = [];\r\n\t\t\t\r\n\t\t\tif (i == 0)\r\n\t\t\t{\r\n\t\t\t\tthis.previousLayerConnectedCells[i].push(this.target);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.previousLayerConnectedCells[i].push(this);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn this.previousLayerConnectedCells[layer - this.minRank - 1];\r\n};\r\n\r\n/**\r\n * Function: isEdge\r\n * \r\n * Returns true.\r\n */\r\nmxGraphHierarchyEdge.prototype.isEdge = function()\r\n{\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: getGeneralPurposeVariable\r\n * \r\n * Gets the value of temp for the specified layer\r\n */\r\nmxGraphHierarchyEdge.prototype.getGeneralPurposeVariable = function(layer)\r\n{\r\n\treturn this.temp[layer - this.minRank - 1];\r\n};\r\n\r\n/**\r\n * Function: setGeneralPurposeVariable\r\n * \r\n * Set the value of temp for the specified layer\r\n */\r\nmxGraphHierarchyEdge.prototype.setGeneralPurposeVariable = function(layer, value)\r\n{\r\n\tthis.temp[layer - this.minRank - 1] = value;\r\n};\r\n\r\n/**\r\n * Function: getCoreCell\r\n * \r\n * Gets the first core edge associated with this wrapper\r\n */\r\nmxGraphHierarchyEdge.prototype.getCoreCell = function()\r\n{\r\n\tif (this.edges != null && this.edges.length > 0)\r\n\t{\r\n\t\treturn this.edges[0];\r\n\t}\r\n\t\r\n\treturn null;\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxGraphHierarchyModel\r\n *\r\n * Internal model of a hierarchical graph. This model stores nodes and edges\r\n * equivalent to the real graph nodes and edges, but also stores the rank of the\r\n * cells, the order within the ranks and the new candidate locations of cells.\r\n * The internal model also reverses edge direction were appropriate , ignores\r\n * self-loop and groups parallels together under one edge object.\r\n *\r\n * Constructor: mxGraphHierarchyModel\r\n *\r\n * Creates an internal ordered graph model using the vertices passed in. If\r\n * there are any, leftward edge need to be inverted in the internal model\r\n *\r\n * Arguments:\r\n *\r\n * graph - the facade describing the graph to be operated on\r\n * vertices - the vertices for this hierarchy\r\n * ordered - whether or not the vertices are already ordered\r\n * deterministic - whether or not this layout should be deterministic on each\r\n * tightenToSource - whether or not to tighten vertices towards the sources\r\n * scanRanksFromSinks - Whether rank assignment is from the sinks or sources.\r\n * usage\r\n */\r\nfunction mxGraphHierarchyModel(layout, vertices, roots, parent, tightenToSource)\r\n{\r\n\tvar graph = layout.getGraph();\r\n\tthis.tightenToSource = tightenToSource;\r\n\tthis.roots = roots;\r\n\tthis.parent = parent;\r\n\r\n\t// map of cells to internal cell needed for second run through\r\n\t// to setup the sink of edges correctly\r\n\tthis.vertexMapper = new mxDictionary();\r\n\tthis.edgeMapper = new mxDictionary();\r\n\tthis.maxRank = 0;\r\n\tvar internalVertices = [];\r\n\r\n\tif (vertices == null)\r\n\t{\r\n\t\tvertices = this.graph.getChildVertices(parent);\r\n\t}\r\n\r\n\tthis.maxRank = this.SOURCESCANSTARTRANK;\r\n\t// map of cells to internal cell needed for second run through\r\n\t// to setup the sink of edges correctly. Guess size by number\r\n\t// of edges is roughly same as number of vertices.\r\n\tthis.createInternalCells(layout, vertices, internalVertices);\r\n\r\n\t// Go through edges set their sink values. Also check the\r\n\t// ordering if and invert edges if necessary\r\n\tfor (var i = 0; i < vertices.length; i++)\r\n\t{\r\n\t\tvar edges = internalVertices[i].connectsAsSource;\r\n\r\n\t\tfor (var j = 0; j < edges.length; j++)\r\n\t\t{\r\n\t\t\tvar internalEdge = edges[j];\r\n\t\t\tvar realEdges = internalEdge.edges;\r\n\r\n\t\t\t// Only need to process the first real edge, since\r\n\t\t\t// all the edges connect to the same other vertex\r\n\t\t\tif (realEdges != null && realEdges.length > 0)\r\n\t\t\t{\r\n\t\t\t\tvar realEdge = realEdges[0];\r\n\t\t\t\tvar targetCell = layout.getVisibleTerminal(\r\n\t\t\t\t\t\trealEdge, false);\r\n\t\t\t\tvar internalTargetCell = this.vertexMapper.get(targetCell);\r\n\r\n\t\t\t\tif (internalVertices[i] == internalTargetCell)\r\n\t\t\t\t{\r\n\t\t\t\t\t// If there are parallel edges going between two vertices and not all are in the same direction\r\n\t\t\t\t\t// you can have navigated across one direction when doing the cycle reversal that isn't the same\r\n\t\t\t\t\t// direction as the first real edge in the array above. When that happens the if above catches\r\n\t\t\t\t\t// that and we correct the target cell before continuing.\r\n\t\t\t\t\t// This branch only detects this single case\r\n\t\t\t\t\ttargetCell = layout.getVisibleTerminal(\r\n\t\t\t\t\t\t\trealEdge, true);\r\n\t\t\t\t\tinternalTargetCell = this.vertexMapper.get(targetCell);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (internalTargetCell != null\r\n\t\t\t\t\t\t&& internalVertices[i] != internalTargetCell)\r\n\t\t\t\t{\r\n\t\t\t\t\tinternalEdge.target = internalTargetCell;\r\n\r\n\t\t\t\t\tif (internalTargetCell.connectsAsTarget.length == 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tinternalTargetCell.connectsAsTarget = [];\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif (mxUtils.indexOf(internalTargetCell.connectsAsTarget, internalEdge) < 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tinternalTargetCell.connectsAsTarget.push(internalEdge);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Use the temp variable in the internal nodes to mark this\r\n\t\t// internal vertex as having been visited.\r\n\t\tinternalVertices[i].temp[0] = 1;\r\n\t}\r\n};\r\n\r\n/**\r\n * Variable: maxRank\r\n *\r\n * Stores the largest rank number allocated\r\n */\r\nmxGraphHierarchyModel.prototype.maxRank = null;\r\n\r\n/**\r\n * Variable: vertexMapper\r\n *\r\n * Map from graph vertices to internal model nodes.\r\n */\r\nmxGraphHierarchyModel.prototype.vertexMapper = null;\r\n\r\n/**\r\n * Variable: edgeMapper\r\n *\r\n * Map from graph edges to internal model edges\r\n */\r\nmxGraphHierarchyModel.prototype.edgeMapper = null;\r\n\r\n/**\r\n * Variable: ranks\r\n *\r\n * Mapping from rank number to actual rank\r\n */\r\nmxGraphHierarchyModel.prototype.ranks = null;\r\n\r\n/**\r\n * Variable: roots\r\n *\r\n * Store of roots of this hierarchy model, these are real graph cells, not\r\n * internal cells\r\n */\r\nmxGraphHierarchyModel.prototype.roots = null;\r\n\r\n/**\r\n * Variable: parent\r\n *\r\n * The parent cell whose children are being laid out\r\n */\r\nmxGraphHierarchyModel.prototype.parent = null;\r\n\r\n/**\r\n * Variable: dfsCount\r\n *\r\n * Count of the number of times the ancestor dfs has been used.\r\n */\r\nmxGraphHierarchyModel.prototype.dfsCount = 0;\r\n\r\n/**\r\n * Variable: SOURCESCANSTARTRANK\r\n *\r\n * High value to start source layering scan rank value from.\r\n */\r\nmxGraphHierarchyModel.prototype.SOURCESCANSTARTRANK = 100000000;\r\n\r\n/**\r\n * Variable: tightenToSource\r\n *\r\n * Whether or not to tighten the assigned ranks of vertices up towards\r\n * the source cells.\r\n */\r\nmxGraphHierarchyModel.prototype.tightenToSource = false;\r\n\r\n/**\r\n * Function: createInternalCells\r\n *\r\n * Creates all edges in the internal model\r\n *\r\n * Parameters:\r\n *\r\n * layout - Reference to the <mxHierarchicalLayout> algorithm.\r\n * vertices - Array of <mxCells> that represent the vertices whom are to\r\n * have an internal representation created.\r\n * internalVertices - The array of <mxGraphHierarchyNodes> to have their\r\n * information filled in using the real vertices.\r\n */\r\nmxGraphHierarchyModel.prototype.createInternalCells = function(layout, vertices, internalVertices)\r\n{\r\n\tvar graph = layout.getGraph();\r\n\r\n\t// Create internal edges\r\n\tfor (var i = 0; i < vertices.length; i++)\r\n\t{\r\n\t\tinternalVertices[i] = new mxGraphHierarchyNode(vertices[i]);\r\n\t\tthis.vertexMapper.put(vertices[i], internalVertices[i]);\r\n\r\n\t\t// If the layout is deterministic, order the cells\r\n\t\t//List outgoingCells = graph.getNeighbours(vertices[i], deterministic);\r\n\t\tvar conns = layout.getEdges(vertices[i]);\r\n\t\tinternalVertices[i].connectsAsSource = [];\r\n\r\n\t\t// Create internal edges, but don't do any rank assignment yet\r\n\t\t// First use the information from the greedy cycle remover to\r\n\t\t// invert the leftward edges internally\r\n\t\tfor (var j = 0; j < conns.length; j++)\r\n\t\t{\r\n\t\t\tvar cell = layout.getVisibleTerminal(conns[j], false);\r\n\r\n\t\t\t// Looking for outgoing edges only\r\n\t\t\tif (cell != vertices[i] && layout.graph.model.isVertex(cell) &&\r\n\t\t\t\t\t!layout.isVertexIgnored(cell))\r\n\t\t\t{\r\n\t\t\t\t// We process all edge between this source and its targets\r\n\t\t\t\t// If there are edges going both ways, we need to collect\r\n\t\t\t\t// them all into one internal edges to avoid looping problems\r\n\t\t\t\t// later. We assume this direction (source -> target) is the \r\n\t\t\t\t// natural direction if at least half the edges are going in\r\n\t\t\t\t// that direction.\r\n\r\n\t\t\t\t// The check below for edges[0] being in the vertex mapper is\r\n\t\t\t\t// in case we've processed this the other way around\r\n\t\t\t\t// (target -> source) and the number of edges in each direction\r\n\t\t\t\t// are the same. All the graph edges will have been assigned to\r\n\t\t\t\t// an internal edge going the other way, so we don't want to \r\n\t\t\t\t// process them again\r\n\t\t\t\tvar undirectedEdges = layout.getEdgesBetween(vertices[i],\r\n\t\t\t\t\t\tcell, false);\r\n\t\t\t\tvar directedEdges = layout.getEdgesBetween(vertices[i],\r\n\t\t\t\t\t\tcell, true);\r\n\t\t\t\t\r\n\t\t\t\tif (undirectedEdges != null &&\r\n\t\t\t\t\t\tundirectedEdges.length > 0 &&\r\n\t\t\t\t\t\tthis.edgeMapper.get(undirectedEdges[0]) == null &&\r\n\t\t\t\t\t\tdirectedEdges.length * 2 >= undirectedEdges.length)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar internalEdge = new mxGraphHierarchyEdge(undirectedEdges);\r\n\r\n\t\t\t\t\tfor (var k = 0; k < undirectedEdges.length; k++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar edge = undirectedEdges[k];\r\n\t\t\t\t\t\tthis.edgeMapper.put(edge, internalEdge);\r\n\r\n\t\t\t\t\t\t// Resets all point on the edge and disables the edge style\r\n\t\t\t\t\t\t// without deleting it from the cell style\r\n\t\t\t\t\t\tgraph.resetEdge(edge);\r\n\r\n\t\t\t\t\t    if (layout.disableEdgeStyle)\r\n\t\t\t\t\t    {\r\n\t\t\t\t\t    \tlayout.setEdgeStyleEnabled(edge, false);\r\n\t\t\t\t\t    \tlayout.setOrthogonalEdge(edge,true);\r\n\t\t\t\t\t    }\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tinternalEdge.source = internalVertices[i];\r\n\r\n\t\t\t\t\tif (mxUtils.indexOf(internalVertices[i].connectsAsSource, internalEdge) < 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tinternalVertices[i].connectsAsSource.push(internalEdge);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Ensure temp variable is cleared from any previous use\r\n\t\tinternalVertices[i].temp[0] = 0;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: initialRank\r\n *\r\n * Basic determination of minimum layer ranking by working from from sources\r\n * or sinks and working through each node in the relevant edge direction.\r\n * Starting at the sinks is basically a longest path layering algorithm.\r\n*/\r\nmxGraphHierarchyModel.prototype.initialRank = function()\r\n{\r\n\tvar startNodes = [];\r\n\r\n\tif (this.roots != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.roots.length; i++)\r\n\t\t{\r\n\t\t\tvar internalNode = this.vertexMapper.get(this.roots[i]);\r\n\r\n\t\t\tif (internalNode != null)\r\n\t\t\t{\r\n\t\t\t\tstartNodes.push(internalNode);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tvar internalNodes = this.vertexMapper.getValues();\r\n\t\r\n\tfor (var i=0; i < internalNodes.length; i++)\r\n\t{\r\n\t\t// Mark the node as not having had a layer assigned\r\n\t\tinternalNodes[i].temp[0] = -1;\r\n\t}\r\n\r\n\tvar startNodesCopy = startNodes.slice();\r\n\r\n\twhile (startNodes.length > 0)\r\n\t{\r\n\t\tvar internalNode = startNodes[0];\r\n\t\tvar layerDeterminingEdges;\r\n\t\tvar edgesToBeMarked;\r\n\r\n\t\tlayerDeterminingEdges = internalNode.connectsAsTarget;\r\n\t\tedgesToBeMarked = internalNode.connectsAsSource;\r\n\r\n\t\t// flag to keep track of whether or not all layer determining\r\n\t\t// edges have been scanned\r\n\t\tvar allEdgesScanned = true;\r\n\r\n\t\t// Work out the layer of this node from the layer determining\r\n\t\t// edges. The minimum layer number of any node connected by one of\r\n\t\t// the layer determining edges variable\r\n\t\tvar minimumLayer = this.SOURCESCANSTARTRANK;\r\n\r\n\t\tfor (var i = 0; i < layerDeterminingEdges.length; i++)\r\n\t\t{\r\n\t\t\tvar internalEdge = layerDeterminingEdges[i];\r\n\r\n\t\t\tif (internalEdge.temp[0] == 5270620)\r\n\t\t\t{\r\n\t\t\t\t// This edge has been scanned, get the layer of the\r\n\t\t\t\t// node on the other end\r\n\t\t\t\tvar otherNode = internalEdge.source;\r\n\t\t\t\tminimumLayer = Math.min(minimumLayer, otherNode.temp[0] - 1);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tallEdgesScanned = false;\r\n\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// If all edge have been scanned, assign the layer, mark all\r\n\t\t// edges in the other direction and remove from the nodes list\r\n\t\tif (allEdgesScanned)\r\n\t\t{\r\n\t\t\tinternalNode.temp[0] = minimumLayer;\r\n\t\t\tthis.maxRank = Math.min(this.maxRank, minimumLayer);\r\n\r\n\t\t\tif (edgesToBeMarked != null)\r\n\t\t\t{\r\n\t\t\t\tfor (var i = 0; i < edgesToBeMarked.length; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar internalEdge = edgesToBeMarked[i];\r\n\r\n\t\t\t\t\t// Assign unique stamp ( y/m/d/h )\r\n\t\t\t\t\tinternalEdge.temp[0] = 5270620;\r\n\r\n\t\t\t\t\t// Add node on other end of edge to LinkedList of\r\n\t\t\t\t\t// nodes to be analysed\r\n\t\t\t\t\tvar otherNode = internalEdge.target;\r\n\r\n\t\t\t\t\t// Only add node if it hasn't been assigned a layer\r\n\t\t\t\t\tif (otherNode.temp[0] == -1)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tstartNodes.push(otherNode);\r\n\r\n\t\t\t\t\t\t// Mark this other node as neither being\r\n\t\t\t\t\t\t// unassigned nor assigned so it isn't\r\n\t\t\t\t\t\t// added to this list again, but it's\r\n\t\t\t\t\t\t// layer isn't used in any calculation.\r\n\t\t\t\t\t\totherNode.temp[0] = -2;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tstartNodes.shift();\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Not all the edges have been scanned, get to the back of\r\n\t\t\t// the class and put the dunces cap on\r\n\t\t\tvar removedCell = startNodes.shift();\r\n\t\t\tstartNodes.push(internalNode);\r\n\r\n\t\t\tif (removedCell == internalNode && startNodes.length == 1)\r\n\t\t\t{\r\n\t\t\t\t// This is an error condition, we can't get out of\r\n\t\t\t\t// this loop. It could happen for more than one node\r\n\t\t\t\t// but that's a lot harder to detect. Log the error\r\n\t\t\t\t// TODO make log comment\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// Normalize the ranks down from their large starting value to place\r\n\t// at least 1 sink on layer 0\r\n\tfor (var i=0; i < internalNodes.length; i++)\r\n\t{\r\n\t\t// Mark the node as not having had a layer assigned\r\n\t\tinternalNodes[i].temp[0] -= this.maxRank;\r\n\t}\r\n\t\r\n\t// Tighten the rank 0 nodes as far as possible\r\n\tfor ( var i = 0; i < startNodesCopy.length; i++)\r\n\t{\r\n\t\tvar internalNode = startNodesCopy[i];\r\n\t\tvar currentMaxLayer = 0;\r\n\t\tvar layerDeterminingEdges = internalNode.connectsAsSource;\r\n\r\n\t\tfor ( var j = 0; j < layerDeterminingEdges.length; j++)\r\n\t\t{\r\n\t\t\tvar internalEdge = layerDeterminingEdges[j];\r\n\t\t\tvar otherNode = internalEdge.target;\r\n\t\t\tinternalNode.temp[0] = Math.max(currentMaxLayer,\r\n\t\t\t\t\totherNode.temp[0] + 1);\r\n\t\t\tcurrentMaxLayer = internalNode.temp[0];\r\n\t\t}\r\n\t}\r\n\t\r\n\t// Reset the maxRank to that which would be expected for a from-sink\r\n\t// scan\r\n\tthis.maxRank = this.SOURCESCANSTARTRANK - this.maxRank;\r\n};\r\n\r\n/**\r\n * Function: fixRanks\r\n *\r\n * Fixes the layer assignments to the values stored in the nodes. Also needs\r\n * to create dummy nodes for edges that cross layers.\r\n */\r\nmxGraphHierarchyModel.prototype.fixRanks = function()\r\n{\r\n\tvar rankList = [];\r\n\tthis.ranks = [];\r\n\r\n\tfor (var i = 0; i < this.maxRank + 1; i++)\r\n\t{\r\n\t\trankList[i] = [];\r\n\t\tthis.ranks[i] = rankList[i];\r\n\t}\r\n\r\n\t// Perform a DFS to obtain an initial ordering for each rank.\r\n\t// Without doing this you would end up having to process\r\n\t// crossings for a standard tree.\r\n\tvar rootsArray = null;\r\n\r\n\tif (this.roots != null)\r\n\t{\r\n\t\tvar oldRootsArray = this.roots;\r\n\t\trootsArray = [];\r\n\r\n\t\tfor (var i = 0; i < oldRootsArray.length; i++)\r\n\t\t{\r\n\t\t\tvar cell = oldRootsArray[i];\r\n\t\t\tvar internalNode = this.vertexMapper.get(cell);\r\n\t\t\trootsArray[i] = internalNode;\r\n\t\t}\r\n\t}\r\n\r\n\tthis.visit(function(parent, node, edge, layer, seen)\r\n\t{\r\n\t\tif (seen == 0 && node.maxRank < 0 && node.minRank < 0)\r\n\t\t{\r\n\t\t\trankList[node.temp[0]].push(node);\r\n\t\t\tnode.maxRank = node.temp[0];\r\n\t\t\tnode.minRank = node.temp[0];\r\n\r\n\t\t\t// Set temp[0] to the nodes position in the rank\r\n\t\t\tnode.temp[0] = rankList[node.maxRank].length - 1;\r\n\t\t}\r\n\r\n\t\tif (parent != null && edge != null)\r\n\t\t{\r\n\t\t\tvar parentToCellRankDifference = parent.maxRank - node.maxRank;\r\n\r\n\t\t\tif (parentToCellRankDifference > 1)\r\n\t\t\t{\r\n\t\t\t\t// There are ranks in between the parent and current cell\r\n\t\t\t\tedge.maxRank = parent.maxRank;\r\n\t\t\t\tedge.minRank = node.maxRank;\r\n\t\t\t\tedge.temp = [];\r\n\t\t\t\tedge.x = [];\r\n\t\t\t\tedge.y = [];\r\n\r\n\t\t\t\tfor (var i = edge.minRank + 1; i < edge.maxRank; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\t// The connecting edge must be added to the\r\n\t\t\t\t\t// appropriate ranks\r\n\t\t\t\t\trankList[i].push(edge);\r\n\t\t\t\t\tedge.setGeneralPurposeVariable(i, rankList[i]\r\n\t\t\t\t\t\t\t.length - 1);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}, rootsArray, false, null);\r\n};\r\n\r\n/**\r\n * Function: visit\r\n *\r\n * A depth first search through the internal heirarchy model.\r\n *\r\n * Parameters:\r\n *\r\n * visitor - The visitor function pattern to be called for each node.\r\n * trackAncestors - Whether or not the search is to keep track all nodes\r\n * directly above this one in the search path.\r\n */\r\nmxGraphHierarchyModel.prototype.visit = function(visitor, dfsRoots, trackAncestors, seenNodes)\r\n{\r\n\t// Run dfs through on all roots\r\n\tif (dfsRoots != null)\r\n\t{\r\n\t\tfor (var i = 0; i < dfsRoots.length; i++)\r\n\t\t{\r\n\t\t\tvar internalNode = dfsRoots[i];\r\n\r\n\t\t\tif (internalNode != null)\r\n\t\t\t{\r\n\t\t\t\tif (seenNodes == null)\r\n\t\t\t\t{\r\n\t\t\t\t\tseenNodes = new Object();\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (trackAncestors)\r\n\t\t\t\t{\r\n\t\t\t\t\t// Set up hash code for root\r\n\t\t\t\t\tinternalNode.hashCode = [];\r\n\t\t\t\t\tinternalNode.hashCode[0] = this.dfsCount;\r\n\t\t\t\t\tinternalNode.hashCode[1] = i;\r\n\t\t\t\t\tthis.extendedDfs(null, internalNode, null, visitor, seenNodes,\r\n\t\t\t\t\t\t\tinternalNode.hashCode, i, 0);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.dfs(null, internalNode, null, visitor, seenNodes, 0);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.dfsCount++;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: dfs\r\n *\r\n * Performs a depth first search on the internal hierarchy model\r\n *\r\n * Parameters:\r\n *\r\n * parent - the parent internal node of the current internal node\r\n * root - the current internal node\r\n * connectingEdge - the internal edge connecting the internal node and the parent\r\n * internal node, if any\r\n * visitor - the visitor pattern to be called for each node\r\n * seen - a set of all nodes seen by this dfs a set of all of the\r\n * ancestor node of the current node\r\n * layer - the layer on the dfs tree ( not the same as the model ranks )\r\n */\r\nmxGraphHierarchyModel.prototype.dfs = function(parent, root, connectingEdge, visitor, seen, layer)\r\n{\r\n\tif (root != null)\r\n\t{\r\n\t\tvar rootId = root.id;\r\n\r\n\t\tif (seen[rootId] == null)\r\n\t\t{\r\n\t\t\tseen[rootId] = root;\r\n\t\t\tvisitor(parent, root, connectingEdge, layer, 0);\r\n\r\n\t\t\t// Copy the connects as source list so that visitors\r\n\t\t\t// can change the original for edge direction inversions\r\n\t\t\tvar outgoingEdges = root.connectsAsSource.slice();\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i< outgoingEdges.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar internalEdge = outgoingEdges[i];\r\n\t\t\t\tvar targetNode = internalEdge.target;\r\n\r\n\t\t\t\t// Root check is O(|roots|)\r\n\t\t\t\tthis.dfs(root, targetNode, internalEdge, visitor, seen,\r\n\t\t\t\t\t\tlayer + 1);\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Use the int field to indicate this node has been seen\r\n\t\t\tvisitor(parent, root, connectingEdge, layer, 1);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: extendedDfs\r\n *\r\n * Performs a depth first search on the internal hierarchy model. This dfs\r\n * extends the default version by keeping track of cells ancestors, but it\r\n * should be only used when necessary because of it can be computationally\r\n * intensive for deep searches.\r\n *\r\n * Parameters:\r\n *\r\n * parent - the parent internal node of the current internal node\r\n * root - the current internal node\r\n * connectingEdge - the internal edge connecting the internal node and the parent\r\n * internal node, if any\r\n * visitor - the visitor pattern to be called for each node\r\n * seen - a set of all nodes seen by this dfs\r\n * ancestors - the parent hash code\r\n * childHash - the new hash code for this node\r\n * layer - the layer on the dfs tree ( not the same as the model ranks )\r\n */\r\nmxGraphHierarchyModel.prototype.extendedDfs = function(parent, root, connectingEdge, visitor, seen, ancestors, childHash, layer)\r\n{\r\n\t// Explanation of custom hash set. Previously, the ancestors variable\r\n\t// was passed through the dfs as a HashSet. The ancestors were copied\r\n\t// into a new HashSet and when the new child was processed it was also\r\n\t// added to the set. If the current node was in its ancestor list it\r\n\t// meant there is a cycle in the graph and this information is passed\r\n\t// to the visitor.visit() in the seen parameter. The HashSet clone was\r\n\t// very expensive on CPU so a custom hash was developed using primitive\r\n\t// types. temp[] couldn't be used so hashCode[] was added to each node.\r\n\t// Each new child adds another int to the array, copying the prefix\r\n\t// from its parent. Child of the same parent add different ints (the\r\n\t// limit is therefore 2^32 children per parent...). If a node has a\r\n\t// child with the hashCode already set then the child code is compared\r\n\t// to the same portion of the current nodes array. If they match there\r\n\t// is a loop.\r\n\t// Note that the basic mechanism would only allow for 1 use of this\r\n\t// functionality, so the root nodes have two ints. The second int is\r\n\t// incremented through each node root and the first is incremented\r\n\t// through each run of the dfs algorithm (therefore the dfs is not\r\n\t// thread safe). The hash code of each node is set if not already set,\r\n\t// or if the first int does not match that of the current run.\r\n\tif (root != null)\r\n\t{\r\n\t\tif (parent != null)\r\n\t\t{\r\n\t\t\t// Form this nodes hash code if necessary, that is, if the\r\n\t\t\t// hashCode variable has not been initialized or if the\r\n\t\t\t// start of the parent hash code does not equal the start of\r\n\t\t\t// this nodes hash code, indicating the code was set on a\r\n\t\t\t// previous run of this dfs.\r\n\t\t\tif (root.hashCode == null ||\r\n\t\t\t\troot.hashCode[0] != parent.hashCode[0])\r\n\t\t\t{\r\n\t\t\t\tvar hashCodeLength = parent.hashCode.length + 1;\r\n\t\t\t\troot.hashCode = parent.hashCode.slice();\r\n\t\t\t\troot.hashCode[hashCodeLength - 1] = childHash;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tvar rootId = root.id;\r\n\r\n\t\tif (seen[rootId] == null)\r\n\t\t{\r\n\t\t\tseen[rootId] = root;\r\n\t\t\tvisitor(parent, root, connectingEdge, layer, 0);\r\n\r\n\t\t\t// Copy the connects as source list so that visitors\r\n\t\t\t// can change the original for edge direction inversions\r\n\t\t\tvar outgoingEdges = root.connectsAsSource.slice();\r\n\r\n\t\t\tfor (var i = 0; i < outgoingEdges.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar internalEdge = outgoingEdges[i];\r\n\t\t\t\tvar targetNode = internalEdge.target;\r\n\r\n\t\t\t\t// Root check is O(|roots|)\r\n\t\t\t\tthis.extendedDfs(root, targetNode, internalEdge, visitor, seen,\r\n\t\t\t\t\t\troot.hashCode, i, layer + 1);\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Use the int field to indicate this node has been seen\r\n\t\t\tvisitor(parent, root, connectingEdge, layer, 1);\r\n\t\t}\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2018, JGraph Ltd\r\n * Copyright (c) 2006-2018, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxSwimlaneModel\r\n *\r\n * Internal model of a hierarchical graph. This model stores nodes and edges\r\n * equivalent to the real graph nodes and edges, but also stores the rank of the\r\n * cells, the order within the ranks and the new candidate locations of cells.\r\n * The internal model also reverses edge direction were appropriate , ignores\r\n * self-loop and groups parallels together under one edge object.\r\n *\r\n * Constructor: mxSwimlaneModel\r\n *\r\n * Creates an internal ordered graph model using the vertices passed in. If\r\n * there are any, leftward edge need to be inverted in the internal model\r\n *\r\n * Arguments:\r\n *\r\n * graph - the facade describing the graph to be operated on\r\n * vertices - the vertices for this hierarchy\r\n * ordered - whether or not the vertices are already ordered\r\n * deterministic - whether or not this layout should be deterministic on each\r\n * tightenToSource - whether or not to tighten vertices towards the sources\r\n * scanRanksFromSinks - Whether rank assignment is from the sinks or sources.\r\n * usage\r\n */\r\nfunction mxSwimlaneModel(layout, vertices, roots, parent, tightenToSource)\r\n{\r\n\tvar graph = layout.getGraph();\r\n\tthis.tightenToSource = tightenToSource;\r\n\tthis.roots = roots;\r\n\tthis.parent = parent;\r\n\r\n\t// map of cells to internal cell needed for second run through\r\n\t// to setup the sink of edges correctly\r\n\tthis.vertexMapper = new mxDictionary();\r\n\tthis.edgeMapper = new mxDictionary();\r\n\tthis.maxRank = 0;\r\n\tvar internalVertices = [];\r\n\r\n\tif (vertices == null)\r\n\t{\r\n\t\tvertices = this.graph.getChildVertices(parent);\r\n\t}\r\n\r\n\tthis.maxRank = this.SOURCESCANSTARTRANK;\r\n\t// map of cells to internal cell needed for second run through\r\n\t// to setup the sink of edges correctly. Guess size by number\r\n\t// of edges is roughly same as number of vertices.\r\n\tthis.createInternalCells(layout, vertices, internalVertices);\r\n\r\n\t// Go through edges set their sink values. Also check the\r\n\t// ordering if and invert edges if necessary\r\n\tfor (var i = 0; i < vertices.length; i++)\r\n\t{\r\n\t\tvar edges = internalVertices[i].connectsAsSource;\r\n\r\n\t\tfor (var j = 0; j < edges.length; j++)\r\n\t\t{\r\n\t\t\tvar internalEdge = edges[j];\r\n\t\t\tvar realEdges = internalEdge.edges;\r\n\r\n\t\t\t// Only need to process the first real edge, since\r\n\t\t\t// all the edges connect to the same other vertex\r\n\t\t\tif (realEdges != null && realEdges.length > 0)\r\n\t\t\t{\r\n\t\t\t\tvar realEdge = realEdges[0];\r\n\t\t\t\tvar targetCell = layout.getVisibleTerminal(\r\n\t\t\t\t\t\trealEdge, false);\r\n\t\t\t\tvar internalTargetCell = this.vertexMapper.get(targetCell);\r\n\r\n\t\t\t\tif (internalVertices[i] == internalTargetCell)\r\n\t\t\t\t{\r\n\t\t\t\t\t// If there are parallel edges going between two vertices and not all are in the same direction\r\n\t\t\t\t\t// you can have navigated across one direction when doing the cycle reversal that isn't the same\r\n\t\t\t\t\t// direction as the first real edge in the array above. When that happens the if above catches\r\n\t\t\t\t\t// that and we correct the target cell before continuing.\r\n\t\t\t\t\t// This branch only detects this single case\r\n\t\t\t\t\ttargetCell = layout.getVisibleTerminal(\r\n\t\t\t\t\t\t\trealEdge, true);\r\n\t\t\t\t\tinternalTargetCell = this.vertexMapper.get(targetCell);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (internalTargetCell != null\r\n\t\t\t\t\t\t&& internalVertices[i] != internalTargetCell)\r\n\t\t\t\t{\r\n\t\t\t\t\tinternalEdge.target = internalTargetCell;\r\n\r\n\t\t\t\t\tif (internalTargetCell.connectsAsTarget.length == 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tinternalTargetCell.connectsAsTarget = [];\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif (mxUtils.indexOf(internalTargetCell.connectsAsTarget, internalEdge) < 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tinternalTargetCell.connectsAsTarget.push(internalEdge);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Use the temp variable in the internal nodes to mark this\r\n\t\t// internal vertex as having been visited.\r\n\t\tinternalVertices[i].temp[0] = 1;\r\n\t}\r\n};\r\n\r\n/**\r\n * Variable: maxRank\r\n *\r\n * Stores the largest rank number allocated\r\n */\r\nmxSwimlaneModel.prototype.maxRank = null;\r\n\r\n/**\r\n * Variable: vertexMapper\r\n *\r\n * Map from graph vertices to internal model nodes.\r\n */\r\nmxSwimlaneModel.prototype.vertexMapper = null;\r\n\r\n/**\r\n * Variable: edgeMapper\r\n *\r\n * Map from graph edges to internal model edges\r\n */\r\nmxSwimlaneModel.prototype.edgeMapper = null;\r\n\r\n/**\r\n * Variable: ranks\r\n *\r\n * Mapping from rank number to actual rank\r\n */\r\nmxSwimlaneModel.prototype.ranks = null;\r\n\r\n/**\r\n * Variable: roots\r\n *\r\n * Store of roots of this hierarchy model, these are real graph cells, not\r\n * internal cells\r\n */\r\nmxSwimlaneModel.prototype.roots = null;\r\n\r\n/**\r\n * Variable: parent\r\n *\r\n * The parent cell whose children are being laid out\r\n */\r\nmxSwimlaneModel.prototype.parent = null;\r\n\r\n/**\r\n * Variable: dfsCount\r\n *\r\n * Count of the number of times the ancestor dfs has been used.\r\n */\r\nmxSwimlaneModel.prototype.dfsCount = 0;\r\n\r\n/**\r\n * Variable: SOURCESCANSTARTRANK\r\n *\r\n * High value to start source layering scan rank value from.\r\n */\r\nmxSwimlaneModel.prototype.SOURCESCANSTARTRANK = 100000000;\r\n\r\n/**\r\n * Variable: tightenToSource\r\n *\r\n * Whether or not to tighten the assigned ranks of vertices up towards\r\n * the source cells.\r\n */\r\nmxSwimlaneModel.prototype.tightenToSource = false;\r\n\r\n/**\r\n * Variable: ranksPerGroup\r\n *\r\n * An array of the number of ranks within each swimlane\r\n */\r\nmxSwimlaneModel.prototype.ranksPerGroup = null;\r\n\r\n/**\r\n * Function: createInternalCells\r\n *\r\n * Creates all edges in the internal model\r\n *\r\n * Parameters:\r\n *\r\n * layout - Reference to the <mxHierarchicalLayout> algorithm.\r\n * vertices - Array of <mxCells> that represent the vertices whom are to\r\n * have an internal representation created.\r\n * internalVertices - The array of <mxGraphHierarchyNodes> to have their\r\n * information filled in using the real vertices.\r\n */\r\nmxSwimlaneModel.prototype.createInternalCells = function(layout, vertices, internalVertices)\r\n{\r\n\tvar graph = layout.getGraph();\r\n\tvar swimlanes = layout.swimlanes;\r\n\r\n\t// Create internal edges\r\n\tfor (var i = 0; i < vertices.length; i++)\r\n\t{\r\n\t\tinternalVertices[i] = new mxGraphHierarchyNode(vertices[i]);\r\n\t\tthis.vertexMapper.put(vertices[i], internalVertices[i]);\r\n\t\tinternalVertices[i].swimlaneIndex = -1;\r\n\r\n\t\tfor (var ii = 0; ii < swimlanes.length; ii++)\r\n\t\t{\r\n\t\t\tif (graph.model.getParent(vertices[i]) == swimlanes[ii])\r\n\t\t\t{\r\n\t\t\t\tinternalVertices[i].swimlaneIndex = ii;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// If the layout is deterministic, order the cells\r\n\t\t//List outgoingCells = graph.getNeighbours(vertices[i], deterministic);\r\n\t\tvar conns = layout.getEdges(vertices[i]);\r\n\t\tinternalVertices[i].connectsAsSource = [];\r\n\r\n\t\t// Create internal edges, but don't do any rank assignment yet\r\n\t\t// First use the information from the greedy cycle remover to\r\n\t\t// invert the leftward edges internally\r\n\t\tfor (var j = 0; j < conns.length; j++)\r\n\t\t{\r\n\t\t\tvar cell = layout.getVisibleTerminal(conns[j], false);\r\n\r\n\t\t\t// Looking for outgoing edges only\r\n\t\t\tif (cell != vertices[i] && layout.graph.model.isVertex(cell) &&\r\n\t\t\t\t\t!layout.isVertexIgnored(cell))\r\n\t\t\t{\r\n\t\t\t\t// We process all edge between this source and its targets\r\n\t\t\t\t// If there are edges going both ways, we need to collect\r\n\t\t\t\t// them all into one internal edges to avoid looping problems\r\n\t\t\t\t// later. We assume this direction (source -> target) is the \r\n\t\t\t\t// natural direction if at least half the edges are going in\r\n\t\t\t\t// that direction.\r\n\r\n\t\t\t\t// The check below for edges[0] being in the vertex mapper is\r\n\t\t\t\t// in case we've processed this the other way around\r\n\t\t\t\t// (target -> source) and the number of edges in each direction\r\n\t\t\t\t// are the same. All the graph edges will have been assigned to\r\n\t\t\t\t// an internal edge going the other way, so we don't want to \r\n\t\t\t\t// process them again\r\n\t\t\t\tvar undirectedEdges = layout.getEdgesBetween(vertices[i],\r\n\t\t\t\t\t\tcell, false);\r\n\t\t\t\tvar directedEdges = layout.getEdgesBetween(vertices[i],\r\n\t\t\t\t\t\tcell, true);\r\n\t\t\t\t\r\n\t\t\t\tif (undirectedEdges != null &&\r\n\t\t\t\t\t\tundirectedEdges.length > 0 &&\r\n\t\t\t\t\t\tthis.edgeMapper.get(undirectedEdges[0]) == null &&\r\n\t\t\t\t\t\tdirectedEdges.length * 2 >= undirectedEdges.length)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar internalEdge = new mxGraphHierarchyEdge(undirectedEdges);\r\n\r\n\t\t\t\t\tfor (var k = 0; k < undirectedEdges.length; k++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar edge = undirectedEdges[k];\r\n\t\t\t\t\t\tthis.edgeMapper.put(edge, internalEdge);\r\n\r\n\t\t\t\t\t\t// Resets all point on the edge and disables the edge style\r\n\t\t\t\t\t\t// without deleting it from the cell style\r\n\t\t\t\t\t\tgraph.resetEdge(edge);\r\n\r\n\t\t\t\t\t    if (layout.disableEdgeStyle)\r\n\t\t\t\t\t    {\r\n\t\t\t\t\t    \tlayout.setEdgeStyleEnabled(edge, false);\r\n\t\t\t\t\t    \tlayout.setOrthogonalEdge(edge,true);\r\n\t\t\t\t\t    }\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tinternalEdge.source = internalVertices[i];\r\n\r\n\t\t\t\t\tif (mxUtils.indexOf(internalVertices[i].connectsAsSource, internalEdge) < 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tinternalVertices[i].connectsAsSource.push(internalEdge);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Ensure temp variable is cleared from any previous use\r\n\t\tinternalVertices[i].temp[0] = 0;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: initialRank\r\n *\r\n * Basic determination of minimum layer ranking by working from from sources\r\n * or sinks and working through each node in the relevant edge direction.\r\n * Starting at the sinks is basically a longest path layering algorithm.\r\n*/\r\nmxSwimlaneModel.prototype.initialRank = function()\r\n{\r\n\tthis.ranksPerGroup = [];\r\n\t\r\n\tvar startNodes = [];\r\n\tvar seen = new Object();\r\n\r\n\tif (this.roots != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.roots.length; i++)\r\n\t\t{\r\n\t\t\tvar internalNode = this.vertexMapper.get(this.roots[i]);\r\n\t\t\tthis.maxChainDfs(null, internalNode, null, seen, 0);\r\n\r\n\t\t\tif (internalNode != null)\r\n\t\t\t{\r\n\t\t\t\tstartNodes.push(internalNode);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// Calculate the lower and upper rank bounds of each swimlane\r\n\tvar lowerRank = [];\r\n\tvar upperRank = [];\r\n\t\r\n\tfor (var i = this.ranksPerGroup.length - 1; i >= 0; i--)\r\n\t{\r\n\t\tif (i == this.ranksPerGroup.length - 1)\r\n\t\t{\r\n\t\t\tlowerRank[i] = 0;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tlowerRank[i] = upperRank[i+1] + 1;\r\n\t\t}\r\n\t\t\r\n\t\tupperRank[i] = lowerRank[i] + this.ranksPerGroup[i];\r\n\t}\r\n\t\r\n\tthis.maxRank = upperRank[0];\r\n\r\n\tvar internalNodes = this.vertexMapper.getValues();\r\n\t\r\n\tfor (var i=0; i < internalNodes.length; i++)\r\n\t{\r\n\t\t// Mark the node as not having had a layer assigned\r\n\t\tinternalNodes[i].temp[0] = -1;\r\n\t}\r\n\r\n\tvar startNodesCopy = startNodes.slice();\r\n\t\r\n\twhile (startNodes.length > 0)\r\n\t{\r\n\t\tvar internalNode = startNodes[0];\r\n\t\tvar layerDeterminingEdges;\r\n\t\tvar edgesToBeMarked;\r\n\r\n\t\tlayerDeterminingEdges = internalNode.connectsAsTarget;\r\n\t\tedgesToBeMarked = internalNode.connectsAsSource;\r\n\r\n\t\t// flag to keep track of whether or not all layer determining\r\n\t\t// edges have been scanned\r\n\t\tvar allEdgesScanned = true;\r\n\r\n\t\t// Work out the layer of this node from the layer determining\r\n\t\t// edges. The minimum layer number of any node connected by one of\r\n\t\t// the layer determining edges variable\r\n\t\tvar minimumLayer = upperRank[0];\r\n\r\n\t\tfor (var i = 0; i < layerDeterminingEdges.length; i++)\r\n\t\t{\r\n\t\t\tvar internalEdge = layerDeterminingEdges[i];\r\n\r\n\t\t\tif (internalEdge.temp[0] == 5270620)\r\n\t\t\t{\r\n\t\t\t\t// This edge has been scanned, get the layer of the\r\n\t\t\t\t// node on the other end\r\n\t\t\t\tvar otherNode = internalEdge.source;\r\n\t\t\t\tminimumLayer = Math.min(minimumLayer, otherNode.temp[0] - 1);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tallEdgesScanned = false;\r\n\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// If all edge have been scanned, assign the layer, mark all\r\n\t\t// edges in the other direction and remove from the nodes list\r\n\t\tif (allEdgesScanned)\r\n\t\t{\r\n\t\t\tif (minimumLayer > upperRank[internalNode.swimlaneIndex])\r\n\t\t\t{\r\n\t\t\t\tminimumLayer = upperRank[internalNode.swimlaneIndex];\r\n\t\t\t}\r\n\r\n\t\t\tinternalNode.temp[0] = minimumLayer;\r\n\r\n\t\t\tif (edgesToBeMarked != null)\r\n\t\t\t{\r\n\t\t\t\tfor (var i = 0; i < edgesToBeMarked.length; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar internalEdge = edgesToBeMarked[i];\r\n\r\n\t\t\t\t\t// Assign unique stamp ( y/m/d/h )\r\n\t\t\t\t\tinternalEdge.temp[0] = 5270620;\r\n\r\n\t\t\t\t\t// Add node on other end of edge to LinkedList of\r\n\t\t\t\t\t// nodes to be analysed\r\n\t\t\t\t\tvar otherNode = internalEdge.target;\r\n\r\n\t\t\t\t\t// Only add node if it hasn't been assigned a layer\r\n\t\t\t\t\tif (otherNode.temp[0] == -1)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tstartNodes.push(otherNode);\r\n\r\n\t\t\t\t\t\t// Mark this other node as neither being\r\n\t\t\t\t\t\t// unassigned nor assigned so it isn't\r\n\t\t\t\t\t\t// added to this list again, but it's\r\n\t\t\t\t\t\t// layer isn't used in any calculation.\r\n\t\t\t\t\t\totherNode.temp[0] = -2;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tstartNodes.shift();\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Not all the edges have been scanned, get to the back of\r\n\t\t\t// the class and put the dunces cap on\r\n\t\t\tvar removedCell = startNodes.shift();\r\n\t\t\tstartNodes.push(internalNode);\r\n\r\n\t\t\tif (removedCell == internalNode && startNodes.length == 1)\r\n\t\t\t{\r\n\t\t\t\t// This is an error condition, we can't get out of\r\n\t\t\t\t// this loop. It could happen for more than one node\r\n\t\t\t\t// but that's a lot harder to detect. Log the error\r\n\t\t\t\t// TODO make log comment\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// Normalize the ranks down from their large starting value to place\r\n\t// at least 1 sink on layer 0\r\n//\tfor (var key in this.vertexMapper)\r\n//\t{\r\n//\t\tvar internalNode = this.vertexMapper[key];\r\n//\t\t// Mark the node as not having had a layer assigned\r\n//\t\tinternalNode.temp[0] -= this.maxRank;\r\n//\t}\r\n\t\r\n\t// Tighten the rank 0 nodes as far as possible\r\n//\tfor ( var i = 0; i < startNodesCopy.length; i++)\r\n//\t{\r\n//\t\tvar internalNode = startNodesCopy[i];\r\n//\t\tvar currentMaxLayer = 0;\r\n//\t\tvar layerDeterminingEdges = internalNode.connectsAsSource;\r\n//\r\n//\t\tfor ( var j = 0; j < layerDeterminingEdges.length; j++)\r\n//\t\t{\r\n//\t\t\tvar internalEdge = layerDeterminingEdges[j];\r\n//\t\t\tvar otherNode = internalEdge.target;\r\n//\t\t\tinternalNode.temp[0] = Math.max(currentMaxLayer,\r\n//\t\t\t\t\totherNode.temp[0] + 1);\r\n//\t\t\tcurrentMaxLayer = internalNode.temp[0];\r\n//\t\t}\r\n//\t}\r\n};\r\n\r\n/**\r\n * Function: maxChainDfs\r\n *\r\n * Performs a depth first search on the internal hierarchy model. This dfs\r\n * extends the default version by keeping track of chains within groups.\r\n * Any cycles should be removed prior to running, but previously seen cells\r\n * are ignored.\r\n *\r\n * Parameters:\r\n *\r\n * parent - the parent internal node of the current internal node\r\n * root - the current internal node\r\n * connectingEdge - the internal edge connecting the internal node and the parent\r\n * internal node, if any\r\n * seen - a set of all nodes seen by this dfs\r\n * chainCount - the number of edges in the chain of vertices going through\r\n * the current swimlane\r\n */\r\nmxSwimlaneModel.prototype.maxChainDfs = function(parent, root, connectingEdge, seen, chainCount)\r\n{\r\n\tif (root != null)\r\n\t{\r\n\t\tvar rootId = mxCellPath.create(root.cell);\r\n\r\n\t\tif (seen[rootId] == null)\r\n\t\t{\r\n\t\t\tseen[rootId] = root;\r\n\t\t\tvar slIndex = root.swimlaneIndex;\r\n\t\t\t\r\n\t\t\tif (this.ranksPerGroup[slIndex] == null || this.ranksPerGroup[slIndex] < chainCount)\r\n\t\t\t{\r\n\t\t\t\tthis.ranksPerGroup[slIndex] = chainCount;\r\n\t\t\t}\r\n\r\n\t\t\t// Copy the connects as source list so that visitors\r\n\t\t\t// can change the original for edge direction inversions\r\n\t\t\tvar outgoingEdges = root.connectsAsSource.slice();\r\n\r\n\t\t\tfor (var i = 0; i < outgoingEdges.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar internalEdge = outgoingEdges[i];\r\n\t\t\t\tvar targetNode = internalEdge.target;\r\n\r\n\t\t\t\t// Only navigate in source->target direction within the same\r\n\t\t\t\t// swimlane, or from a lower index swimlane to a higher one\r\n\t\t\t\tif (root.swimlaneIndex < targetNode.swimlaneIndex)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.maxChainDfs(root, targetNode, internalEdge, mxUtils.clone(seen, null , true), 0);\r\n\t\t\t\t}\r\n\t\t\t\telse if (root.swimlaneIndex == targetNode.swimlaneIndex)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.maxChainDfs(root, targetNode, internalEdge, mxUtils.clone(seen, null , true), chainCount + 1);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: fixRanks\r\n *\r\n * Fixes the layer assignments to the values stored in the nodes. Also needs\r\n * to create dummy nodes for edges that cross layers.\r\n */\r\nmxSwimlaneModel.prototype.fixRanks = function()\r\n{\r\n\tvar rankList = [];\r\n\tthis.ranks = [];\r\n\r\n\tfor (var i = 0; i < this.maxRank + 1; i++)\r\n\t{\r\n\t\trankList[i] = [];\r\n\t\tthis.ranks[i] = rankList[i];\r\n\t}\r\n\r\n\t// Perform a DFS to obtain an initial ordering for each rank.\r\n\t// Without doing this you would end up having to process\r\n\t// crossings for a standard tree.\r\n\tvar rootsArray = null;\r\n\r\n\tif (this.roots != null)\r\n\t{\r\n\t\tvar oldRootsArray = this.roots;\r\n\t\trootsArray = [];\r\n\r\n\t\tfor (var i = 0; i < oldRootsArray.length; i++)\r\n\t\t{\r\n\t\t\tvar cell = oldRootsArray[i];\r\n\t\t\tvar internalNode = this.vertexMapper.get(cell);\r\n\t\t\trootsArray[i] = internalNode;\r\n\t\t}\r\n\t}\r\n\r\n\tthis.visit(function(parent, node, edge, layer, seen)\r\n\t{\r\n\t\tif (seen == 0 && node.maxRank < 0 && node.minRank < 0)\r\n\t\t{\r\n\t\t\trankList[node.temp[0]].push(node);\r\n\t\t\tnode.maxRank = node.temp[0];\r\n\t\t\tnode.minRank = node.temp[0];\r\n\r\n\t\t\t// Set temp[0] to the nodes position in the rank\r\n\t\t\tnode.temp[0] = rankList[node.maxRank].length - 1;\r\n\t\t}\r\n\r\n\t\tif (parent != null && edge != null)\r\n\t\t{\r\n\t\t\tvar parentToCellRankDifference = parent.maxRank - node.maxRank;\r\n\r\n\t\t\tif (parentToCellRankDifference > 1)\r\n\t\t\t{\r\n\t\t\t\t// There are ranks in between the parent and current cell\r\n\t\t\t\tedge.maxRank = parent.maxRank;\r\n\t\t\t\tedge.minRank = node.maxRank;\r\n\t\t\t\tedge.temp = [];\r\n\t\t\t\tedge.x = [];\r\n\t\t\t\tedge.y = [];\r\n\r\n\t\t\t\tfor (var i = edge.minRank + 1; i < edge.maxRank; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\t// The connecting edge must be added to the\r\n\t\t\t\t\t// appropriate ranks\r\n\t\t\t\t\trankList[i].push(edge);\r\n\t\t\t\t\tedge.setGeneralPurposeVariable(i, rankList[i]\r\n\t\t\t\t\t\t\t.length - 1);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}, rootsArray, false, null);\r\n};\r\n\r\n/**\r\n * Function: visit\r\n *\r\n * A depth first search through the internal heirarchy model.\r\n *\r\n * Parameters:\r\n *\r\n * visitor - The visitor function pattern to be called for each node.\r\n * trackAncestors - Whether or not the search is to keep track all nodes\r\n * directly above this one in the search path.\r\n */\r\nmxSwimlaneModel.prototype.visit = function(visitor, dfsRoots, trackAncestors, seenNodes)\r\n{\r\n\t// Run dfs through on all roots\r\n\tif (dfsRoots != null)\r\n\t{\r\n\t\tfor (var i = 0; i < dfsRoots.length; i++)\r\n\t\t{\r\n\t\t\tvar internalNode = dfsRoots[i];\r\n\r\n\t\t\tif (internalNode != null)\r\n\t\t\t{\r\n\t\t\t\tif (seenNodes == null)\r\n\t\t\t\t{\r\n\t\t\t\t\tseenNodes = new Object();\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (trackAncestors)\r\n\t\t\t\t{\r\n\t\t\t\t\t// Set up hash code for root\r\n\t\t\t\t\tinternalNode.hashCode = [];\r\n\t\t\t\t\tinternalNode.hashCode[0] = this.dfsCount;\r\n\t\t\t\t\tinternalNode.hashCode[1] = i;\r\n\t\t\t\t\tthis.extendedDfs(null, internalNode, null, visitor, seenNodes,\r\n\t\t\t\t\t\t\tinternalNode.hashCode, i, 0);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.dfs(null, internalNode, null, visitor, seenNodes, 0);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.dfsCount++;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: dfs\r\n *\r\n * Performs a depth first search on the internal hierarchy model\r\n *\r\n * Parameters:\r\n *\r\n * parent - the parent internal node of the current internal node\r\n * root - the current internal node\r\n * connectingEdge - the internal edge connecting the internal node and the parent\r\n * internal node, if any\r\n * visitor - the visitor pattern to be called for each node\r\n * seen - a set of all nodes seen by this dfs a set of all of the\r\n * ancestor node of the current node\r\n * layer - the layer on the dfs tree ( not the same as the model ranks )\r\n */\r\nmxSwimlaneModel.prototype.dfs = function(parent, root, connectingEdge, visitor, seen, layer)\r\n{\r\n\tif (root != null)\r\n\t{\r\n\t\tvar rootId = root.id;\r\n\r\n\t\tif (seen[rootId] == null)\r\n\t\t{\r\n\t\t\tseen[rootId] = root;\r\n\t\t\tvisitor(parent, root, connectingEdge, layer, 0);\r\n\r\n\t\t\t// Copy the connects as source list so that visitors\r\n\t\t\t// can change the original for edge direction inversions\r\n\t\t\tvar outgoingEdges = root.connectsAsSource.slice();\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i< outgoingEdges.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar internalEdge = outgoingEdges[i];\r\n\t\t\t\tvar targetNode = internalEdge.target;\r\n\r\n\t\t\t\t// Root check is O(|roots|)\r\n\t\t\t\tthis.dfs(root, targetNode, internalEdge, visitor, seen,\r\n\t\t\t\t\t\tlayer + 1);\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Use the int field to indicate this node has been seen\r\n\t\t\tvisitor(parent, root, connectingEdge, layer, 1);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: extendedDfs\r\n *\r\n * Performs a depth first search on the internal hierarchy model. This dfs\r\n * extends the default version by keeping track of cells ancestors, but it\r\n * should be only used when necessary because of it can be computationally\r\n * intensive for deep searches.\r\n *\r\n * Parameters:\r\n *\r\n * parent - the parent internal node of the current internal node\r\n * root - the current internal node\r\n * connectingEdge - the internal edge connecting the internal node and the parent\r\n * internal node, if any\r\n * visitor - the visitor pattern to be called for each node\r\n * seen - a set of all nodes seen by this dfs\r\n * ancestors - the parent hash code\r\n * childHash - the new hash code for this node\r\n * layer - the layer on the dfs tree ( not the same as the model ranks )\r\n */\r\nmxSwimlaneModel.prototype.extendedDfs = function(parent, root, connectingEdge, visitor, seen, ancestors, childHash, layer)\r\n{\r\n\t// Explanation of custom hash set. Previously, the ancestors variable\r\n\t// was passed through the dfs as a HashSet. The ancestors were copied\r\n\t// into a new HashSet and when the new child was processed it was also\r\n\t// added to the set. If the current node was in its ancestor list it\r\n\t// meant there is a cycle in the graph and this information is passed\r\n\t// to the visitor.visit() in the seen parameter. The HashSet clone was\r\n\t// very expensive on CPU so a custom hash was developed using primitive\r\n\t// types. temp[] couldn't be used so hashCode[] was added to each node.\r\n\t// Each new child adds another int to the array, copying the prefix\r\n\t// from its parent. Child of the same parent add different ints (the\r\n\t// limit is therefore 2^32 children per parent...). If a node has a\r\n\t// child with the hashCode already set then the child code is compared\r\n\t// to the same portion of the current nodes array. If they match there\r\n\t// is a loop.\r\n\t// Note that the basic mechanism would only allow for 1 use of this\r\n\t// functionality, so the root nodes have two ints. The second int is\r\n\t// incremented through each node root and the first is incremented\r\n\t// through each run of the dfs algorithm (therefore the dfs is not\r\n\t// thread safe). The hash code of each node is set if not already set,\r\n\t// or if the first int does not match that of the current run.\r\n\tif (root != null)\r\n\t{\r\n\t\tif (parent != null)\r\n\t\t{\r\n\t\t\t// Form this nodes hash code if necessary, that is, if the\r\n\t\t\t// hashCode variable has not been initialized or if the\r\n\t\t\t// start of the parent hash code does not equal the start of\r\n\t\t\t// this nodes hash code, indicating the code was set on a\r\n\t\t\t// previous run of this dfs.\r\n\t\t\tif (root.hashCode == null ||\r\n\t\t\t\troot.hashCode[0] != parent.hashCode[0])\r\n\t\t\t{\r\n\t\t\t\tvar hashCodeLength = parent.hashCode.length + 1;\r\n\t\t\t\troot.hashCode = parent.hashCode.slice();\r\n\t\t\t\troot.hashCode[hashCodeLength - 1] = childHash;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tvar rootId = root.id;\r\n\r\n\t\tif (seen[rootId] == null)\r\n\t\t{\r\n\t\t\tseen[rootId] = root;\r\n\t\t\tvisitor(parent, root, connectingEdge, layer, 0);\r\n\r\n\t\t\t// Copy the connects as source list so that visitors\r\n\t\t\t// can change the original for edge direction inversions\r\n\t\t\tvar outgoingEdges = root.connectsAsSource.slice();\r\n\t\t\tvar incomingEdges = root.connectsAsTarget.slice();\r\n\r\n\t\t\tfor (var i = 0; i < outgoingEdges.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar internalEdge = outgoingEdges[i];\r\n\t\t\t\tvar targetNode = internalEdge.target;\r\n\t\t\t\t\r\n\t\t\t\t// Only navigate in source->target direction within the same\r\n\t\t\t\t// swimlane, or from a lower index swimlane to a higher one\r\n\t\t\t\tif (root.swimlaneIndex <= targetNode.swimlaneIndex)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.extendedDfs(root, targetNode, internalEdge, visitor, seen,\r\n\t\t\t\t\t\t\troot.hashCode, i, layer + 1);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < incomingEdges.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar internalEdge = incomingEdges[i];\r\n\t\t\t\tvar targetNode = internalEdge.source;\r\n\r\n\t\t\t\t// Only navigate in target->source direction from a lower index \r\n\t\t\t\t// swimlane to a higher one\r\n\t\t\t\tif (root.swimlaneIndex < targetNode.swimlaneIndex)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.extendedDfs(root, targetNode, internalEdge, visitor, seen,\r\n\t\t\t\t\t\t\troot.hashCode, i, layer + 1);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Use the int field to indicate this node has been seen\r\n\t\t\tvisitor(parent, root, connectingEdge, layer, 1);\r\n\t\t}\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxHierarchicalLayoutStage\r\n * \r\n * The specific layout interface for hierarchical layouts. It adds a\r\n * <code>run</code> method with a parameter for the hierarchical layout model\r\n * that is shared between the layout stages.\r\n * \r\n * Constructor: mxHierarchicalLayoutStage\r\n *\r\n * Constructs a new hierarchical layout stage.\r\n */\r\nfunction mxHierarchicalLayoutStage() { };\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Takes the graph detail and configuration information within the facade\r\n * and creates the resulting laid out graph within that facade for further\r\n * use.\r\n */\r\nmxHierarchicalLayoutStage.prototype.execute = function(parent) { };\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxMedianHybridCrossingReduction\r\n * \r\n * Sets the horizontal locations of node and edge dummy nodes on each layer.\r\n * Uses median down and up weighings as well heuristic to straighten edges as\r\n * far as possible.\r\n * \r\n * Constructor: mxMedianHybridCrossingReduction\r\n *\r\n * Creates a coordinate assignment.\r\n * \r\n * Arguments:\r\n * \r\n * intraCellSpacing - the minimum buffer between cells on the same rank\r\n * interRankCellSpacing - the minimum distance between cells on adjacent ranks\r\n * orientation - the position of the root node(s) relative to the graph\r\n * initialX - the leftmost coordinate node placement starts at\r\n */\r\nfunction mxMedianHybridCrossingReduction(layout)\r\n{\r\n\tthis.layout = layout;\r\n};\r\n\r\n/**\r\n * Extends mxMedianHybridCrossingReduction.\r\n */\r\nmxMedianHybridCrossingReduction.prototype = new mxHierarchicalLayoutStage();\r\nmxMedianHybridCrossingReduction.prototype.constructor = mxMedianHybridCrossingReduction;\r\n\r\n/**\r\n * Variable: layout\r\n * \r\n * Reference to the enclosing <mxHierarchicalLayout>.\r\n */\r\nmxMedianHybridCrossingReduction.prototype.layout = null;\r\n\r\n/**\r\n * Variable: maxIterations\r\n * \r\n * The maximum number of iterations to perform whilst reducing edge\r\n * crossings. Default is 24.\r\n */\r\nmxMedianHybridCrossingReduction.prototype.maxIterations = 24;\r\n\r\n/**\r\n * Variable: nestedBestRanks\r\n * \r\n * Stores each rank as a collection of cells in the best order found for\r\n * each layer so far\r\n */\r\nmxMedianHybridCrossingReduction.prototype.nestedBestRanks = null;\r\n\r\n/**\r\n * Variable: currentBestCrossings\r\n * \r\n * The total number of crossings found in the best configuration so far\r\n */\r\nmxMedianHybridCrossingReduction.prototype.currentBestCrossings = 0;\r\n\r\n/**\r\n * Variable: iterationsWithoutImprovement\r\n * \r\n * The total number of crossings found in the best configuration so far\r\n */\r\nmxMedianHybridCrossingReduction.prototype.iterationsWithoutImprovement = 0;\r\n\r\n/**\r\n * Variable: maxNoImprovementIterations\r\n * \r\n * The total number of crossings found in the best configuration so far\r\n */\r\nmxMedianHybridCrossingReduction.prototype.maxNoImprovementIterations = 2;\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Performs a vertex ordering within ranks as described by Gansner et al\r\n * 1993\r\n */\r\nmxMedianHybridCrossingReduction.prototype.execute = function(parent)\r\n{\r\n\tvar model = this.layout.getModel();\r\n\r\n\t// Stores initial ordering as being the best one found so far\r\n\tthis.nestedBestRanks = [];\r\n\t\r\n\tfor (var i = 0; i < model.ranks.length; i++)\r\n\t{\r\n\t\tthis.nestedBestRanks[i] = model.ranks[i].slice();\r\n\t}\r\n\r\n\tvar iterationsWithoutImprovement = 0;\r\n\tvar currentBestCrossings = this.calculateCrossings(model);\r\n\r\n\tfor (var i = 0; i < this.maxIterations &&\r\n\t\titerationsWithoutImprovement < this.maxNoImprovementIterations; i++)\r\n\t{\r\n\t\tthis.weightedMedian(i, model);\r\n\t\tthis.transpose(i, model);\r\n\t\tvar candidateCrossings = this.calculateCrossings(model);\r\n\r\n\t\tif (candidateCrossings < currentBestCrossings)\r\n\t\t{\r\n\t\t\tcurrentBestCrossings = candidateCrossings;\r\n\t\t\titerationsWithoutImprovement = 0;\r\n\r\n\t\t\t// Store the current rankings as the best ones\r\n\t\t\tfor (var j = 0; j < this.nestedBestRanks.length; j++)\r\n\t\t\t{\r\n\t\t\t\tvar rank = model.ranks[j];\r\n\r\n\t\t\t\tfor (var k = 0; k < rank.length; k++)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar cell = rank[k];\r\n\t\t\t\t\tthis.nestedBestRanks[j][cell.getGeneralPurposeVariable(j)] = cell;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Increase count of iterations where we haven't improved the\r\n\t\t\t// layout\r\n\t\t\titerationsWithoutImprovement++;\r\n\r\n\t\t\t// Restore the best values to the cells\r\n\t\t\tfor (var j = 0; j < this.nestedBestRanks.length; j++)\r\n\t\t\t{\r\n\t\t\t\tvar rank = model.ranks[j];\r\n\t\t\t\t\r\n\t\t\t\tfor (var k = 0; k < rank.length; k++)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar cell = rank[k];\r\n\t\t\t\t\tcell.setGeneralPurposeVariable(j, k);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (currentBestCrossings == 0)\r\n\t\t{\r\n\t\t\t// Do nothing further\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n\t// Store the best rankings but in the model\r\n\tvar ranks = [];\r\n\tvar rankList = [];\r\n\r\n\tfor (var i = 0; i < model.maxRank + 1; i++)\r\n\t{\r\n\t\trankList[i] = [];\r\n\t\tranks[i] = rankList[i];\r\n\t}\r\n\r\n\tfor (var i = 0; i < this.nestedBestRanks.length; i++)\r\n\t{\r\n\t\tfor (var j = 0; j < this.nestedBestRanks[i].length; j++)\r\n\t\t{\r\n\t\t\trankList[i].push(this.nestedBestRanks[i][j]);\r\n\t\t}\r\n\t}\r\n\r\n\tmodel.ranks = ranks;\r\n};\r\n\r\n\r\n/**\r\n * Function: calculateCrossings\r\n * \r\n * Calculates the total number of edge crossing in the current graph.\r\n * Returns the current number of edge crossings in the hierarchy graph\r\n * model in the current candidate layout\r\n * \r\n * Parameters:\r\n * \r\n * model - the internal model describing the hierarchy\r\n */\r\nmxMedianHybridCrossingReduction.prototype.calculateCrossings = function(model)\r\n{\r\n\tvar numRanks = model.ranks.length;\r\n\tvar totalCrossings = 0;\r\n\r\n\tfor (var i = 1; i < numRanks; i++)\r\n\t{\r\n\t\ttotalCrossings += this.calculateRankCrossing(i, model);\r\n\t}\r\n\t\r\n\treturn totalCrossings;\r\n};\r\n\r\n/**\r\n * Function: calculateRankCrossing\r\n * \r\n * Calculates the number of edges crossings between the specified rank and\r\n * the rank below it. Returns the number of edges crossings with the rank\r\n * beneath\r\n * \r\n * Parameters:\r\n * \r\n * i -  the topmost rank of the pair ( higher rank value )\r\n * model - the internal model describing the hierarchy\r\n */\r\nmxMedianHybridCrossingReduction.prototype.calculateRankCrossing = function(i, model)\r\n{\r\n\tvar totalCrossings = 0;\r\n\tvar rank = model.ranks[i];\r\n\tvar previousRank = model.ranks[i - 1];\r\n\r\n\tvar tmpIndices = [];\r\n\r\n\t// Iterate over the top rank and fill in the connection information\r\n\tfor (var j = 0; j < rank.length; j++)\r\n\t{\r\n\t\tvar node = rank[j];\r\n\t\tvar rankPosition = node.getGeneralPurposeVariable(i);\r\n\t\tvar connectedCells = node.getPreviousLayerConnectedCells(i);\r\n\t\tvar nodeIndices = [];\r\n\r\n\t\tfor (var k = 0; k < connectedCells.length; k++)\r\n\t\t{\r\n\t\t\tvar connectedNode = connectedCells[k];\r\n\t\t\tvar otherCellRankPosition = connectedNode.getGeneralPurposeVariable(i - 1);\r\n\t\t\tnodeIndices.push(otherCellRankPosition);\r\n\t\t}\r\n\t\t\r\n\t\tnodeIndices.sort(function(x, y) { return x - y; });\r\n\t\ttmpIndices[rankPosition] = nodeIndices;\r\n\t}\r\n\t\r\n\tvar indices = [];\r\n\r\n\tfor (var j = 0; j < tmpIndices.length; j++)\r\n\t{\r\n\t\tindices = indices.concat(tmpIndices[j]);\r\n\t}\r\n\r\n\tvar firstIndex = 1;\r\n\t\r\n\twhile (firstIndex < previousRank.length)\r\n\t{\r\n\t\tfirstIndex <<= 1;\r\n\t}\r\n\r\n\tvar treeSize = 2 * firstIndex - 1;\r\n\tfirstIndex -= 1;\r\n\r\n\tvar tree = [];\r\n\t\r\n\tfor (var j = 0; j < treeSize; ++j)\r\n\t{\r\n\t\ttree[j] = 0;\r\n\t}\r\n\r\n\tfor (var j = 0; j < indices.length; j++)\r\n\t{\r\n\t\tvar index = indices[j];\r\n\t    var treeIndex = index + firstIndex;\r\n\t    ++tree[treeIndex];\r\n\t    \r\n\t    while (treeIndex > 0)\r\n\t    {\r\n\t    \tif (treeIndex % 2)\r\n\t    \t{\r\n\t    \t\ttotalCrossings += tree[treeIndex + 1];\r\n\t    \t}\r\n\t      \r\n\t    \ttreeIndex = (treeIndex - 1) >> 1;\r\n\t    \t++tree[treeIndex];\r\n\t    }\r\n\t}\r\n\r\n\treturn totalCrossings;\r\n};\r\n\r\n/**\r\n * Function: transpose\r\n * \r\n * Takes each possible adjacent cell pair on each rank and checks if\r\n * swapping them around reduces the number of crossing\r\n * \r\n * Parameters:\r\n * \r\n * mainLoopIteration - the iteration number of the main loop\r\n * model - the internal model describing the hierarchy\r\n */\r\nmxMedianHybridCrossingReduction.prototype.transpose = function(mainLoopIteration, model)\r\n{\r\n\tvar improved = true;\r\n\r\n\t// Track the number of iterations in case of looping\r\n\tvar count = 0;\r\n\tvar maxCount = 10;\r\n\twhile (improved && count++ < maxCount)\r\n\t{\r\n\t\t// On certain iterations allow allow swapping of cell pairs with\r\n\t\t// equal edge crossings switched or not switched. This help to\r\n\t\t// nudge a stuck layout into a lower crossing total.\r\n\t\tvar nudge = mainLoopIteration % 2 == 1 && count % 2 == 1;\r\n\t\timproved = false;\r\n\t\t\r\n\t\tfor (var i = 0; i < model.ranks.length; i++)\r\n\t\t{\r\n\t\t\tvar rank = model.ranks[i];\r\n\t\t\tvar orderedCells = [];\r\n\t\t\t\r\n\t\t\tfor (var j = 0; j < rank.length; j++)\r\n\t\t\t{\r\n\t\t\t\tvar cell = rank[j];\r\n\t\t\t\tvar tempRank = cell.getGeneralPurposeVariable(i);\r\n\t\t\t\t\r\n\t\t\t\t// FIXME: Workaround to avoid negative tempRanks\r\n\t\t\t\tif (tempRank < 0)\r\n\t\t\t\t{\r\n\t\t\t\t\ttempRank = j;\r\n\t\t\t\t}\r\n\t\t\t\torderedCells[tempRank] = cell;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar leftCellAboveConnections = null;\r\n\t\t\tvar leftCellBelowConnections = null;\r\n\t\t\tvar rightCellAboveConnections = null;\r\n\t\t\tvar rightCellBelowConnections = null;\r\n\t\t\t\r\n\t\t\tvar leftAbovePositions = null;\r\n\t\t\tvar leftBelowPositions = null;\r\n\t\t\tvar rightAbovePositions = null;\r\n\t\t\tvar rightBelowPositions = null;\r\n\t\t\t\r\n\t\t\tvar leftCell = null;\r\n\t\t\tvar rightCell = null;\r\n\r\n\t\t\tfor (var j = 0; j < (rank.length - 1); j++)\r\n\t\t\t{\r\n\t\t\t\t// For each intra-rank adjacent pair of cells\r\n\t\t\t\t// see if swapping them around would reduce the\r\n\t\t\t\t// number of edges crossing they cause in total\r\n\t\t\t\t// On every cell pair except the first on each rank, we\r\n\t\t\t\t// can save processing using the previous values for the\r\n\t\t\t\t// right cell on the new left cell\r\n\t\t\t\tif (j == 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tleftCell = orderedCells[j];\r\n\t\t\t\t\tleftCellAboveConnections = leftCell\r\n\t\t\t\t\t\t\t.getNextLayerConnectedCells(i);\r\n\t\t\t\t\tleftCellBelowConnections = leftCell\r\n\t\t\t\t\t\t\t.getPreviousLayerConnectedCells(i);\r\n\t\t\t\t\tleftAbovePositions = [];\r\n\t\t\t\t\tleftBelowPositions = [];\r\n\t\t\t\t\t\r\n\t\t\t\t\tfor (var k = 0; k < leftCellAboveConnections.length; k++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tleftAbovePositions[k] = leftCellAboveConnections[k].getGeneralPurposeVariable(i + 1);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tfor (var k = 0; k < leftCellBelowConnections.length; k++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tleftBelowPositions[k] = leftCellBelowConnections[k].getGeneralPurposeVariable(i - 1);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tleftCellAboveConnections = rightCellAboveConnections;\r\n\t\t\t\t\tleftCellBelowConnections = rightCellBelowConnections;\r\n\t\t\t\t\tleftAbovePositions = rightAbovePositions;\r\n\t\t\t\t\tleftBelowPositions = rightBelowPositions;\r\n\t\t\t\t\tleftCell = rightCell;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\trightCell = orderedCells[j + 1];\r\n\t\t\t\trightCellAboveConnections = rightCell\r\n\t\t\t\t\t\t.getNextLayerConnectedCells(i);\r\n\t\t\t\trightCellBelowConnections = rightCell\r\n\t\t\t\t\t\t.getPreviousLayerConnectedCells(i);\r\n\r\n\t\t\t\trightAbovePositions = [];\r\n\t\t\t\trightBelowPositions = [];\r\n\r\n\t\t\t\tfor (var k = 0; k < rightCellAboveConnections.length; k++)\r\n\t\t\t\t{\r\n\t\t\t\t\trightAbovePositions[k] = rightCellAboveConnections[k].getGeneralPurposeVariable(i + 1);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tfor (var k = 0; k < rightCellBelowConnections.length; k++)\r\n\t\t\t\t{\r\n\t\t\t\t\trightBelowPositions[k] = rightCellBelowConnections[k].getGeneralPurposeVariable(i - 1);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tvar totalCurrentCrossings = 0;\r\n\t\t\t\tvar totalSwitchedCrossings = 0;\r\n\t\t\t\t\r\n\t\t\t\tfor (var k = 0; k < leftAbovePositions.length; k++)\r\n\t\t\t\t{\r\n\t\t\t\t\tfor (var ik = 0; ik < rightAbovePositions.length; ik++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (leftAbovePositions[k] > rightAbovePositions[ik])\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\ttotalCurrentCrossings++;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tif (leftAbovePositions[k] < rightAbovePositions[ik])\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\ttotalSwitchedCrossings++;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tfor (var k = 0; k < leftBelowPositions.length; k++)\r\n\t\t\t\t{\r\n\t\t\t\t\tfor (var ik = 0; ik < rightBelowPositions.length; ik++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (leftBelowPositions[k] > rightBelowPositions[ik])\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\ttotalCurrentCrossings++;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tif (leftBelowPositions[k] < rightBelowPositions[ik])\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\ttotalSwitchedCrossings++;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif ((totalSwitchedCrossings < totalCurrentCrossings) ||\r\n\t\t\t\t\t(totalSwitchedCrossings == totalCurrentCrossings &&\r\n\t\t\t\t\tnudge))\r\n\t\t\t\t{\r\n\t\t\t\t\tvar temp = leftCell.getGeneralPurposeVariable(i);\r\n\t\t\t\t\tleftCell.setGeneralPurposeVariable(i, rightCell\r\n\t\t\t\t\t\t\t.getGeneralPurposeVariable(i));\r\n\t\t\t\t\trightCell.setGeneralPurposeVariable(i, temp);\r\n\r\n\t\t\t\t\t// With this pair exchanged we have to switch all of\r\n\t\t\t\t\t// values for the left cell to the right cell so the\r\n\t\t\t\t\t// next iteration for this rank uses it as the left\r\n\t\t\t\t\t// cell again\r\n\t\t\t\t\trightCellAboveConnections = leftCellAboveConnections;\r\n\t\t\t\t\trightCellBelowConnections = leftCellBelowConnections;\r\n\t\t\t\t\trightAbovePositions = leftAbovePositions;\r\n\t\t\t\t\trightBelowPositions = leftBelowPositions;\r\n\t\t\t\t\trightCell = leftCell;\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (!nudge)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t// Don't count nudges as improvement or we'll end\r\n\t\t\t\t\t\t// up stuck in two combinations and not finishing\r\n\t\t\t\t\t\t// as early as we should\r\n\t\t\t\t\t\timproved = true;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: weightedMedian\r\n * \r\n * Sweeps up or down the layout attempting to minimise the median placement\r\n * of connected cells on adjacent ranks\r\n * \r\n * Parameters:\r\n * \r\n * iteration - the iteration number of the main loop\r\n * model - the internal model describing the hierarchy\r\n */\r\nmxMedianHybridCrossingReduction.prototype.weightedMedian = function(iteration, model)\r\n{\r\n\t// Reverse sweep direction each time through this method\r\n\tvar downwardSweep = (iteration % 2 == 0);\r\n\tif (downwardSweep)\r\n\t{\r\n\t\tfor (var j = model.maxRank - 1; j >= 0; j--)\r\n\t\t{\r\n\t\t\tthis.medianRank(j, downwardSweep);\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tfor (var j = 1; j < model.maxRank; j++)\r\n\t\t{\r\n\t\t\tthis.medianRank(j, downwardSweep);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: medianRank\r\n * \r\n * Attempts to minimise the median placement of connected cells on this rank\r\n * and one of the adjacent ranks\r\n * \r\n * Parameters:\r\n * \r\n * rankValue - the layer number of this rank\r\n * downwardSweep - whether or not this is a downward sweep through the graph\r\n */\r\nmxMedianHybridCrossingReduction.prototype.medianRank = function(rankValue, downwardSweep)\r\n{\r\n\tvar numCellsForRank = this.nestedBestRanks[rankValue].length;\r\n\tvar medianValues = [];\r\n\tvar reservedPositions = [];\r\n\r\n\tfor (var i = 0; i < numCellsForRank; i++)\r\n\t{\r\n\t\tvar cell = this.nestedBestRanks[rankValue][i];\r\n\t\tvar sorterEntry = new MedianCellSorter();\r\n\t\tsorterEntry.cell = cell;\r\n\r\n\t\t// Flip whether or not equal medians are flipped on up and down\r\n\t\t// sweeps\r\n\t\t// TODO re-implement some kind of nudge\r\n\t\t// medianValues[i].nudge = !downwardSweep;\r\n\t\tvar nextLevelConnectedCells;\r\n\t\t\r\n\t\tif (downwardSweep)\r\n\t\t{\r\n\t\t\tnextLevelConnectedCells = cell\r\n\t\t\t\t\t.getNextLayerConnectedCells(rankValue);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tnextLevelConnectedCells = cell\r\n\t\t\t\t\t.getPreviousLayerConnectedCells(rankValue);\r\n\t\t}\r\n\t\t\r\n\t\tvar nextRankValue;\r\n\t\t\r\n\t\tif (downwardSweep)\r\n\t\t{\r\n\t\t\tnextRankValue = rankValue + 1;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tnextRankValue = rankValue - 1;\r\n\t\t}\r\n\r\n\t\tif (nextLevelConnectedCells != null\r\n\t\t\t\t&& nextLevelConnectedCells.length != 0)\r\n\t\t{\r\n\t\t\tsorterEntry.medianValue = this.medianValue(\r\n\t\t\t\t\tnextLevelConnectedCells, nextRankValue);\r\n\t\t\tmedianValues.push(sorterEntry);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Nodes with no adjacent vertices are flagged in the reserved array\r\n\t\t\t// to indicate they should be left in their current position.\r\n\t\t\treservedPositions[cell.getGeneralPurposeVariable(rankValue)] = true;\r\n\t\t}\r\n\t}\r\n\t\r\n\tmedianValues.sort(MedianCellSorter.prototype.compare);\r\n\t\r\n\t// Set the new position of each node within the rank using\r\n\t// its temp variable\r\n\tfor (var i = 0; i < numCellsForRank; i++)\r\n\t{\r\n\t\tif (reservedPositions[i] == null)\r\n\t\t{\r\n\t\t\tvar cell = medianValues.shift().cell;\r\n\t\t\tcell.setGeneralPurposeVariable(rankValue, i);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: medianValue\r\n * \r\n * Calculates the median rank order positioning for the specified cell using\r\n * the connected cells on the specified rank. Returns the median rank\r\n * ordering value of the connected cells\r\n * \r\n * Parameters:\r\n * \r\n * connectedCells - the cells on the specified rank connected to the\r\n * specified cell\r\n * rankValue - the rank that the connected cell lie upon\r\n */\r\nmxMedianHybridCrossingReduction.prototype.medianValue = function(connectedCells, rankValue)\r\n{\r\n\tvar medianValues = [];\r\n\tvar arrayCount = 0;\r\n\t\r\n\tfor (var i = 0; i < connectedCells.length; i++)\r\n\t{\r\n\t\tvar cell = connectedCells[i];\r\n\t\tmedianValues[arrayCount++] = cell.getGeneralPurposeVariable(rankValue);\r\n\t}\r\n\r\n\t// Sort() sorts lexicographically by default (i.e. 11 before 9) so force\r\n\t// numerical order sort\r\n\tmedianValues.sort(function(a,b){return a - b;});\r\n\t\r\n\tif (arrayCount % 2 == 1)\r\n\t{\r\n\t\t// For odd numbers of adjacent vertices return the median\r\n\t\treturn medianValues[Math.floor(arrayCount / 2)];\r\n\t}\r\n\telse if (arrayCount == 2)\r\n\t{\r\n\t\treturn ((medianValues[0] + medianValues[1]) / 2.0);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar medianPoint = arrayCount / 2;\r\n\t\tvar leftMedian = medianValues[medianPoint - 1] - medianValues[0];\r\n\t\tvar rightMedian = medianValues[arrayCount - 1]\r\n\t\t\t\t- medianValues[medianPoint];\r\n\r\n\t\treturn (medianValues[medianPoint - 1] * rightMedian + medianValues[medianPoint]\r\n\t\t\t\t* leftMedian)\r\n\t\t\t\t/ (leftMedian + rightMedian);\r\n\t}\r\n};\r\n\r\n/**\r\n * Class: MedianCellSorter\r\n * \r\n * A utility class used to track cells whilst sorting occurs on the median\r\n * values. Does not violate (x.compareTo(y)==0) == (x.equals(y))\r\n *\r\n * Constructor: MedianCellSorter\r\n * \r\n * Constructs a new median cell sorter.\r\n */\r\nfunction MedianCellSorter()\r\n{\r\n\t// empty\r\n};\r\n\r\n/**\r\n * Variable: medianValue\r\n * \r\n * The weighted value of the cell stored.\r\n */\r\nMedianCellSorter.prototype.medianValue = 0;\r\n\r\n/**\r\n * Variable: cell\r\n * \r\n * The cell whose median value is being calculated\r\n */\r\nMedianCellSorter.prototype.cell = false;\r\n\r\n/**\r\n * Function: compare\r\n * \r\n * Compares two MedianCellSorters.\r\n */\r\nMedianCellSorter.prototype.compare = function(a, b)\r\n{\r\n\tif (a != null && b != null)\r\n\t{\r\n\t\tif (b.medianValue > a.medianValue)\r\n\t\t{\r\n\t\t\treturn -1;\r\n\t\t}\r\n\t\telse if (b.medianValue < a.medianValue)\r\n\t\t{\r\n\t\t\treturn 1;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn 0;\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\treturn 0;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxMinimumCycleRemover\r\n * \r\n * An implementation of the first stage of the Sugiyama layout. Straightforward\r\n * longest path calculation of layer assignment\r\n * \r\n * Constructor: mxMinimumCycleRemover\r\n *\r\n * Creates a cycle remover for the given internal model.\r\n */\r\nfunction mxMinimumCycleRemover(layout)\r\n{\r\n\tthis.layout = layout;\r\n};\r\n\r\n/**\r\n * Extends mxHierarchicalLayoutStage.\r\n */\r\nmxMinimumCycleRemover.prototype = new mxHierarchicalLayoutStage();\r\nmxMinimumCycleRemover.prototype.constructor = mxMinimumCycleRemover;\r\n\r\n/**\r\n * Variable: layout\r\n * \r\n * Reference to the enclosing <mxHierarchicalLayout>.\r\n */\r\nmxMinimumCycleRemover.prototype.layout = null;\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Takes the graph detail and configuration information within the facade\r\n * and creates the resulting laid out graph within that facade for further\r\n * use.\r\n */\r\nmxMinimumCycleRemover.prototype.execute = function(parent)\r\n{\r\n\tvar model = this.layout.getModel();\r\n\tvar seenNodes = new Object();\r\n\tvar unseenNodesArray = model.vertexMapper.getValues();\r\n\tvar unseenNodes = new Object();\r\n\t\r\n\tfor (var i = 0; i < unseenNodesArray.length; i++)\r\n\t{\r\n\t\tunseenNodes[unseenNodesArray[i].id] = unseenNodesArray[i];\r\n\t}\r\n\t\r\n\t// Perform a dfs through the internal model. If a cycle is found,\r\n\t// reverse it.\r\n\tvar rootsArray = null;\r\n\t\r\n\tif (model.roots != null)\r\n\t{\r\n\t\tvar modelRoots = model.roots;\r\n\t\trootsArray = [];\r\n\t\t\r\n\t\tfor (var i = 0; i < modelRoots.length; i++)\r\n\t\t{\r\n\t\t\trootsArray[i] = model.vertexMapper.get(modelRoots[i]);\r\n\t\t}\r\n\t}\r\n\r\n\tmodel.visit(function(parent, node, connectingEdge, layer, seen)\r\n\t{\r\n\t\t// Check if the cell is in it's own ancestor list, if so\r\n\t\t// invert the connecting edge and reverse the target/source\r\n\t\t// relationship to that edge in the parent and the cell\r\n\t\tif (node.isAncestor(parent))\r\n\t\t{\r\n\t\t\tconnectingEdge.invert();\r\n\t\t\tmxUtils.remove(connectingEdge, parent.connectsAsSource);\r\n\t\t\tparent.connectsAsTarget.push(connectingEdge);\r\n\t\t\tmxUtils.remove(connectingEdge, node.connectsAsTarget);\r\n\t\t\tnode.connectsAsSource.push(connectingEdge);\r\n\t\t}\r\n\t\t\r\n\t\tseenNodes[node.id] = node;\r\n\t\tdelete unseenNodes[node.id];\r\n\t}, rootsArray, true, null);\r\n\r\n\t// If there are any nodes that should be nodes that the dfs can miss\r\n\t// these need to be processed with the dfs and the roots assigned\r\n\t// correctly to form a correct internal model\r\n\tvar seenNodesCopy = mxUtils.clone(seenNodes, null, true);\r\n\r\n\t// Pick a random cell and dfs from it\r\n\tmodel.visit(function(parent, node, connectingEdge, layer, seen)\r\n\t{\r\n\t\t// Check if the cell is in it's own ancestor list, if so\r\n\t\t// invert the connecting edge and reverse the target/source\r\n\t\t// relationship to that edge in the parent and the cell\r\n\t\tif (node.isAncestor(parent))\r\n\t\t{\r\n\t\t\tconnectingEdge.invert();\r\n\t\t\tmxUtils.remove(connectingEdge, parent.connectsAsSource);\r\n\t\t\tnode.connectsAsSource.push(connectingEdge);\r\n\t\t\tparent.connectsAsTarget.push(connectingEdge);\r\n\t\t\tmxUtils.remove(connectingEdge, node.connectsAsTarget);\r\n\t\t}\r\n\t\t\r\n\t\tseenNodes[node.id] = node;\r\n\t\tdelete unseenNodes[node.id];\r\n\t}, unseenNodes, true, seenNodesCopy);\r\n};\r\n/**\r\n * Copyright (c) 2006-2018, JGraph Ltd\r\n * Copyright (c) 2006-2018, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxCoordinateAssignment\r\n * \r\n * Sets the horizontal locations of node and edge dummy nodes on each layer.\r\n * Uses median down and up weighings as well as heuristics to straighten edges as\r\n * far as possible.\r\n * \r\n * Constructor: mxCoordinateAssignment\r\n *\r\n * Creates a coordinate assignment.\r\n * \r\n * Arguments:\r\n * \r\n * intraCellSpacing - the minimum buffer between cells on the same rank\r\n * interRankCellSpacing - the minimum distance between cells on adjacent ranks\r\n * orientation - the position of the root node(s) relative to the graph\r\n * initialX - the leftmost coordinate node placement starts at\r\n */\r\nfunction mxCoordinateAssignment(layout, intraCellSpacing, interRankCellSpacing,\r\n\torientation, initialX, parallelEdgeSpacing)\r\n{\r\n\tthis.layout = layout;\r\n\tthis.intraCellSpacing = intraCellSpacing;\r\n\tthis.interRankCellSpacing = interRankCellSpacing;\r\n\tthis.orientation = orientation;\r\n\tthis.initialX = initialX;\r\n\tthis.parallelEdgeSpacing = parallelEdgeSpacing;\r\n};\r\n\r\n/**\r\n * Extends mxHierarchicalLayoutStage.\r\n */\r\nmxCoordinateAssignment.prototype = new mxHierarchicalLayoutStage();\r\nmxCoordinateAssignment.prototype.constructor = mxCoordinateAssignment;\r\n\r\n/**\r\n * Variable: layout\r\n * \r\n * Reference to the enclosing <mxHierarchicalLayout>.\r\n */\r\nmxCoordinateAssignment.prototype.layout = null;\r\n\r\n/**\r\n * Variable: intraCellSpacing\r\n * \r\n * The minimum buffer between cells on the same rank. Default is 30.\r\n */\r\nmxCoordinateAssignment.prototype.intraCellSpacing = 30;\r\n\r\n/**\r\n * Variable: interRankCellSpacing\r\n * \r\n * The minimum distance between cells on adjacent ranks. Default is 10.\r\n */\r\nmxCoordinateAssignment.prototype.interRankCellSpacing = 100;\r\n\r\n/**\r\n * Variable: parallelEdgeSpacing\r\n * \r\n * The distance between each parallel edge on each ranks for long edges.\r\n * Default is 10.\r\n */\r\nmxCoordinateAssignment.prototype.parallelEdgeSpacing = 10;\r\n\r\n/**\r\n * Variable: maxIterations\r\n * \r\n * The number of heuristic iterations to run. Default is 8.\r\n */\r\nmxCoordinateAssignment.prototype.maxIterations = 8;\r\n\r\n/**\r\n * Variable: prefHozEdgeSep\r\n * \r\n * The preferred horizontal distance between edges exiting a vertex\r\n */\r\nmxCoordinateAssignment.prototype.prefHozEdgeSep = 5;\r\n\r\n/**\r\n * Variable: prefVertEdgeOff\r\n * \r\n * The preferred vertical offset between edges exiting a vertex\r\n */\r\nmxCoordinateAssignment.prototype.prefVertEdgeOff = 2;\r\n\r\n/**\r\n * Variable: minEdgeJetty\r\n * \r\n * The minimum distance for an edge jetty from a vertex\r\n */\r\nmxCoordinateAssignment.prototype.minEdgeJetty = 12;\r\n\r\n/**\r\n * Variable: channelBuffer\r\n * \r\n * The size of the vertical buffer in the center of inter-rank channels\r\n * where edge control points should not be placed\r\n */\r\nmxCoordinateAssignment.prototype.channelBuffer = 4;\r\n\r\n/**\r\n * Variable: jettyPositions\r\n * \r\n * Map of internal edges and (x,y) pair of positions of the start and end jetty\r\n * for that edge where it connects to the source and target vertices.\r\n * Note this should technically be a WeakHashMap, but since JS does not\r\n * have an equivalent, housekeeping must be performed before using.\r\n * i.e. check all edges are still in the model and clear the values.\r\n * Note that the y co-ord is the offset of the jetty, not the\r\n * absolute point\r\n */\r\nmxCoordinateAssignment.prototype.jettyPositions = null;\r\n\r\n/**\r\n * Variable: orientation\r\n * \r\n * The position of the root ( start ) node(s) relative to the rest of the\r\n * laid out graph. Default is <mxConstants.DIRECTION_NORTH>.\r\n */\r\nmxCoordinateAssignment.prototype.orientation = mxConstants.DIRECTION_NORTH;\r\n\r\n/**\r\n * Variable: initialX\r\n * \r\n * The minimum x position node placement starts at\r\n */\r\nmxCoordinateAssignment.prototype.initialX = null;\r\n\r\n/**\r\n * Variable: limitX\r\n * \r\n * The maximum x value this positioning lays up to\r\n */\r\nmxCoordinateAssignment.prototype.limitX = null;\r\n\r\n/**\r\n * Variable: currentXDelta\r\n * \r\n * The sum of x-displacements for the current iteration\r\n */\r\nmxCoordinateAssignment.prototype.currentXDelta = null;\r\n\r\n/**\r\n * Variable: widestRank\r\n * \r\n * The rank that has the widest x position\r\n */\r\nmxCoordinateAssignment.prototype.widestRank = null;\r\n\r\n/**\r\n * Variable: rankTopY\r\n * \r\n * Internal cache of top-most values of Y for each rank\r\n */\r\nmxCoordinateAssignment.prototype.rankTopY = null;\r\n\r\n/**\r\n * Variable: rankBottomY\r\n * \r\n * Internal cache of bottom-most value of Y for each rank\r\n */\r\nmxCoordinateAssignment.prototype.rankBottomY = null;\r\n\r\n/**\r\n * Variable: widestRankValue\r\n * \r\n * The X-coordinate of the edge of the widest rank\r\n */\r\nmxCoordinateAssignment.prototype.widestRankValue = null;\r\n\r\n/**\r\n * Variable: rankWidths\r\n * \r\n * The width of all the ranks\r\n */\r\nmxCoordinateAssignment.prototype.rankWidths = null;\r\n\r\n/**\r\n * Variable: rankY\r\n * \r\n * The Y-coordinate of all the ranks\r\n */\r\nmxCoordinateAssignment.prototype.rankY = null;\r\n\r\n/**\r\n * Variable: fineTuning\r\n * \r\n * Whether or not to perform local optimisations and iterate multiple times\r\n * through the algorithm. Default is true.\r\n */\r\nmxCoordinateAssignment.prototype.fineTuning = true;\r\n\r\n/**\r\n * Variable: nextLayerConnectedCache\r\n * \r\n * A store of connections to the layer above for speed\r\n */\r\nmxCoordinateAssignment.prototype.nextLayerConnectedCache = null;\r\n\r\n/**\r\n * Variable: previousLayerConnectedCache\r\n * \r\n * A store of connections to the layer below for speed\r\n */\r\nmxCoordinateAssignment.prototype.previousLayerConnectedCache = null;\r\n\r\n/**\r\n * Variable: groupPadding\r\n * \r\n * Padding added to resized parents\r\n */\r\nmxCoordinateAssignment.prototype.groupPadding = 10;\r\n\r\n/**\r\n * Utility method to display current positions\r\n */\r\nmxCoordinateAssignment.prototype.printStatus = function()\r\n{\r\n\tvar model = this.layout.getModel();\r\n\tmxLog.show();\r\n\r\n\tmxLog.writeln('======Coord assignment debug=======');\r\n\r\n\tfor (var j = 0; j < model.ranks.length; j++)\r\n\t{\r\n\t\tmxLog.write('Rank ', j, ' : ' );\r\n\t\tvar rank = model.ranks[j];\r\n\t\t\r\n\t\tfor (var k = 0; k < rank.length; k++)\r\n\t\t{\r\n\t\t\tvar cell = rank[k];\r\n\t\t\t\r\n\t\t\tmxLog.write(cell.getGeneralPurposeVariable(j), '  ');\r\n\t\t}\r\n\t\tmxLog.writeln();\r\n\t}\r\n\t\r\n\tmxLog.writeln('====================================');\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * A basic horizontal coordinate assignment algorithm\r\n */\r\nmxCoordinateAssignment.prototype.execute = function(parent)\r\n{\r\n\tthis.jettyPositions = Object();\r\n\tvar model = this.layout.getModel();\r\n\tthis.currentXDelta = 0.0;\r\n\r\n\tthis.initialCoords(this.layout.getGraph(), model);\r\n\t\r\n//\tthis.printStatus();\r\n\t\r\n\tif (this.fineTuning)\r\n\t{\r\n\t\tthis.minNode(model);\r\n\t}\r\n\t\r\n\tvar bestXDelta = 100000000.0;\r\n\t\r\n\tif (this.fineTuning)\r\n\t{\r\n\t\tfor (var i = 0; i < this.maxIterations; i++)\r\n\t\t{\r\n//\t\t\tthis.printStatus();\r\n\t\t\r\n\t\t\t// Median Heuristic\r\n\t\t\tif (i != 0)\r\n\t\t\t{\r\n\t\t\t\tthis.medianPos(i, model);\r\n\t\t\t\tthis.minNode(model);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// if the total offset is less for the current positioning,\r\n\t\t\t// there are less heavily angled edges and so the current\r\n\t\t\t// positioning is used\r\n\t\t\tif (this.currentXDelta < bestXDelta)\r\n\t\t\t{\r\n\t\t\t\tfor (var j = 0; j < model.ranks.length; j++)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar rank = model.ranks[j];\r\n\t\t\t\t\t\r\n\t\t\t\t\tfor (var k = 0; k < rank.length; k++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar cell = rank[k];\r\n\t\t\t\t\t\tcell.setX(j, cell.getGeneralPurposeVariable(j));\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tbestXDelta = this.currentXDelta;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\t// Restore the best positions\r\n\t\t\t\tfor (var j = 0; j < model.ranks.length; j++)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar rank = model.ranks[j];\r\n\t\t\t\t\t\r\n\t\t\t\t\tfor (var k = 0; k < rank.length; k++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar cell = rank[k];\r\n\t\t\t\t\t\tcell.setGeneralPurposeVariable(j, cell.getX(j));\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.minPath(this.layout.getGraph(), model);\r\n\t\t\t\r\n\t\t\tthis.currentXDelta = 0;\r\n\t\t}\r\n\t}\r\n\t\r\n\tthis.setCellLocations(this.layout.getGraph(), model);\r\n};\r\n\r\n/**\r\n * Function: minNode\r\n * \r\n * Performs one median positioning sweep in both directions\r\n */\r\nmxCoordinateAssignment.prototype.minNode = function(model)\r\n{\r\n\t// Queue all nodes\r\n\tvar nodeList = [];\r\n\t\r\n\t// Need to be able to map from cell to cellWrapper\r\n\tvar map = new mxDictionary();\r\n\tvar rank = [];\r\n\t\r\n\tfor (var i = 0; i <= model.maxRank; i++)\r\n\t{\r\n\t\trank[i] = model.ranks[i];\r\n\t\t\r\n\t\tfor (var j = 0; j < rank[i].length; j++)\r\n\t\t{\r\n\t\t\t// Use the weight to store the rank and visited to store whether\r\n\t\t\t// or not the cell is in the list\r\n\t\t\tvar node = rank[i][j];\r\n\t\t\tvar nodeWrapper = new WeightedCellSorter(node, i);\r\n\t\t\tnodeWrapper.rankIndex = j;\r\n\t\t\tnodeWrapper.visited = true;\r\n\t\t\tnodeList.push(nodeWrapper);\r\n\t\t\t\r\n\t\t\tmap.put(node, nodeWrapper);\r\n\t\t}\r\n\t}\r\n\t\r\n\t// Set a limit of the maximum number of times we will access the queue\r\n\t// in case a loop appears\r\n\tvar maxTries = nodeList.length * 10;\r\n\tvar count = 0;\r\n\t\r\n\t// Don't move cell within this value of their median\r\n\tvar tolerance = 1;\r\n\t\r\n\twhile (nodeList.length > 0 && count <= maxTries)\r\n\t{\r\n\t\tvar cellWrapper = nodeList.shift();\r\n\t\tvar cell = cellWrapper.cell;\r\n\t\t\r\n\t\tvar rankValue = cellWrapper.weightedValue;\r\n\t\tvar rankIndex = parseInt(cellWrapper.rankIndex);\r\n\t\t\r\n\t\tvar nextLayerConnectedCells = cell.getNextLayerConnectedCells(rankValue);\r\n\t\tvar previousLayerConnectedCells = cell.getPreviousLayerConnectedCells(rankValue);\r\n\t\t\r\n\t\tvar numNextLayerConnected = nextLayerConnectedCells.length;\r\n\t\tvar numPreviousLayerConnected = previousLayerConnectedCells.length;\r\n\r\n\t\tvar medianNextLevel = this.medianXValue(nextLayerConnectedCells,\r\n\t\t\t\trankValue + 1);\r\n\t\tvar medianPreviousLevel = this.medianXValue(previousLayerConnectedCells,\r\n\t\t\t\trankValue - 1);\r\n\r\n\t\tvar numConnectedNeighbours = numNextLayerConnected\r\n\t\t\t\t+ numPreviousLayerConnected;\r\n\t\tvar currentPosition = cell.getGeneralPurposeVariable(rankValue);\r\n\t\tvar cellMedian = currentPosition;\r\n\t\t\r\n\t\tif (numConnectedNeighbours > 0)\r\n\t\t{\r\n\t\t\tcellMedian = (medianNextLevel * numNextLayerConnected + medianPreviousLevel\r\n\t\t\t\t\t* numPreviousLayerConnected)\r\n\t\t\t\t\t/ numConnectedNeighbours;\r\n\t\t}\r\n\r\n\t\t// Flag storing whether or not position has changed\r\n\t\tvar positionChanged = false;\r\n\t\t\r\n\t\tif (cellMedian < currentPosition - tolerance)\r\n\t\t{\r\n\t\t\tif (rankIndex == 0)\r\n\t\t\t{\r\n\t\t\t\tcell.setGeneralPurposeVariable(rankValue, cellMedian);\r\n\t\t\t\tpositionChanged = true;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tvar leftCell = rank[rankValue][rankIndex - 1];\r\n\t\t\t\tvar leftLimit = leftCell\r\n\t\t\t\t\t\t.getGeneralPurposeVariable(rankValue);\r\n\t\t\t\tleftLimit = leftLimit + leftCell.width / 2\r\n\t\t\t\t\t\t+ this.intraCellSpacing + cell.width / 2;\r\n\r\n\t\t\t\tif (leftLimit < cellMedian)\r\n\t\t\t\t{\r\n\t\t\t\t\tcell.setGeneralPurposeVariable(rankValue, cellMedian);\r\n\t\t\t\t\tpositionChanged = true;\r\n\t\t\t\t}\r\n\t\t\t\telse if (leftLimit < cell\r\n\t\t\t\t\t\t.getGeneralPurposeVariable(rankValue)\r\n\t\t\t\t\t\t- tolerance)\r\n\t\t\t\t{\r\n\t\t\t\t\tcell.setGeneralPurposeVariable(rankValue, leftLimit);\r\n\t\t\t\t\tpositionChanged = true;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (cellMedian > currentPosition + tolerance)\r\n\t\t{\r\n\t\t\tvar rankSize = rank[rankValue].length;\r\n\t\t\t\r\n\t\t\tif (rankIndex == rankSize - 1)\r\n\t\t\t{\r\n\t\t\t\tcell.setGeneralPurposeVariable(rankValue, cellMedian);\r\n\t\t\t\tpositionChanged = true;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tvar rightCell = rank[rankValue][rankIndex + 1];\r\n\t\t\t\tvar rightLimit = rightCell\r\n\t\t\t\t\t\t.getGeneralPurposeVariable(rankValue);\r\n\t\t\t\trightLimit = rightLimit - rightCell.width / 2\r\n\t\t\t\t\t\t- this.intraCellSpacing - cell.width / 2;\r\n\t\t\t\t\r\n\t\t\t\tif (rightLimit > cellMedian)\r\n\t\t\t\t{\r\n\t\t\t\t\tcell.setGeneralPurposeVariable(rankValue, cellMedian);\r\n\t\t\t\t\tpositionChanged = true;\r\n\t\t\t\t}\r\n\t\t\t\telse if (rightLimit > cell\r\n\t\t\t\t\t\t.getGeneralPurposeVariable(rankValue)\r\n\t\t\t\t\t\t+ tolerance)\r\n\t\t\t\t{\r\n\t\t\t\t\tcell.setGeneralPurposeVariable(rankValue, rightLimit);\r\n\t\t\t\t\tpositionChanged = true;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (positionChanged)\r\n\t\t{\r\n\t\t\t// Add connected nodes to map and list\r\n\t\t\tfor (var i = 0; i < nextLayerConnectedCells.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar connectedCell = nextLayerConnectedCells[i];\r\n\t\t\t\tvar connectedCellWrapper = map.get(connectedCell);\r\n\t\t\t\t\r\n\t\t\t\tif (connectedCellWrapper != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (connectedCellWrapper.visited == false)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tconnectedCellWrapper.visited = true;\r\n\t\t\t\t\t\tnodeList.push(connectedCellWrapper);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// Add connected nodes to map and list\r\n\t\t\tfor (var i = 0; i < previousLayerConnectedCells.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar connectedCell = previousLayerConnectedCells[i];\r\n\t\t\t\tvar connectedCellWrapper = map.get(connectedCell);\r\n\r\n\t\t\t\tif (connectedCellWrapper != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (connectedCellWrapper.visited == false)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tconnectedCellWrapper.visited = true;\r\n\t\t\t\t\t\tnodeList.push(connectedCellWrapper);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tcellWrapper.visited = false;\r\n\t\tcount++;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: medianPos\r\n * \r\n * Performs one median positioning sweep in one direction\r\n * \r\n * Parameters:\r\n * \r\n * i - the iteration of the whole process\r\n * model - an internal model of the hierarchical layout\r\n */\r\nmxCoordinateAssignment.prototype.medianPos = function(i, model)\r\n{\r\n\t// Reverse sweep direction each time through this method\r\n\tvar downwardSweep = (i % 2 == 0);\r\n\t\r\n\tif (downwardSweep)\r\n\t{\r\n\t\tfor (var j = model.maxRank; j > 0; j--)\r\n\t\t{\r\n\t\t\tthis.rankMedianPosition(j - 1, model, j);\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tfor (var j = 0; j < model.maxRank - 1; j++)\r\n\t\t{\r\n\t\t\tthis.rankMedianPosition(j + 1, model, j);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: rankMedianPosition\r\n * \r\n * Performs median minimisation over one rank.\r\n * \r\n * Parameters:\r\n * \r\n * rankValue - the layer number of this rank\r\n * model - an internal model of the hierarchical layout\r\n * nextRankValue - the layer number whose connected cels are to be laid out\r\n * relative to\r\n */\r\nmxCoordinateAssignment.prototype.rankMedianPosition = function(rankValue, model, nextRankValue)\r\n{\r\n\tvar rank = model.ranks[rankValue];\r\n\r\n\t// Form an array of the order in which the cell are to be processed\r\n\t// , the order is given by the weighted sum of the in or out edges,\r\n\t// depending on whether we're traveling up or down the hierarchy.\r\n\tvar weightedValues = [];\r\n\tvar cellMap = new Object();\r\n\r\n\tfor (var i = 0; i < rank.length; i++)\r\n\t{\r\n\t\tvar currentCell = rank[i];\r\n\t\tweightedValues[i] = new WeightedCellSorter();\r\n\t\tweightedValues[i].cell = currentCell;\r\n\t\tweightedValues[i].rankIndex = i;\r\n\t\tcellMap[currentCell.id] = weightedValues[i];\r\n\t\tvar nextLayerConnectedCells = null;\r\n\t\t\r\n\t\tif (nextRankValue < rankValue)\r\n\t\t{\r\n\t\t\tnextLayerConnectedCells = currentCell\r\n\t\t\t\t\t.getPreviousLayerConnectedCells(rankValue);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tnextLayerConnectedCells = currentCell\r\n\t\t\t\t\t.getNextLayerConnectedCells(rankValue);\r\n\t\t}\r\n\r\n\t\t// Calculate the weighing based on this node type and those this\r\n\t\t// node is connected to on the next layer\r\n\t\tweightedValues[i].weightedValue = this.calculatedWeightedValue(\r\n\t\t\t\tcurrentCell, nextLayerConnectedCells);\r\n\t}\r\n\r\n\tweightedValues.sort(WeightedCellSorter.prototype.compare);\r\n\r\n\t// Set the new position of each node within the rank using\r\n\t// its temp variable\r\n\t\r\n\tfor (var i = 0; i < weightedValues.length; i++)\r\n\t{\r\n\t\tvar numConnectionsNextLevel = 0;\r\n\t\tvar cell = weightedValues[i].cell;\r\n\t\tvar nextLayerConnectedCells = null;\r\n\t\tvar medianNextLevel = 0;\r\n\r\n\t\tif (nextRankValue < rankValue)\r\n\t\t{\r\n\t\t\tnextLayerConnectedCells = cell.getPreviousLayerConnectedCells(\r\n\t\t\t\t\trankValue).slice();\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tnextLayerConnectedCells = cell.getNextLayerConnectedCells(\r\n\t\t\t\t\trankValue).slice();\r\n\t\t}\r\n\r\n\t\tif (nextLayerConnectedCells != null)\r\n\t\t{\r\n\t\t\tnumConnectionsNextLevel = nextLayerConnectedCells.length;\r\n\t\t\t\r\n\t\t\tif (numConnectionsNextLevel > 0)\r\n\t\t\t{\r\n\t\t\t\tmedianNextLevel = this.medianXValue(nextLayerConnectedCells,\r\n\t\t\t\t\t\tnextRankValue);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\t// For case of no connections on the next level set the\r\n\t\t\t\t// median to be the current position and try to be\r\n\t\t\t\t// positioned there\r\n\t\t\t\tmedianNextLevel = cell.getGeneralPurposeVariable(rankValue);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tvar leftBuffer = 0.0;\r\n\t\tvar leftLimit = -100000000.0;\r\n\t\t\r\n\t\tfor (var j = weightedValues[i].rankIndex - 1; j >= 0;)\r\n\t\t{\r\n\t\t\tvar weightedValue = cellMap[rank[j].id];\r\n\t\t\t\r\n\t\t\tif (weightedValue != null)\r\n\t\t\t{\r\n\t\t\t\tvar leftCell = weightedValue.cell;\r\n\t\t\t\t\r\n\t\t\t\tif (weightedValue.visited)\r\n\t\t\t\t{\r\n\t\t\t\t\t// The left limit is the right hand limit of that\r\n\t\t\t\t\t// cell plus any allowance for unallocated cells\r\n\t\t\t\t\t// in-between\r\n\t\t\t\t\tleftLimit = leftCell\r\n\t\t\t\t\t\t\t.getGeneralPurposeVariable(rankValue)\r\n\t\t\t\t\t\t\t+ leftCell.width\r\n\t\t\t\t\t\t\t/ 2.0\r\n\t\t\t\t\t\t\t+ this.intraCellSpacing\r\n\t\t\t\t\t\t\t+ leftBuffer + cell.width / 2.0;\r\n\t\t\t\t\tj = -1;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tleftBuffer += leftCell.width + this.intraCellSpacing;\r\n\t\t\t\t\tj--;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tvar rightBuffer = 0.0;\r\n\t\tvar rightLimit = 100000000.0;\r\n\t\t\r\n\t\tfor (var j = weightedValues[i].rankIndex + 1; j < weightedValues.length;)\r\n\t\t{\r\n\t\t\tvar weightedValue = cellMap[rank[j].id];\r\n\t\t\t\r\n\t\t\tif (weightedValue != null)\r\n\t\t\t{\r\n\t\t\t\tvar rightCell = weightedValue.cell;\r\n\t\t\t\t\r\n\t\t\t\tif (weightedValue.visited)\r\n\t\t\t\t{\r\n\t\t\t\t\t// The left limit is the right hand limit of that\r\n\t\t\t\t\t// cell plus any allowance for unallocated cells\r\n\t\t\t\t\t// in-between\r\n\t\t\t\t\trightLimit = rightCell\r\n\t\t\t\t\t\t\t.getGeneralPurposeVariable(rankValue)\r\n\t\t\t\t\t\t\t- rightCell.width\r\n\t\t\t\t\t\t\t/ 2.0\r\n\t\t\t\t\t\t\t- this.intraCellSpacing\r\n\t\t\t\t\t\t\t- rightBuffer - cell.width / 2.0;\r\n\t\t\t\t\tj = weightedValues.length;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\trightBuffer += rightCell.width + this.intraCellSpacing;\r\n\t\t\t\t\tj++;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (medianNextLevel >= leftLimit && medianNextLevel <= rightLimit)\r\n\t\t{\r\n\t\t\tcell.setGeneralPurposeVariable(rankValue, medianNextLevel);\r\n\t\t}\r\n\t\telse if (medianNextLevel < leftLimit)\r\n\t\t{\r\n\t\t\t// Couldn't place at median value, place as close to that\r\n\t\t\t// value as possible\r\n\t\t\tcell.setGeneralPurposeVariable(rankValue, leftLimit);\r\n\t\t\tthis.currentXDelta += leftLimit - medianNextLevel;\r\n\t\t}\r\n\t\telse if (medianNextLevel > rightLimit)\r\n\t\t{\r\n\t\t\t// Couldn't place at median value, place as close to that\r\n\t\t\t// value as possible\r\n\t\t\tcell.setGeneralPurposeVariable(rankValue, rightLimit);\r\n\t\t\tthis.currentXDelta += medianNextLevel - rightLimit;\r\n\t\t}\r\n\r\n\t\tweightedValues[i].visited = true;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: calculatedWeightedValue\r\n * \r\n * Calculates the priority the specified cell has based on the type of its\r\n * cell and the cells it is connected to on the next layer\r\n * \r\n * Parameters:\r\n * \r\n * currentCell - the cell whose weight is to be calculated\r\n * collection - the cells the specified cell is connected to\r\n */\r\nmxCoordinateAssignment.prototype.calculatedWeightedValue = function(currentCell, collection)\r\n{\r\n\tvar totalWeight = 0;\r\n\t\r\n\tfor (var i = 0; i < collection.length; i++)\r\n\t{\r\n\t\tvar cell = collection[i];\r\n\r\n\t\tif (currentCell.isVertex() && cell.isVertex())\r\n\t\t{\r\n\t\t\ttotalWeight++;\r\n\t\t}\r\n\t\telse if (currentCell.isEdge() && cell.isEdge())\r\n\t\t{\r\n\t\t\ttotalWeight += 8;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\ttotalWeight += 2;\r\n\t\t}\r\n\t}\r\n\r\n\treturn totalWeight;\r\n};\r\n\r\n/**\r\n * Function: medianXValue\r\n * \r\n * Calculates the median position of the connected cell on the specified\r\n * rank\r\n * \r\n * Parameters:\r\n * \r\n * connectedCells - the cells the candidate connects to on this level\r\n * rankValue - the layer number of this rank\r\n */\r\nmxCoordinateAssignment.prototype.medianXValue = function(connectedCells, rankValue)\r\n{\r\n\tif (connectedCells.length == 0)\r\n\t{\r\n\t\treturn 0;\r\n\t}\r\n\r\n\tvar medianValues = [];\r\n\r\n\tfor (var i = 0; i < connectedCells.length; i++)\r\n\t{\r\n\t\tmedianValues[i] = connectedCells[i].getGeneralPurposeVariable(rankValue);\r\n\t}\r\n\r\n\tmedianValues.sort(function(a,b){return a - b;});\r\n\t\r\n\tif (connectedCells.length % 2 == 1)\r\n\t{\r\n\t\t// For odd numbers of adjacent vertices return the median\r\n\t\treturn medianValues[Math.floor(connectedCells.length / 2)];\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar medianPoint = connectedCells.length / 2;\r\n\t\tvar leftMedian = medianValues[medianPoint - 1];\r\n\t\tvar rightMedian = medianValues[medianPoint];\r\n\r\n\t\treturn ((leftMedian + rightMedian) / 2);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: initialCoords\r\n * \r\n * Sets up the layout in an initial positioning. The ranks are all centered\r\n * as much as possible along the middle vertex in each rank. The other cells\r\n * are then placed as close as possible on either side.\r\n * \r\n * Parameters:\r\n * \r\n * facade - the facade describing the input graph\r\n * model - an internal model of the hierarchical layout\r\n */\r\nmxCoordinateAssignment.prototype.initialCoords = function(facade, model)\r\n{\r\n\tthis.calculateWidestRank(facade, model);\r\n\r\n\t// Sweep up and down from the widest rank\r\n\tfor (var i = this.widestRank; i >= 0; i--)\r\n\t{\r\n\t\tif (i < model.maxRank)\r\n\t\t{\r\n\t\t\tthis.rankCoordinates(i, facade, model);\r\n\t\t}\r\n\t}\r\n\r\n\tfor (var i = this.widestRank+1; i <= model.maxRank; i++)\r\n\t{\r\n\t\tif (i > 0)\r\n\t\t{\r\n\t\t\tthis.rankCoordinates(i, facade, model);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: rankCoordinates\r\n * \r\n * Sets up the layout in an initial positioning. All the first cells in each\r\n * rank are moved to the left and the rest of the rank inserted as close\r\n * together as their size and buffering permits. This method works on just\r\n * the specified rank.\r\n * \r\n * Parameters:\r\n * \r\n * rankValue - the current rank being processed\r\n * graph - the facade describing the input graph\r\n * model - an internal model of the hierarchical layout\r\n */\r\nmxCoordinateAssignment.prototype.rankCoordinates = function(rankValue, graph, model)\r\n{\r\n\tvar rank = model.ranks[rankValue];\r\n\tvar maxY = 0.0;\r\n\tvar localX = this.initialX + (this.widestRankValue - this.rankWidths[rankValue])\r\n\t\t\t/ 2;\r\n\r\n\t// Store whether or not any of the cells' bounds were unavailable so\r\n\t// to only issue the warning once for all cells\r\n\tvar boundsWarning = false;\r\n\t\r\n\tfor (var i = 0; i < rank.length; i++)\r\n\t{\r\n\t\tvar node = rank[i];\r\n\t\t\r\n\t\tif (node.isVertex())\r\n\t\t{\r\n\t\t\tvar bounds = this.layout.getVertexBounds(node.cell);\r\n\r\n\t\t\tif (bounds != null)\r\n\t\t\t{\r\n\t\t\t\tif (this.orientation == mxConstants.DIRECTION_NORTH ||\r\n\t\t\t\t\tthis.orientation == mxConstants.DIRECTION_SOUTH)\r\n\t\t\t\t{\r\n\t\t\t\t\tnode.width = bounds.width;\r\n\t\t\t\t\tnode.height = bounds.height;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tnode.width = bounds.height;\r\n\t\t\t\t\tnode.height = bounds.width;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tboundsWarning = true;\r\n\t\t\t}\r\n\r\n\t\t\tmaxY = Math.max(maxY, node.height);\r\n\t\t}\r\n\t\telse if (node.isEdge())\r\n\t\t{\r\n\t\t\t// The width is the number of additional parallel edges\r\n\t\t\t// time the parallel edge spacing\r\n\t\t\tvar numEdges = 1;\r\n\r\n\t\t\tif (node.edges != null)\r\n\t\t\t{\r\n\t\t\t\tnumEdges = node.edges.length;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tmxLog.warn('edge.edges is null');\r\n\t\t\t}\r\n\r\n\t\t\tnode.width = (numEdges - 1) * this.parallelEdgeSpacing;\r\n\t\t}\r\n\r\n\t\t// Set the initial x-value as being the best result so far\r\n\t\tlocalX += node.width / 2.0;\r\n\t\tnode.setX(rankValue, localX);\r\n\t\tnode.setGeneralPurposeVariable(rankValue, localX);\r\n\t\tlocalX += node.width / 2.0;\r\n\t\tlocalX += this.intraCellSpacing;\r\n\t}\r\n\r\n\tif (boundsWarning == true)\r\n\t{\r\n\t\tmxLog.warn('At least one cell has no bounds');\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: calculateWidestRank\r\n * \r\n * Calculates the width rank in the hierarchy. Also set the y value of each\r\n * rank whilst performing the calculation\r\n * \r\n * Parameters:\r\n * \r\n * graph - the facade describing the input graph\r\n * model - an internal model of the hierarchical layout\r\n */\r\nmxCoordinateAssignment.prototype.calculateWidestRank = function(graph, model)\r\n{\r\n\t// Starting y co-ordinate\r\n\tvar y = -this.interRankCellSpacing;\r\n\t\r\n\t// Track the widest cell on the last rank since the y\r\n\t// difference depends on it\r\n\tvar lastRankMaxCellHeight = 0.0;\r\n\tthis.rankWidths = [];\r\n\tthis.rankY = [];\r\n\r\n\tfor (var rankValue = model.maxRank; rankValue >= 0; rankValue--)\r\n\t{\r\n\t\t// Keep track of the widest cell on this rank\r\n\t\tvar maxCellHeight = 0.0;\r\n\t\tvar rank = model.ranks[rankValue];\r\n\t\tvar localX = this.initialX;\r\n\r\n\t\t// Store whether or not any of the cells' bounds were unavailable so\r\n\t\t// to only issue the warning once for all cells\r\n\t\tvar boundsWarning = false;\r\n\t\t\r\n\t\tfor (var i = 0; i < rank.length; i++)\r\n\t\t{\r\n\t\t\tvar node = rank[i];\r\n\r\n\t\t\tif (node.isVertex())\r\n\t\t\t{\r\n\t\t\t\tvar bounds = this.layout.getVertexBounds(node.cell);\r\n\r\n\t\t\t\tif (bounds != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (this.orientation == mxConstants.DIRECTION_NORTH ||\r\n\t\t\t\t\t\tthis.orientation == mxConstants.DIRECTION_SOUTH)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tnode.width = bounds.width;\r\n\t\t\t\t\t\tnode.height = bounds.height;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tnode.width = bounds.height;\r\n\t\t\t\t\t\tnode.height = bounds.width;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tboundsWarning = true;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tmaxCellHeight = Math.max(maxCellHeight, node.height);\r\n\t\t\t}\r\n\t\t\telse if (node.isEdge())\r\n\t\t\t{\r\n\t\t\t\t// The width is the number of additional parallel edges\r\n\t\t\t\t// time the parallel edge spacing\r\n\t\t\t\tvar numEdges = 1;\r\n\r\n\t\t\t\tif (node.edges != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tnumEdges = node.edges.length;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tmxLog.warn('edge.edges is null');\r\n\t\t\t\t}\r\n\r\n\t\t\t\tnode.width = (numEdges - 1) * this.parallelEdgeSpacing;\r\n\t\t\t}\r\n\r\n\t\t\t// Set the initial x-value as being the best result so far\r\n\t\t\tlocalX += node.width / 2.0;\r\n\t\t\tnode.setX(rankValue, localX);\r\n\t\t\tnode.setGeneralPurposeVariable(rankValue, localX);\r\n\t\t\tlocalX += node.width / 2.0;\r\n\t\t\tlocalX += this.intraCellSpacing;\r\n\r\n\t\t\tif (localX > this.widestRankValue)\r\n\t\t\t{\r\n\t\t\t\tthis.widestRankValue = localX;\r\n\t\t\t\tthis.widestRank = rankValue;\r\n\t\t\t}\r\n\r\n\t\t\tthis.rankWidths[rankValue] = localX;\r\n\t\t}\r\n\r\n\t\tif (boundsWarning == true)\r\n\t\t{\r\n\t\t\tmxLog.warn('At least one cell has no bounds');\r\n\t\t}\r\n\r\n\t\tthis.rankY[rankValue] = y;\r\n\t\tvar distanceToNextRank = maxCellHeight / 2.0\r\n\t\t\t\t+ lastRankMaxCellHeight / 2.0 + this.interRankCellSpacing;\r\n\t\tlastRankMaxCellHeight = maxCellHeight;\r\n\r\n\t\tif (this.orientation == mxConstants.DIRECTION_NORTH ||\r\n\t\t\tthis.orientation == mxConstants.DIRECTION_WEST)\r\n\t\t{\r\n\t\t\ty += distanceToNextRank;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\ty -= distanceToNextRank;\r\n\t\t}\r\n\r\n\t\tfor (var i = 0; i < rank.length; i++)\r\n\t\t{\r\n\t\t\tvar cell = rank[i];\r\n\t\t\tcell.setY(rankValue, y);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: minPath\r\n * \r\n * Straightens out chains of virtual nodes where possibleacade to those stored after this layout\r\n * processing step has completed.\r\n * \r\n * Parameters:\r\n *\r\n * graph - the facade describing the input graph\r\n * model - an internal model of the hierarchical layout\r\n */\r\nmxCoordinateAssignment.prototype.minPath = function(graph, model)\r\n{\r\n\t// Work down and up each edge with at least 2 control points\r\n\t// trying to straighten each one out. If the same number of\r\n\t// straight segments are formed in both directions, the \r\n\t// preferred direction used is the one where the final\r\n\t// control points have the least offset from the connectable \r\n\t// region of the terminating vertices\r\n\tvar edges = model.edgeMapper.getValues();\r\n\t\r\n\tfor (var j = 0; j < edges.length; j++)\r\n\t{\r\n\t\tvar cell = edges[j];\r\n\t\t\r\n\t\tif (cell.maxRank - cell.minRank - 1 < 1)\r\n\t\t{\r\n\t\t\tcontinue;\r\n\t\t}\r\n\r\n\t\t// At least two virtual nodes in the edge\r\n\t\t// Check first whether the edge is already straight\r\n\t\tvar referenceX = cell\r\n\t\t\t\t.getGeneralPurposeVariable(cell.minRank + 1);\r\n\t\tvar edgeStraight = true;\r\n\t\tvar refSegCount = 0;\r\n\t\t\r\n\t\tfor (var i = cell.minRank + 2; i < cell.maxRank; i++)\r\n\t\t{\r\n\t\t\tvar x = cell.getGeneralPurposeVariable(i);\r\n\r\n\t\t\tif (referenceX != x)\r\n\t\t\t{\r\n\t\t\t\tedgeStraight = false;\r\n\t\t\t\treferenceX = x;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\trefSegCount++;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (!edgeStraight)\r\n\t\t{\r\n\t\t\tvar upSegCount = 0;\r\n\t\t\tvar downSegCount = 0;\r\n\t\t\tvar upXPositions = [];\r\n\t\t\tvar downXPositions = [];\r\n\r\n\t\t\tvar currentX = cell.getGeneralPurposeVariable(cell.minRank + 1);\r\n\r\n\t\t\tfor (var i = cell.minRank + 1; i < cell.maxRank - 1; i++)\r\n\t\t\t{\r\n\t\t\t\t// Attempt to straight out the control point on the\r\n\t\t\t\t// next segment up with the current control point.\r\n\t\t\t\tvar nextX = cell.getX(i + 1);\r\n\r\n\t\t\t\tif (currentX == nextX)\r\n\t\t\t\t{\r\n\t\t\t\t\tupXPositions[i - cell.minRank - 1] = currentX;\r\n\t\t\t\t\tupSegCount++;\r\n\t\t\t\t}\r\n\t\t\t\telse if (this.repositionValid(model, cell, i + 1, currentX))\r\n\t\t\t\t{\r\n\t\t\t\t\tupXPositions[i - cell.minRank - 1] = currentX;\r\n\t\t\t\t\tupSegCount++;\r\n\t\t\t\t\t// Leave currentX at same value\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tupXPositions[i - cell.minRank - 1] = nextX;\r\n\t\t\t\t\tcurrentX = nextX;\r\n\t\t\t\t}\t\t\t\t\r\n\t\t\t}\r\n\r\n\t\t\tcurrentX = cell.getX(i);\r\n\r\n\t\t\tfor (var i = cell.maxRank - 1; i > cell.minRank + 1; i--)\r\n\t\t\t{\r\n\t\t\t\t// Attempt to straight out the control point on the\r\n\t\t\t\t// next segment down with the current control point.\r\n\t\t\t\tvar nextX = cell.getX(i - 1);\r\n\r\n\t\t\t\tif (currentX == nextX)\r\n\t\t\t\t{\r\n\t\t\t\t\tdownXPositions[i - cell.minRank - 2] = currentX;\r\n\t\t\t\t\tdownSegCount++;\r\n\t\t\t\t}\r\n\t\t\t\telse if (this.repositionValid(model, cell, i - 1, currentX))\r\n\t\t\t\t{\r\n\t\t\t\t\tdownXPositions[i - cell.minRank - 2] = currentX;\r\n\t\t\t\t\tdownSegCount++;\r\n\t\t\t\t\t// Leave currentX at same value\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tdownXPositions[i - cell.minRank - 2] = cell.getX(i-1);\r\n\t\t\t\t\tcurrentX = nextX;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (downSegCount > refSegCount || upSegCount > refSegCount)\r\n\t\t\t{\r\n\t\t\t\tif (downSegCount >= upSegCount)\r\n\t\t\t\t{\r\n\t\t\t\t\t// Apply down calculation values\r\n\t\t\t\t\tfor (var i = cell.maxRank - 2; i > cell.minRank; i--)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tcell.setX(i, downXPositions[i - cell.minRank - 1]);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if (upSegCount > downSegCount)\r\n\t\t\t\t{\r\n\t\t\t\t\t// Apply up calculation values\r\n\t\t\t\t\tfor (var i = cell.minRank + 2; i < cell.maxRank; i++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tcell.setX(i, upXPositions[i - cell.minRank - 2]);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\t// Neither direction provided a favourable result\r\n\t\t\t\t\t// But both calculations are better than the\r\n\t\t\t\t\t// existing solution, so apply the one with minimal\r\n\t\t\t\t\t// offset to attached vertices at either end.\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: repositionValid\r\n * \r\n * Determines whether or not a node may be moved to the specified x \r\n * position on the specified rank\r\n * \r\n * Parameters:\r\n *\r\n * model - the layout model\r\n * cell - the cell being analysed\r\n * rank - the layer of the cell\r\n * position - the x position being sought\r\n */\r\nmxCoordinateAssignment.prototype.repositionValid = function(model, cell, rank, position)\r\n{\r\n\tvar rankArray = model.ranks[rank];\r\n\tvar rankIndex = -1;\r\n\r\n\tfor (var i = 0; i < rankArray.length; i++)\r\n\t{\r\n\t\tif (cell == rankArray[i])\r\n\t\t{\r\n\t\t\trankIndex = i;\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n\tif (rankIndex < 0)\r\n\t{\r\n\t\treturn false;\r\n\t}\r\n\r\n\tvar currentX = cell.getGeneralPurposeVariable(rank);\r\n\r\n\tif (position < currentX)\r\n\t{\r\n\t\t// Trying to move node to the left.\r\n\t\tif (rankIndex == 0)\r\n\t\t{\r\n\t\t\t// Left-most node, can move anywhere\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\tvar leftCell = rankArray[rankIndex - 1];\r\n\t\tvar leftLimit = leftCell.getGeneralPurposeVariable(rank);\r\n\t\tleftLimit = leftLimit + leftCell.width / 2\r\n\t\t\t\t+ this.intraCellSpacing + cell.width / 2;\r\n\r\n\t\tif (leftLimit <= position)\r\n\t\t{\r\n\t\t\treturn true;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}\r\n\telse if (position > currentX)\r\n\t{\r\n\t\t// Trying to move node to the right.\r\n\t\tif (rankIndex == rankArray.length - 1)\r\n\t\t{\r\n\t\t\t// Right-most node, can move anywhere\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\tvar rightCell = rankArray[rankIndex + 1];\r\n\t\tvar rightLimit = rightCell.getGeneralPurposeVariable(rank);\r\n\t\trightLimit = rightLimit - rightCell.width / 2\r\n\t\t\t\t- this.intraCellSpacing - cell.width / 2;\r\n\r\n\t\tif (rightLimit >= position)\r\n\t\t{\r\n\t\t\treturn true;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}\r\n\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: setCellLocations\r\n * \r\n * Sets the cell locations in the facade to those stored after this layout\r\n * processing step has completed.\r\n * \r\n * Parameters:\r\n *\r\n * graph - the input graph\r\n * model - the layout model\r\n */\r\nmxCoordinateAssignment.prototype.setCellLocations = function(graph, model)\r\n{\r\n\tthis.rankTopY = [];\r\n\tthis.rankBottomY = [];\r\n\r\n\tfor (var i = 0; i < model.ranks.length; i++)\r\n\t{\r\n\t\tthis.rankTopY[i] = Number.MAX_VALUE;\r\n\t\tthis.rankBottomY[i] = -Number.MAX_VALUE;\r\n\t}\r\n\t\r\n\tvar vertices = model.vertexMapper.getValues();\r\n\r\n\t// Process vertices all first, since they define the lower and \r\n\t// limits of each rank. Between these limits lie the channels\r\n\t// where the edges can be routed across the graph\r\n\r\n\tfor (var i = 0; i < vertices.length; i++)\r\n\t{\r\n\t\tthis.setVertexLocation(vertices[i]);\r\n\t}\r\n\t\r\n\t// Post process edge styles. Needs the vertex locations set for initial\r\n\t// values of the top and bottoms of each rank\r\n\tif (this.layout.edgeStyle == mxHierarchicalEdgeStyle.ORTHOGONAL\r\n\t\t\t|| this.layout.edgeStyle == mxHierarchicalEdgeStyle.POLYLINE\r\n\t\t\t|| this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE)\r\n\t{\r\n\t\tthis.localEdgeProcessing(model);\r\n\t}\r\n\r\n\tvar edges = model.edgeMapper.getValues();\r\n\r\n\tfor (var i = 0; i < edges.length; i++)\r\n\t{\r\n\t\tthis.setEdgePosition(edges[i]);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: localEdgeProcessing\r\n * \r\n * Separates the x position of edges as they connect to vertices\r\n * \r\n * Parameters:\r\n *\r\n * model - the layout model\r\n */\r\nmxCoordinateAssignment.prototype.localEdgeProcessing = function(model)\r\n{\r\n\t// Iterate through each vertex, look at the edges connected in\r\n\t// both directions.\r\n\tfor (var rankIndex = 0; rankIndex < model.ranks.length; rankIndex++)\r\n\t{\r\n\t\tvar rank = model.ranks[rankIndex];\r\n\r\n\t\tfor (var cellIndex = 0; cellIndex < rank.length; cellIndex++)\r\n\t\t{\r\n\t\t\tvar cell = rank[cellIndex];\r\n\r\n\t\t\tif (cell.isVertex())\r\n\t\t\t{\r\n\t\t\t\tvar currentCells = cell.getPreviousLayerConnectedCells(rankIndex);\r\n\r\n\t\t\t\tvar currentRank = rankIndex - 1;\r\n\r\n\t\t\t\t// Two loops, last connected cells, and next\r\n\t\t\t\tfor (var k = 0; k < 2; k++)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (currentRank > -1\r\n\t\t\t\t\t\t\t&& currentRank < model.ranks.length\r\n\t\t\t\t\t\t\t&& currentCells != null\r\n\t\t\t\t\t\t\t&& currentCells.length > 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar sortedCells = [];\r\n\r\n\t\t\t\t\t\tfor (var j = 0; j < currentCells.length; j++)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar sorter = new WeightedCellSorter(\r\n\t\t\t\t\t\t\t\t\tcurrentCells[j], currentCells[j].getX(currentRank));\r\n\t\t\t\t\t\t\tsortedCells.push(sorter);\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tsortedCells.sort(WeightedCellSorter.prototype.compare);\r\n\r\n\t\t\t\t\t\tvar leftLimit = cell.x[0] - cell.width / 2;\r\n\t\t\t\t\t\tvar rightLimit = leftLimit + cell.width;\r\n\r\n\t\t\t\t\t\t// Connected edge count starts at 1 to allow for buffer\r\n\t\t\t\t\t\t// with edge of vertex\r\n\t\t\t\t\t\tvar connectedEdgeCount = 0;\r\n\t\t\t\t\t\tvar connectedEdgeGroupCount = 0;\r\n\t\t\t\t\t\tvar connectedEdges = [];\r\n\t\t\t\t\t\t// Calculate width requirements for all connected edges\r\n\t\t\t\t\t\tfor (var j = 0; j < sortedCells.length; j++)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar innerCell = sortedCells[j].cell;\r\n\t\t\t\t\t\t\tvar connections;\r\n\r\n\t\t\t\t\t\t\tif (innerCell.isVertex())\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t// Get the connecting edge\r\n\t\t\t\t\t\t\t\tif (k == 0)\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\tconnections = cell.connectsAsSource;\r\n\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\tconnections = cell.connectsAsTarget;\r\n\t\t\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t\t\tfor (var connIndex = 0; connIndex < connections.length; connIndex++)\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\tif (connections[connIndex].source == innerCell\r\n\t\t\t\t\t\t\t\t\t\t\t|| connections[connIndex].target == innerCell)\r\n\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\tconnectedEdgeCount += connections[connIndex].edges\r\n\t\t\t\t\t\t\t\t\t\t\t\t.length;\r\n\t\t\t\t\t\t\t\t\t\tconnectedEdgeGroupCount++;\r\n\r\n\t\t\t\t\t\t\t\t\t\tconnectedEdges.push(connections[connIndex]);\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tconnectedEdgeCount += innerCell.edges.length;\r\n\t\t\t\t\t\t\t\tconnectedEdgeGroupCount++;\r\n\t\t\t\t\t\t\t\tconnectedEdges.push(innerCell);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tvar requiredWidth = (connectedEdgeCount + 1)\r\n\t\t\t\t\t\t\t\t* this.prefHozEdgeSep;\r\n\r\n\t\t\t\t\t\t// Add a buffer on the edges of the vertex if the edge count allows\r\n\t\t\t\t\t\tif (cell.width > requiredWidth\r\n\t\t\t\t\t\t\t\t+ (2 * this.prefHozEdgeSep))\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tleftLimit += this.prefHozEdgeSep;\r\n\t\t\t\t\t\t\trightLimit -= this.prefHozEdgeSep;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tvar availableWidth = rightLimit - leftLimit;\r\n\t\t\t\t\t\tvar edgeSpacing = availableWidth / connectedEdgeCount;\r\n\r\n\t\t\t\t\t\tvar currentX = leftLimit + edgeSpacing / 2.0;\r\n\t\t\t\t\t\tvar currentYOffset = this.minEdgeJetty - this.prefVertEdgeOff;\r\n\t\t\t\t\t\tvar maxYOffset = 0;\r\n\r\n\t\t\t\t\t\tfor (var j = 0; j < connectedEdges.length; j++)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar numActualEdges = connectedEdges[j].edges\r\n\t\t\t\t\t\t\t\t\t.length;\r\n\t\t\t\t\t\t\tvar pos = this.jettyPositions[connectedEdges[j].ids[0]];\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tif (pos == null)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tpos = [];\r\n\t\t\t\t\t\t\t\tthis.jettyPositions[connectedEdges[j].ids[0]] = pos;\r\n\t\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t\tif (j < connectedEdgeCount / 2)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tcurrentYOffset += this.prefVertEdgeOff;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\telse if (j > connectedEdgeCount / 2)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tcurrentYOffset -= this.prefVertEdgeOff;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t// Ignore the case if equals, this means the second of 2\r\n\t\t\t\t\t\t\t// jettys with the same y (even number of edges)\r\n\r\n\t\t\t\t\t\t\tfor (var m = 0; m < numActualEdges; m++)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tpos[m * 4 + k * 2] = currentX;\r\n\t\t\t\t\t\t\t\tcurrentX += edgeSpacing;\r\n\t\t\t\t\t\t\t\tpos[m * 4 + k * 2 + 1] = currentYOffset;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tmaxYOffset = Math.max(maxYOffset,\r\n\t\t\t\t\t\t\t\t\tcurrentYOffset);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tcurrentCells = cell.getNextLayerConnectedCells(rankIndex);\r\n\r\n\t\t\t\t\tcurrentRank = rankIndex + 1;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setEdgePosition\r\n * \r\n * Fixes the control points\r\n */\r\nmxCoordinateAssignment.prototype.setEdgePosition = function(cell)\r\n{\r\n\t// For parallel edges we need to seperate out the points a\r\n\t// little\r\n\tvar offsetX = 0;\r\n\t// Only set the edge control points once\r\n\r\n\tif (cell.temp[0] != 101207)\r\n\t{\r\n\t\tvar maxRank = cell.maxRank;\r\n\t\tvar minRank = cell.minRank;\r\n\t\t\r\n\t\tif (maxRank == minRank)\r\n\t\t{\r\n\t\t\tmaxRank = cell.source.maxRank;\r\n\t\t\tminRank = cell.target.minRank;\r\n\t\t}\r\n\t\t\r\n\t\tvar parallelEdgeCount = 0;\r\n\t\tvar jettys = this.jettyPositions[cell.ids[0]];\r\n\r\n\t\tvar source = cell.isReversed ? cell.target.cell : cell.source.cell;\r\n\t\tvar graph = this.layout.graph;\r\n\t\tvar layoutReversed = this.orientation == mxConstants.DIRECTION_EAST\r\n\t\t\t\t|| this.orientation == mxConstants.DIRECTION_SOUTH;\r\n\r\n\t\tfor (var i = 0; i < cell.edges.length; i++)\r\n\t\t{\r\n\t\t\tvar realEdge = cell.edges[i];\r\n\t\t\tvar realSource = this.layout.getVisibleTerminal(realEdge, true);\r\n\r\n\t\t\t//List oldPoints = graph.getPoints(realEdge);\r\n\t\t\tvar newPoints = [];\r\n\r\n\t\t\t// Single length reversed edges end up with the jettys in the wrong\r\n\t\t\t// places. Since single length edges only have jettys, not segment\r\n\t\t\t// control points, we just say the edge isn't reversed in this section\r\n\t\t\tvar reversed = cell.isReversed;\r\n\t\t\t\r\n\t\t\tif (realSource != source)\r\n\t\t\t{\r\n\t\t\t\t// The real edges include all core model edges and these can go\r\n\t\t\t\t// in both directions. If the source of the hierarchical model edge\r\n\t\t\t\t// isn't the source of the specific real edge in this iteration\r\n\t\t\t\t// treat if as reversed\r\n\t\t\t\treversed = !reversed;\r\n\t\t\t}\r\n\r\n\t\t\t// First jetty of edge\r\n\t\t\tif (jettys != null)\r\n\t\t\t{\r\n\t\t\t\tvar arrayOffset = reversed ? 2 : 0;\r\n\t\t\t\tvar y = reversed ?\r\n\t\t\t\t\t\t(layoutReversed ? this.rankBottomY[minRank] : this.rankTopY[minRank]) :\r\n\t\t\t\t\t\t\t(layoutReversed ? this.rankTopY[maxRank] : this.rankBottomY[maxRank]);\r\n\t\t\t\tvar jetty = jettys[parallelEdgeCount * 4 + 1 + arrayOffset];\r\n\t\t\t\t\r\n\t\t\t\tif (reversed != layoutReversed)\r\n\t\t\t\t{\r\n\t\t\t\t\tjetty = -jetty;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\ty += jetty;\r\n\t\t\t\tvar x = jettys[parallelEdgeCount * 4 + arrayOffset];\r\n\t\t\t\t\r\n\t\t\t\tvar modelSource = graph.model.getTerminal(realEdge, true);\r\n\r\n\t\t\t\tif (this.layout.isPort(modelSource) && graph.model.getParent(modelSource) == realSource)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar state = graph.view.getState(modelSource);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (state != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tx = state.x;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tx = realSource.geometry.x + cell.source.width * modelSource.geometry.x;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (this.orientation == mxConstants.DIRECTION_NORTH\r\n\t\t\t\t\t\t|| this.orientation == mxConstants.DIRECTION_SOUTH)\r\n\t\t\t\t{\r\n\t\t\t\t\tnewPoints.push(new mxPoint(x, y));\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tnewPoints.push(new mxPoint(x, y + jetty));\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tnewPoints.push(new mxPoint(y, x));\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tnewPoints.push(new mxPoint(y + jetty, x));\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// Declare variables to define loop through edge points and \r\n\t\t\t// change direction if edge is reversed\r\n\r\n\t\t\tvar loopStart = cell.x.length - 1;\r\n\t\t\tvar loopLimit = -1;\r\n\t\t\tvar loopDelta = -1;\r\n\t\t\tvar currentRank = cell.maxRank - 1;\r\n\r\n\t\t\tif (reversed)\r\n\t\t\t{\r\n\t\t\t\tloopStart = 0;\r\n\t\t\t\tloopLimit = cell.x.length;\r\n\t\t\t\tloopDelta = 1;\r\n\t\t\t\tcurrentRank = cell.minRank + 1;\r\n\t\t\t}\r\n\t\t\t// Reversed edges need the points inserted in\r\n\t\t\t// reverse order\r\n\t\t\tfor (var j = loopStart; (cell.maxRank != cell.minRank) && j != loopLimit; j += loopDelta)\r\n\t\t\t{\r\n\t\t\t\t// The horizontal position in a vertical layout\r\n\t\t\t\tvar positionX = cell.x[j] + offsetX;\r\n\r\n\t\t\t\t// Work out the vertical positions in a vertical layout\r\n\t\t\t\t// in the edge buffer channels above and below this rank\r\n\t\t\t\tvar topChannelY = (this.rankTopY[currentRank] + this.rankBottomY[currentRank + 1]) / 2.0;\r\n\t\t\t\tvar bottomChannelY = (this.rankTopY[currentRank - 1] + this.rankBottomY[currentRank]) / 2.0;\r\n\r\n\t\t\t\tif (reversed)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar tmp = topChannelY;\r\n\t\t\t\t\ttopChannelY = bottomChannelY;\r\n\t\t\t\t\tbottomChannelY = tmp;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (this.orientation == mxConstants.DIRECTION_NORTH ||\r\n\t\t\t\t\tthis.orientation == mxConstants.DIRECTION_SOUTH)\r\n\t\t\t\t{\r\n\t\t\t\t\tnewPoints.push(new mxPoint(positionX, topChannelY));\r\n\t\t\t\t\tnewPoints.push(new mxPoint(positionX, bottomChannelY));\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tnewPoints.push(new mxPoint(topChannelY, positionX));\r\n\t\t\t\t\tnewPoints.push(new mxPoint(bottomChannelY, positionX));\r\n\t\t\t\t}\r\n\r\n\t\t\t\tthis.limitX = Math.max(this.limitX, positionX);\r\n\t\t\t\tcurrentRank += loopDelta;\r\n\t\t\t}\r\n\r\n\t\t\t// Second jetty of edge\r\n\t\t\tif (jettys != null)\r\n\t\t\t{\r\n\t\t\t\tvar arrayOffset = reversed ? 2 : 0;\r\n\t\t\t\tvar rankY = reversed ?\r\n\t\t\t\t\t\t(layoutReversed ? this.rankTopY[maxRank] : this.rankBottomY[maxRank]) :\r\n\t\t\t\t\t\t\t(layoutReversed ? this.rankBottomY[minRank] : this.rankTopY[minRank]);\r\n\t\t\t\tvar jetty = jettys[parallelEdgeCount * 4 + 3 - arrayOffset];\r\n\t\t\t\t\r\n\t\t\t\tif (reversed != layoutReversed)\r\n\t\t\t\t{\r\n\t\t\t\t\tjetty = -jetty;\r\n\t\t\t\t}\r\n\t\t\t\tvar y = rankY - jetty;\r\n\t\t\t\tvar x = jettys[parallelEdgeCount * 4 + 2 - arrayOffset];\r\n\t\t\t\t\r\n\t\t\t\tvar modelTarget = graph.model.getTerminal(realEdge, false);\r\n\t\t\t\tvar realTarget = this.layout.getVisibleTerminal(realEdge, false);\r\n\r\n\t\t\t\tif (this.layout.isPort(modelTarget) && graph.model.getParent(modelTarget) == realTarget)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar state = graph.view.getState(modelTarget);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (state != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tx = state.x;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tx = realTarget.geometry.x + cell.target.width * modelTarget.geometry.x;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (this.orientation == mxConstants.DIRECTION_NORTH ||\r\n\t\t\t\t\t\tthis.orientation == mxConstants.DIRECTION_SOUTH)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tnewPoints.push(new mxPoint(x, y - jetty));\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tnewPoints.push(new mxPoint(x, y));\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tif (this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tnewPoints.push(new mxPoint(y - jetty, x));\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tnewPoints.push(new mxPoint(y, x));\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (cell.isReversed)\r\n\t\t\t{\r\n\t\t\t\tthis.processReversedEdge(cell, realEdge);\r\n\t\t\t}\r\n\r\n\t\t\tthis.layout.setEdgePoints(realEdge, newPoints);\r\n\r\n\t\t\t// Increase offset so next edge is drawn next to\r\n\t\t\t// this one\r\n\t\t\tif (offsetX == 0.0)\r\n\t\t\t{\r\n\t\t\t\toffsetX = this.parallelEdgeSpacing;\r\n\t\t\t}\r\n\t\t\telse if (offsetX > 0)\r\n\t\t\t{\r\n\t\t\t\toffsetX = -offsetX;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\toffsetX = -offsetX + this.parallelEdgeSpacing;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tparallelEdgeCount++;\r\n\t\t}\r\n\r\n\t\tcell.temp[0] = 101207;\r\n\t}\r\n};\r\n\r\n\r\n/**\r\n * Function: setVertexLocation\r\n * \r\n * Fixes the position of the specified vertex.\r\n * \r\n * Parameters:\r\n * \r\n * cell - the vertex to position\r\n */\r\nmxCoordinateAssignment.prototype.setVertexLocation = function(cell)\r\n{\r\n\tvar realCell = cell.cell;\r\n\tvar positionX = cell.x[0] - cell.width / 2;\r\n\tvar positionY = cell.y[0] - cell.height / 2;\r\n\r\n\tthis.rankTopY[cell.minRank] = Math.min(this.rankTopY[cell.minRank], positionY);\r\n\tthis.rankBottomY[cell.minRank] = Math.max(this.rankBottomY[cell.minRank],\r\n\t\t\tpositionY + cell.height);\r\n\r\n\tif (this.orientation == mxConstants.DIRECTION_NORTH ||\r\n\t\tthis.orientation == mxConstants.DIRECTION_SOUTH)\r\n\t{\r\n\t\tthis.layout.setVertexLocation(realCell, positionX, positionY);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.layout.setVertexLocation(realCell, positionY, positionX);\r\n\t}\r\n\r\n\tthis.limitX = Math.max(this.limitX, positionX + cell.width);\r\n};\r\n\r\n/**\r\n * Function: processReversedEdge\r\n * \r\n * Hook to add additional processing\r\n * \r\n * Parameters:\r\n * \r\n * edge - the hierarchical model edge\r\n * realEdge - the real edge in the graph\r\n */\r\nmxCoordinateAssignment.prototype.processReversedEdge = function(graph, model)\r\n{\r\n\t// hook for subclassers\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxSwimlaneOrdering\r\n * \r\n * An implementation of the first stage of the Sugiyama layout. Straightforward\r\n * longest path calculation of layer assignment\r\n * \r\n * Constructor: mxSwimlaneOrdering\r\n *\r\n * Creates a cycle remover for the given internal model.\r\n */\r\nfunction mxSwimlaneOrdering(layout)\r\n{\r\n\tthis.layout = layout;\r\n};\r\n\r\n/**\r\n * Extends mxHierarchicalLayoutStage.\r\n */\r\nmxSwimlaneOrdering.prototype = new mxHierarchicalLayoutStage();\r\nmxSwimlaneOrdering.prototype.constructor = mxSwimlaneOrdering;\r\n\r\n/**\r\n * Variable: layout\r\n * \r\n * Reference to the enclosing <mxHierarchicalLayout>.\r\n */\r\nmxSwimlaneOrdering.prototype.layout = null;\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Takes the graph detail and configuration information within the facade\r\n * and creates the resulting laid out graph within that facade for further\r\n * use.\r\n */\r\nmxSwimlaneOrdering.prototype.execute = function(parent)\r\n{\r\n\tvar model = this.layout.getModel();\r\n\tvar seenNodes = new Object();\r\n\tvar unseenNodes = mxUtils.clone(model.vertexMapper, null, true);\r\n\t\r\n\t// Perform a dfs through the internal model. If a cycle is found,\r\n\t// reverse it.\r\n\tvar rootsArray = null;\r\n\t\r\n\tif (model.roots != null)\r\n\t{\r\n\t\tvar modelRoots = model.roots;\r\n\t\trootsArray = [];\r\n\t\t\r\n\t\tfor (var i = 0; i < modelRoots.length; i++)\r\n\t\t{\r\n\t\t\tvar nodeId = mxCellPath.create(modelRoots[i]);\r\n\t\t\trootsArray[i] = model.vertexMapper.get(modelRoots[i]);\r\n\t\t}\r\n\t}\r\n\r\n\tmodel.visit(function(parent, node, connectingEdge, layer, seen)\r\n\t{\r\n\t\t// Check if the cell is in it's own ancestor list, if so\r\n\t\t// invert the connecting edge and reverse the target/source\r\n\t\t// relationship to that edge in the parent and the cell\r\n\t\t// Ancestor hashes only line up within a swimlane\r\n\t\tvar isAncestor = parent != null && parent.swimlaneIndex == node.swimlaneIndex && node.isAncestor(parent);\r\n\r\n\t\t// If the source->target swimlane indices go from higher to\r\n\t\t// lower, the edge is reverse\r\n\t\tvar reversedOverSwimlane = parent != null && connectingEdge != null &&\r\n\t\t\t\t\t\tparent.swimlaneIndex < node.swimlaneIndex && connectingEdge.source == node;\r\n\r\n\t\tif (isAncestor)\r\n\t\t{\r\n\t\t\tconnectingEdge.invert();\r\n\t\t\tmxUtils.remove(connectingEdge, parent.connectsAsSource);\r\n\t\t\tnode.connectsAsSource.push(connectingEdge);\r\n\t\t\tparent.connectsAsTarget.push(connectingEdge);\r\n\t\t\tmxUtils.remove(connectingEdge, node.connectsAsTarget);\r\n\t\t}\r\n\t\telse if (reversedOverSwimlane)\r\n\t\t{\r\n\t\t\tconnectingEdge.invert();\r\n\t\t\tmxUtils.remove(connectingEdge, parent.connectsAsTarget);\r\n\t\t\tnode.connectsAsTarget.push(connectingEdge);\r\n\t\t\tparent.connectsAsSource.push(connectingEdge);\r\n\t\t\tmxUtils.remove(connectingEdge, node.connectsAsSource);\r\n\t\t}\r\n\t\t\r\n\t\tvar cellId = mxCellPath.create(node.cell);\r\n\t\tseenNodes[cellId] = node;\r\n\t\tdelete unseenNodes[cellId];\r\n\t}, rootsArray, true, null);\r\n};\r\n/**\r\n * Copyright (c) 2006-2018, JGraph Ltd\r\n * Copyright (c) 2006-2018, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxHierarchicalLayout\r\n * \r\n * A hierarchical layout algorithm.\r\n * \r\n * Constructor: mxHierarchicalLayout\r\n *\r\n * Constructs a new hierarchical layout algorithm.\r\n *\r\n * Arguments:\r\n * \r\n * graph - Reference to the enclosing <mxGraph>.\r\n * orientation - Optional constant that defines the orientation of this\r\n * layout.\r\n * deterministic - Optional boolean that specifies if this layout should be\r\n * deterministic. Default is true.\r\n */\r\nfunction mxHierarchicalLayout(graph, orientation, deterministic)\r\n{\r\n\tmxGraphLayout.call(this, graph);\r\n\tthis.orientation = (orientation != null) ? orientation : mxConstants.DIRECTION_NORTH;\r\n\tthis.deterministic = (deterministic != null) ? deterministic : true;\r\n};\r\n\r\nvar mxHierarchicalEdgeStyle =\r\n{\r\n\tORTHOGONAL: 1,\r\n\tPOLYLINE: 2,\r\n\tSTRAIGHT: 3,\r\n\tCURVE: 4\r\n};\r\n\r\n/**\r\n * Extends mxGraphLayout.\r\n */\r\nmxHierarchicalLayout.prototype = new mxGraphLayout();\r\nmxHierarchicalLayout.prototype.constructor = mxHierarchicalLayout;\r\n\r\n/**\r\n * Variable: roots\r\n * \r\n * Holds the array of <mxCell> that this layout contains.\r\n */\r\nmxHierarchicalLayout.prototype.roots = null;\r\n\r\n/**\r\n * Variable: resizeParent\r\n * \r\n * Specifies if the parent should be resized after the layout so that it\r\n * contains all the child cells. Default is false. See also <parentBorder>.\r\n */\r\nmxHierarchicalLayout.prototype.resizeParent = false;\r\n\r\n/**\r\n * Variable: maintainParentLocation\r\n * \r\n * Specifies if the parent location should be maintained, so that the\r\n * top, left corner stays the same before and after execution of\r\n * the layout. Default is false for backwards compatibility.\r\n */\r\nmxHierarchicalLayout.prototype.maintainParentLocation = false;\r\n\r\n/**\r\n * Variable: moveParent\r\n * \r\n * Specifies if the parent should be moved if <resizeParent> is enabled.\r\n * Default is false.\r\n */\r\nmxHierarchicalLayout.prototype.moveParent = false;\r\n\r\n/**\r\n * Variable: parentBorder\r\n * \r\n * The border to be added around the children if the parent is to be\r\n * resized using <resizeParent>. Default is 0.\r\n */\r\nmxHierarchicalLayout.prototype.parentBorder = 0;\r\n\r\n/**\r\n * Variable: intraCellSpacing\r\n * \r\n * The spacing buffer added between cells on the same layer. Default is 30.\r\n */\r\nmxHierarchicalLayout.prototype.intraCellSpacing = 30;\r\n\r\n/**\r\n * Variable: interRankCellSpacing\r\n * \r\n * The spacing buffer added between cell on adjacent layers. Default is 50.\r\n */\r\nmxHierarchicalLayout.prototype.interRankCellSpacing = 100;\r\n\r\n/**\r\n * Variable: interHierarchySpacing\r\n * \r\n * The spacing buffer between unconnected hierarchies. Default is 60.\r\n */\r\nmxHierarchicalLayout.prototype.interHierarchySpacing = 60;\r\n\r\n/**\r\n * Variable: parallelEdgeSpacing\r\n * \r\n * The distance between each parallel edge on each ranks for long edges\r\n */\r\nmxHierarchicalLayout.prototype.parallelEdgeSpacing = 10;\r\n\r\n/**\r\n * Variable: orientation\r\n * \r\n * The position of the root node(s) relative to the laid out graph in.\r\n * Default is <mxConstants.DIRECTION_NORTH>.\r\n */\r\nmxHierarchicalLayout.prototype.orientation = mxConstants.DIRECTION_NORTH;\r\n\r\n/**\r\n * Variable: fineTuning\r\n * \r\n * Whether or not to perform local optimisations and iterate multiple times\r\n * through the algorithm. Default is true.\r\n */\r\nmxHierarchicalLayout.prototype.fineTuning = true;\r\n\r\n/**\r\n * \r\n * Variable: tightenToSource\r\n * \r\n * Whether or not to tighten the assigned ranks of vertices up towards\r\n * the source cells.\r\n */\r\nmxHierarchicalLayout.prototype.tightenToSource = true;\r\n\r\n/**\r\n * Variable: disableEdgeStyle\r\n * \r\n * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are\r\n * modified by the result. Default is true.\r\n */\r\nmxHierarchicalLayout.prototype.disableEdgeStyle = true;\r\n\r\n/**\r\n * Variable: traverseAncestors\r\n * \r\n * Whether or not to drill into child cells and layout in reverse\r\n * group order. This also cause the layout to navigate edges whose \r\n * terminal vertices have different parents but are in the same \r\n * ancestry chain\r\n */\r\nmxHierarchicalLayout.prototype.traverseAncestors = true;\r\n\r\n/**\r\n * Variable: model\r\n * \r\n * The internal <mxGraphHierarchyModel> formed of the layout.\r\n */\r\nmxHierarchicalLayout.prototype.model = null;\r\n\r\n/**\r\n * Variable: edgesSet\r\n * \r\n * A cache of edges whose source terminal is the key\r\n */\r\nmxHierarchicalLayout.prototype.edgesCache = null;\r\n\r\n/**\r\n * Variable: edgesSet\r\n * \r\n * A cache of edges whose source terminal is the key\r\n */\r\nmxHierarchicalLayout.prototype.edgeSourceTermCache = null;\r\n\r\n/**\r\n * Variable: edgesSet\r\n * \r\n * A cache of edges whose source terminal is the key\r\n */\r\nmxHierarchicalLayout.prototype.edgesTargetTermCache = null;\r\n\r\n/**\r\n * Variable: edgeStyle\r\n * \r\n * The style to apply between cell layers to edge segments\r\n */\r\nmxHierarchicalLayout.prototype.edgeStyle = mxHierarchicalEdgeStyle.POLYLINE;\r\n\r\n/**\r\n * Function: getModel\r\n * \r\n * Returns the internal <mxGraphHierarchyModel> for this layout algorithm.\r\n */\r\nmxHierarchicalLayout.prototype.getModel = function()\r\n{\r\n\treturn this.model;\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Executes the layout for the children of the specified parent.\r\n * \r\n * Parameters:\r\n * \r\n * parent - Parent <mxCell> that contains the children to be laid out.\r\n * roots - Optional starting roots of the layout.\r\n */\r\nmxHierarchicalLayout.prototype.execute = function(parent, roots)\r\n{\r\n\tthis.parent = parent;\r\n\tvar model = this.graph.model;\r\n\tthis.edgesCache = new mxDictionary();\r\n\tthis.edgeSourceTermCache = new mxDictionary();\r\n\tthis.edgesTargetTermCache = new mxDictionary();\r\n\r\n\tif (roots != null && !(roots instanceof Array))\r\n\t{\r\n\t\troots = [roots];\r\n\t}\r\n\t\r\n\t// If the roots are set and the parent is set, only\r\n\t// use the roots that are some dependent of the that\r\n\t// parent.\r\n\t// If just the root are set, use them as-is\r\n\t// If just the parent is set use it's immediate\r\n\t// children as the initial set\r\n\r\n\tif (roots == null && parent == null)\r\n\t{\r\n\t\t// TODO indicate the problem\r\n\t\treturn;\r\n\t}\r\n\t\r\n\t//  Maintaining parent location\r\n\tthis.parentX = null;\r\n\tthis.parentY = null;\r\n\t\r\n\tif (parent != this.root && model.isVertex(parent) != null && this.maintainParentLocation)\r\n\t{\r\n\t\tvar geo = this.graph.getCellGeometry(parent);\r\n\t\t\r\n\t\tif (geo != null)\r\n\t\t{\r\n\t\t\tthis.parentX = geo.x;\r\n\t\t\tthis.parentY = geo.y;\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (roots != null)\r\n\t{\r\n\t\tvar rootsCopy = [];\r\n\r\n\t\tfor (var i = 0; i < roots.length; i++)\r\n\t\t{\r\n\t\t\tvar ancestor = parent != null ? model.isAncestor(parent, roots[i]) : true;\r\n\t\t\t\r\n\t\t\tif (ancestor && model.isVertex(roots[i]))\r\n\t\t\t{\r\n\t\t\t\trootsCopy.push(roots[i]);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.roots = rootsCopy;\r\n\t}\r\n\t\r\n\tmodel.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tthis.run(parent);\r\n\t\t\r\n\t\tif (this.resizeParent && !this.graph.isCellCollapsed(parent))\r\n\t\t{\r\n\t\t\tthis.graph.updateGroupBounds([parent], this.parentBorder, this.moveParent);\r\n\t\t}\r\n\t\t\r\n\t\t// Maintaining parent location\r\n\t\tif (this.parentX != null && this.parentY != null)\r\n\t\t{\r\n\t\t\tvar geo = this.graph.getCellGeometry(parent);\r\n\t\t\t\r\n\t\t\tif (geo != null)\r\n\t\t\t{\r\n\t\t\t\tgeo = geo.clone();\r\n\t\t\t\tgeo.x = this.parentX;\r\n\t\t\t\tgeo.y = this.parentY;\r\n\t\t\t\tmodel.setGeometry(parent, geo);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tmodel.endUpdate();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: findRoots\r\n * \r\n * Returns all visible children in the given parent which do not have\r\n * incoming edges. If the result is empty then the children with the\r\n * maximum difference between incoming and outgoing edges are returned.\r\n * This takes into account edges that are being promoted to the given\r\n * root due to invisible children or collapsed cells.\r\n * \r\n * Parameters:\r\n * \r\n * parent - <mxCell> whose children should be checked.\r\n * vertices - array of vertices to limit search to\r\n */\r\nmxHierarchicalLayout.prototype.findRoots = function(parent, vertices)\r\n{\r\n\tvar roots = [];\r\n\t\r\n\tif (parent != null && vertices != null)\r\n\t{\r\n\t\tvar model = this.graph.model;\r\n\t\tvar best = null;\r\n\t\tvar maxDiff = -100000;\r\n\t\t\r\n\t\tfor (var i in vertices)\r\n\t\t{\r\n\t\t\tvar cell = vertices[i];\r\n\r\n\t\t\tif (model.isVertex(cell) && this.graph.isCellVisible(cell))\r\n\t\t\t{\r\n\t\t\t\tvar conns = this.getEdges(cell);\r\n\t\t\t\tvar fanOut = 0;\r\n\t\t\t\tvar fanIn = 0;\r\n\r\n\t\t\t\tfor (var k = 0; k < conns.length; k++)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar src = this.getVisibleTerminal(conns[k], true);\r\n\r\n\t\t\t\t\tif (src == cell)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tfanOut++;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tfanIn++;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (fanIn == 0 && fanOut > 0)\r\n\t\t\t\t{\r\n\t\t\t\t\troots.push(cell);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tvar diff = fanOut - fanIn;\r\n\r\n\t\t\t\tif (diff > maxDiff)\r\n\t\t\t\t{\r\n\t\t\t\t\tmaxDiff = diff;\r\n\t\t\t\t\tbest = cell;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (roots.length == 0 && best != null)\r\n\t\t{\r\n\t\t\troots.push(best);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn roots;\r\n};\r\n\r\n/**\r\n * Function: getEdges\r\n * \r\n * Returns the connected edges for the given cell.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose edges should be returned.\r\n */\r\nmxHierarchicalLayout.prototype.getEdges = function(cell)\r\n{\r\n\tvar cachedEdges = this.edgesCache.get(cell);\r\n\t\r\n\tif (cachedEdges != null)\r\n\t{\r\n\t\treturn cachedEdges;\r\n\t}\r\n\r\n\tvar model = this.graph.model;\r\n\tvar edges = [];\r\n\tvar isCollapsed = this.graph.isCellCollapsed(cell);\r\n\tvar childCount = model.getChildCount(cell);\r\n\r\n\tfor (var i = 0; i < childCount; i++)\r\n\t{\r\n\t\tvar child = model.getChildAt(cell, i);\r\n\r\n\t\tif (this.isPort(child))\r\n\t\t{\r\n\t\t\tedges = edges.concat(model.getEdges(child, true, true));\r\n\t\t}\r\n\t\telse if (isCollapsed || !this.graph.isCellVisible(child))\r\n\t\t{\r\n\t\t\tedges = edges.concat(model.getEdges(child, true, true));\r\n\t\t}\r\n\t}\r\n\r\n\tedges = edges.concat(model.getEdges(cell, true, true));\r\n\tvar result = [];\r\n\t\r\n\tfor (var i = 0; i < edges.length; i++)\r\n\t{\r\n\t\tvar source = this.getVisibleTerminal(edges[i], true);\r\n\t\tvar target = this.getVisibleTerminal(edges[i], false);\r\n\t\t\r\n\t\tif ((source == target) ||\r\n\t\t\t\t((source != target) &&\r\n\t\t\t\t\t\t((target == cell && (this.parent == null || this.isAncestor(this.parent, source, this.traverseAncestors))) ||\r\n\t\t\t\t\t\t \t(source == cell && (this.parent == null || this.isAncestor(this.parent, target, this.traverseAncestors))))))\r\n\t\t{\r\n\t\t\tresult.push(edges[i]);\r\n\t\t}\r\n\t}\r\n\r\n\tthis.edgesCache.put(cell, result);\r\n\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: getVisibleTerminal\r\n * \r\n * Helper function to return visible terminal for edge allowing for ports\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCell> whose edges should be returned.\r\n * source - Boolean that specifies whether the source or target terminal is to be returned\r\n */\r\nmxHierarchicalLayout.prototype.getVisibleTerminal = function(edge, source)\r\n{\r\n\tvar terminalCache = this.edgesTargetTermCache;\r\n\t\r\n\tif (source)\r\n\t{\r\n\t\tterminalCache = this.edgeSourceTermCache;\r\n\t}\r\n\r\n\tvar term = terminalCache.get(edge);\r\n\r\n\tif (term != null)\r\n\t{\r\n\t\treturn term;\r\n\t}\r\n\r\n\tvar state = this.graph.view.getState(edge);\r\n\t\r\n\tvar terminal = (state != null) ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);\r\n\t\r\n\tif (terminal == null)\r\n\t{\r\n\t\tterminal = (state != null) ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);\r\n\t}\r\n\r\n\tif (terminal != null)\r\n\t{\r\n\t\tif (this.isPort(terminal))\r\n\t\t{\r\n\t\t\tterminal = this.graph.model.getParent(terminal);\r\n\t\t}\r\n\t\t\r\n\t\tterminalCache.put(edge, terminal);\r\n\t}\r\n\r\n\treturn terminal;\r\n};\r\n\r\n/**\r\n * Function: run\r\n * \r\n * The API method used to exercise the layout upon the graph description\r\n * and produce a separate description of the vertex position and edge\r\n * routing changes made. It runs each stage of the layout that has been\r\n * created.\r\n */\r\nmxHierarchicalLayout.prototype.run = function(parent)\r\n{\r\n\t// Separate out unconnected hierarchies\r\n\tvar hierarchyVertices = [];\r\n\tvar allVertexSet = [];\r\n\r\n\tif (this.roots == null && parent != null)\r\n\t{\r\n\t\tvar filledVertexSet = Object();\r\n\t\tthis.filterDescendants(parent, filledVertexSet);\r\n\r\n\t\tthis.roots = [];\r\n\t\tvar filledVertexSetEmpty = true;\r\n\r\n\t\t// Poor man's isSetEmpty\r\n\t\tfor (var key in filledVertexSet)\r\n\t\t{\r\n\t\t\tif (filledVertexSet[key] != null)\r\n\t\t\t{\r\n\t\t\t\tfilledVertexSetEmpty = false;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\twhile (!filledVertexSetEmpty)\r\n\t\t{\r\n\t\t\tvar candidateRoots = this.findRoots(parent, filledVertexSet);\r\n\t\t\t\r\n\t\t\t// If the candidate root is an unconnected group cell, remove it from\r\n\t\t\t// the layout. We may need a custom set that holds such groups and forces\r\n\t\t\t// them to be processed for resizing and/or moving.\r\n\t\t\t\r\n\r\n\t\t\tfor (var i = 0; i < candidateRoots.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar vertexSet = Object();\r\n\t\t\t\thierarchyVertices.push(vertexSet);\r\n\r\n\t\t\t\tthis.traverse(candidateRoots[i], true, null, allVertexSet, vertexSet,\r\n\t\t\t\t\t\thierarchyVertices, filledVertexSet);\r\n\t\t\t}\r\n\r\n\t\t\tfor (var i = 0; i < candidateRoots.length; i++)\r\n\t\t\t{\r\n\t\t\t\tthis.roots.push(candidateRoots[i]);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tfilledVertexSetEmpty = true;\r\n\t\t\t\r\n\t\t\t// Poor man's isSetEmpty\r\n\t\t\tfor (var key in filledVertexSet)\r\n\t\t\t{\r\n\t\t\t\tif (filledVertexSet[key] != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tfilledVertexSetEmpty = false;\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\t// Find vertex set as directed traversal from roots\r\n\r\n\t\tfor (var i = 0; i < this.roots.length; i++)\r\n\t\t{\r\n\t\t\tvar vertexSet = Object();\r\n\t\t\thierarchyVertices.push(vertexSet);\r\n\r\n\t\t\tthis.traverse(this.roots[i], true, null, allVertexSet, vertexSet,\r\n\t\t\t\t\thierarchyVertices, null);\r\n\t\t}\r\n\t}\r\n\r\n\t// Iterate through the result removing parents who have children in this layout\r\n\t\r\n\t// Perform a layout for each seperate hierarchy\r\n\t// Track initial coordinate x-positioning\r\n\tvar initialX = 0;\r\n\r\n\tfor (var i = 0; i < hierarchyVertices.length; i++)\r\n\t{\r\n\t\tvar vertexSet = hierarchyVertices[i];\r\n\t\tvar tmp = [];\r\n\t\t\r\n\t\tfor (var key in vertexSet)\r\n\t\t{\r\n\t\t\ttmp.push(vertexSet[key]);\r\n\t\t}\r\n\t\t\r\n\t\tthis.model = new mxGraphHierarchyModel(this, tmp, this.roots,\r\n\t\t\tparent, this.tightenToSource);\r\n\r\n\t\tthis.cycleStage(parent);\r\n\t\tthis.layeringStage();\r\n\t\t\r\n\t\tthis.crossingStage(parent);\r\n\t\tinitialX = this.placementStage(initialX, parent);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: filterDescendants\r\n * \r\n * Creates an array of descendant cells\r\n */\r\nmxHierarchicalLayout.prototype.filterDescendants = function(cell, result)\r\n{\r\n\tvar model = this.graph.model;\r\n\r\n\tif (model.isVertex(cell) && cell != this.parent && this.graph.isCellVisible(cell))\r\n\t{\r\n\t\tresult[mxObjectIdentity.get(cell)] = cell;\r\n\t}\r\n\r\n\tif (this.traverseAncestors || cell == this.parent\r\n\t\t\t&& this.graph.isCellVisible(cell))\r\n\t{\r\n\t\tvar childCount = model.getChildCount(cell);\r\n\r\n\t\tfor (var i = 0; i < childCount; i++)\r\n\t\t{\r\n\t\t\tvar child = model.getChildAt(cell, i);\r\n\t\t\t\r\n\t\t\t// Ignore ports in the layout vertex list, they are dealt with\r\n\t\t\t// in the traversal mechanisms\r\n\t\t\tif (!this.isPort(child))\r\n\t\t\t{\r\n\t\t\t\tthis.filterDescendants(child, result);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isPort\r\n * \r\n * Returns true if the given cell is a \"port\", that is, when connecting to\r\n * it, its parent is the connecting vertex in terms of graph traversal\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> that represents the port.\r\n */\r\nmxHierarchicalLayout.prototype.isPort = function(cell)\r\n{\r\n\tif (cell != null && cell.geometry != null)\r\n\t{\r\n\t\treturn cell.geometry.relative;\r\n\t}\r\n\telse\r\n\t{\r\n\t\treturn false;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getEdgesBetween\r\n * \r\n * Returns the edges between the given source and target. This takes into\r\n * account collapsed and invisible cells and ports.\r\n * \r\n * Parameters:\r\n * \r\n * source -\r\n * target -\r\n * directed -\r\n */\r\nmxHierarchicalLayout.prototype.getEdgesBetween = function(source, target, directed)\r\n{\r\n\tdirected = (directed != null) ? directed : false;\r\n\tvar edges = this.getEdges(source);\r\n\tvar result = [];\r\n\r\n\t// Checks if the edge is connected to the correct\r\n\t// cell and returns the first match\r\n\tfor (var i = 0; i < edges.length; i++)\r\n\t{\r\n\t\tvar src = this.getVisibleTerminal(edges[i], true);\r\n\t\tvar trg = this.getVisibleTerminal(edges[i], false);\r\n\r\n\t\tif ((src == source && trg == target) || (!directed && src == target && trg == source))\r\n\t\t{\r\n\t\t\tresult.push(edges[i]);\r\n\t\t}\r\n\t}\r\n\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Traverses the (directed) graph invoking the given function for each\r\n * visited vertex and edge. The function is invoked with the current vertex\r\n * and the incoming edge as a parameter. This implementation makes sure\r\n * each vertex is only visited once. The function may return false if the\r\n * traversal should stop at the given vertex.\r\n * \r\n * Parameters:\r\n * \r\n * vertex - <mxCell> that represents the vertex where the traversal starts.\r\n * directed - boolean indicating if edges should only be traversed\r\n * from source to target. Default is true.\r\n * edge - Optional <mxCell> that represents the incoming edge. This is\r\n * null for the first step of the traversal.\r\n * allVertices - Array of cell paths for the visited cells.\r\n */\r\nmxHierarchicalLayout.prototype.traverse = function(vertex, directed, edge, allVertices, currentComp,\r\n\t\t\t\t\t\t\t\t\t\t\thierarchyVertices, filledVertexSet)\r\n{\r\n\tif (vertex != null && allVertices != null)\r\n\t{\r\n\t\t// Has this vertex been seen before in any traversal\r\n\t\t// And if the filled vertex set is populated, only \r\n\t\t// process vertices in that it contains\r\n\t\tvar vertexID = mxObjectIdentity.get(vertex);\r\n\t\t\r\n\t\tif ((allVertices[vertexID] == null)\r\n\t\t\t\t&& (filledVertexSet == null ? true : filledVertexSet[vertexID] != null))\r\n\t\t{\r\n\t\t\tif (currentComp[vertexID] == null)\r\n\t\t\t{\r\n\t\t\t\tcurrentComp[vertexID] = vertex;\r\n\t\t\t}\r\n\t\t\tif (allVertices[vertexID] == null)\r\n\t\t\t{\r\n\t\t\t\tallVertices[vertexID] = vertex;\r\n\t\t\t}\r\n\r\n\t\t\tif (filledVertexSet !== null)\r\n\t\t\t{\r\n\t\t\t\tdelete filledVertexSet[vertexID];\r\n\t\t\t}\r\n\r\n\t\t\tvar edges = this.getEdges(vertex);\r\n\t\t\tvar edgeIsSource = [];\r\n\r\n\t\t\tfor (var i = 0; i < edges.length; i++)\r\n\t\t\t{\r\n\t\t\t\tedgeIsSource[i] = (this.getVisibleTerminal(edges[i], true) == vertex);\r\n\t\t\t}\r\n\r\n\t\t\tfor (var i = 0; i < edges.length; i++)\r\n\t\t\t{\r\n\t\t\t\tif (!directed || edgeIsSource[i])\r\n\t\t\t\t{\r\n\t\t\t\t\tvar next = this.getVisibleTerminal(edges[i], !edgeIsSource[i]);\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Check whether there are more edges incoming from the target vertex than outgoing\r\n\t\t\t\t\t// The hierarchical model treats bi-directional parallel edges as being sourced\r\n\t\t\t\t\t// from the more \"sourced\" terminal. If the directions are equal in number, the direction\r\n\t\t\t\t\t// is that of the natural direction from the roots of the layout.\r\n\t\t\t\t\t// The checks below are slightly more verbose than need be for performance reasons\r\n\t\t\t\t\tvar netCount = 1;\r\n\r\n\t\t\t\t\tfor (var j = 0; j < edges.length; j++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (j == i)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar isSource2 = edgeIsSource[j];\r\n\t\t\t\t\t\t\tvar otherTerm = this.getVisibleTerminal(edges[j], !isSource2);\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tif (otherTerm == next)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tif (isSource2)\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\tnetCount++;\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\tnetCount--;\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif (netCount >= 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tcurrentComp = this.traverse(next, directed, edges[i], allVertices,\r\n\t\t\t\t\t\t\tcurrentComp, hierarchyVertices,\r\n\t\t\t\t\t\t\tfilledVertexSet);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tif (currentComp[vertexID] == null)\r\n\t\t\t{\r\n\t\t\t\t// We've seen this vertex before, but not in the current component\r\n\t\t\t\t// This component and the one it's in need to be merged\r\n\r\n\t\t\t\tfor (var i = 0; i < hierarchyVertices.length; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar comp = hierarchyVertices[i];\r\n\r\n\t\t\t\t\tif (comp[vertexID] != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tfor (var key in comp)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tcurrentComp[key] = comp[key];\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t// Remove the current component from the hierarchy set\r\n\t\t\t\t\t\thierarchyVertices.splice(i, 1);\r\n\t\t\t\t\t\treturn currentComp;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn currentComp;\r\n};\r\n\r\n/**\r\n * Function: cycleStage\r\n * \r\n * Executes the cycle stage using mxMinimumCycleRemover.\r\n */\r\nmxHierarchicalLayout.prototype.cycleStage = function(parent)\r\n{\r\n\tvar cycleStage = new mxMinimumCycleRemover(this);\r\n\tcycleStage.execute(parent);\r\n};\r\n\r\n/**\r\n * Function: layeringStage\r\n * \r\n * Implements first stage of a Sugiyama layout.\r\n */\r\nmxHierarchicalLayout.prototype.layeringStage = function()\r\n{\r\n\tthis.model.initialRank();\r\n\tthis.model.fixRanks();\r\n};\r\n\r\n/**\r\n * Function: crossingStage\r\n * \r\n * Executes the crossing stage using mxMedianHybridCrossingReduction.\r\n */\r\nmxHierarchicalLayout.prototype.crossingStage = function(parent)\r\n{\r\n\tvar crossingStage = new mxMedianHybridCrossingReduction(this);\r\n\tcrossingStage.execute(parent);\r\n};\r\n\r\n/**\r\n * Function: placementStage\r\n * \r\n * Executes the placement stage using mxCoordinateAssignment.\r\n */\r\nmxHierarchicalLayout.prototype.placementStage = function(initialX, parent)\r\n{\r\n\tvar placementStage = new mxCoordinateAssignment(this, this.intraCellSpacing,\r\n\t\t\tthis.interRankCellSpacing, this.orientation, initialX,\r\n\t\t\tthis.parallelEdgeSpacing);\r\n\tplacementStage.fineTuning = this.fineTuning;\r\n\tplacementStage.execute(parent);\r\n\t\r\n\treturn placementStage.limitX + this.interHierarchySpacing;\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxSwimlaneLayout\r\n * \r\n * A hierarchical layout algorithm.\r\n * \r\n * Constructor: mxSwimlaneLayout\r\n *\r\n * Constructs a new hierarchical layout algorithm.\r\n *\r\n * Arguments:\r\n * \r\n * graph - Reference to the enclosing <mxGraph>.\r\n * orientation - Optional constant that defines the orientation of this\r\n * layout.\r\n * deterministic - Optional boolean that specifies if this layout should be\r\n * deterministic. Default is true.\r\n */\r\nfunction mxSwimlaneLayout(graph, orientation, deterministic)\r\n{\r\n\tmxGraphLayout.call(this, graph);\r\n\tthis.orientation = (orientation != null) ? orientation : mxConstants.DIRECTION_NORTH;\r\n\tthis.deterministic = (deterministic != null) ? deterministic : true;\r\n};\r\n\r\n/**\r\n * Extends mxGraphLayout.\r\n */\r\nmxSwimlaneLayout.prototype = new mxGraphLayout();\r\nmxSwimlaneLayout.prototype.constructor = mxSwimlaneLayout;\r\n\r\n/**\r\n * Variable: roots\r\n * \r\n * Holds the array of <mxCell> that this layout contains.\r\n */\r\nmxSwimlaneLayout.prototype.roots = null;\r\n\r\n/**\r\n * Variable: swimlanes\r\n * \r\n * Holds the array of <mxCell> of the ordered swimlanes to lay out\r\n */\r\nmxSwimlaneLayout.prototype.swimlanes = null;\r\n\r\n/**\r\n * Variable: dummyVertices\r\n * \r\n * Holds an array of <mxCell> of dummy vertices inserted during the layout\r\n * to pad out empty swimlanes\r\n */\r\nmxSwimlaneLayout.prototype.dummyVertices = null;\r\n\r\n/**\r\n * Variable: dummyVertexWidth\r\n * \r\n * The cell width of any dummy vertices inserted\r\n */\r\nmxSwimlaneLayout.prototype.dummyVertexWidth = 50;\r\n\r\n/**\r\n * Variable: resizeParent\r\n * \r\n * Specifies if the parent should be resized after the layout so that it\r\n * contains all the child cells. Default is false. See also <parentBorder>.\r\n */\r\nmxSwimlaneLayout.prototype.resizeParent = false;\r\n\r\n/**\r\n * Variable: maintainParentLocation\r\n * \r\n * Specifies if the parent location should be maintained, so that the\r\n * top, left corner stays the same before and after execution of\r\n * the layout. Default is false for backwards compatibility.\r\n */\r\nmxSwimlaneLayout.prototype.maintainParentLocation = false;\r\n\r\n/**\r\n * Variable: moveParent\r\n * \r\n * Specifies if the parent should be moved if <resizeParent> is enabled.\r\n * Default is false.\r\n */\r\nmxSwimlaneLayout.prototype.moveParent = false;\r\n\r\n/**\r\n * Variable: parentBorder\r\n * \r\n * The border to be added around the children if the parent is to be\r\n * resized using <resizeParent>. Default is 0.\r\n */\r\nmxSwimlaneLayout.prototype.parentBorder = 30;\r\n\r\n/**\r\n * Variable: intraCellSpacing\r\n * \r\n * The spacing buffer added between cells on the same layer. Default is 30.\r\n */\r\nmxSwimlaneLayout.prototype.intraCellSpacing = 30;\r\n\r\n/**\r\n * Variable: interRankCellSpacing\r\n * \r\n * The spacing buffer added between cell on adjacent layers. Default is 50.\r\n */\r\nmxSwimlaneLayout.prototype.interRankCellSpacing = 100;\r\n\r\n/**\r\n * Variable: interHierarchySpacing\r\n * \r\n * The spacing buffer between unconnected hierarchies. Default is 60.\r\n */\r\nmxSwimlaneLayout.prototype.interHierarchySpacing = 60;\r\n\r\n/**\r\n * Variable: parallelEdgeSpacing\r\n * \r\n * The distance between each parallel edge on each ranks for long edges\r\n */\r\nmxSwimlaneLayout.prototype.parallelEdgeSpacing = 10;\r\n\r\n/**\r\n * Variable: orientation\r\n * \r\n * The position of the root node(s) relative to the laid out graph in.\r\n * Default is <mxConstants.DIRECTION_NORTH>.\r\n */\r\nmxSwimlaneLayout.prototype.orientation = mxConstants.DIRECTION_NORTH;\r\n\r\n/**\r\n * Variable: fineTuning\r\n * \r\n * Whether or not to perform local optimisations and iterate multiple times\r\n * through the algorithm. Default is true.\r\n */\r\nmxSwimlaneLayout.prototype.fineTuning = true;\r\n\r\n/**\r\n * \r\n * Variable: tightenToSource\r\n * \r\n * Whether or not to tighten the assigned ranks of vertices up towards\r\n * the source cells.\r\n */\r\nmxSwimlaneLayout.prototype.tightenToSource = true;\r\n\r\n/**\r\n * Variable: disableEdgeStyle\r\n * \r\n * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are\r\n * modified by the result. Default is true.\r\n */\r\nmxSwimlaneLayout.prototype.disableEdgeStyle = true;\r\n\r\n/**\r\n * Variable: traverseAncestors\r\n * \r\n * Whether or not to drill into child cells and layout in reverse\r\n * group order. This also cause the layout to navigate edges whose \r\n * terminal vertices  * have different parents but are in the same \r\n * ancestry chain\r\n */\r\nmxSwimlaneLayout.prototype.traverseAncestors = true;\r\n\r\n/**\r\n * Variable: model\r\n * \r\n * The internal <mxSwimlaneModel> formed of the layout.\r\n */\r\nmxSwimlaneLayout.prototype.model = null;\r\n\r\n/**\r\n * Variable: edgesSet\r\n * \r\n * A cache of edges whose source terminal is the key\r\n */\r\nmxSwimlaneLayout.prototype.edgesCache = null;\r\n\r\n/**\r\n * Variable: edgesSet\r\n * \r\n * A cache of edges whose source terminal is the key\r\n */\r\nmxHierarchicalLayout.prototype.edgeSourceTermCache = null;\r\n\r\n/**\r\n * Variable: edgesSet\r\n * \r\n * A cache of edges whose source terminal is the key\r\n */\r\nmxHierarchicalLayout.prototype.edgesTargetTermCache = null;\r\n\r\n/**\r\n * Variable: edgeStyle\r\n * \r\n * The style to apply between cell layers to edge segments\r\n */\r\nmxHierarchicalLayout.prototype.edgeStyle = mxHierarchicalEdgeStyle.POLYLINE;\r\n\r\n/**\r\n * Function: getModel\r\n * \r\n * Returns the internal <mxSwimlaneModel> for this layout algorithm.\r\n */\r\nmxSwimlaneLayout.prototype.getModel = function()\r\n{\r\n\treturn this.model;\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Executes the layout for the children of the specified parent.\r\n * \r\n * Parameters:\r\n * \r\n * parent - Parent <mxCell> that contains the children to be laid out.\r\n * swimlanes - Ordered array of swimlanes to be laid out\r\n */\r\nmxSwimlaneLayout.prototype.execute = function(parent, swimlanes)\r\n{\r\n\tthis.parent = parent;\r\n\tvar model = this.graph.model;\r\n\tthis.edgesCache = new mxDictionary();\r\n\tthis.edgeSourceTermCache = new mxDictionary();\r\n\tthis.edgesTargetTermCache = new mxDictionary();\r\n\r\n\t// If the roots are set and the parent is set, only\r\n\t// use the roots that are some dependent of the that\r\n\t// parent.\r\n\t// If just the root are set, use them as-is\r\n\t// If just the parent is set use it's immediate\r\n\t// children as the initial set\r\n\r\n\tif (swimlanes == null || swimlanes.length < 1)\r\n\t{\r\n\t\t// TODO indicate the problem\r\n\t\treturn;\r\n\t}\r\n\r\n\tif (parent == null)\r\n\t{\r\n\t\tparent = model.getParent(swimlanes[0]);\r\n\t}\r\n\r\n\t//  Maintaining parent location\r\n\tthis.parentX = null;\r\n\tthis.parentY = null;\r\n\t\r\n\tif (parent != this.root && model.isVertex(parent) != null && this.maintainParentLocation)\r\n\t{\r\n\t\tvar geo = this.graph.getCellGeometry(parent);\r\n\t\t\r\n\t\tif (geo != null)\r\n\t\t{\r\n\t\t\tthis.parentX = geo.x;\r\n\t\t\tthis.parentY = geo.y;\r\n\t\t}\r\n\t}\r\n\r\n\tthis.swimlanes = swimlanes;\r\n\tthis.dummyVertices = [];\r\n\t// Check the swimlanes all have vertices\r\n\t// in them\r\n\tfor (var i = 0; i < swimlanes.length; i++)\r\n\t{\r\n\t\tvar children = this.graph.getChildCells(swimlanes[i]);\r\n\t\t\r\n\t\tif (children == null || children.length == 0)\r\n\t\t{\r\n\t\t\tvar vertex = this.graph.insertVertex(swimlanes[i], null, null, 0, 0, this.dummyVertexWidth, 0);\r\n\t\t\tthis.dummyVertices.push(vertex);\r\n\t\t}\r\n\t}\r\n\t\r\n\tmodel.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tthis.run(parent);\r\n\t\t\r\n\t\tif (this.resizeParent && !this.graph.isCellCollapsed(parent))\r\n\t\t{\r\n\t\t\tthis.graph.updateGroupBounds([parent], this.parentBorder, this.moveParent);\r\n\t\t}\r\n\t\t\r\n\t\t// Maintaining parent location\r\n\t\tif (this.parentX != null && this.parentY != null)\r\n\t\t{\r\n\t\t\tvar geo = this.graph.getCellGeometry(parent);\r\n\t\t\t\r\n\t\t\tif (geo != null)\r\n\t\t\t{\r\n\t\t\t\tgeo = geo.clone();\r\n\t\t\t\tgeo.x = this.parentX;\r\n\t\t\t\tgeo.y = this.parentY;\r\n\t\t\t\tmodel.setGeometry(parent, geo);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.graph.removeCells(this.dummyVertices);\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tmodel.endUpdate();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: updateGroupBounds\r\n * \r\n * Updates the bounds of the given array of groups so that it includes\r\n * all child vertices.\r\n * \r\n */\r\nmxSwimlaneLayout.prototype.updateGroupBounds = function()\r\n{\r\n\t// Get all vertices and edge in the layout\r\n\tvar cells = [];\r\n\tvar model = this.model;\r\n\t\r\n\tfor (var key in model.edgeMapper)\r\n\t{\r\n\t\tvar edge = model.edgeMapper[key];\r\n\t\t\r\n\t\tfor (var i = 0; i < edge.edges.length; i++)\r\n\t\t{\r\n\t\t\tcells.push(edge.edges[i]);\r\n\t\t}\r\n\t}\r\n\t\r\n\tvar layoutBounds = this.graph.getBoundingBoxFromGeometry(cells, true);\r\n\tvar childBounds = [];\r\n\r\n\tfor (var i = 0; i < this.swimlanes.length; i++)\r\n\t{\r\n\t\tvar lane = this.swimlanes[i];\r\n\t\tvar geo = this.graph.getCellGeometry(lane);\r\n\t\t\r\n\t\tif (geo != null)\r\n\t\t{\r\n\t\t\tvar children = this.graph.getChildCells(lane);\r\n\t\t\t\r\n\t\t\tvar size = (this.graph.isSwimlane(lane)) ?\r\n\t\t\t\t\tthis.graph.getStartSize(lane) : new mxRectangle();\r\n\r\n\t\t\tvar bounds = this.graph.getBoundingBoxFromGeometry(children);\r\n\t\t\tchildBounds[i] = bounds;\r\n\t\t\tvar childrenY = bounds.y + geo.y - size.height - this.parentBorder;\r\n\t\t\tvar maxChildrenY = bounds.y + geo.y + bounds.height;\r\n\r\n\t\t\tif (layoutBounds == null)\r\n\t\t\t{\r\n\t\t\t\tlayoutBounds = new mxRectangle(0, childrenY, 0, maxChildrenY - childrenY);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tlayoutBounds.y = Math.min(layoutBounds.y, childrenY);\r\n\t\t\t\tvar maxY = Math.max(layoutBounds.y + layoutBounds.height, maxChildrenY);\r\n\t\t\t\tlayoutBounds.height = maxY - layoutBounds.y;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t\r\n\tfor (var i = 0; i < this.swimlanes.length; i++)\r\n\t{\r\n\t\tvar lane = this.swimlanes[i];\r\n\t\tvar geo = this.graph.getCellGeometry(lane);\r\n\t\t\r\n\t\tif (geo != null)\r\n\t\t{\r\n\t\t\tvar children = this.graph.getChildCells(lane);\r\n\t\t\t\r\n\t\t\tvar size = (this.graph.isSwimlane(lane)) ?\r\n\t\t\t\t\tthis.graph.getStartSize(lane) : new mxRectangle();\r\n\r\n\t\t\tvar newGeo = geo.clone();\r\n\t\t\t\r\n\t\t\tvar leftGroupBorder = (i == 0) ? this.parentBorder : this.interRankCellSpacing/2;\r\n\t\t\tnewGeo.x += childBounds[i].x - size.width - leftGroupBorder;\r\n\t\t\tnewGeo.y = newGeo.y + layoutBounds.y - geo.y - this.parentBorder;\r\n\t\t\t\r\n\t\t\tnewGeo.width = childBounds[i].width + size.width + this.interRankCellSpacing/2 + leftGroupBorder;\r\n\t\t\tnewGeo.height = layoutBounds.height + size.height + 2 * this.parentBorder;\r\n\t\t\t\r\n\t\t\tthis.graph.model.setGeometry(lane, newGeo);\r\n\t\t\tthis.graph.moveCells(children, -childBounds[i].x + size.width + leftGroupBorder, \r\n\t\t\t\t\tgeo.y - layoutBounds.y + this.parentBorder);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: findRoots\r\n * \r\n * Returns all visible children in the given parent which do not have\r\n * incoming edges. If the result is empty then the children with the\r\n * maximum difference between incoming and outgoing edges are returned.\r\n * This takes into account edges that are being promoted to the given\r\n * root due to invisible children or collapsed cells.\r\n * \r\n * Parameters:\r\n * \r\n * parent - <mxCell> whose children should be checked.\r\n * vertices - array of vertices to limit search to\r\n */\r\nmxSwimlaneLayout.prototype.findRoots = function(parent, vertices)\r\n{\r\n\tvar roots = [];\r\n\t\r\n\tif (parent != null && vertices != null)\r\n\t{\r\n\t\tvar model = this.graph.model;\r\n\t\tvar best = null;\r\n\t\tvar maxDiff = -100000;\r\n\t\t\r\n\t\tfor (var i in vertices)\r\n\t\t{\r\n\t\t\tvar cell = vertices[i];\r\n\r\n\t\t\tif (cell != null && model.isVertex(cell) && this.graph.isCellVisible(cell) && model.isAncestor(parent, cell))\r\n\t\t\t{\r\n\t\t\t\tvar conns = this.getEdges(cell);\r\n\t\t\t\tvar fanOut = 0;\r\n\t\t\t\tvar fanIn = 0;\r\n\r\n\t\t\t\tfor (var k = 0; k < conns.length; k++)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar src = this.getVisibleTerminal(conns[k], true);\r\n\r\n\t\t\t\t\tif (src == cell)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t// Only count connection within this swimlane\r\n\t\t\t\t\t\tvar other = this.getVisibleTerminal(conns[k], false);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (model.isAncestor(parent, other))\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tfanOut++;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (model.isAncestor(parent, src))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tfanIn++;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (fanIn == 0 && fanOut > 0)\r\n\t\t\t\t{\r\n\t\t\t\t\troots.push(cell);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tvar diff = fanOut - fanIn;\r\n\r\n\t\t\t\tif (diff > maxDiff)\r\n\t\t\t\t{\r\n\t\t\t\t\tmaxDiff = diff;\r\n\t\t\t\t\tbest = cell;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (roots.length == 0 && best != null)\r\n\t\t{\r\n\t\t\troots.push(best);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn roots;\r\n};\r\n\r\n/**\r\n * Function: getEdges\r\n * \r\n * Returns the connected edges for the given cell.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose edges should be returned.\r\n */\r\nmxSwimlaneLayout.prototype.getEdges = function(cell)\r\n{\r\n\tvar cachedEdges = this.edgesCache.get(cell);\r\n\t\r\n\tif (cachedEdges != null)\r\n\t{\r\n\t\treturn cachedEdges;\r\n\t}\r\n\r\n\tvar model = this.graph.model;\r\n\tvar edges = [];\r\n\tvar isCollapsed = this.graph.isCellCollapsed(cell);\r\n\tvar childCount = model.getChildCount(cell);\r\n\r\n\tfor (var i = 0; i < childCount; i++)\r\n\t{\r\n\t\tvar child = model.getChildAt(cell, i);\r\n\r\n\t\tif (this.isPort(child))\r\n\t\t{\r\n\t\t\tedges = edges.concat(model.getEdges(child, true, true));\r\n\t\t}\r\n\t\telse if (isCollapsed || !this.graph.isCellVisible(child))\r\n\t\t{\r\n\t\t\tedges = edges.concat(model.getEdges(child, true, true));\r\n\t\t}\r\n\t}\r\n\r\n\tedges = edges.concat(model.getEdges(cell, true, true));\r\n\tvar result = [];\r\n\t\r\n\tfor (var i = 0; i < edges.length; i++)\r\n\t{\r\n\t\tvar source = this.getVisibleTerminal(edges[i], true);\r\n\t\tvar target = this.getVisibleTerminal(edges[i], false);\r\n\t\t\r\n\t\tif ((source == target) || ((source != target) && ((target == cell && (this.parent == null || this.graph.isValidAncestor(source, this.parent, this.traverseAncestors))) ||\r\n\t\t\t(source == cell && (this.parent == null ||\r\n\t\t\t\t\tthis.graph.isValidAncestor(target, this.parent, this.traverseAncestors))))))\r\n\t\t{\r\n\t\t\tresult.push(edges[i]);\r\n\t\t}\r\n\t}\r\n\r\n\tthis.edgesCache.put(cell, result);\r\n\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: getVisibleTerminal\r\n * \r\n * Helper function to return visible terminal for edge allowing for ports\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCell> whose edges should be returned.\r\n * source - Boolean that specifies whether the source or target terminal is to be returned\r\n */\r\nmxSwimlaneLayout.prototype.getVisibleTerminal = function(edge, source)\r\n{\r\n\tvar terminalCache = this.edgesTargetTermCache;\r\n\t\r\n\tif (source)\r\n\t{\r\n\t\tterminalCache = this.edgeSourceTermCache;\r\n\t}\r\n\r\n\tvar term = terminalCache.get(edge);\r\n\r\n\tif (term != null)\r\n\t{\r\n\t\treturn term;\r\n\t}\r\n\r\n\tvar state = this.graph.view.getState(edge);\r\n\t\r\n\tvar terminal = (state != null) ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);\r\n\t\r\n\tif (terminal == null)\r\n\t{\r\n\t\tterminal = (state != null) ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);\r\n\t}\r\n\r\n\tif (terminal != null)\r\n\t{\r\n\t\tif (this.isPort(terminal))\r\n\t\t{\r\n\t\t\tterminal = this.graph.model.getParent(terminal);\r\n\t\t}\r\n\t\t\r\n\t\tterminalCache.put(edge, terminal);\r\n\t}\r\n\r\n\treturn terminal;\r\n};\r\n\r\n/**\r\n * Function: run\r\n * \r\n * The API method used to exercise the layout upon the graph description\r\n * and produce a separate description of the vertex position and edge\r\n * routing changes made. It runs each stage of the layout that has been\r\n * created.\r\n */\r\nmxSwimlaneLayout.prototype.run = function(parent)\r\n{\r\n\t// Separate out unconnected hierarchies\r\n\tvar hierarchyVertices = [];\r\n\tvar allVertexSet = [];\r\n\r\n\tif (this.swimlanes != null && this.swimlanes.length > 0 && parent != null)\r\n\t{\r\n\t\tvar filledVertexSet = Object();\r\n\t\t\r\n\t\tfor (var i = 0; i < this.swimlanes.length; i++)\r\n\t\t{\r\n\t\t\tthis.filterDescendants(this.swimlanes[i], filledVertexSet);\r\n\t\t}\r\n\r\n\t\tthis.roots = [];\r\n\t\tvar filledVertexSetEmpty = true;\r\n\r\n\t\t// Poor man's isSetEmpty\r\n\t\tfor (var key in filledVertexSet)\r\n\t\t{\r\n\t\t\tif (filledVertexSet[key] != null)\r\n\t\t\t{\r\n\t\t\t\tfilledVertexSetEmpty = false;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Only test for candidates in each swimlane in order\r\n\t\tvar laneCounter = 0;\r\n\r\n\t\twhile (!filledVertexSetEmpty && laneCounter < this.swimlanes.length)\r\n\t\t{\r\n\t\t\tvar candidateRoots = this.findRoots(this.swimlanes[laneCounter], filledVertexSet);\r\n\t\t\t\r\n\t\t\tif (candidateRoots.length == 0)\r\n\t\t\t{\r\n\t\t\t\tlaneCounter++;\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// If the candidate root is an unconnected group cell, remove it from\r\n\t\t\t// the layout. We may need a custom set that holds such groups and forces\r\n\t\t\t// them to be processed for resizing and/or moving.\r\n\t\t\tfor (var i = 0; i < candidateRoots.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar vertexSet = Object();\r\n\t\t\t\thierarchyVertices.push(vertexSet);\r\n\r\n\t\t\t\tthis.traverse(candidateRoots[i], true, null, allVertexSet, vertexSet,\r\n\t\t\t\t\t\thierarchyVertices, filledVertexSet, laneCounter);\r\n\t\t\t}\r\n\r\n\t\t\tfor (var i = 0; i < candidateRoots.length; i++)\r\n\t\t\t{\r\n\t\t\t\tthis.roots.push(candidateRoots[i]);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tfilledVertexSetEmpty = true;\r\n\t\t\t\r\n\t\t\t// Poor man's isSetEmpty\r\n\t\t\tfor (var key in filledVertexSet)\r\n\t\t\t{\r\n\t\t\t\tif (filledVertexSet[key] != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tfilledVertexSetEmpty = false;\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\t// Find vertex set as directed traversal from roots\r\n\r\n\t\tfor (var i = 0; i < this.roots.length; i++)\r\n\t\t{\r\n\t\t\tvar vertexSet = Object();\r\n\t\t\thierarchyVertices.push(vertexSet);\r\n\r\n\t\t\tthis.traverse(this.roots[i], true, null, allVertexSet, vertexSet,\r\n\t\t\t\t\thierarchyVertices, null);\r\n\t\t}\r\n\t}\r\n\r\n\tvar tmp = [];\r\n\t\r\n\tfor (var key in allVertexSet)\r\n\t{\r\n\t\ttmp.push(allVertexSet[key]);\r\n\t}\r\n\t\r\n\tthis.model = new mxSwimlaneModel(this, tmp, this.roots,\r\n\t\tparent, this.tightenToSource);\r\n\r\n\tthis.cycleStage(parent);\r\n\tthis.layeringStage();\r\n\t\r\n\tthis.crossingStage(parent);\r\n\tinitialX = this.placementStage(0, parent);\r\n};\r\n\r\n/**\r\n * Function: filterDescendants\r\n * \r\n * Creates an array of descendant cells\r\n */\r\nmxSwimlaneLayout.prototype.filterDescendants = function(cell, result)\r\n{\r\n\tvar model = this.graph.model;\r\n\r\n\tif (model.isVertex(cell) && cell != this.parent && model.getParent(cell) != this.parent && this.graph.isCellVisible(cell))\r\n\t{\r\n\t\tresult[mxObjectIdentity.get(cell)] = cell;\r\n\t}\r\n\r\n\tif (this.traverseAncestors || cell == this.parent\r\n\t\t\t&& this.graph.isCellVisible(cell))\r\n\t{\r\n\t\tvar childCount = model.getChildCount(cell);\r\n\r\n\t\tfor (var i = 0; i < childCount; i++)\r\n\t\t{\r\n\t\t\tvar child = model.getChildAt(cell, i);\r\n\t\t\t\r\n\t\t\t// Ignore ports in the layout vertex list, they are dealt with\r\n\t\t\t// in the traversal mechanisms\r\n\t\t\tif (!this.isPort(child))\r\n\t\t\t{\r\n\t\t\t\tthis.filterDescendants(child, result);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isPort\r\n * \r\n * Returns true if the given cell is a \"port\", that is, when connecting to\r\n * it, its parent is the connecting vertex in terms of graph traversal\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> that represents the port.\r\n */\r\nmxSwimlaneLayout.prototype.isPort = function(cell)\r\n{\r\n\tif (cell.geometry.relative)\r\n\t{\r\n\t\treturn true;\r\n\t}\r\n\t\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: getEdgesBetween\r\n * \r\n * Returns the edges between the given source and target. This takes into\r\n * account collapsed and invisible cells and ports.\r\n * \r\n * Parameters:\r\n * \r\n * source -\r\n * target -\r\n * directed -\r\n */\r\nmxSwimlaneLayout.prototype.getEdgesBetween = function(source, target, directed)\r\n{\r\n\tdirected = (directed != null) ? directed : false;\r\n\tvar edges = this.getEdges(source);\r\n\tvar result = [];\r\n\r\n\t// Checks if the edge is connected to the correct\r\n\t// cell and returns the first match\r\n\tfor (var i = 0; i < edges.length; i++)\r\n\t{\r\n\t\tvar src = this.getVisibleTerminal(edges[i], true);\r\n\t\tvar trg = this.getVisibleTerminal(edges[i], false);\r\n\r\n\t\tif ((src == source && trg == target) || (!directed && src == target && trg == source))\r\n\t\t{\r\n\t\t\tresult.push(edges[i]);\r\n\t\t}\r\n\t}\r\n\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Traverses the (directed) graph invoking the given function for each\r\n * visited vertex and edge. The function is invoked with the current vertex\r\n * and the incoming edge as a parameter. This implementation makes sure\r\n * each vertex is only visited once. The function may return false if the\r\n * traversal should stop at the given vertex.\r\n * \r\n * Parameters:\r\n * \r\n * vertex - <mxCell> that represents the vertex where the traversal starts.\r\n * directed - boolean indicating if edges should only be traversed\r\n * from source to target. Default is true.\r\n * edge - Optional <mxCell> that represents the incoming edge. This is\r\n * null for the first step of the traversal.\r\n * allVertices - Array of cell paths for the visited cells.\r\n * swimlaneIndex - the laid out order index of the swimlane vertex is contained in\r\n */\r\nmxSwimlaneLayout.prototype.traverse = function(vertex, directed, edge, allVertices, currentComp,\r\n\t\t\t\t\t\t\t\t\t\t\thierarchyVertices, filledVertexSet, swimlaneIndex)\r\n{\r\n\tif (vertex != null && allVertices != null)\r\n\t{\r\n\t\t// Has this vertex been seen before in any traversal\r\n\t\t// And if the filled vertex set is populated, only \r\n\t\t// process vertices in that it contains\r\n\t\tvar vertexID = mxObjectIdentity.get(vertex);\r\n\t\t\r\n\t\tif ((allVertices[vertexID] == null)\r\n\t\t\t\t&& (filledVertexSet == null ? true : filledVertexSet[vertexID] != null))\r\n\t\t{\r\n\t\t\tif (currentComp[vertexID] == null)\r\n\t\t\t{\r\n\t\t\t\tcurrentComp[vertexID] = vertex;\r\n\t\t\t}\r\n\t\t\tif (allVertices[vertexID] == null)\r\n\t\t\t{\r\n\t\t\t\tallVertices[vertexID] = vertex;\r\n\t\t\t}\r\n\r\n\t\t\tif (filledVertexSet !== null)\r\n\t\t\t{\r\n\t\t\t\tdelete filledVertexSet[vertexID];\r\n\t\t\t}\r\n\r\n\t\t\tvar edges = this.getEdges(vertex);\r\n\t\t\tvar model = this.graph.model;\r\n\r\n\t\t\tfor (var i = 0; i < edges.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar otherVertex = this.getVisibleTerminal(edges[i], true);\r\n\t\t\t\tvar isSource = otherVertex == vertex;\r\n\t\t\t\t\r\n\t\t\t\tif (isSource)\r\n\t\t\t\t{\r\n\t\t\t\t\totherVertex = this.getVisibleTerminal(edges[i], false);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tvar otherIndex = 0;\r\n\t\t\t\t// Get the swimlane index of the other terminal\r\n\t\t\t\tfor (otherIndex = 0; otherIndex < this.swimlanes.length; otherIndex++)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (model.isAncestor(this.swimlanes[otherIndex], otherVertex))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (otherIndex >= this.swimlanes.length)\r\n\t\t\t\t{\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Traverse if the other vertex is within the same swimlane as\r\n\t\t\t\t// as the current vertex, or if the swimlane index of the other\r\n\t\t\t\t// vertex is greater than that of this vertex\r\n\t\t\t\tif ((otherIndex > swimlaneIndex) ||\r\n\t\t\t\t\t\t((!directed || isSource) && otherIndex == swimlaneIndex))\r\n\t\t\t\t{\r\n\t\t\t\t\tcurrentComp = this.traverse(otherVertex, directed, edges[i], allVertices,\r\n\t\t\t\t\t\t\tcurrentComp, hierarchyVertices,\r\n\t\t\t\t\t\t\tfilledVertexSet, otherIndex);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tif (currentComp[vertexID] == null)\r\n\t\t\t{\r\n\t\t\t\t// We've seen this vertex before, but not in the current component\r\n\t\t\t\t// This component and the one it's in need to be merged\r\n\t\t\t\tfor (var i = 0; i < hierarchyVertices.length; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar comp = hierarchyVertices[i];\r\n\r\n\t\t\t\t\tif (comp[vertexID] != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tfor (var key in comp)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tcurrentComp[key] = comp[key];\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t// Remove the current component from the hierarchy set\r\n\t\t\t\t\t\thierarchyVertices.splice(i, 1);\r\n\t\t\t\t\t\treturn currentComp;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn currentComp;\r\n};\r\n\r\n/**\r\n * Function: cycleStage\r\n * \r\n * Executes the cycle stage using mxMinimumCycleRemover.\r\n */\r\nmxSwimlaneLayout.prototype.cycleStage = function(parent)\r\n{\r\n\tvar cycleStage = new mxSwimlaneOrdering(this);\r\n\tcycleStage.execute(parent);\r\n};\r\n\r\n/**\r\n * Function: layeringStage\r\n * \r\n * Implements first stage of a Sugiyama layout.\r\n */\r\nmxSwimlaneLayout.prototype.layeringStage = function()\r\n{\r\n\tthis.model.initialRank();\r\n\tthis.model.fixRanks();\r\n};\r\n\r\n/**\r\n * Function: crossingStage\r\n * \r\n * Executes the crossing stage using mxMedianHybridCrossingReduction.\r\n */\r\nmxSwimlaneLayout.prototype.crossingStage = function(parent)\r\n{\r\n\tvar crossingStage = new mxMedianHybridCrossingReduction(this);\r\n\tcrossingStage.execute(parent);\r\n};\r\n\r\n/**\r\n * Function: placementStage\r\n * \r\n * Executes the placement stage using mxCoordinateAssignment.\r\n */\r\nmxSwimlaneLayout.prototype.placementStage = function(initialX, parent)\r\n{\r\n\tvar placementStage = new mxCoordinateAssignment(this, this.intraCellSpacing,\r\n\t\t\tthis.interRankCellSpacing, this.orientation, initialX,\r\n\t\t\tthis.parallelEdgeSpacing);\r\n\tplacementStage.fineTuning = this.fineTuning;\r\n\tplacementStage.execute(parent);\r\n\t\r\n\treturn placementStage.limitX + this.interHierarchySpacing;\r\n};\r\n/**\r\n * Copyright (c) 2006-2018, JGraph Ltd\r\n * Copyright (c) 2006-2018, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxGraphModel\r\n * \r\n * Extends <mxEventSource> to implement a graph model. The graph model acts as\r\n * a wrapper around the cells which are in charge of storing the actual graph\r\n * datastructure. The model acts as a transactional wrapper with event\r\n * notification for all changes, whereas the cells contain the atomic\r\n * operations for updating the actual datastructure.\r\n * \r\n * Layers:\r\n * \r\n * The cell hierarchy in the model must have a top-level root cell which\r\n * contains the layers (typically one default layer), which in turn contain the\r\n * top-level cells of the layers. This means each cell is contained in a layer.\r\n * If no layers are required, then all new cells should be added to the default\r\n * layer.\r\n * \r\n * Layers are useful for hiding and showing groups of cells, or for placing\r\n * groups of cells on top of other cells in the display. To identify a layer,\r\n * the <isLayer> function is used. It returns true if the parent of the given\r\n * cell is the root of the model.\r\n * \r\n * Events:\r\n * \r\n * See events section for more details. There is a new set of events for\r\n * tracking transactional changes as they happen. The events are called\r\n * startEdit for the initial beginUpdate, executed for each executed change\r\n * and endEdit for the terminal endUpdate. The executed event contains a\r\n * property called change which represents the change after execution.\r\n * \r\n * Encoding the model:\r\n * \r\n * To encode a graph model, use the following code:\r\n * \r\n * (code)\r\n * var enc = new mxCodec();\r\n * var node = enc.encode(graph.getModel());\r\n * (end)\r\n * \r\n * This will create an XML node that contains all the model information.\r\n * \r\n * Encoding and decoding changes:\r\n * \r\n * For the encoding of changes, a graph model listener is required that encodes\r\n * each change from the given array of changes.\r\n * \r\n * (code)\r\n * model.addListener(mxEvent.CHANGE, function(sender, evt)\r\n * {\r\n *   var changes = evt.getProperty('edit').changes;\r\n *   var nodes = [];\r\n *   var codec = new mxCodec();\r\n * \r\n *   for (var i = 0; i < changes.length; i++)\r\n *   {\r\n *     nodes.push(codec.encode(changes[i]));\r\n *   }\r\n *   // do something with the nodes\r\n * });\r\n * (end)\r\n * \r\n * For the decoding and execution of changes, the codec needs a lookup function\r\n * that allows it to resolve cell IDs as follows:\r\n * \r\n * (code)\r\n * var codec = new mxCodec();\r\n * codec.lookup = function(id)\r\n * {\r\n *   return model.getCell(id);\r\n * }\r\n * (end)\r\n * \r\n * For each encoded change (represented by a node), the following code can be\r\n * used to carry out the decoding and create a change object.\r\n * \r\n * (code)\r\n * var changes = [];\r\n * var change = codec.decode(node);\r\n * change.model = model;\r\n * change.execute();\r\n * changes.push(change);\r\n * (end)\r\n * \r\n * The changes can then be dispatched using the model as follows.\r\n * \r\n * (code)\r\n * var edit = new mxUndoableEdit(model, false);\r\n * edit.changes = changes;\r\n * \r\n * edit.notify = function()\r\n * {\r\n *   edit.source.fireEvent(new mxEventObject(mxEvent.CHANGE,\r\n *   \t'edit', edit, 'changes', edit.changes));\r\n *   edit.source.fireEvent(new mxEventObject(mxEvent.NOTIFY,\r\n *   \t'edit', edit, 'changes', edit.changes));\r\n * }\r\n * \r\n * model.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));\r\n * model.fireEvent(new mxEventObject(mxEvent.CHANGE,\r\n * \t\t'edit', edit, 'changes', changes));\r\n * (end)\r\n *\r\n * Event: mxEvent.CHANGE\r\n *\r\n * Fires when an undoable edit is dispatched. The <code>edit</code> property\r\n * contains the <mxUndoableEdit>. The <code>changes</code> property contains\r\n * the array of atomic changes inside the undoable edit. The changes property\r\n * is <strong>deprecated</strong>, please use edit.changes instead.\r\n *\r\n * Example:\r\n * \r\n * For finding newly inserted cells, the following code can be used:\r\n * \r\n * (code)\r\n * graph.model.addListener(mxEvent.CHANGE, function(sender, evt)\r\n * {\r\n *   var changes = evt.getProperty('edit').changes;\r\n * \r\n *   for (var i = 0; i < changes.length; i++)\r\n *   {\r\n *     var change = changes[i];\r\n *     \r\n *     if (change instanceof mxChildChange &&\r\n *       change.change.previous == null)\r\n *     {\r\n *       graph.startEditingAtCell(change.child);\r\n *       break;\r\n *     }\r\n *   }\r\n * });\r\n * (end)\r\n * \r\n * \r\n * Event: mxEvent.NOTIFY\r\n *\r\n * Same as <mxEvent.CHANGE>, this event can be used for classes that need to\r\n * implement a sync mechanism between this model and, say, a remote model. In\r\n * such a setup, only local changes should trigger a notify event and all\r\n * changes should trigger a change event.\r\n * \r\n * Event: mxEvent.EXECUTE\r\n * \r\n * Fires between begin- and endUpdate and after an atomic change was executed\r\n * in the model. The <code>change</code> property contains the atomic change\r\n * that was executed.\r\n * \r\n * Event: mxEvent.EXECUTED\r\n * \r\n * Fires between START_EDIT and END_EDIT after an atomic change was executed.\r\n * The <code>change</code> property contains the change that was executed.\r\n *\r\n * Event: mxEvent.BEGIN_UPDATE\r\n *\r\n * Fires after the <updateLevel> was incremented in <beginUpdate>. This event\r\n * contains no properties.\r\n * \r\n * Event: mxEvent.START_EDIT\r\n *\r\n * Fires after the <updateLevel> was changed from 0 to 1. This event\r\n * contains no properties.\r\n * \r\n * Event: mxEvent.END_UPDATE\r\n * \r\n * Fires after the <updateLevel> was decreased in <endUpdate> but before any\r\n * notification or change dispatching. The <code>edit</code> property contains\r\n * the <currentEdit>.\r\n * \r\n * Event: mxEvent.END_EDIT\r\n *\r\n * Fires after the <updateLevel> was changed from 1 to 0. This event\r\n * contains no properties.\r\n * \r\n * Event: mxEvent.BEFORE_UNDO\r\n * \r\n * Fires before the change is dispatched after the update level has reached 0\r\n * in <endUpdate>. The <code>edit</code> property contains the <curreneEdit>.\r\n * \r\n * Event: mxEvent.UNDO\r\n * \r\n * Fires after the change was dispatched in <endUpdate>. The <code>edit</code>\r\n * property contains the <currentEdit>.\r\n * \r\n * Constructor: mxGraphModel\r\n * \r\n * Constructs a new graph model. If no root is specified then a new root\r\n * <mxCell> with a default layer is created.\r\n * \r\n * Parameters:\r\n * \r\n * root - <mxCell> that represents the root cell.\r\n */\r\nfunction mxGraphModel(root)\r\n{\r\n\tthis.currentEdit = this.createUndoableEdit();\r\n\t\r\n\tif (root != null)\r\n\t{\r\n\t\tthis.setRoot(root);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.clear();\r\n\t}\r\n};\r\n\r\n/**\r\n * Extends mxEventSource.\r\n */\r\nmxGraphModel.prototype = new mxEventSource();\r\nmxGraphModel.prototype.constructor = mxGraphModel;\r\n\r\n/**\r\n * Variable: root\r\n * \r\n * Holds the root cell, which in turn contains the cells that represent the\r\n * layers of the diagram as child cells. That is, the actual elements of the\r\n * diagram are supposed to live in the third generation of cells and below.\r\n */\r\nmxGraphModel.prototype.root = null;\r\n\r\n/**\r\n * Variable: cells\r\n * \r\n * Maps from Ids to cells.\r\n */\r\nmxGraphModel.prototype.cells = null;\r\n\r\n/**\r\n * Variable: maintainEdgeParent\r\n * \r\n * Specifies if edges should automatically be moved into the nearest common\r\n * ancestor of their terminals. Default is true.\r\n */\r\nmxGraphModel.prototype.maintainEdgeParent = true;\r\n\r\n/**\r\n * Variable: ignoreRelativeEdgeParent\r\n * \r\n * Specifies if relative edge parents should be ignored for finding the nearest\r\n * common ancestors of an edge's terminals. Default is true.\r\n */\r\nmxGraphModel.prototype.ignoreRelativeEdgeParent = true;\r\n\r\n/**\r\n * Variable: createIds\r\n * \r\n * Specifies if the model should automatically create Ids for new cells.\r\n * Default is true.\r\n */\r\nmxGraphModel.prototype.createIds = true;\r\n\r\n/**\r\n * Variable: prefix\r\n * \r\n * Defines the prefix of new Ids. Default is an empty string.\r\n */\r\nmxGraphModel.prototype.prefix = '';\r\n\r\n/**\r\n * Variable: postfix\r\n * \r\n * Defines the postfix of new Ids. Default is an empty string.\r\n */\r\nmxGraphModel.prototype.postfix = '';\r\n\r\n/**\r\n * Variable: nextId\r\n * \r\n * Specifies the next Id to be created. Initial value is 0.\r\n */\r\nmxGraphModel.prototype.nextId = 0;\r\n\r\n/**\r\n * Variable: currentEdit\r\n * \r\n * Holds the changes for the current transaction. If the transaction is\r\n * closed then a new object is created for this variable using\r\n * <createUndoableEdit>.\r\n */\r\nmxGraphModel.prototype.currentEdit = null;\r\n\r\n/**\r\n * Variable: updateLevel\r\n * \r\n * Counter for the depth of nested transactions. Each call to <beginUpdate>\r\n * will increment this number and each call to <endUpdate> will decrement\r\n * it. When the counter reaches 0, the transaction is closed and the\r\n * respective events are fired. Initial value is 0.\r\n */\r\nmxGraphModel.prototype.updateLevel = 0;\r\n\r\n/**\r\n * Variable: endingUpdate\r\n * \r\n * True if the program flow is currently inside endUpdate.\r\n */\r\nmxGraphModel.prototype.endingUpdate = false;\r\n\r\n/**\r\n * Function: clear\r\n *\r\n * Sets a new root using <createRoot>.\r\n */\r\nmxGraphModel.prototype.clear = function()\r\n{\r\n\tthis.setRoot(this.createRoot());\r\n};\r\n\r\n/**\r\n * Function: isCreateIds\r\n *\r\n * Returns <createIds>.\r\n */\r\nmxGraphModel.prototype.isCreateIds = function()\r\n{\r\n\treturn this.createIds;\r\n};\r\n\r\n/**\r\n * Function: setCreateIds\r\n *\r\n * Sets <createIds>.\r\n */\r\nmxGraphModel.prototype.setCreateIds = function(value)\r\n{\r\n\tthis.createIds = value;\r\n};\r\n\r\n/**\r\n * Function: createRoot\r\n *\r\n * Creates a new root cell with a default layer (child 0).\r\n */\r\nmxGraphModel.prototype.createRoot = function()\r\n{\r\n\tvar cell = new mxCell();\r\n\tcell.insert(new mxCell());\r\n\t\r\n\treturn cell;\r\n};\r\n\r\n/**\r\n * Function: getCell\r\n *\r\n * Returns the <mxCell> for the specified Id or null if no cell can be\r\n * found for the given Id.\r\n *\r\n * Parameters:\r\n * \r\n * id - A string representing the Id of the cell.\r\n */\r\nmxGraphModel.prototype.getCell = function(id)\r\n{\r\n\treturn (this.cells != null) ? this.cells[id] : null;\r\n};\r\n\r\n/**\r\n * Function: filterCells\r\n * \r\n * Returns the cells from the given array where the given filter function\r\n * returns true.\r\n */\r\nmxGraphModel.prototype.filterCells = function(cells, filter)\r\n{\r\n\tvar result = null;\r\n\t\r\n\tif (cells != null)\r\n\t{\r\n\t\tresult = [];\r\n\t\t\r\n\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t{\r\n\t\t\tif (filter(cells[i]))\r\n\t\t\t{\r\n\t\t\t\tresult.push(cells[i]);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: getDescendants\r\n * \r\n * Returns all descendants of the given cell and the cell itself in an array.\r\n * \r\n * Parameters:\r\n * \r\n * parent - <mxCell> whose descendants should be returned.\r\n */\r\nmxGraphModel.prototype.getDescendants = function(parent)\r\n{\r\n\treturn this.filterDescendants(null, parent);\r\n};\r\n\r\n/**\r\n * Function: filterDescendants\r\n * \r\n * Visits all cells recursively and applies the specified filter function\r\n * to each cell. If the function returns true then the cell is added\r\n * to the resulting array. The parent and result paramters are optional.\r\n * If parent is not specified then the recursion starts at <root>.\r\n * \r\n * Example:\r\n * The following example extracts all vertices from a given model:\r\n * (code)\r\n * var filter = function(cell)\r\n * {\r\n * \treturn model.isVertex(cell);\r\n * }\r\n * var vertices = model.filterDescendants(filter);\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * filter - JavaScript function that takes an <mxCell> as an argument\r\n * and returns a boolean.\r\n * parent - Optional <mxCell> that is used as the root of the recursion.\r\n */\r\nmxGraphModel.prototype.filterDescendants = function(filter, parent)\r\n{\r\n\t// Creates a new array for storing the result\r\n\tvar result = [];\r\n\r\n\t// Recursion starts at the root of the model\r\n\tparent = parent || this.getRoot();\r\n\t\r\n\t// Checks if the filter returns true for the cell\r\n\t// and adds it to the result array\r\n\tif (filter == null || filter(parent))\r\n\t{\r\n\t\tresult.push(parent);\r\n\t}\r\n\t\r\n\t// Visits the children of the cell\r\n\tvar childCount = this.getChildCount(parent);\r\n\t\r\n\tfor (var i = 0; i < childCount; i++)\r\n\t{\r\n\t\tvar child = this.getChildAt(parent, i);\r\n\t\tresult = result.concat(this.filterDescendants(filter, child));\r\n\t}\r\n\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: getRoot\r\n * \r\n * Returns the root of the model or the topmost parent of the given cell.\r\n *\r\n * Parameters:\r\n * \r\n * cell - Optional <mxCell> that specifies the child.\r\n */\r\nmxGraphModel.prototype.getRoot = function(cell)\r\n{\r\n\tvar root = cell || this.root;\r\n\t\r\n\tif (cell != null)\r\n\t{\r\n\t\twhile (cell != null)\r\n\t\t{\r\n\t\t\troot = cell;\r\n\t\t\tcell = this.getParent(cell);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn root;\r\n};\r\n\r\n/**\r\n * Function: setRoot\r\n * \r\n * Sets the <root> of the model using <mxRootChange> and adds the change to\r\n * the current transaction. This resets all datastructures in the model and\r\n * is the preferred way of clearing an existing model. Returns the new\r\n * root.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * var root = new mxCell();\r\n * root.insert(new mxCell());\r\n * model.setRoot(root);\r\n * (end)\r\n *\r\n * Parameters:\r\n * \r\n * root - <mxCell> that specifies the new root.\r\n */\r\nmxGraphModel.prototype.setRoot = function(root)\r\n{\r\n\tthis.execute(new mxRootChange(this, root));\r\n\t\r\n\treturn root;\r\n};\r\n\r\n/**\r\n * Function: rootChanged\r\n * \r\n * Inner callback to change the root of the model and update the internal\r\n * datastructures, such as <cells> and <nextId>. Returns the previous root.\r\n *\r\n * Parameters:\r\n * \r\n * root - <mxCell> that specifies the new root.\r\n */\r\nmxGraphModel.prototype.rootChanged = function(root)\r\n{\r\n\tvar oldRoot = this.root;\r\n\tthis.root = root;\r\n\t\r\n\t// Resets counters and datastructures\r\n\tthis.nextId = 0;\r\n\tthis.cells = null;\r\n\tthis.cellAdded(root);\r\n\t\r\n\treturn oldRoot;\r\n};\r\n\r\n/**\r\n * Function: isRoot\r\n * \r\n * Returns true if the given cell is the root of the model and a non-null\r\n * value.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> that represents the possible root.\r\n */\r\nmxGraphModel.prototype.isRoot = function(cell)\r\n{\r\n\treturn cell != null && this.root == cell;\r\n};\r\n\r\n/**\r\n * Function: isLayer\r\n * \r\n * Returns true if <isRoot> returns true for the parent of the given cell.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> that represents the possible layer.\r\n */\r\nmxGraphModel.prototype.isLayer = function(cell)\r\n{\r\n\treturn this.isRoot(this.getParent(cell));\r\n};\r\n\r\n/**\r\n * Function: isAncestor\r\n * \r\n * Returns true if the given parent is an ancestor of the given child. Note \r\n * returns true if child == parent.\r\n *\r\n * Parameters:\r\n * \r\n * parent - <mxCell> that specifies the parent.\r\n * child - <mxCell> that specifies the child.\r\n */\r\nmxGraphModel.prototype.isAncestor = function(parent, child)\r\n{\r\n\twhile (child != null && child != parent)\r\n\t{\r\n\t\tchild = this.getParent(child);\r\n\t}\r\n\t\r\n\treturn child == parent;\r\n};\r\n\r\n/**\r\n * Function: contains\r\n * \r\n * Returns true if the model contains the given <mxCell>.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> that specifies the cell.\r\n */\r\nmxGraphModel.prototype.contains = function(cell)\r\n{\r\n\treturn this.isAncestor(this.root, cell);\r\n};\r\n\r\n/**\r\n * Function: getParent\r\n * \r\n * Returns the parent of the given cell.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose parent should be returned.\r\n */\r\nmxGraphModel.prototype.getParent = function(cell)\r\n{\r\n\treturn (cell != null) ? cell.getParent() : null;\r\n};\r\n\r\n/**\r\n * Function: add\r\n * \r\n * Adds the specified child to the parent at the given index using\r\n * <mxChildChange> and adds the change to the current transaction. If no\r\n * index is specified then the child is appended to the parent's array of\r\n * children. Returns the inserted child.\r\n * \r\n * Parameters:\r\n * \r\n * parent - <mxCell> that specifies the parent to contain the child.\r\n * child - <mxCell> that specifies the child to be inserted.\r\n * index - Optional integer that specifies the index of the child.\r\n */\r\nmxGraphModel.prototype.add = function(parent, child, index)\r\n{\r\n\tif (child != parent && parent != null && child != null)\r\n\t{\t\r\n\t\t// Appends the child if no index was specified\r\n\t\tif (index == null)\r\n\t\t{\r\n\t\t\tindex = this.getChildCount(parent);\r\n\t\t}\r\n\t\t\r\n\t\tvar parentChanged = parent != this.getParent(child);\r\n\t\tthis.execute(new mxChildChange(this, parent, child, index));\r\n\r\n\t\t// Maintains the edges parents by moving the edges\r\n\t\t// into the nearest common ancestor of its terminals\r\n\t\tif (this.maintainEdgeParent && parentChanged)\r\n\t\t{\r\n\t\t\tthis.updateEdgeParents(child);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn child;\r\n};\r\n\r\n/**\r\n * Function: cellAdded\r\n * \r\n * Inner callback to update <cells> when a cell has been added. This\r\n * implementation resolves collisions by creating new Ids. To change the\r\n * ID of a cell after it was inserted into the model, use the following\r\n * code:\r\n * \r\n * (code\r\n * delete model.cells[cell.getId()];\r\n * cell.setId(newId);\r\n * model.cells[cell.getId()] = cell;\r\n * (end)\r\n *\r\n * If the change of the ID should be part of the command history, then the\r\n * cell should be removed from the model and a clone with the new ID should\r\n * be reinserted into the model instead.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> that specifies the cell that has been added.\r\n */\r\nmxGraphModel.prototype.cellAdded = function(cell)\r\n{\r\n\tif (cell != null)\r\n\t{\r\n\t\t// Creates an Id for the cell if not Id exists\r\n\t\tif (cell.getId() == null && this.createIds)\r\n\t\t{\r\n\t\t\tcell.setId(this.createId(cell));\r\n\t\t}\r\n\t\t\r\n\t\tif (cell.getId() != null)\r\n\t\t{\r\n\t\t\tvar collision = this.getCell(cell.getId());\r\n\t\t\t\r\n\t\t\tif (collision != cell)\r\n\t\t\t{\t\r\n\t\t\t\t// Creates new Id for the cell\r\n\t\t\t\t// as long as there is a collision\r\n\t\t\t\twhile (collision != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tcell.setId(this.createId(cell));\r\n\t\t\t\t\tcollision = this.getCell(cell.getId());\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Lazily creates the cells dictionary\r\n\t\t\t\tif (this.cells == null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.cells = new Object();\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.cells[cell.getId()] = cell;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Makes sure IDs of deleted cells are not reused\r\n\t\tif (mxUtils.isNumeric(cell.getId()))\r\n\t\t{\r\n\t\t\tthis.nextId = Math.max(this.nextId, cell.getId());\r\n\t\t}\r\n\t\t\r\n\t\t// Recursively processes child cells\r\n\t\tvar childCount = this.getChildCount(cell);\r\n\t\t\r\n\t\tfor (var i=0; i<childCount; i++)\r\n\t\t{\r\n\t\t\tthis.cellAdded(this.getChildAt(cell, i));\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createId\r\n * \r\n * Hook method to create an Id for the specified cell. This implementation\r\n * concatenates <prefix>, id and <postfix> to create the Id and increments\r\n * <nextId>. The cell is ignored by this implementation, but can be used in\r\n * overridden methods to prefix the Ids with eg. the cell type.\r\n *\r\n * Parameters:\r\n *\r\n * cell - <mxCell> to create the Id for.\r\n */\r\nmxGraphModel.prototype.createId = function(cell)\r\n{\r\n\tvar id = this.nextId;\r\n\tthis.nextId++;\r\n\t\r\n\treturn this.prefix + id + this.postfix;\r\n};\r\n\r\n/**\r\n * Function: updateEdgeParents\r\n * \r\n * Updates the parent for all edges that are connected to cell or one of\r\n * its descendants using <updateEdgeParent>.\r\n */\r\nmxGraphModel.prototype.updateEdgeParents = function(cell, root)\r\n{\r\n\t// Gets the topmost node of the hierarchy\r\n\troot = root || this.getRoot(cell);\r\n\t\r\n\t// Updates edges on children first\r\n\tvar childCount = this.getChildCount(cell);\r\n\t\r\n\tfor (var i = 0; i < childCount; i++)\r\n\t{\r\n\t\tvar child = this.getChildAt(cell, i);\r\n\t\tthis.updateEdgeParents(child, root);\r\n\t}\r\n\t\r\n\t// Updates the parents of all connected edges\r\n\tvar edgeCount = this.getEdgeCount(cell);\r\n\tvar edges = [];\r\n\r\n\tfor (var i = 0; i < edgeCount; i++)\r\n\t{\r\n\t\tedges.push(this.getEdgeAt(cell, i));\r\n\t}\r\n\t\r\n\tfor (var i = 0; i < edges.length; i++)\r\n\t{\r\n\t\tvar edge = edges[i];\r\n\t\t\r\n\t\t// Updates edge parent if edge and child have\r\n\t\t// a common root node (does not need to be the\r\n\t\t// model root node)\r\n\t\tif (this.isAncestor(root, edge))\r\n\t\t{\r\n\t\t\tthis.updateEdgeParent(edge, root);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: updateEdgeParent\r\n *\r\n * Inner callback to update the parent of the specified <mxCell> to the\r\n * nearest-common-ancestor of its two terminals.\r\n *\r\n * Parameters:\r\n * \r\n * edge - <mxCell> that specifies the edge.\r\n * root - <mxCell> that represents the current root of the model.\r\n */\r\nmxGraphModel.prototype.updateEdgeParent = function(edge, root)\r\n{\r\n\tvar source = this.getTerminal(edge, true);\r\n\tvar target = this.getTerminal(edge, false);\r\n\tvar cell = null;\r\n\t\r\n\t// Uses the first non-relative descendants of the source terminal\r\n\twhile (source != null && !this.isEdge(source) &&\r\n\t\tsource.geometry != null && source.geometry.relative)\r\n\t{\r\n\t\tsource = this.getParent(source);\r\n\t}\r\n\t\r\n\t// Uses the first non-relative descendants of the target terminal\r\n\twhile (target != null && this.ignoreRelativeEdgeParent &&\r\n\t\t!this.isEdge(target) && target.geometry != null && \r\n\t\ttarget.geometry.relative)\r\n\t{\r\n\t\ttarget = this.getParent(target);\r\n\t}\r\n\t\r\n\tif (this.isAncestor(root, source) && this.isAncestor(root, target))\r\n\t{\r\n\t\tif (source == target)\r\n\t\t{\r\n\t\t\tcell = this.getParent(source);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tcell = this.getNearestCommonAncestor(source, target);\r\n\t\t}\r\n\r\n\t\tif (cell != null && (this.getParent(cell) != this.root ||\r\n\t\t\tthis.isAncestor(cell, edge)) && this.getParent(edge) != cell)\r\n\t\t{\r\n\t\t\tvar geo = this.getGeometry(edge);\r\n\t\t\t\r\n\t\t\tif (geo != null)\r\n\t\t\t{\r\n\t\t\t\tvar origin1 = this.getOrigin(this.getParent(edge));\r\n\t\t\t\tvar origin2 = this.getOrigin(cell);\r\n\t\t\t\t\r\n\t\t\t\tvar dx = origin2.x - origin1.x;\r\n\t\t\t\tvar dy = origin2.y - origin1.y;\r\n\t\t\t\t\r\n\t\t\t\tgeo = geo.clone();\r\n\t\t\t\tgeo.translate(-dx, -dy);\r\n\t\t\t\tthis.setGeometry(edge, geo);\r\n\t\t\t}\r\n\r\n\t\t\tthis.add(cell, edge, this.getChildCount(cell));\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getOrigin\r\n * \r\n * Returns the absolute, accumulated origin for the children inside the\r\n * given parent as an <mxPoint>.\r\n */\r\nmxGraphModel.prototype.getOrigin = function(cell)\r\n{\r\n\tvar result = null;\r\n\t\r\n\tif (cell != null)\r\n\t{\r\n\t\tresult = this.getOrigin(this.getParent(cell));\r\n\t\t\r\n\t\tif (!this.isEdge(cell))\r\n\t\t{\r\n\t\t\tvar geo = this.getGeometry(cell);\r\n\t\t\t\r\n\t\t\tif (geo != null)\r\n\t\t\t{\r\n\t\t\t\tresult.x += geo.x;\r\n\t\t\t\tresult.y += geo.y;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tresult = new mxPoint();\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: getNearestCommonAncestor\r\n * \r\n * Returns the nearest common ancestor for the specified cells.\r\n *\r\n * Parameters:\r\n * \r\n * cell1 - <mxCell> that specifies the first cell in the tree.\r\n * cell2 - <mxCell> that specifies the second cell in the tree.\r\n */\r\nmxGraphModel.prototype.getNearestCommonAncestor = function(cell1, cell2)\r\n{\r\n\tif (cell1 != null && cell2 != null)\r\n\t{\t\t\r\n\t\t// Creates the cell path for the second cell\r\n\t\tvar path = mxCellPath.create(cell2);\r\n\r\n\t\tif (path != null && path.length > 0)\r\n\t\t{\r\n\t\t\t// Bubbles through the ancestors of the first\r\n\t\t\t// cell to find the nearest common ancestor.\r\n\t\t\tvar cell = cell1;\r\n\t\t\tvar current = mxCellPath.create(cell);\r\n\t\t\t\r\n\t\t\t// Inverts arguments\r\n\t\t\tif (path.length < current.length)\r\n\t\t\t{\r\n\t\t\t\tcell = cell2;\r\n\t\t\t\tvar tmp = current;\r\n\t\t\t\tcurrent = path;\r\n\t\t\t\tpath = tmp;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\twhile (cell != null)\r\n\t\t\t{\r\n\t\t\t\tvar parent = this.getParent(cell);\r\n\t\t\t\t\r\n\t\t\t\t// Checks if the cell path is equal to the beginning of the given cell path\r\n\t\t\t\tif (path.indexOf(current + mxCellPath.PATH_SEPARATOR) == 0 && parent != null)\r\n\t\t\t\t{\r\n\t\t\t\t\treturn cell;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tcurrent = mxCellPath.getParentPath(current);\r\n\t\t\t\tcell = parent;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: remove\r\n * \r\n * Removes the specified cell from the model using <mxChildChange> and adds\r\n * the change to the current transaction. This operation will remove the\r\n * cell and all of its children from the model. Returns the removed cell.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> that should be removed.\r\n */\r\nmxGraphModel.prototype.remove = function(cell)\r\n{\r\n\tif (cell == this.root)\r\n\t{\r\n\t\tthis.setRoot(null);\r\n\t}\r\n\telse if (this.getParent(cell) != null)\r\n\t{\r\n\t\tthis.execute(new mxChildChange(this, null, cell));\r\n\t}\r\n\t\r\n\treturn cell;\r\n};\r\n\r\n/**\r\n * Function: cellRemoved\r\n * \r\n * Inner callback to update <cells> when a cell has been removed.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> that specifies the cell that has been removed.\r\n */\r\nmxGraphModel.prototype.cellRemoved = function(cell)\r\n{\r\n\tif (cell != null && this.cells != null)\r\n\t{\r\n\t\t// Recursively processes child cells\r\n\t\tvar childCount = this.getChildCount(cell);\r\n\t\t\r\n\t\tfor (var i = childCount - 1; i >= 0; i--)\r\n\t\t{\r\n\t\t\tthis.cellRemoved(this.getChildAt(cell, i));\r\n\t\t}\r\n\t\t\r\n\t\t// Removes the dictionary entry for the cell\r\n\t\tif (this.cells != null && cell.getId() != null)\r\n\t\t{\r\n\t\t\tdelete this.cells[cell.getId()];\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: parentForCellChanged\r\n * \r\n * Inner callback to update the parent of a cell using <mxCell.insert>\r\n * on the parent and return the previous parent.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> to update the parent for.\r\n * parent - <mxCell> that specifies the new parent of the cell.\r\n * index - Optional integer that defines the index of the child\r\n * in the parent's child array.\r\n */\r\nmxGraphModel.prototype.parentForCellChanged = function(cell, parent, index)\r\n{\r\n\tvar previous = this.getParent(cell);\r\n\t\r\n\tif (parent != null)\r\n\t{\r\n\t\tif (parent != previous || previous.getIndex(cell) != index)\r\n\t\t{\r\n\t\t\tparent.insert(cell, index);\r\n\t\t}\r\n\t}\r\n\telse if (previous != null)\r\n\t{\r\n\t\tvar oldIndex = previous.getIndex(cell);\r\n\t\tprevious.remove(oldIndex);\r\n\t}\r\n\t\r\n\t// Adds or removes the cell from the model\r\n\tvar par = this.contains(parent);\r\n\tvar pre = this.contains(previous);\r\n\t\r\n\tif (par && !pre)\r\n\t{\r\n\t\tthis.cellAdded(cell);\r\n\t}\r\n\telse if (pre && !par)\r\n\t{\r\n\t\tthis.cellRemoved(cell);\r\n\t}\r\n\t\r\n\treturn previous;\r\n};\r\n\r\n/**\r\n * Function: getChildCount\r\n *\r\n * Returns the number of children in the given cell.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose number of children should be returned.\r\n */\r\nmxGraphModel.prototype.getChildCount = function(cell)\r\n{\r\n\treturn (cell != null) ? cell.getChildCount() : 0;\r\n};\r\n\r\n/**\r\n * Function: getChildAt\r\n *\r\n * Returns the child of the given <mxCell> at the given index.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> that represents the parent.\r\n * index - Integer that specifies the index of the child to be returned.\r\n */\r\nmxGraphModel.prototype.getChildAt = function(cell, index)\r\n{\r\n\treturn (cell != null) ? cell.getChildAt(index) : null;\r\n};\r\n\r\n/**\r\n * Function: getChildren\r\n * \r\n * Returns all children of the given <mxCell> as an array of <mxCells>. The\r\n * return value should be only be read.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> the represents the parent.\r\n */\r\nmxGraphModel.prototype.getChildren = function(cell)\r\n{\r\n\treturn (cell != null) ? cell.children : null;\r\n};\r\n\t\r\n/**\r\n * Function: getChildVertices\r\n * \r\n * Returns the child vertices of the given parent.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose child vertices should be returned.\r\n */\r\nmxGraphModel.prototype.getChildVertices = function(parent)\r\n{\r\n\treturn this.getChildCells(parent, true, false);\r\n};\r\n\t\t\r\n/**\r\n * Function: getChildEdges\r\n * \r\n * Returns the child edges of the given parent.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose child edges should be returned.\r\n */\r\nmxGraphModel.prototype.getChildEdges = function(parent)\r\n{\r\n\treturn this.getChildCells(parent, false, true);\r\n};\r\n\r\n/**\r\n * Function: getChildCells\r\n * \r\n * Returns the children of the given cell that are vertices and/or edges\r\n * depending on the arguments.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> the represents the parent.\r\n * vertices - Boolean indicating if child vertices should be returned.\r\n * Default is false.\r\n * edges - Boolean indicating if child edges should be returned.\r\n * Default is false.\r\n */\r\nmxGraphModel.prototype.getChildCells = function(parent, vertices, edges)\r\n{\r\n\tvertices = (vertices != null) ? vertices : false;\r\n\tedges = (edges != null) ? edges : false;\r\n\t\r\n\tvar childCount = this.getChildCount(parent);\r\n\tvar result = [];\r\n\r\n\tfor (var i = 0; i < childCount; i++)\r\n\t{\r\n\t\tvar child = this.getChildAt(parent, i);\r\n\r\n\t\tif ((!edges && !vertices) || (edges && this.isEdge(child)) ||\r\n\t\t\t(vertices && this.isVertex(child)))\r\n\t\t{\r\n\t\t\tresult.push(child);\r\n\t\t}\r\n\t}\r\n\r\n\treturn result;\r\n};\r\n\t\t\r\n/**\r\n * Function: getTerminal\r\n * \r\n * Returns the source or target <mxCell> of the given edge depending on the\r\n * value of the boolean parameter.\r\n *\r\n * Parameters:\r\n * \r\n * edge - <mxCell> that specifies the edge.\r\n * isSource - Boolean indicating which end of the edge should be returned.\r\n */\r\nmxGraphModel.prototype.getTerminal = function(edge, isSource)\r\n{\r\n\treturn (edge != null) ? edge.getTerminal(isSource) : null;\r\n};\r\n\r\n/**\r\n * Function: setTerminal\r\n * \r\n * Sets the source or target terminal of the given <mxCell> using\r\n * <mxTerminalChange> and adds the change to the current transaction.\r\n * This implementation updates the parent of the edge using <updateEdgeParent>\r\n * if required.\r\n *\r\n * Parameters:\r\n * \r\n * edge - <mxCell> that specifies the edge.\r\n * terminal - <mxCell> that specifies the new terminal.\r\n * isSource - Boolean indicating if the terminal is the new source or\r\n * target terminal of the edge.\r\n */\r\nmxGraphModel.prototype.setTerminal = function(edge, terminal, isSource)\r\n{\r\n\tvar terminalChanged = terminal != this.getTerminal(edge, isSource);\r\n\tthis.execute(new mxTerminalChange(this, edge, terminal, isSource));\r\n\t\r\n\tif (this.maintainEdgeParent && terminalChanged)\r\n\t{\r\n\t\tthis.updateEdgeParent(edge, this.getRoot());\r\n\t}\r\n\t\r\n\treturn terminal;\r\n};\r\n\t\r\n/**\r\n * Function: setTerminals\r\n * \r\n * Sets the source and target <mxCell> of the given <mxCell> in a single\r\n * transaction using <setTerminal> for each end of the edge.\r\n *\r\n * Parameters:\r\n * \r\n * edge - <mxCell> that specifies the edge.\r\n * source - <mxCell> that specifies the new source terminal.\r\n * target - <mxCell> that specifies the new target terminal.\r\n */\r\nmxGraphModel.prototype.setTerminals = function(edge, source, target)\r\n{\r\n\tthis.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tthis.setTerminal(edge, source, true);\r\n\t\tthis.setTerminal(edge, target, false);\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tthis.endUpdate();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: terminalForCellChanged\r\n * \r\n * Inner helper function to update the terminal of the edge using\r\n * <mxCell.insertEdge> and return the previous terminal.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCell> that specifies the edge to be updated.\r\n * terminal - <mxCell> that specifies the new terminal.\r\n * isSource - Boolean indicating if the terminal is the new source or\r\n * target terminal of the edge.\r\n */\r\nmxGraphModel.prototype.terminalForCellChanged = function(edge, terminal, isSource)\r\n{\r\n\tvar previous = this.getTerminal(edge, isSource);\r\n\t\r\n\tif (terminal != null)\r\n\t{\r\n\t\tterminal.insertEdge(edge, isSource);\r\n\t}\r\n\telse if (previous != null)\r\n\t{\r\n\t\tprevious.removeEdge(edge, isSource);\r\n\t}\r\n\t\r\n\treturn previous;\r\n};\r\n\r\n/**\r\n * Function: getEdgeCount\r\n * \r\n * Returns the number of distinct edges connected to the given cell.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> that represents the vertex.\r\n */\r\nmxGraphModel.prototype.getEdgeCount = function(cell)\r\n{\r\n\treturn (cell != null) ? cell.getEdgeCount() : 0;\r\n};\r\n\r\n/**\r\n * Function: getEdgeAt\r\n * \r\n * Returns the edge of cell at the given index.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> that specifies the vertex.\r\n * index - Integer that specifies the index of the edge\r\n * to return.\r\n */\r\nmxGraphModel.prototype.getEdgeAt = function(cell, index)\r\n{\r\n\treturn (cell != null) ? cell.getEdgeAt(index) : null;\r\n};\r\n\t\r\n/**\r\n * Function: getDirectedEdgeCount\r\n * \r\n * Returns the number of incoming or outgoing edges, ignoring the given\r\n * edge.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose edge count should be returned.\r\n * outgoing - Boolean that specifies if the number of outgoing or\r\n * incoming edges should be returned.\r\n * ignoredEdge - <mxCell> that represents an edge to be ignored.\r\n */\r\nmxGraphModel.prototype.getDirectedEdgeCount = function(cell, outgoing, ignoredEdge)\r\n{\r\n\tvar count = 0;\r\n\tvar edgeCount = this.getEdgeCount(cell);\r\n\r\n\tfor (var i = 0; i < edgeCount; i++)\r\n\t{\r\n\t\tvar edge = this.getEdgeAt(cell, i);\r\n\r\n\t\tif (edge != ignoredEdge && this.getTerminal(edge, outgoing) == cell)\r\n\t\t{\r\n\t\t\tcount++;\r\n\t\t}\r\n\t}\r\n\r\n\treturn count;\r\n};\r\n\r\n/**\r\n * Function: getConnections\r\n * \r\n * Returns all edges of the given cell without loops.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose edges should be returned.\r\n * \r\n */\r\nmxGraphModel.prototype.getConnections = function(cell)\r\n{\r\n\treturn this.getEdges(cell, true, true, false);\r\n};\r\n\r\n/**\r\n * Function: getIncomingEdges\r\n * \r\n * Returns the incoming edges of the given cell without loops.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose incoming edges should be returned.\r\n * \r\n */\r\nmxGraphModel.prototype.getIncomingEdges = function(cell)\r\n{\r\n\treturn this.getEdges(cell, true, false, false);\r\n};\r\n\r\n/**\r\n * Function: getOutgoingEdges\r\n * \r\n * Returns the outgoing edges of the given cell without loops.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose outgoing edges should be returned.\r\n * \r\n */\r\nmxGraphModel.prototype.getOutgoingEdges = function(cell)\r\n{\r\n\treturn this.getEdges(cell, false, true, false);\r\n};\r\n\r\n/**\r\n * Function: getEdges\r\n * \r\n * Returns all distinct edges connected to this cell as a new array of\r\n * <mxCells>. If at least one of incoming or outgoing is true, then loops\r\n * are ignored, otherwise if both are false, then all edges connected to\r\n * the given cell are returned including loops.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> that specifies the cell.\r\n * incoming - Optional boolean that specifies if incoming edges should be\r\n * returned. Default is true.\r\n * outgoing - Optional boolean that specifies if outgoing edges should be\r\n * returned. Default is true.\r\n * includeLoops - Optional boolean that specifies if loops should be returned.\r\n * Default is true. \r\n */\r\nmxGraphModel.prototype.getEdges = function(cell, incoming, outgoing, includeLoops)\r\n{\r\n\tincoming = (incoming != null) ? incoming : true;\r\n\toutgoing = (outgoing != null) ? outgoing : true;\r\n\tincludeLoops = (includeLoops != null) ? includeLoops : true;\r\n\t\r\n\tvar edgeCount = this.getEdgeCount(cell);\r\n\tvar result = [];\r\n\r\n\tfor (var i = 0; i < edgeCount; i++)\r\n\t{\r\n\t\tvar edge = this.getEdgeAt(cell, i);\r\n\t\tvar source = this.getTerminal(edge, true);\r\n\t\tvar target = this.getTerminal(edge, false);\r\n\r\n\t\tif ((includeLoops && source == target) || ((source != target) && ((incoming && target == cell) ||\r\n\t\t\t(outgoing && source == cell))))\r\n\t\t{\r\n\t\t\tresult.push(edge);\r\n\t\t}\r\n\t}\r\n\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: getEdgesBetween\r\n * \r\n * Returns all edges between the given source and target pair. If directed\r\n * is true, then only edges from the source to the target are returned,\r\n * otherwise, all edges between the two cells are returned.\r\n * \r\n * Parameters:\r\n * \r\n * source - <mxCell> that defines the source terminal of the edge to be\r\n * returned.\r\n * target - <mxCell> that defines the target terminal of the edge to be\r\n * returned.\r\n * directed - Optional boolean that specifies if the direction of the\r\n * edge should be taken into account. Default is false.\r\n */\r\nmxGraphModel.prototype.getEdgesBetween = function(source, target, directed)\r\n{\r\n\tdirected = (directed != null) ? directed : false;\r\n\t\r\n\tvar tmp1 = this.getEdgeCount(source);\r\n\tvar tmp2 = this.getEdgeCount(target);\r\n\t\r\n\t// Assumes the source has less connected edges\r\n\tvar terminal = source;\r\n\tvar edgeCount = tmp1;\r\n\t\r\n\t// Uses the smaller array of connected edges\r\n\t// for searching the edge\r\n\tif (tmp2 < tmp1)\r\n\t{\r\n\t\tedgeCount = tmp2;\r\n\t\tterminal = target;\r\n\t}\r\n\t\r\n\tvar result = [];\r\n\t\r\n\t// Checks if the edge is connected to the correct\r\n\t// cell and returns the first match\r\n\tfor (var i = 0; i < edgeCount; i++)\r\n\t{\r\n\t\tvar edge = this.getEdgeAt(terminal, i);\r\n\t\tvar src = this.getTerminal(edge, true);\r\n\t\tvar trg = this.getTerminal(edge, false);\r\n\t\tvar directedMatch = (src == source) && (trg == target);\r\n\t\tvar oppositeMatch = (trg == source) && (src == target);\r\n\r\n\t\tif (directedMatch || (!directed && oppositeMatch))\r\n\t\t{\r\n\t\t\tresult.push(edge);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: getOpposites\r\n * \r\n * Returns all opposite vertices wrt terminal for the given edges, only\r\n * returning sources and/or targets as specified. The result is returned\r\n * as an array of <mxCells>.\r\n * \r\n * Parameters:\r\n * \r\n * edges - Array of <mxCells> that contain the edges to be examined.\r\n * terminal - <mxCell> that specifies the known end of the edges.\r\n * sources - Boolean that specifies if source terminals should be contained\r\n * in the result. Default is true.\r\n * targets - Boolean that specifies if target terminals should be contained\r\n * in the result. Default is true.\r\n */\r\nmxGraphModel.prototype.getOpposites = function(edges, terminal, sources, targets)\r\n{\r\n\tsources = (sources != null) ? sources : true;\r\n\ttargets = (targets != null) ? targets : true;\r\n\t\r\n\tvar terminals = [];\r\n\t\r\n\tif (edges != null)\r\n\t{\r\n\t\tfor (var i = 0; i < edges.length; i++)\r\n\t\t{\r\n\t\t\tvar source = this.getTerminal(edges[i], true);\r\n\t\t\tvar target = this.getTerminal(edges[i], false);\r\n\t\t\t\r\n\t\t\t// Checks if the terminal is the source of\r\n\t\t\t// the edge and if the target should be\r\n\t\t\t// stored in the result\r\n\t\t\tif (source == terminal && target != null && target != terminal && targets)\r\n\t\t\t{\r\n\t\t\t\tterminals.push(target);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Checks if the terminal is the taget of\r\n\t\t\t// the edge and if the source should be\r\n\t\t\t// stored in the result\r\n\t\t\telse if (target == terminal && source != null && source != terminal && sources)\r\n\t\t\t{\r\n\t\t\t\tterminals.push(source);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn terminals;\r\n};\r\n\r\n/**\r\n * Function: getTopmostCells\r\n * \r\n * Returns the topmost cells of the hierarchy in an array that contains no\r\n * descendants for each <mxCell> that it contains. Duplicates should be\r\n * removed in the cells array to improve performance.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> whose topmost ancestors should be returned.\r\n */\r\nmxGraphModel.prototype.getTopmostCells = function(cells)\r\n{\r\n\tvar dict = new mxDictionary();\r\n\tvar tmp = [];\r\n\t\r\n\tfor (var i = 0; i < cells.length; i++)\r\n\t{\r\n\t\tdict.put(cells[i], true);\r\n\t}\r\n\t\r\n\tfor (var i = 0; i < cells.length; i++)\r\n\t{\r\n\t\tvar cell = cells[i];\r\n\t\tvar topmost = true;\r\n\t\tvar parent = this.getParent(cell);\r\n\t\t\r\n\t\twhile (parent != null)\r\n\t\t{\r\n\t\t\tif (dict.get(parent))\r\n\t\t\t{\r\n\t\t\t\ttopmost = false;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tparent = this.getParent(parent);\r\n\t\t}\r\n\t\t\r\n\t\tif (topmost)\r\n\t\t{\r\n\t\t\ttmp.push(cell);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn tmp;\r\n};\r\n\r\n/**\r\n * Function: isVertex\r\n * \r\n * Returns true if the given cell is a vertex.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> that represents the possible vertex.\r\n */\r\nmxGraphModel.prototype.isVertex = function(cell)\r\n{\r\n\treturn (cell != null) ? cell.isVertex() : false;\r\n};\r\n\r\n/**\r\n * Function: isEdge\r\n * \r\n * Returns true if the given cell is an edge.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> that represents the possible edge.\r\n */\r\nmxGraphModel.prototype.isEdge = function(cell)\r\n{\r\n\treturn (cell != null) ? cell.isEdge() : false;\r\n};\r\n\r\n/**\r\n * Function: isConnectable\r\n * \r\n * Returns true if the given <mxCell> is connectable. If <edgesConnectable>\r\n * is false, then this function returns false for all edges else it returns\r\n * the return value of <mxCell.isConnectable>.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose connectable state should be returned.\r\n */\r\nmxGraphModel.prototype.isConnectable = function(cell)\r\n{\r\n\treturn (cell != null) ? cell.isConnectable() : false;\r\n};\r\n\r\n/**\r\n * Function: getValue\r\n * \r\n * Returns the user object of the given <mxCell> using <mxCell.getValue>.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose user object should be returned.\r\n */\r\nmxGraphModel.prototype.getValue = function(cell)\r\n{\r\n\treturn (cell != null) ? cell.getValue() : null;\r\n};\r\n\r\n/**\r\n * Function: setValue\r\n * \r\n * Sets the user object of then given <mxCell> using <mxValueChange>\r\n * and adds the change to the current transaction.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose user object should be changed.\r\n * value - Object that defines the new user object.\r\n */\r\nmxGraphModel.prototype.setValue = function(cell, value)\r\n{\r\n\tthis.execute(new mxValueChange(this, cell, value));\r\n\t\r\n\treturn value;\r\n};\r\n\r\n/**\r\n * Function: valueForCellChanged\r\n * \r\n * Inner callback to update the user object of the given <mxCell>\r\n * using <mxCell.valueChanged> and return the previous value,\r\n * that is, the return value of <mxCell.valueChanged>.\r\n * \r\n * To change a specific attribute in an XML node, the following code can be\r\n * used.\r\n * \r\n * (code)\r\n * graph.getModel().valueForCellChanged = function(cell, value)\r\n * {\r\n *   var previous = cell.value.getAttribute('label');\r\n *   cell.value.setAttribute('label', value);\r\n *   \r\n *   return previous;\r\n * };\r\n * (end) \r\n */\r\nmxGraphModel.prototype.valueForCellChanged = function(cell, value)\r\n{\r\n\treturn cell.valueChanged(value);\r\n};\r\n\r\n/**\r\n * Function: getGeometry\r\n * \r\n * Returns the <mxGeometry> of the given <mxCell>.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose geometry should be returned.\r\n */\r\nmxGraphModel.prototype.getGeometry = function(cell)\r\n{\r\n\treturn (cell != null) ? cell.getGeometry() : null;\r\n};\r\n\r\n/**\r\n * Function: setGeometry\r\n * \r\n * Sets the <mxGeometry> of the given <mxCell>. The actual update\r\n * of the cell is carried out in <geometryForCellChanged>. The\r\n * <mxGeometryChange> action is used to encapsulate the change.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose geometry should be changed.\r\n * geometry - <mxGeometry> that defines the new geometry.\r\n */\r\nmxGraphModel.prototype.setGeometry = function(cell, geometry)\r\n{\r\n\tif (geometry != this.getGeometry(cell))\r\n\t{\r\n\t\tthis.execute(new mxGeometryChange(this, cell, geometry));\r\n\t}\r\n\t\r\n\treturn geometry;\r\n};\r\n\r\n/**\r\n * Function: geometryForCellChanged\r\n * \r\n * Inner callback to update the <mxGeometry> of the given <mxCell> using\r\n * <mxCell.setGeometry> and return the previous <mxGeometry>.\r\n */\r\nmxGraphModel.prototype.geometryForCellChanged = function(cell, geometry)\r\n{\r\n\tvar previous = this.getGeometry(cell);\r\n\tcell.setGeometry(geometry);\r\n\t\r\n\treturn previous;\r\n};\r\n\r\n/**\r\n * Function: getStyle\r\n * \r\n * Returns the style of the given <mxCell>.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose style should be returned.\r\n */\r\nmxGraphModel.prototype.getStyle = function(cell)\r\n{\r\n\treturn (cell != null) ? cell.getStyle() : null;\r\n};\r\n\r\n/**\r\n * Function: setStyle\r\n * \r\n * Sets the style of the given <mxCell> using <mxStyleChange> and\r\n * adds the change to the current transaction.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose style should be changed.\r\n * style - String of the form [stylename;|key=value;] to specify\r\n * the new cell style.\r\n */\r\nmxGraphModel.prototype.setStyle = function(cell, style)\r\n{\r\n\tif (style != this.getStyle(cell))\r\n\t{\r\n\t\tthis.execute(new mxStyleChange(this, cell, style));\r\n\t}\r\n\t\r\n\treturn style;\r\n};\r\n\r\n/**\r\n * Function: styleForCellChanged\r\n * \r\n * Inner callback to update the style of the given <mxCell>\r\n * using <mxCell.setStyle> and return the previous style.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> that specifies the cell to be updated.\r\n * style - String of the form [stylename;|key=value;] to specify\r\n * the new cell style.\r\n */\r\nmxGraphModel.prototype.styleForCellChanged = function(cell, style)\r\n{\r\n\tvar previous = this.getStyle(cell);\r\n\tcell.setStyle(style);\r\n\t\r\n\treturn previous;\r\n};\r\n\r\n/**\r\n * Function: isCollapsed\r\n * \r\n * Returns true if the given <mxCell> is collapsed.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose collapsed state should be returned.\r\n */\r\nmxGraphModel.prototype.isCollapsed = function(cell)\r\n{\r\n\treturn (cell != null) ? cell.isCollapsed() : false;\r\n};\r\n\r\n/**\r\n * Function: setCollapsed\r\n * \r\n * Sets the collapsed state of the given <mxCell> using <mxCollapseChange>\r\n * and adds the change to the current transaction.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose collapsed state should be changed.\r\n * collapsed - Boolean that specifies the new collpased state.\r\n */\r\nmxGraphModel.prototype.setCollapsed = function(cell, collapsed)\r\n{\r\n\tif (collapsed != this.isCollapsed(cell))\r\n\t{\r\n\t\tthis.execute(new mxCollapseChange(this, cell, collapsed));\r\n\t}\r\n\t\r\n\treturn collapsed;\r\n};\r\n\t\r\n/**\r\n * Function: collapsedStateForCellChanged\r\n *\r\n * Inner callback to update the collapsed state of the\r\n * given <mxCell> using <mxCell.setCollapsed> and return\r\n * the previous collapsed state.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> that specifies the cell to be updated.\r\n * collapsed - Boolean that specifies the new collpased state.\r\n */\r\nmxGraphModel.prototype.collapsedStateForCellChanged = function(cell, collapsed)\r\n{\r\n\tvar previous = this.isCollapsed(cell);\r\n\tcell.setCollapsed(collapsed);\r\n\t\r\n\treturn previous;\r\n};\r\n\r\n/**\r\n * Function: isVisible\r\n * \r\n * Returns true if the given <mxCell> is visible.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose visible state should be returned.\r\n */\r\nmxGraphModel.prototype.isVisible = function(cell)\r\n{\r\n\treturn (cell != null) ? cell.isVisible() : false;\r\n};\r\n\r\n/**\r\n * Function: setVisible\r\n * \r\n * Sets the visible state of the given <mxCell> using <mxVisibleChange> and\r\n * adds the change to the current transaction.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose visible state should be changed.\r\n * visible - Boolean that specifies the new visible state.\r\n */\r\nmxGraphModel.prototype.setVisible = function(cell, visible)\r\n{\r\n\tif (visible != this.isVisible(cell))\r\n\t{\r\n\t\tthis.execute(new mxVisibleChange(this, cell, visible));\r\n\t}\r\n\t\r\n\treturn visible;\r\n};\r\n\t\r\n/**\r\n * Function: visibleStateForCellChanged\r\n *\r\n * Inner callback to update the visible state of the\r\n * given <mxCell> using <mxCell.setCollapsed> and return\r\n * the previous visible state.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> that specifies the cell to be updated.\r\n * visible - Boolean that specifies the new visible state.\r\n */\r\nmxGraphModel.prototype.visibleStateForCellChanged = function(cell, visible)\r\n{\r\n\tvar previous = this.isVisible(cell);\r\n\tcell.setVisible(visible);\r\n\t\r\n\treturn previous;\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Executes the given edit and fires events if required. The edit object\r\n * requires an execute function which is invoked. The edit is added to the\r\n * <currentEdit> between <beginUpdate> and <endUpdate> calls, so that\r\n * events will be fired if this execute is an individual transaction, that\r\n * is, if no previous <beginUpdate> calls have been made without calling\r\n * <endUpdate>. This implementation fires an <execute> event before\r\n * executing the given change.\r\n * \r\n * Parameters:\r\n * \r\n * change - Object that described the change.\r\n */\r\nmxGraphModel.prototype.execute = function(change)\r\n{\r\n\tchange.execute();\r\n\tthis.beginUpdate();\r\n\tthis.currentEdit.add(change);\r\n\tthis.fireEvent(new mxEventObject(mxEvent.EXECUTE, 'change', change));\r\n\t// New global executed event\r\n\tthis.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change));\r\n\tthis.endUpdate();\r\n};\r\n\r\n/**\r\n * Function: beginUpdate\r\n * \r\n * Increments the <updateLevel> by one. The event notification\r\n * is queued until <updateLevel> reaches 0 by use of\r\n * <endUpdate>.\r\n *\r\n * All changes on <mxGraphModel> are transactional,\r\n * that is, they are executed in a single undoable change\r\n * on the model (without transaction isolation).\r\n * Therefore, if you want to combine any\r\n * number of changes into a single undoable change,\r\n * you should group any two or more API calls that\r\n * modify the graph model between <beginUpdate>\r\n * and <endUpdate> calls as shown here:\r\n * \r\n * (code)\r\n * var model = graph.getModel();\r\n * var parent = graph.getDefaultParent();\r\n * var index = model.getChildCount(parent);\r\n * model.beginUpdate();\r\n * try\r\n * {\r\n *   model.add(parent, v1, index);\r\n *   model.add(parent, v2, index+1);\r\n * }\r\n * finally\r\n * {\r\n *   model.endUpdate();\r\n * }\r\n * (end)\r\n * \r\n * Of course there is a shortcut for appending a\r\n * sequence of cells into the default parent:\r\n * \r\n * (code)\r\n * graph.addCells([v1, v2]).\r\n * (end)\r\n */\r\nmxGraphModel.prototype.beginUpdate = function()\r\n{\r\n\tthis.updateLevel++;\r\n\tthis.fireEvent(new mxEventObject(mxEvent.BEGIN_UPDATE));\r\n\t\r\n\tif (this.updateLevel == 1)\r\n\t{\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.START_EDIT));\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: endUpdate\r\n * \r\n * Decrements the <updateLevel> by one and fires an <undo>\r\n * event if the <updateLevel> reaches 0. This function\r\n * indirectly fires a <change> event by invoking the notify\r\n * function on the <currentEdit> und then creates a new\r\n * <currentEdit> using <createUndoableEdit>.\r\n *\r\n * The <undo> event is fired only once per edit, whereas\r\n * the <change> event is fired whenever the notify\r\n * function is invoked, that is, on undo and redo of\r\n * the edit.\r\n */\r\nmxGraphModel.prototype.endUpdate = function()\r\n{\r\n\tthis.updateLevel--;\r\n\t\r\n\tif (this.updateLevel == 0)\r\n\t{\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.END_EDIT));\r\n\t}\r\n\t\r\n\tif (!this.endingUpdate)\r\n\t{\r\n\t\tthis.endingUpdate = this.updateLevel == 0;\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.END_UPDATE, 'edit', this.currentEdit));\r\n\r\n\t\ttry\r\n\t\t{\t\t\r\n\t\t\tif (this.endingUpdate && !this.currentEdit.isEmpty())\r\n\t\t\t{\r\n\t\t\t\tthis.fireEvent(new mxEventObject(mxEvent.BEFORE_UNDO, 'edit', this.currentEdit));\r\n\t\t\t\tvar tmp = this.currentEdit;\r\n\t\t\t\tthis.currentEdit = this.createUndoableEdit();\r\n\t\t\t\ttmp.notify();\r\n\t\t\t\tthis.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', tmp));\r\n\t\t\t}\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tthis.endingUpdate = false;\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createUndoableEdit\r\n * \r\n * Creates a new <mxUndoableEdit> that implements the\r\n * notify function to fire a <change> and <notify> event\r\n * through the <mxUndoableEdit>'s source.\r\n * \r\n * Parameters:\r\n * \r\n * significant - Optional boolean that specifies if the edit to be created is\r\n * significant. Default is true.\r\n */\r\nmxGraphModel.prototype.createUndoableEdit = function(significant)\r\n{\r\n\tvar edit = new mxUndoableEdit(this, (significant != null) ? significant : true);\r\n\t\r\n\tedit.notify = function()\r\n\t{\r\n\t\t// LATER: Remove changes property (deprecated)\r\n\t\tedit.source.fireEvent(new mxEventObject(mxEvent.CHANGE,\r\n\t\t\t'edit', edit, 'changes', edit.changes));\r\n\t\tedit.source.fireEvent(new mxEventObject(mxEvent.NOTIFY,\r\n\t\t\t'edit', edit, 'changes', edit.changes));\r\n\t};\r\n\t\r\n\treturn edit;\r\n};\r\n\r\n/**\r\n * Function: mergeChildren\r\n * \r\n * Merges the children of the given cell into the given target cell inside\r\n * this model. All cells are cloned unless there is a corresponding cell in\r\n * the model with the same id, in which case the source cell is ignored and\r\n * all edges are connected to the corresponding cell in this model. Edges\r\n * are considered to have no identity and are always cloned unless the\r\n * cloneAllEdges flag is set to false, in which case edges with the same\r\n * id in the target model are reconnected to reflect the terminals of the\r\n * source edges.\r\n */\r\nmxGraphModel.prototype.mergeChildren = function(from, to, cloneAllEdges)\r\n{\r\n\tcloneAllEdges = (cloneAllEdges != null) ? cloneAllEdges : true;\r\n\t\r\n\tthis.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tvar mapping = new Object();\r\n\t\tthis.mergeChildrenImpl(from, to, cloneAllEdges, mapping);\r\n\t\t\r\n\t\t// Post-processes all edges in the mapping and\r\n\t\t// reconnects the terminals to the corresponding\r\n\t\t// cells in the target model\r\n\t\tfor (var key in mapping)\r\n\t\t{\r\n\t\t\tvar cell = mapping[key];\r\n\t\t\tvar terminal = this.getTerminal(cell, true);\r\n\r\n\t\t\tif (terminal != null)\r\n\t\t\t{\r\n\t\t\t\tterminal = mapping[mxCellPath.create(terminal)];\r\n\t\t\t\tthis.setTerminal(cell, terminal, true);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tterminal = this.getTerminal(cell, false);\r\n\t\t\t\r\n\t\t\tif (terminal != null)\r\n\t\t\t{\r\n\t\t\t\tterminal = mapping[mxCellPath.create(terminal)];\r\n\t\t\t\tthis.setTerminal(cell, terminal, false);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tthis.endUpdate();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: mergeChildren\r\n * \r\n * Clones the children of the source cell into the given target cell in\r\n * this model and adds an entry to the mapping that maps from the source\r\n * cell to the target cell with the same id or the clone of the source cell\r\n * that was inserted into this model.\r\n */\r\nmxGraphModel.prototype.mergeChildrenImpl = function(from, to, cloneAllEdges, mapping)\r\n{\r\n\tthis.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tvar childCount = from.getChildCount();\r\n\t\t\r\n\t\tfor (var i = 0; i < childCount; i++)\r\n\t\t{\r\n\t\t\tvar cell = from.getChildAt(i);\r\n\t\t\t\r\n\t\t\tif (typeof(cell.getId) == 'function')\r\n\t\t\t{\r\n\t\t\t\tvar id = cell.getId();\r\n\t\t\t\tvar target = (id != null && (!this.isEdge(cell) || !cloneAllEdges)) ?\r\n\t\t\t\t\t\tthis.getCell(id) : null;\r\n\t\t\t\t\r\n\t\t\t\t// Clones and adds the child if no cell exists for the id\r\n\t\t\t\tif (target == null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar clone = cell.clone();\r\n\t\t\t\t\tclone.setId(id);\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Sets the terminals from the original cell to the clone\r\n\t\t\t\t\t// because the lookup uses strings not cells in JS\r\n\t\t\t\t\tclone.setTerminal(cell.getTerminal(true), true);\r\n\t\t\t\t\tclone.setTerminal(cell.getTerminal(false), false);\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Do *NOT* use model.add as this will move the edge away\r\n\t\t\t\t\t// from the parent in updateEdgeParent if maintainEdgeParent\r\n\t\t\t\t\t// is enabled in the target model\r\n\t\t\t\t\ttarget = to.insert(clone);\r\n\t\t\t\t\tthis.cellAdded(target);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Stores the mapping for later reconnecting edges\r\n\t\t\t\tmapping[mxCellPath.create(cell)] = target;\r\n\t\t\t\t\r\n\t\t\t\t// Recurses\r\n\t\t\t\tthis.mergeChildrenImpl(cell, target, cloneAllEdges, mapping);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tthis.endUpdate();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getParents\r\n * \r\n * Returns an array that represents the set (no duplicates) of all parents\r\n * for the given array of cells.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of cells whose parents should be returned.\r\n */\r\nmxGraphModel.prototype.getParents = function(cells)\r\n{\r\n\tvar parents = [];\r\n\t\r\n\tif (cells != null)\r\n\t{\r\n\t\tvar dict = new mxDictionary();\r\n\t\t\r\n\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t{\r\n\t\t\tvar parent = this.getParent(cells[i]);\r\n\t\t\t\r\n\t\t\tif (parent != null && !dict.get(parent))\r\n\t\t\t{\r\n\t\t\t\tdict.put(parent, true);\r\n\t\t\t\tparents.push(parent);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn parents;\r\n};\r\n\r\n//\r\n// Cell Cloning\r\n//\r\n\r\n/**\r\n * Function: cloneCell\r\n * \r\n * Returns a deep clone of the given <mxCell> (including\r\n * the children) which is created using <cloneCells>.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> to be cloned.\r\n */\r\nmxGraphModel.prototype.cloneCell = function(cell)\r\n{\r\n\tif (cell != null)\r\n\t{\r\n\t\treturn this.cloneCells([cell], true)[0];\r\n\t}\r\n\t\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: cloneCells\r\n * \r\n * Returns an array of clones for the given array of <mxCells>.\r\n * Depending on the value of includeChildren, a deep clone is created for\r\n * each cell. Connections are restored based if the corresponding\r\n * cell is contained in the passed in array.\r\n *\r\n * Parameters:\r\n * \r\n * cells - Array of <mxCell> to be cloned.\r\n * includeChildren - Boolean indicating if the cells should be cloned\r\n * with all descendants.\r\n * mapping - Optional mapping for existing clones.\r\n */\r\nmxGraphModel.prototype.cloneCells = function(cells, includeChildren, mapping)\r\n{\r\n\tmapping = (mapping != null) ? mapping : new Object();\r\n\tvar clones = [];\r\n\t\r\n\tfor (var i = 0; i < cells.length; i++)\r\n\t{\r\n\t\tif (cells[i] != null)\r\n\t\t{\r\n\t\t\tclones.push(this.cloneCellImpl(cells[i], mapping, includeChildren));\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tclones.push(null);\r\n\t\t}\r\n\t}\r\n\t\r\n\tfor (var i = 0; i < clones.length; i++)\r\n\t{\r\n\t\tif (clones[i] != null)\r\n\t\t{\r\n\t\t\tthis.restoreClone(clones[i], cells[i], mapping);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn clones;\r\n};\r\n\t\t\t\r\n/**\r\n * Function: cloneCellImpl\r\n * \r\n * Inner helper method for cloning cells recursively.\r\n */\r\nmxGraphModel.prototype.cloneCellImpl = function(cell, mapping, includeChildren)\r\n{\r\n\tvar ident = mxObjectIdentity.get(cell);\r\n\tvar clone = mapping[ident];\r\n\t\r\n\tif (clone == null)\r\n\t{\r\n\t\tclone = this.cellCloned(cell);\r\n\t\tmapping[ident] = clone;\r\n\r\n\t\tif (includeChildren)\r\n\t\t{\r\n\t\t\tvar childCount = this.getChildCount(cell);\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < childCount; i++)\r\n\t\t\t{\r\n\t\t\t\tvar cloneChild = this.cloneCellImpl(\r\n\t\t\t\t\tthis.getChildAt(cell, i), mapping, true);\r\n\t\t\t\tclone.insert(cloneChild);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn clone;\r\n};\r\n\r\n/**\r\n * Function: cellCloned\r\n * \r\n * Hook for cloning the cell. This returns cell.clone() or\r\n * any possible exceptions.\r\n */\r\nmxGraphModel.prototype.cellCloned = function(cell)\r\n{\r\n\treturn cell.clone();\r\n};\r\n\r\n/**\r\n * Function: restoreClone\r\n * \r\n * Inner helper method for restoring the connections in\r\n * a network of cloned cells.\r\n */\r\nmxGraphModel.prototype.restoreClone = function(clone, cell, mapping)\r\n{\r\n\tvar source = this.getTerminal(cell, true);\r\n\t\r\n\tif (source != null)\r\n\t{\r\n\t\tvar tmp = mapping[mxObjectIdentity.get(source)];\r\n\t\t\r\n\t\tif (tmp != null)\r\n\t\t{\r\n\t\t\ttmp.insertEdge(clone, true);\r\n\t\t}\r\n\t}\r\n\t\r\n\tvar target = this.getTerminal(cell, false);\r\n\t\r\n\tif (target != null)\r\n\t{\r\n\t\tvar tmp = mapping[mxObjectIdentity.get(target)];\r\n\t\t\r\n\t\tif (tmp != null)\r\n\t\t{\t\r\n\t\t\ttmp.insertEdge(clone, false);\r\n\t\t}\r\n\t}\r\n\t\r\n\tvar childCount = this.getChildCount(clone);\r\n\t\r\n\tfor (var i = 0; i < childCount; i++)\r\n\t{\r\n\t\tthis.restoreClone(this.getChildAt(clone, i),\r\n\t\t\tthis.getChildAt(cell, i), mapping);\r\n\t}\r\n};\r\n\r\n//\r\n// Atomic changes\r\n//\r\n\r\n/**\r\n * Class: mxRootChange\r\n * \r\n * Action to change the root in a model.\r\n *\r\n * Constructor: mxRootChange\r\n * \r\n * Constructs a change of the root in the\r\n * specified model.\r\n */\r\nfunction mxRootChange(model, root)\r\n{\r\n\tthis.model = model;\r\n\tthis.root = root;\r\n\tthis.previous = root;\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Carries out a change of the root using\r\n * <mxGraphModel.rootChanged>.\r\n */\r\nmxRootChange.prototype.execute = function()\r\n{\r\n\tthis.root = this.previous;\r\n\tthis.previous = this.model.rootChanged(this.previous);\r\n};\r\n\r\n/**\r\n * Class: mxChildChange\r\n * \r\n * Action to add or remove a child in a model.\r\n *\r\n * Constructor: mxChildChange\r\n * \r\n * Constructs a change of a child in the\r\n * specified model.\r\n */\r\nfunction mxChildChange(model, parent, child, index)\r\n{\r\n\tthis.model = model;\r\n\tthis.parent = parent;\r\n\tthis.previous = parent;\r\n\tthis.child = child;\r\n\tthis.index = index;\r\n\tthis.previousIndex = index;\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Changes the parent of <child> using\r\n * <mxGraphModel.parentForCellChanged> and\r\n * removes or restores the cell's\r\n * connections.\r\n */\r\nmxChildChange.prototype.execute = function()\r\n{\r\n\tif (this.child != null)\r\n\t{\r\n\t\tvar tmp = this.model.getParent(this.child);\r\n\t\tvar tmp2 = (tmp != null) ? tmp.getIndex(this.child) : 0;\r\n\t\t\r\n\t\tif (this.previous == null)\r\n\t\t{\r\n\t\t\tthis.connect(this.child, false);\r\n\t\t}\r\n\t\t\r\n\t\ttmp = this.model.parentForCellChanged(\r\n\t\t\tthis.child, this.previous, this.previousIndex);\r\n\t\t\t\r\n\t\tif (this.previous != null)\r\n\t\t{\r\n\t\t\tthis.connect(this.child, true);\r\n\t\t}\r\n\t\t\r\n\t\tthis.parent = this.previous;\r\n\t\tthis.previous = tmp;\r\n\t\tthis.index = this.previousIndex;\r\n\t\tthis.previousIndex = tmp2;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: disconnect\r\n * \r\n * Disconnects the given cell recursively from its\r\n * terminals and stores the previous terminal in the\r\n * cell's terminals.\r\n */\r\nmxChildChange.prototype.connect = function(cell, isConnect)\r\n{\r\n\tisConnect = (isConnect != null) ? isConnect : true;\r\n\t\r\n\tvar source = cell.getTerminal(true);\r\n\tvar target = cell.getTerminal(false);\r\n\t\r\n\tif (source != null)\r\n\t{\r\n\t\tif (isConnect)\r\n\t\t{\r\n\t\t\tthis.model.terminalForCellChanged(cell, source, true);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.model.terminalForCellChanged(cell, null, true);\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (target != null)\r\n\t{\r\n\t\tif (isConnect)\r\n\t\t{\r\n\t\t\tthis.model.terminalForCellChanged(cell, target, false);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.model.terminalForCellChanged(cell, null, false);\r\n\t\t}\r\n\t}\r\n\t\r\n\tcell.setTerminal(source, true);\r\n\tcell.setTerminal(target, false);\r\n\t\r\n\tvar childCount = this.model.getChildCount(cell);\r\n\t\r\n\tfor (var i=0; i<childCount; i++)\r\n\t{\r\n\t\tthis.connect(this.model.getChildAt(cell, i), isConnect);\r\n\t}\r\n};\r\n\r\n/**\r\n * Class: mxTerminalChange\r\n * \r\n * Action to change a terminal in a model.\r\n *\r\n * Constructor: mxTerminalChange\r\n * \r\n * Constructs a change of a terminal in the \r\n * specified model.\r\n */\r\nfunction mxTerminalChange(model, cell, terminal, source)\r\n{\r\n\tthis.model = model;\r\n\tthis.cell = cell;\r\n\tthis.terminal = terminal;\r\n\tthis.previous = terminal;\r\n\tthis.source = source;\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Changes the terminal of <cell> to <previous> using\r\n * <mxGraphModel.terminalForCellChanged>.\r\n */\r\nmxTerminalChange.prototype.execute = function()\r\n{\r\n\tif (this.cell != null)\r\n\t{\r\n\t\tthis.terminal = this.previous;\r\n\t\tthis.previous = this.model.terminalForCellChanged(\r\n\t\t\tthis.cell, this.previous, this.source);\r\n\t}\r\n};\r\n\r\n/**\r\n * Class: mxValueChange\r\n * \r\n * Action to change a user object in a model.\r\n *\r\n * Constructor: mxValueChange\r\n * \r\n * Constructs a change of a user object in the \r\n * specified model.\r\n */\r\nfunction mxValueChange(model, cell, value)\r\n{\r\n\tthis.model = model;\r\n\tthis.cell = cell;\r\n\tthis.value = value;\r\n\tthis.previous = value;\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Changes the value of <cell> to <previous> using\r\n * <mxGraphModel.valueForCellChanged>.\r\n */\r\nmxValueChange.prototype.execute = function()\r\n{\r\n\tif (this.cell != null)\r\n\t{\r\n\t\tthis.value = this.previous;\r\n\t\tthis.previous = this.model.valueForCellChanged(\r\n\t\t\tthis.cell, this.previous);\r\n\t}\r\n};\r\n\r\n/**\r\n * Class: mxStyleChange\r\n * \r\n * Action to change a cell's style in a model.\r\n *\r\n * Constructor: mxStyleChange\r\n * \r\n * Constructs a change of a style in the\r\n * specified model.\r\n */\r\nfunction mxStyleChange(model, cell, style)\r\n{\r\n\tthis.model = model;\r\n\tthis.cell = cell;\r\n\tthis.style = style;\r\n\tthis.previous = style;\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Changes the style of <cell> to <previous> using\r\n * <mxGraphModel.styleForCellChanged>.\r\n */\r\nmxStyleChange.prototype.execute = function()\r\n{\r\n\tif (this.cell != null)\r\n\t{\r\n\t\tthis.style = this.previous;\r\n\t\tthis.previous = this.model.styleForCellChanged(\r\n\t\t\tthis.cell, this.previous);\r\n\t}\r\n};\r\n\r\n/**\r\n * Class: mxGeometryChange\r\n * \r\n * Action to change a cell's geometry in a model.\r\n *\r\n * Constructor: mxGeometryChange\r\n * \r\n * Constructs a change of a geometry in the\r\n * specified model.\r\n */\r\nfunction mxGeometryChange(model, cell, geometry)\r\n{\r\n\tthis.model = model;\r\n\tthis.cell = cell;\r\n\tthis.geometry = geometry;\r\n\tthis.previous = geometry;\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Changes the geometry of <cell> ro <previous> using\r\n * <mxGraphModel.geometryForCellChanged>.\r\n */\r\nmxGeometryChange.prototype.execute = function()\r\n{\r\n\tif (this.cell != null)\r\n\t{\r\n\t\tthis.geometry = this.previous;\r\n\t\tthis.previous = this.model.geometryForCellChanged(\r\n\t\t\tthis.cell, this.previous);\r\n\t}\r\n};\r\n\r\n/**\r\n * Class: mxCollapseChange\r\n * \r\n * Action to change a cell's collapsed state in a model.\r\n *\r\n * Constructor: mxCollapseChange\r\n * \r\n * Constructs a change of a collapsed state in the\r\n * specified model.\r\n */\r\nfunction mxCollapseChange(model, cell, collapsed)\r\n{\r\n\tthis.model = model;\r\n\tthis.cell = cell;\r\n\tthis.collapsed = collapsed;\r\n\tthis.previous = collapsed;\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Changes the collapsed state of <cell> to <previous> using\r\n * <mxGraphModel.collapsedStateForCellChanged>.\r\n */\r\nmxCollapseChange.prototype.execute = function()\r\n{\r\n\tif (this.cell != null)\r\n\t{\r\n\t\tthis.collapsed = this.previous;\r\n\t\tthis.previous = this.model.collapsedStateForCellChanged(\r\n\t\t\tthis.cell, this.previous);\r\n\t}\r\n};\r\n\r\n/**\r\n * Class: mxVisibleChange\r\n * \r\n * Action to change a cell's visible state in a model.\r\n *\r\n * Constructor: mxVisibleChange\r\n * \r\n * Constructs a change of a visible state in the\r\n * specified model.\r\n */\r\nfunction mxVisibleChange(model, cell, visible)\r\n{\r\n\tthis.model = model;\r\n\tthis.cell = cell;\r\n\tthis.visible = visible;\r\n\tthis.previous = visible;\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Changes the visible state of <cell> to <previous> using\r\n * <mxGraphModel.visibleStateForCellChanged>.\r\n */\r\nmxVisibleChange.prototype.execute = function()\r\n{\r\n\tif (this.cell != null)\r\n\t{\r\n\t\tthis.visible = this.previous;\r\n\t\tthis.previous = this.model.visibleStateForCellChanged(\r\n\t\t\tthis.cell, this.previous);\r\n\t}\r\n};\r\n\r\n/**\r\n * Class: mxCellAttributeChange\r\n * \r\n * Action to change the attribute of a cell's user object.\r\n * There is no method on the graph model that uses this\r\n * action. To use the action, you can use the code shown\r\n * in the example below.\r\n * \r\n * Example:\r\n * \r\n * To change the attributeName in the cell's user object\r\n * to attributeValue, use the following code:\r\n * \r\n * (code)\r\n * model.beginUpdate();\r\n * try\r\n * {\r\n *   var edit = new mxCellAttributeChange(\r\n *     cell, attributeName, attributeValue);\r\n *   model.execute(edit);\r\n * }\r\n * finally\r\n * {\r\n *   model.endUpdate();\r\n * } \r\n * (end)\r\n *\r\n * Constructor: mxCellAttributeChange\r\n * \r\n * Constructs a change of a attribute of the DOM node\r\n * stored as the value of the given <mxCell>.\r\n */\r\nfunction mxCellAttributeChange(cell, attribute, value)\r\n{\r\n\tthis.cell = cell;\r\n\tthis.attribute = attribute;\r\n\tthis.value = value;\r\n\tthis.previous = value;\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Changes the attribute of the cell's user object by\r\n * using <mxCell.setAttribute>.\r\n */\r\nmxCellAttributeChange.prototype.execute = function()\r\n{\r\n\tif (this.cell != null)\r\n\t{\r\n\t\tvar tmp = this.cell.getAttribute(this.attribute);\r\n\t\t\r\n\t\tif (this.previous == null)\r\n\t\t{\r\n\t\t\tthis.cell.value.removeAttribute(this.attribute);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.cell.setAttribute(this.attribute, this.previous);\r\n\t\t}\r\n\t\t\r\n\t\tthis.previous = tmp;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxCell\r\n *\r\n * Cells are the elements of the graph model. They represent the state\r\n * of the groups, vertices and edges in a graph.\r\n * \r\n * Custom attributes:\r\n * \r\n * For custom attributes we recommend using an XML node as the value of a cell.\r\n * The following code can be used to create a cell with an XML node as the\r\n * value:\r\n * \r\n * (code)\r\n * var doc = mxUtils.createXmlDocument();\r\n * var node = doc.createElement('MyNode')\r\n * node.setAttribute('label', 'MyLabel');\r\n * node.setAttribute('attribute1', 'value1');\r\n * graph.insertVertex(graph.getDefaultParent(), null, node, 40, 40, 80, 30);\r\n * (end)\r\n * \r\n * For the label to work, <mxGraph.convertValueToString> and\r\n * <mxGraph.cellLabelChanged> should be overridden as follows:\r\n * \r\n * (code)\r\n * graph.convertValueToString = function(cell)\r\n * {\r\n *   if (mxUtils.isNode(cell.value))\r\n *   {\r\n *     return cell.getAttribute('label', '')\r\n *   }\r\n * };\r\n * \r\n * var cellLabelChanged = graph.cellLabelChanged;\r\n * graph.cellLabelChanged = function(cell, newValue, autoSize)\r\n * {\r\n *   if (mxUtils.isNode(cell.value))\r\n *   {\r\n *     // Clones the value for correct undo/redo\r\n *     var elt = cell.value.cloneNode(true);\r\n *     elt.setAttribute('label', newValue);\r\n *     newValue = elt;\r\n *   }\r\n *   \r\n *   cellLabelChanged.apply(this, arguments);\r\n * };\r\n * (end)\r\n * \r\n * Callback: onInit\r\n *\r\n * Called from within the constructor.\r\n * \r\n * Constructor: mxCell\r\n *\r\n * Constructs a new cell to be used in a graph model.\r\n * This method invokes <onInit> upon completion.\r\n * \r\n * Parameters:\r\n * \r\n * value - Optional object that represents the cell value.\r\n * geometry - Optional <mxGeometry> that specifies the geometry.\r\n * style - Optional formatted string that defines the style.\r\n */\r\nfunction mxCell(value, geometry, style)\r\n{\r\n\tthis.value = value;\r\n\tthis.setGeometry(geometry);\r\n\tthis.setStyle(style);\r\n\t\r\n\tif (this.onInit != null)\r\n\t{\r\n\t\tthis.onInit();\r\n\t}\r\n};\r\n\r\n/**\r\n * Variable: id\r\n *\r\n * Holds the Id. Default is null.\r\n */\r\nmxCell.prototype.id = null;\r\n\r\n/**\r\n * Variable: value\r\n *\r\n * Holds the user object. Default is null.\r\n */\r\nmxCell.prototype.value = null;\r\n\r\n/**\r\n * Variable: geometry\r\n *\r\n * Holds the <mxGeometry>. Default is null.\r\n */\r\nmxCell.prototype.geometry = null;\r\n\r\n/**\r\n * Variable: style\r\n *\r\n * Holds the style as a string of the form [(stylename|key=value);]. Default is\r\n * null.\r\n */\r\nmxCell.prototype.style = null;\r\n\r\n/**\r\n * Variable: vertex\r\n *\r\n * Specifies whether the cell is a vertex. Default is false.\r\n */\r\nmxCell.prototype.vertex = false;\r\n\r\n/**\r\n * Variable: edge\r\n *\r\n * Specifies whether the cell is an edge. Default is false.\r\n */\r\nmxCell.prototype.edge = false;\r\n\r\n/**\r\n * Variable: connectable\r\n *\r\n * Specifies whether the cell is connectable. Default is true.\r\n */\r\nmxCell.prototype.connectable = true;\r\n\r\n/**\r\n * Variable: visible\r\n *\r\n * Specifies whether the cell is visible. Default is true.\r\n */\r\nmxCell.prototype.visible = true;\r\n\r\n/**\r\n * Variable: collapsed\r\n *\r\n * Specifies whether the cell is collapsed. Default is false.\r\n */\r\nmxCell.prototype.collapsed = false;\r\n\r\n/**\r\n * Variable: parent\r\n *\r\n * Reference to the parent cell.\r\n */\r\nmxCell.prototype.parent = null;\r\n\r\n/**\r\n * Variable: source\r\n *\r\n * Reference to the source terminal.\r\n */\r\nmxCell.prototype.source = null;\r\n\r\n/**\r\n * Variable: target\r\n *\r\n * Reference to the target terminal.\r\n */\r\nmxCell.prototype.target = null;\r\n\r\n/**\r\n * Variable: children\r\n *\r\n * Holds the child cells.\r\n */\r\nmxCell.prototype.children = null;\r\n\r\n/**\r\n * Variable: edges\r\n *\r\n * Holds the edges.\r\n */\r\nmxCell.prototype.edges = null;\r\n\r\n/**\r\n * Variable: mxTransient\r\n *\r\n * List of members that should not be cloned inside <clone>. This field is\r\n * passed to <mxUtils.clone> and is not made persistent in <mxCellCodec>.\r\n * This is not a convention for all classes, it is only used in this class\r\n * to mark transient fields since transient modifiers are not supported by\r\n * the language.\r\n */\r\nmxCell.prototype.mxTransient = ['id', 'value', 'parent', 'source',\r\n                                'target', 'children', 'edges'];\r\n\r\n/**\r\n * Function: getId\r\n *\r\n * Returns the Id of the cell as a string.\r\n */\r\nmxCell.prototype.getId = function()\r\n{\r\n\treturn this.id;\r\n};\r\n\t\t\r\n/**\r\n * Function: setId\r\n *\r\n * Sets the Id of the cell to the given string.\r\n */\r\nmxCell.prototype.setId = function(id)\r\n{\r\n\tthis.id = id;\r\n};\r\n\r\n/**\r\n * Function: getValue\r\n *\r\n * Returns the user object of the cell. The user\r\n * object is stored in <value>.\r\n */\r\nmxCell.prototype.getValue = function()\r\n{\r\n\treturn this.value;\r\n};\r\n\t\t\r\n/**\r\n * Function: setValue\r\n *\r\n * Sets the user object of the cell. The user object\r\n * is stored in <value>.\r\n */\r\nmxCell.prototype.setValue = function(value)\r\n{\r\n\tthis.value = value;\r\n};\r\n\r\n/**\r\n * Function: valueChanged\r\n *\r\n * Changes the user object after an in-place edit\r\n * and returns the previous value. This implementation\r\n * replaces the user object with the given value and\r\n * returns the old user object.\r\n */\r\nmxCell.prototype.valueChanged = function(newValue)\r\n{\r\n\tvar previous = this.getValue();\r\n\tthis.setValue(newValue);\r\n\t\r\n\treturn previous;\r\n};\r\n\r\n/**\r\n * Function: getGeometry\r\n *\r\n * Returns the <mxGeometry> that describes the <geometry>.\r\n */\r\nmxCell.prototype.getGeometry = function()\r\n{\r\n\treturn this.geometry;\r\n};\r\n\r\n/**\r\n * Function: setGeometry\r\n *\r\n * Sets the <mxGeometry> to be used as the <geometry>.\r\n */\r\nmxCell.prototype.setGeometry = function(geometry)\r\n{\r\n\tthis.geometry = geometry;\r\n};\r\n\r\n/**\r\n * Function: getStyle\r\n *\r\n * Returns a string that describes the <style>.\r\n */\r\nmxCell.prototype.getStyle = function()\r\n{\r\n\treturn this.style;\r\n};\r\n\r\n/**\r\n * Function: setStyle\r\n *\r\n * Sets the string to be used as the <style>.\r\n */\r\nmxCell.prototype.setStyle = function(style)\r\n{\r\n\tthis.style = style;\r\n};\r\n\r\n/**\r\n * Function: isVertex\r\n *\r\n * Returns true if the cell is a vertex.\r\n */\r\nmxCell.prototype.isVertex = function()\r\n{\r\n\treturn this.vertex != 0;\r\n};\r\n\r\n/**\r\n * Function: setVertex\r\n *\r\n * Specifies if the cell is a vertex. This should only be assigned at\r\n * construction of the cell and not be changed during its lifecycle.\r\n * \r\n * Parameters:\r\n * \r\n * vertex - Boolean that specifies if the cell is a vertex.\r\n */\r\nmxCell.prototype.setVertex = function(vertex)\r\n{\r\n\tthis.vertex = vertex;\r\n};\r\n\r\n/**\r\n * Function: isEdge\r\n *\r\n * Returns true if the cell is an edge.\r\n */\r\nmxCell.prototype.isEdge = function()\r\n{\r\n\treturn this.edge != 0;\r\n};\r\n\t\r\n/**\r\n * Function: setEdge\r\n * \r\n * Specifies if the cell is an edge. This should only be assigned at\r\n * construction of the cell and not be changed during its lifecycle.\r\n * \r\n * Parameters:\r\n * \r\n * edge - Boolean that specifies if the cell is an edge.\r\n */\r\nmxCell.prototype.setEdge = function(edge)\r\n{\r\n\tthis.edge = edge;\r\n};\r\n\r\n/**\r\n * Function: isConnectable\r\n *\r\n * Returns true if the cell is connectable.\r\n */\r\nmxCell.prototype.isConnectable = function()\r\n{\r\n\treturn this.connectable != 0;\r\n};\r\n\r\n/**\r\n * Function: setConnectable\r\n *\r\n * Sets the connectable state.\r\n * \r\n * Parameters:\r\n * \r\n * connectable - Boolean that specifies the new connectable state.\r\n */\r\nmxCell.prototype.setConnectable = function(connectable)\r\n{\r\n\tthis.connectable = connectable;\r\n};\r\n\r\n/**\r\n * Function: isVisible\r\n *\r\n * Returns true if the cell is visibile.\r\n */\r\nmxCell.prototype.isVisible = function()\r\n{\r\n\treturn this.visible != 0;\r\n};\r\n\r\n/**\r\n * Function: setVisible\r\n *\r\n * Specifies if the cell is visible.\r\n * \r\n * Parameters:\r\n * \r\n * visible - Boolean that specifies the new visible state.\r\n */\r\nmxCell.prototype.setVisible = function(visible)\r\n{\r\n\tthis.visible = visible;\r\n};\r\n\r\n/**\r\n * Function: isCollapsed\r\n *\r\n * Returns true if the cell is collapsed.\r\n */\r\nmxCell.prototype.isCollapsed = function()\r\n{\r\n\treturn this.collapsed != 0;\r\n};\r\n\r\n/**\r\n * Function: setCollapsed\r\n *\r\n * Sets the collapsed state.\r\n * \r\n * Parameters:\r\n * \r\n * collapsed - Boolean that specifies the new collapsed state.\r\n */\r\nmxCell.prototype.setCollapsed = function(collapsed)\r\n{\r\n\tthis.collapsed = collapsed;\r\n};\r\n\r\n/**\r\n * Function: getParent\r\n *\r\n * Returns the cell's parent.\r\n */\r\nmxCell.prototype.getParent = function()\r\n{\r\n\treturn this.parent;\r\n};\r\n\r\n/**\r\n * Function: setParent\r\n *\r\n * Sets the parent cell.\r\n * \r\n * Parameters:\r\n * \r\n * parent - <mxCell> that represents the new parent.\r\n */\r\nmxCell.prototype.setParent = function(parent)\r\n{\r\n\tthis.parent = parent;\r\n};\r\n\r\n/**\r\n * Function: getTerminal\r\n *\r\n * Returns the source or target terminal.\r\n * \r\n * Parameters:\r\n * \r\n * source - Boolean that specifies if the source terminal should be\r\n * returned.\r\n */\r\nmxCell.prototype.getTerminal = function(source)\r\n{\r\n\treturn (source) ? this.source : this.target;\r\n};\r\n\r\n/**\r\n * Function: setTerminal\r\n *\r\n * Sets the source or target terminal and returns the new terminal.\r\n * \r\n * Parameters:\r\n * \r\n * terminal - <mxCell> that represents the new source or target terminal.\r\n * isSource - Boolean that specifies if the source or target terminal\r\n * should be set.\r\n */\r\nmxCell.prototype.setTerminal = function(terminal, isSource)\r\n{\r\n\tif (isSource)\r\n\t{\r\n\t\tthis.source = terminal;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.target = terminal;\r\n\t}\r\n\t\r\n\treturn terminal;\r\n};\r\n\r\n/**\r\n * Function: getChildCount\r\n *\r\n * Returns the number of child cells.\r\n */\r\nmxCell.prototype.getChildCount = function()\r\n{\r\n\treturn (this.children == null) ? 0 : this.children.length;\r\n};\r\n\r\n/**\r\n * Function: getIndex\r\n *\r\n * Returns the index of the specified child in the child array.\r\n * \r\n * Parameters:\r\n * \r\n * child - Child whose index should be returned.\r\n */\r\nmxCell.prototype.getIndex = function(child)\r\n{\r\n\treturn mxUtils.indexOf(this.children, child);\r\n};\r\n\r\n/**\r\n * Function: getChildAt\r\n *\r\n * Returns the child at the specified index.\r\n * \r\n * Parameters:\r\n * \r\n * index - Integer that specifies the child to be returned.\r\n */\r\nmxCell.prototype.getChildAt = function(index)\r\n{\r\n\treturn (this.children == null) ? null : this.children[index];\r\n};\r\n\r\n/**\r\n * Function: insert\r\n *\r\n * Inserts the specified child into the child array at the specified index\r\n * and updates the parent reference of the child. If not childIndex is\r\n * specified then the child is appended to the child array. Returns the\r\n * inserted child.\r\n * \r\n * Parameters:\r\n * \r\n * child - <mxCell> to be inserted or appended to the child array.\r\n * index - Optional integer that specifies the index at which the child\r\n * should be inserted into the child array.\r\n */\r\nmxCell.prototype.insert = function(child, index)\r\n{\r\n\tif (child != null)\r\n\t{\r\n\t\tif (index == null)\r\n\t\t{\r\n\t\t\tindex = this.getChildCount();\r\n\t\t\t\r\n\t\t\tif (child.getParent() == this)\r\n\t\t\t{\r\n\t\t\t\tindex--;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tchild.removeFromParent();\r\n\t\tchild.setParent(this);\r\n\t\t\r\n\t\tif (this.children == null)\r\n\t\t{\r\n\t\t\tthis.children = [];\r\n\t\t\tthis.children.push(child);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.children.splice(index, 0, child);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn child;\r\n};\r\n\r\n/**\r\n * Function: remove\r\n *\r\n * Removes the child at the specified index from the child array and\r\n * returns the child that was removed. Will remove the parent reference of\r\n * the child.\r\n * \r\n * Parameters:\r\n * \r\n * index - Integer that specifies the index of the child to be\r\n * removed.\r\n */\r\nmxCell.prototype.remove = function(index)\r\n{\r\n\tvar child = null;\r\n\t\r\n\tif (this.children != null && index >= 0)\r\n\t{\r\n\t\tchild = this.getChildAt(index);\r\n\t\t\r\n\t\tif (child != null)\r\n\t\t{\r\n\t\t\tthis.children.splice(index, 1);\r\n\t\t\tchild.setParent(null);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn child;\r\n};\r\n\r\n/**\r\n * Function: removeFromParent\r\n *\r\n * Removes the cell from its parent.\r\n */\r\nmxCell.prototype.removeFromParent = function()\r\n{\r\n\tif (this.parent != null)\r\n\t{\r\n\t\tvar index = this.parent.getIndex(this);\r\n\t\tthis.parent.remove(index);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getEdgeCount\r\n *\r\n * Returns the number of edges in the edge array.\r\n */\r\nmxCell.prototype.getEdgeCount = function()\r\n{\r\n\treturn (this.edges == null) ? 0 : this.edges.length;\r\n};\r\n\r\n/**\r\n * Function: getEdgeIndex\r\n *\r\n * Returns the index of the specified edge in <edges>.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCell> whose index in <edges> should be returned.\r\n */\r\nmxCell.prototype.getEdgeIndex = function(edge)\r\n{\r\n\treturn mxUtils.indexOf(this.edges, edge);\r\n};\r\n\r\n/**\r\n * Function: getEdgeAt\r\n *\r\n * Returns the edge at the specified index in <edges>.\r\n * \r\n * Parameters:\r\n * \r\n * index - Integer that specifies the index of the edge to be returned.\r\n */\r\nmxCell.prototype.getEdgeAt = function(index)\r\n{\r\n\treturn (this.edges == null) ? null : this.edges[index];\r\n};\r\n\r\n/**\r\n * Function: insertEdge\r\n *\r\n * Inserts the specified edge into the edge array and returns the edge.\r\n * Will update the respective terminal reference of the edge.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCell> to be inserted into the edge array.\r\n * isOutgoing - Boolean that specifies if the edge is outgoing.\r\n */\r\nmxCell.prototype.insertEdge = function(edge, isOutgoing)\r\n{\r\n\tif (edge != null)\r\n\t{\r\n\t\tedge.removeFromTerminal(isOutgoing);\r\n\t\tedge.setTerminal(this, isOutgoing);\r\n\t\t\r\n\t\tif (this.edges == null ||\r\n\t\t\tedge.getTerminal(!isOutgoing) != this ||\r\n\t\t\tmxUtils.indexOf(this.edges, edge) < 0)\r\n\t\t{\r\n\t\t\tif (this.edges == null)\r\n\t\t\t{\r\n\t\t\t\tthis.edges = [];\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.edges.push(edge);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn edge;\r\n};\r\n\r\n/**\r\n * Function: removeEdge\r\n *\r\n * Removes the specified edge from the edge array and returns the edge.\r\n * Will remove the respective terminal reference from the edge.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCell> to be removed from the edge array.\r\n * isOutgoing - Boolean that specifies if the edge is outgoing.\r\n */\r\nmxCell.prototype.removeEdge = function(edge, isOutgoing)\r\n{\r\n\tif (edge != null)\r\n\t{\r\n\t\tif (edge.getTerminal(!isOutgoing) != this &&\r\n\t\t\tthis.edges != null)\r\n\t\t{\r\n\t\t\tvar index = this.getEdgeIndex(edge);\r\n\t\t\t\r\n\t\t\tif (index >= 0)\r\n\t\t\t{\r\n\t\t\t\tthis.edges.splice(index, 1);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tedge.setTerminal(null, isOutgoing);\r\n\t}\r\n\t\r\n\treturn edge;\r\n};\r\n\r\n/**\r\n * Function: removeFromTerminal\r\n *\r\n * Removes the edge from its source or target terminal.\r\n * \r\n * Parameters:\r\n * \r\n * isSource - Boolean that specifies if the edge should be removed from its\r\n * source or target terminal.\r\n */\r\nmxCell.prototype.removeFromTerminal = function(isSource)\r\n{\r\n\tvar terminal = this.getTerminal(isSource);\r\n\t\r\n\tif (terminal != null)\r\n\t{\r\n\t\tterminal.removeEdge(this, isSource);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: hasAttribute\r\n * \r\n * Returns true if the user object is an XML node that contains the given\r\n * attribute.\r\n * \r\n * Parameters:\r\n * \r\n * name - Name of the attribute.\r\n */\r\nmxCell.prototype.hasAttribute = function(name)\r\n{\r\n\tvar userObject = this.getValue();\r\n\t\r\n\treturn (userObject != null &&\r\n\t\tuserObject.nodeType == mxConstants.NODETYPE_ELEMENT && userObject.hasAttribute) ?\r\n\t\tuserObject.hasAttribute(name) : userObject.getAttribute(name) != null;\r\n};\r\n\r\n/**\r\n * Function: getAttribute\r\n *\r\n * Returns the specified attribute from the user object if it is an XML\r\n * node.\r\n * \r\n * Parameters:\r\n * \r\n * name - Name of the attribute whose value should be returned.\r\n * defaultValue - Optional default value to use if the attribute has no\r\n * value.\r\n */\r\nmxCell.prototype.getAttribute = function(name, defaultValue)\r\n{\r\n\tvar userObject = this.getValue();\r\n\t\r\n\tvar val = (userObject != null &&\r\n\t\tuserObject.nodeType == mxConstants.NODETYPE_ELEMENT) ?\r\n\t\tuserObject.getAttribute(name) : null;\r\n\t\t\r\n\treturn val || defaultValue;\r\n};\r\n\r\n/**\r\n * Function: setAttribute\r\n *\r\n * Sets the specified attribute on the user object if it is an XML node.\r\n * \r\n * Parameters:\r\n * \r\n * name - Name of the attribute whose value should be set.\r\n * value - New value of the attribute.\r\n */\r\nmxCell.prototype.setAttribute = function(name, value)\r\n{\r\n\tvar userObject = this.getValue();\r\n\t\r\n\tif (userObject != null &&\r\n\t\tuserObject.nodeType == mxConstants.NODETYPE_ELEMENT)\r\n\t{\r\n\t\tuserObject.setAttribute(name, value);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: clone\r\n *\r\n * Returns a clone of the cell. Uses <cloneValue> to clone\r\n * the user object. All fields in <mxTransient> are ignored\r\n * during the cloning.\r\n */\r\nmxCell.prototype.clone = function()\r\n{\r\n\tvar clone = mxUtils.clone(this, this.mxTransient);\r\n\tclone.setValue(this.cloneValue());\r\n\t\r\n\treturn clone;\r\n};\r\n\r\n/**\r\n * Function: cloneValue\r\n *\r\n * Returns a clone of the cell's user object.\r\n */\r\nmxCell.prototype.cloneValue = function()\r\n{\r\n\tvar value = this.getValue();\r\n\t\r\n\tif (value != null)\r\n\t{\r\n\t\tif (typeof(value.clone) == 'function')\r\n\t\t{\r\n\t\t\tvalue = value.clone();\r\n\t\t}\r\n\t\telse if (!isNaN(value.nodeType))\r\n\t\t{\r\n\t\t\tvalue = value.cloneNode(true);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn value;\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxGeometry\r\n * \r\n * Extends <mxRectangle> to represent the geometry of a cell.\r\n * \r\n * For vertices, the geometry consists of the x- and y-location, and the width\r\n * and height. For edges, the geometry consists of the optional terminal- and\r\n * control points. The terminal points are only required if an edge is\r\n * unconnected, and are stored in the sourcePoint> and <targetPoint>\r\n * variables, respectively.\r\n * \r\n * Example:\r\n * \r\n * If an edge is unconnected, that is, it has no source or target terminal,\r\n * then a geometry with terminal points for a new edge can be defined as\r\n * follows.\r\n * \r\n * (code)\r\n * geometry.setTerminalPoint(new mxPoint(x1, y1), true);\r\n * geometry.points = [new mxPoint(x2, y2)];\r\n * geometry.setTerminalPoint(new mxPoint(x3, y3), false);\r\n * (end)\r\n * \r\n * Control points are used regardless of the connected state of an edge and may\r\n * be ignored or interpreted differently depending on the edge's <mxEdgeStyle>.\r\n * \r\n * To disable automatic reset of control points after a cell has been moved or\r\n * resized, the the <mxGraph.resizeEdgesOnMove> and\r\n * <mxGraph.resetEdgesOnResize> may be used.\r\n *\r\n * Edge Labels:\r\n * \r\n * Using the x- and y-coordinates of a cell's geometry, it is possible to\r\n * position the label on edges on a specific location on the actual edge shape\r\n * as it appears on the screen. The x-coordinate of an edge's geometry is used\r\n * to describe the distance from the center of the edge from -1 to 1 with 0\r\n * being the center of the edge and the default value. The y-coordinate of an\r\n * edge's geometry is used to describe the absolute, orthogonal distance in\r\n * pixels from that point. In addition, the <mxGeometry.offset> is used as an\r\n * absolute offset vector from the resulting point.\r\n * \r\n * This coordinate system is applied if <relative> is true, otherwise the\r\n * offset defines the absolute vector from the edge's center point to the\r\n * label and the values for <x> and <y> are ignored.\r\n * \r\n * The width and height parameter for edge geometries can be used to set the\r\n * label width and height (eg. for word wrapping).\r\n * \r\n * Ports:\r\n * \r\n * The term \"port\" refers to a relatively positioned, connectable child cell,\r\n * which is used to specify the connection between the parent and another cell\r\n * in the graph. Ports are typically modeled as vertices with relative\r\n * geometries.\r\n * \r\n * Offsets:\r\n * \r\n * The <offset> field is interpreted in 3 different ways, depending on the cell\r\n * and the geometry. For edges, the offset defines the absolute offset for the\r\n * edge label. For relative geometries, the offset defines the absolute offset\r\n * for the origin (top, left corner) of the vertex, otherwise the offset\r\n * defines the absolute offset for the label inside the vertex or group.\r\n * \r\n * Constructor: mxGeometry\r\n *\r\n * Constructs a new object to describe the size and location of a vertex or\r\n * the control points of an edge.\r\n */\r\nfunction mxGeometry(x, y, width, height)\r\n{\r\n\tmxRectangle.call(this, x, y, width, height);\r\n};\r\n\r\n/**\r\n * Extends mxRectangle.\r\n */\r\nmxGeometry.prototype = new mxRectangle();\r\nmxGeometry.prototype.constructor = mxGeometry;\r\n\r\n/**\r\n * Variable: TRANSLATE_CONTROL_POINTS\r\n * \r\n * Global switch to translate the points in translate. Default is true.\r\n */\r\nmxGeometry.prototype.TRANSLATE_CONTROL_POINTS = true;\r\n\r\n/**\r\n * Variable: alternateBounds\r\n *\r\n * Stores alternate values for x, y, width and height in a rectangle. See\r\n * <swap> to exchange the values. Default is null.\r\n */\r\nmxGeometry.prototype.alternateBounds = null;\r\n\r\n/**\r\n * Variable: sourcePoint\r\n *\r\n * Defines the source <mxPoint> of the edge. This is used if the\r\n * corresponding edge does not have a source vertex. Otherwise it is\r\n * ignored. Default is  null.\r\n */\r\nmxGeometry.prototype.sourcePoint = null;\r\n\r\n/**\r\n * Variable: targetPoint\r\n *\r\n * Defines the target <mxPoint> of the edge. This is used if the\r\n * corresponding edge does not have a target vertex. Otherwise it is\r\n * ignored. Default is null.\r\n */\r\nmxGeometry.prototype.targetPoint = null;\r\n\r\n/**\r\n * Variable: points\r\n *\r\n * Array of <mxPoints> which specifies the control points along the edge.\r\n * These points are the intermediate points on the edge, for the endpoints\r\n * use <targetPoint> and <sourcePoint> or set the terminals of the edge to\r\n * a non-null value. Default is null.\r\n */\r\nmxGeometry.prototype.points = null;\r\n\r\n/**\r\n * Variable: offset\r\n *\r\n * For edges, this holds the offset (in pixels) from the position defined\r\n * by <x> and <y> on the edge. For relative geometries (for vertices), this\r\n * defines the absolute offset from the point defined by the relative\r\n * coordinates. For absolute geometries (for vertices), this defines the\r\n * offset for the label. Default is null.\r\n */\r\nmxGeometry.prototype.offset = null;\r\n\r\n/**\r\n * Variable: relative\r\n *\r\n * Specifies if the coordinates in the geometry are to be interpreted as\r\n * relative coordinates. For edges, this is used to define the location of\r\n * the edge label relative to the edge as rendered on the display. For\r\n * vertices, this specifies the relative location inside the bounds of the\r\n * parent cell.\r\n * \r\n * If this is false, then the coordinates are relative to the origin of the\r\n * parent cell or, for edges, the edge label position is relative to the\r\n * center of the edge as rendered on screen.\r\n * \r\n * Default is false.\r\n */\r\nmxGeometry.prototype.relative = false;\r\n\r\n/**\r\n * Function: swap\r\n * \r\n * Swaps the x, y, width and height with the values stored in\r\n * <alternateBounds> and puts the previous values into <alternateBounds> as\r\n * a rectangle. This operation is carried-out in-place, that is, using the\r\n * existing geometry instance. If this operation is called during a graph\r\n * model transactional change, then the geometry should be cloned before\r\n * calling this method and setting the geometry of the cell using\r\n * <mxGraphModel.setGeometry>.\r\n */\r\nmxGeometry.prototype.swap = function()\r\n{\r\n\tif (this.alternateBounds != null)\r\n\t{\r\n\t\tvar old = new mxRectangle(\r\n\t\t\tthis.x, this.y, this.width, this.height);\r\n\r\n\t\tthis.x = this.alternateBounds.x;\r\n\t\tthis.y = this.alternateBounds.y;\r\n\t\tthis.width = this.alternateBounds.width;\r\n\t\tthis.height = this.alternateBounds.height;\r\n\r\n\t\tthis.alternateBounds = old;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getTerminalPoint\r\n * \r\n * Returns the <mxPoint> representing the source or target point of this\r\n * edge. This is only used if the edge has no source or target vertex.\r\n * \r\n * Parameters:\r\n * \r\n * isSource - Boolean that specifies if the source or target point\r\n * should be returned.\r\n */\r\nmxGeometry.prototype.getTerminalPoint = function(isSource)\r\n{\r\n\treturn (isSource) ? this.sourcePoint : this.targetPoint;\r\n};\r\n\r\n/**\r\n * Function: setTerminalPoint\r\n * \r\n * Sets the <sourcePoint> or <targetPoint> to the given <mxPoint> and\r\n * returns the new point.\r\n * \r\n * Parameters:\r\n * \r\n * point - Point to be used as the new source or target point.\r\n * isSource - Boolean that specifies if the source or target point\r\n * should be set.\r\n */\r\nmxGeometry.prototype.setTerminalPoint = function(point, isSource)\r\n{\r\n\tif (isSource)\r\n\t{\r\n\t\tthis.sourcePoint = point;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.targetPoint = point;\r\n\t}\r\n\t\r\n\treturn point;\r\n};\r\n\r\n/**\r\n * Function: rotate\r\n * \r\n * Rotates the geometry by the given angle around the given center. That is,\r\n * <x> and <y> of the geometry, the <sourcePoint>, <targetPoint> and all\r\n * <points> are translated by the given amount. <x> and <y> are only\r\n * translated if <relative> is false.\r\n * \r\n * Parameters:\r\n * \r\n * angle - Number that specifies the rotation angle in degrees.\r\n * cx - <mxPoint> that specifies the center of the rotation.\r\n */\r\nmxGeometry.prototype.rotate = function(angle, cx)\r\n{\r\n\tvar rad = mxUtils.toRadians(angle);\r\n\tvar cos = Math.cos(rad);\r\n\tvar sin = Math.sin(rad);\r\n\t\r\n\t// Rotates the geometry\r\n\tif (!this.relative)\r\n\t{\r\n\t\tvar ct = new mxPoint(this.getCenterX(), this.getCenterY());\r\n\t\tvar pt = mxUtils.getRotatedPoint(ct, cos, sin, cx);\r\n\t\t\r\n\t\tthis.x = Math.round(pt.x - this.width / 2);\r\n\t\tthis.y = Math.round(pt.y - this.height / 2);\r\n\t}\r\n\r\n\t// Rotates the source point\r\n\tif (this.sourcePoint != null)\r\n\t{\r\n\t\tvar pt = mxUtils.getRotatedPoint(this.sourcePoint, cos, sin, cx);\r\n\t\tthis.sourcePoint.x = Math.round(pt.x);\r\n\t\tthis.sourcePoint.y = Math.round(pt.y);\r\n\t}\r\n\t\r\n\t// Translates the target point\r\n\tif (this.targetPoint != null)\r\n\t{\r\n\t\tvar pt = mxUtils.getRotatedPoint(this.targetPoint, cos, sin, cx);\r\n\t\tthis.targetPoint.x = Math.round(pt.x);\r\n\t\tthis.targetPoint.y = Math.round(pt.y);\t\r\n\t}\r\n\t\r\n\t// Translate the control points\r\n\tif (this.points != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.points.length; i++)\r\n\t\t{\r\n\t\t\tif (this.points[i] != null)\r\n\t\t\t{\r\n\t\t\t\tvar pt = mxUtils.getRotatedPoint(this.points[i], cos, sin, cx);\r\n\t\t\t\tthis.points[i].x = Math.round(pt.x);\r\n\t\t\t\tthis.points[i].y = Math.round(pt.y);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: translate\r\n * \r\n * Translates the geometry by the specified amount. That is, <x> and <y> of the\r\n * geometry, the <sourcePoint>, <targetPoint> and all <points> are translated\r\n * by the given amount. <x> and <y> are only translated if <relative> is false.\r\n * If <TRANSLATE_CONTROL_POINTS> is false, then <points> are not modified by\r\n * this function.\r\n * \r\n * Parameters:\r\n * \r\n * dx - Number that specifies the x-coordinate of the translation.\r\n * dy - Number that specifies the y-coordinate of the translation.\r\n */\r\nmxGeometry.prototype.translate = function(dx, dy)\r\n{\r\n\tdx = parseFloat(dx);\r\n\tdy = parseFloat(dy);\r\n\t\r\n\t// Translates the geometry\r\n\tif (!this.relative)\r\n\t{\r\n\t\tthis.x = parseFloat(this.x) + dx;\r\n\t\tthis.y = parseFloat(this.y) + dy;\r\n\t}\r\n\r\n\t// Translates the source point\r\n\tif (this.sourcePoint != null)\r\n\t{\r\n\t\tthis.sourcePoint.x = parseFloat(this.sourcePoint.x) + dx;\r\n\t\tthis.sourcePoint.y = parseFloat(this.sourcePoint.y) + dy;\r\n\t}\r\n\t\r\n\t// Translates the target point\r\n\tif (this.targetPoint != null)\r\n\t{\r\n\t\tthis.targetPoint.x = parseFloat(this.targetPoint.x) + dx;\r\n\t\tthis.targetPoint.y = parseFloat(this.targetPoint.y) + dy;\t\t\r\n\t}\r\n\r\n\t// Translate the control points\r\n\tif (this.TRANSLATE_CONTROL_POINTS && this.points != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.points.length; i++)\r\n\t\t{\r\n\t\t\tif (this.points[i] != null)\r\n\t\t\t{\r\n\t\t\t\tthis.points[i].x = parseFloat(this.points[i].x) + dx;\r\n\t\t\t\tthis.points[i].y = parseFloat(this.points[i].y) + dy;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: scale\r\n * \r\n * Scales the geometry by the given amount. That is, <x> and <y> of the\r\n * geometry, the <sourcePoint>, <targetPoint> and all <points> are scaled\r\n * by the given amount. <x>, <y>, <width> and <height> are only scaled if\r\n * <relative> is false. If <fixedAspect> is true, then the smaller value\r\n * is used to scale the width and the height.\r\n * \r\n * Parameters:\r\n * \r\n * sx - Number that specifies the horizontal scale factor.\r\n * sy - Number that specifies the vertical scale factor.\r\n * fixedAspect - Optional boolean to keep the aspect ratio fixed.\r\n */\r\nmxGeometry.prototype.scale = function(sx, sy, fixedAspect)\r\n{\r\n\tsx = parseFloat(sx);\r\n\tsy = parseFloat(sy);\r\n\r\n\t// Translates the source point\r\n\tif (this.sourcePoint != null)\r\n\t{\r\n\t\tthis.sourcePoint.x = parseFloat(this.sourcePoint.x) * sx;\r\n\t\tthis.sourcePoint.y = parseFloat(this.sourcePoint.y) * sy;\r\n\t}\r\n\t\r\n\t// Translates the target point\r\n\tif (this.targetPoint != null)\r\n\t{\r\n\t\tthis.targetPoint.x = parseFloat(this.targetPoint.x) * sx;\r\n\t\tthis.targetPoint.y = parseFloat(this.targetPoint.y) * sy;\t\t\r\n\t}\r\n\r\n\t// Translate the control points\r\n\tif (this.points != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.points.length; i++)\r\n\t\t{\r\n\t\t\tif (this.points[i] != null)\r\n\t\t\t{\r\n\t\t\t\tthis.points[i].x = parseFloat(this.points[i].x) * sx;\r\n\t\t\t\tthis.points[i].y = parseFloat(this.points[i].y) * sy;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\t// Translates the geometry\r\n\tif (!this.relative)\r\n\t{\r\n\t\tthis.x = parseFloat(this.x) * sx;\r\n\t\tthis.y = parseFloat(this.y) * sy;\r\n\r\n\t\tif (fixedAspect)\r\n\t\t{\r\n\t\t\tsy = sx = Math.min(sx, sy);\r\n\t\t}\r\n\t\t\r\n\t\tthis.width = parseFloat(this.width) * sx;\r\n\t\tthis.height = parseFloat(this.height) * sy;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: equals\r\n * \r\n * Returns true if the given object equals this geometry.\r\n */\r\nmxGeometry.prototype.equals = function(obj)\r\n{\r\n\treturn mxRectangle.prototype.equals.apply(this, arguments) &&\r\n\t\tthis.relative == obj.relative &&\r\n\t\t((this.sourcePoint == null && obj.sourcePoint == null) || (this.sourcePoint != null && this.sourcePoint.equals(obj.sourcePoint))) &&\r\n\t\t((this.targetPoint == null && obj.targetPoint == null) || (this.targetPoint != null && this.targetPoint.equals(obj.targetPoint))) &&\r\n\t\t((this.points == null && obj.points == null) || (this.points != null && mxUtils.equalPoints(this.points, obj.points))) &&\r\n\t\t((this.alternateBounds == null && obj.alternateBounds == null) || (this.alternateBounds != null && this.alternateBounds.equals(obj.alternateBounds))) &&\r\n\t\t((this.offset == null && obj.offset == null) || (this.offset != null && this.offset.equals(obj.offset)));\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nvar mxCellPath =\r\n{\r\n\r\n\t/**\r\n\t * Class: mxCellPath\r\n\t * \r\n\t * Implements a mechanism for temporary cell Ids.\r\n\t * \r\n\t * Variable: PATH_SEPARATOR\r\n\t * \r\n\t * Defines the separator between the path components. Default is \".\".\r\n\t */\r\n\tPATH_SEPARATOR: '.',\r\n\t\r\n\t/**\r\n\t * Function: create\r\n\t * \r\n\t * Creates the cell path for the given cell. The cell path is a\r\n\t * concatenation of the indices of all ancestors on the (finite) path to\r\n\t * the root, eg. \"0.0.0.1\".\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * cell - Cell whose path should be returned.\r\n\t */\r\n\tcreate: function(cell)\r\n\t{\r\n\t\tvar result = '';\r\n\t\t\r\n\t\tif (cell != null)\r\n\t\t{\r\n\t\t\tvar parent = cell.getParent();\r\n\t\t\t\r\n\t\t\twhile (parent != null)\r\n\t\t\t{\r\n\t\t\t\tvar index = parent.getIndex(cell);\r\n\t\t\t\tresult = index + mxCellPath.PATH_SEPARATOR + result;\r\n\t\t\t\t\r\n\t\t\t\tcell = parent;\r\n\t\t\t\tparent = cell.getParent();\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Removes trailing separator\r\n\t\tvar n = result.length;\r\n\t\t\r\n\t\tif (n > 1)\r\n\t\t{\r\n\t\t\tresult = result.substring(0, n - 1);\r\n\t\t}\r\n\t\t\r\n\t\treturn result;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: getParentPath\r\n\t * \r\n\t * Returns the path for the parent of the cell represented by the given\r\n\t * path. Returns null if the given path has no parent.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * path - Path whose parent path should be returned.\r\n\t */\r\n\tgetParentPath: function(path)\r\n\t{\r\n\t\tif (path != null)\r\n\t\t{\r\n\t\t\tvar index = path.lastIndexOf(mxCellPath.PATH_SEPARATOR);\r\n\r\n\t\t\tif (index >= 0)\r\n\t\t\t{\r\n\t\t\t\treturn path.substring(0, index);\r\n\t\t\t}\r\n\t\t\telse if (path.length > 0)\r\n\t\t\t{\r\n\t\t\t\treturn '';\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn null;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: resolve\r\n\t * \r\n\t * Returns the cell for the specified cell path using the given root as the\r\n\t * root of the path.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * root - Root cell of the path to be resolved.\r\n\t * path - String that defines the path.\r\n\t */\r\n\tresolve: function(root, path)\r\n\t{\r\n\t\tvar parent = root;\r\n\t\t\r\n\t\tif (path != null)\r\n\t\t{\r\n\t\t\tvar tokens = path.split(mxCellPath.PATH_SEPARATOR);\r\n\t\t\t\r\n\t\t\tfor (var i=0; i<tokens.length; i++)\r\n\t\t\t{\r\n\t\t\t\tparent = parent.getChildAt(parseInt(tokens[i]));\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn parent;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: compare\r\n\t * \r\n\t * Compares the given cell paths and returns -1 if p1 is smaller, 0 if\r\n\t * p1 is equal and 1 if p1 is greater than p2.\r\n\t */\r\n\tcompare: function(p1, p2)\r\n\t{\r\n\t\tvar min = Math.min(p1.length, p2.length);\r\n\t\tvar comp = 0;\r\n\t\t\r\n\t\tfor (var i = 0; i < min; i++)\r\n\t\t{\r\n\t\t\tif (p1[i] != p2[i])\r\n\t\t\t{\r\n\t\t\t\tif (p1[i].length == 0 ||\r\n\t\t\t\t\tp2[i].length == 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tcomp = (p1[i] == p2[i]) ? 0 : ((p1[i] > p2[i]) ? 1 : -1);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tvar t1 = parseInt(p1[i]);\r\n\t\t\t\t\tvar t2 = parseInt(p2[i]);\r\n\t\t\t\t\t\r\n\t\t\t\t\tcomp = (t1 == t2) ? 0 : ((t1 > t2) ? 1 : -1);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Compares path length if both paths are equal to this point\r\n\t\tif (comp == 0)\r\n\t\t{\r\n\t\t\tvar t1 = p1.length;\r\n\t\t\tvar t2 = p2.length;\r\n\t\t\t\r\n\t\t\tif (t1 != t2)\r\n\t\t\t{\r\n\t\t\t\tcomp = (t1 > t2) ? 1 : -1;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn comp;\r\n\t}\r\n\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nvar mxPerimeter =\r\n{\r\n\t/**\r\n\t * Class: mxPerimeter\r\n\t * \r\n\t * Provides various perimeter functions to be used in a style\r\n\t * as the value of <mxConstants.STYLE_PERIMETER>. Perimeters for\r\n\t * rectangle, circle, rhombus and triangle are available.\r\n\t *\r\n\t * Example:\r\n\t * \r\n\t * (code)\r\n\t * <add as=\"perimeter\">mxPerimeter.RectanglePerimeter</add>\r\n\t * (end)\r\n\t * \r\n\t * Or programmatically:\r\n\t * \r\n\t * (code)\r\n\t * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;\r\n\t * (end)\r\n\t * \r\n\t * When adding new perimeter functions, it is recommended to use the \r\n\t * mxPerimeter-namespace as follows:\r\n\t * \r\n\t * (code)\r\n\t * mxPerimeter.CustomPerimeter = function (bounds, vertex, next, orthogonal)\r\n\t * {\r\n\t *   var x = 0; // Calculate x-coordinate\r\n\t *   var y = 0; // Calculate y-coordainte\r\n\t *   \r\n\t *   return new mxPoint(x, y);\r\n\t * }\r\n\t * (end)\r\n\t * \r\n\t * The new perimeter should then be registered in the <mxStyleRegistry> as follows:\r\n\t * (code)\r\n\t * mxStyleRegistry.putValue('customPerimeter', mxPerimeter.CustomPerimeter);\r\n\t * (end)\r\n\t * \r\n\t * The custom perimeter above can now be used in a specific vertex as follows:\r\n\t * \r\n\t * (code)\r\n\t * model.setStyle(vertex, 'perimeter=customPerimeter');\r\n\t * (end)\r\n\t * \r\n\t * Note that the key of the <mxStyleRegistry> entry for the function should\r\n\t * be used in string values, unless <mxGraphView.allowEval> is true, in\r\n\t * which case you can also use mxPerimeter.CustomPerimeter for the value in\r\n\t * the cell style above.\r\n\t * \r\n\t * Or it can be used for all vertices in the graph as follows:\r\n\t * \r\n\t * (code)\r\n\t * var style = graph.getStylesheet().getDefaultVertexStyle();\r\n\t * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.CustomPerimeter;\r\n\t * (end)\r\n\t * \r\n\t * Note that the object can be used directly when programmatically setting\r\n\t * the value, but the key in the <mxStyleRegistry> should be used when\r\n\t * setting the value via a key, value pair in a cell style.\r\n\t * \r\n\t * The parameters are explained in <RectanglePerimeter>.\r\n\t * \r\n\t * Function: RectanglePerimeter\r\n\t * \r\n\t * Describes a rectangular perimeter for the given bounds.\r\n\t *\r\n\t * Parameters:\r\n\t * \r\n\t * bounds - <mxRectangle> that represents the absolute bounds of the\r\n\t * vertex.\r\n\t * vertex - <mxCellState> that represents the vertex.\r\n\t * next - <mxPoint> that represents the nearest neighbour point on the\r\n\t * given edge.\r\n\t * orthogonal - Boolean that specifies if the orthogonal projection onto\r\n\t * the perimeter should be returned. If this is false then the intersection\r\n\t * of the perimeter and the line between the next and the center point is\r\n\t * returned.\r\n\t */\r\n\tRectanglePerimeter: function (bounds, vertex, next, orthogonal)\r\n\t{\r\n\t\tvar cx = bounds.getCenterX();\r\n\t\tvar cy = bounds.getCenterY();\r\n\t\tvar dx = next.x - cx;\r\n\t\tvar dy = next.y - cy;\r\n\t\tvar alpha = Math.atan2(dy, dx);\r\n\t\tvar p = new mxPoint(0, 0);\r\n\t\tvar pi = Math.PI;\r\n\t\tvar pi2 = Math.PI/2;\r\n\t\tvar beta = pi2 - alpha;\r\n\t\tvar t = Math.atan2(bounds.height, bounds.width);\r\n\t\t\r\n\t\tif (alpha < -pi + t || alpha > pi - t)\r\n\t\t{\r\n\t\t\t// Left edge\r\n\t\t\tp.x = bounds.x;\r\n\t\t\tp.y = cy - bounds.width * Math.tan(alpha) / 2;\r\n\t\t}\r\n\t\telse if (alpha < -t)\r\n\t\t{\r\n\t\t\t// Top Edge\r\n\t\t\tp.y = bounds.y;\r\n\t\t\tp.x = cx - bounds.height * Math.tan(beta) / 2;\r\n\t\t}\r\n\t\telse if (alpha < t)\r\n\t\t{\r\n\t\t\t// Right Edge\r\n\t\t\tp.x = bounds.x + bounds.width;\r\n\t\t\tp.y = cy + bounds.width * Math.tan(alpha) / 2;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Bottom Edge\r\n\t\t\tp.y = bounds.y + bounds.height;\r\n\t\t\tp.x = cx + bounds.height * Math.tan(beta) / 2;\r\n\t\t}\r\n\t\t\r\n\t\tif (orthogonal)\r\n\t\t{\r\n\t\t\tif (next.x >= bounds.x &&\r\n\t\t\t\tnext.x <= bounds.x + bounds.width)\r\n\t\t\t{\r\n\t\t\t\tp.x = next.x;\r\n\t\t\t}\r\n\t\t\telse if (next.y >= bounds.y &&\r\n\t\t\t\t\t   next.y <= bounds.y + bounds.height)\r\n\t\t\t{\r\n\t\t\t\tp.y = next.y;\r\n\t\t\t}\r\n\t\t\tif (next.x < bounds.x)\r\n\t\t\t{\r\n\t\t\t\tp.x = bounds.x;\r\n\t\t\t}\r\n\t\t\telse if (next.x > bounds.x + bounds.width)\r\n\t\t\t{\r\n\t\t\t\tp.x = bounds.x + bounds.width;\r\n\t\t\t}\r\n\t\t\tif (next.y < bounds.y)\r\n\t\t\t{\r\n\t\t\t\tp.y = bounds.y;\r\n\t\t\t}\r\n\t\t\telse if (next.y > bounds.y + bounds.height)\r\n\t\t\t{\r\n\t\t\t\tp.y = bounds.y + bounds.height;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn p;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: EllipsePerimeter\r\n\t * \r\n\t * Describes an elliptic perimeter. See <RectanglePerimeter>\r\n\t * for a description of the parameters.\r\n\t */\r\n\tEllipsePerimeter: function (bounds, vertex, next, orthogonal)\r\n\t{\r\n\t\tvar x = bounds.x;\r\n\t\tvar y = bounds.y;\r\n\t\tvar a = bounds.width / 2;\r\n\t\tvar b = bounds.height / 2;\r\n\t\tvar cx = x + a;\r\n\t\tvar cy = y + b;\r\n\t\tvar px = next.x;\r\n\t\tvar py = next.y;\r\n\t\t\r\n\t\t// Calculates straight line equation through\r\n\t\t// point and ellipse center y = d * x + h\r\n\t\tvar dx = parseInt(px - cx);\r\n\t\tvar dy = parseInt(py - cy);\r\n\t\t\r\n\t\tif (dx == 0 && dy != 0)\r\n\t\t{\r\n\t\t\treturn new mxPoint(cx, cy + b * dy / Math.abs(dy));\r\n\t\t}\r\n\t\telse if (dx == 0 && dy == 0)\r\n\t\t{\r\n\t\t\treturn new mxPoint(px, py);\r\n\t\t}\r\n\r\n\t\tif (orthogonal)\r\n\t\t{\r\n\t\t\tif (py >= y && py <= y + bounds.height)\r\n\t\t\t{\r\n\t\t\t\tvar ty = py - cy;\r\n\t\t\t\tvar tx = Math.sqrt(a*a*(1-(ty*ty)/(b*b))) || 0;\r\n\t\t\t\t\r\n\t\t\t\tif (px <= x)\r\n\t\t\t\t{\r\n\t\t\t\t\ttx = -tx;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\treturn new mxPoint(cx+tx, py);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (px >= x && px <= x + bounds.width)\r\n\t\t\t{\r\n\t\t\t\tvar tx = px - cx;\r\n\t\t\t\tvar ty = Math.sqrt(b*b*(1-(tx*tx)/(a*a))) || 0;\r\n\t\t\t\t\r\n\t\t\t\tif (py <= y)\r\n\t\t\t\t{\r\n\t\t\t\t\tty = -ty;\t\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\treturn new mxPoint(px, cy+ty);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Calculates intersection\r\n\t\tvar d = dy / dx;\r\n\t\tvar h = cy - d * cx;\r\n\t\tvar e = a * a * d * d + b * b;\r\n\t\tvar f = -2 * cx * e;\r\n\t\tvar g = a * a * d * d * cx * cx +\r\n\t\t\t\tb * b * cx * cx -\r\n\t\t\t\ta * a * b * b;\r\n\t\tvar det = Math.sqrt(f * f - 4 * e * g);\r\n\t\t\r\n\t\t// Two solutions (perimeter points)\r\n\t\tvar xout1 = (-f + det) / (2 * e);\r\n\t\tvar xout2 = (-f - det) / (2 * e);\r\n\t\tvar yout1 = d * xout1 + h;\r\n\t\tvar yout2 = d * xout2 + h;\r\n\t\tvar dist1 = Math.sqrt(Math.pow((xout1 - px), 2)\r\n\t\t\t\t\t+ Math.pow((yout1 - py), 2));\r\n\t\tvar dist2 = Math.sqrt(Math.pow((xout2 - px), 2)\r\n\t\t\t\t\t+ Math.pow((yout2 - py), 2));\r\n\t\t\t\t\t\r\n\t\t// Correct solution\r\n\t\tvar xout = 0;\r\n\t\tvar yout = 0;\r\n\t\t\r\n\t\tif (dist1 < dist2)\r\n\t\t{\r\n\t\t\txout = xout1;\r\n\t\t\tyout = yout1;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\txout = xout2;\r\n\t\t\tyout = yout2;\r\n\t\t}\r\n\t\t\r\n\t\treturn new mxPoint(xout, yout);\r\n\t},\r\n\r\n\t/**\r\n\t * Function: RhombusPerimeter\r\n\t * \r\n\t * Describes a rhombus (aka diamond) perimeter. See <RectanglePerimeter>\r\n\t * for a description of the parameters.\r\n\t */\r\n\tRhombusPerimeter: function (bounds, vertex, next, orthogonal)\r\n\t{\r\n\t\tvar x = bounds.x;\r\n\t\tvar y = bounds.y;\r\n\t\tvar w = bounds.width;\r\n\t\tvar h = bounds.height;\r\n\t\t\r\n\t\tvar cx = x + w / 2;\r\n\t\tvar cy = y + h / 2;\r\n\r\n\t\tvar px = next.x;\r\n\t\tvar py = next.y;\r\n\r\n\t\t// Special case for intersecting the diamond's corners\r\n\t\tif (cx == px)\r\n\t\t{\r\n\t\t\tif (cy > py)\r\n\t\t\t{\r\n\t\t\t\treturn new mxPoint(cx, y); // top\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\treturn new mxPoint(cx, y + h); // bottom\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (cy == py)\r\n\t\t{\r\n\t\t\tif (cx > px)\r\n\t\t\t{\r\n\t\t\t\treturn new mxPoint(x, cy); // left\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\treturn new mxPoint(x + w, cy); // right\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tvar tx = cx;\r\n\t\tvar ty = cy;\r\n\t\t\r\n\t\tif (orthogonal)\r\n\t\t{\r\n\t\t\tif (px >= x && px <= x + w)\r\n\t\t\t{\r\n\t\t\t\ttx = px;\r\n\t\t\t}\r\n\t\t\telse if (py >= y && py <= y + h)\r\n\t\t\t{\r\n\t\t\t\tty = py;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// In which quadrant will the intersection be?\r\n\t\t// set the slope and offset of the border line accordingly\r\n\t\tif (px < cx)\r\n\t\t{\r\n\t\t\tif (py < cy)\r\n\t\t\t{\r\n\t\t\t\treturn mxUtils.intersection(px, py, tx, ty, cx, y, x, cy);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\treturn mxUtils.intersection(px, py, tx, ty, cx, y + h, x, cy);\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (py < cy)\r\n\t\t{\r\n\t\t\treturn mxUtils.intersection(px, py, tx, ty, cx, y, x + w, cy);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn mxUtils.intersection(px, py, tx, ty, cx, y + h, x + w, cy);\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: TrianglePerimeter\r\n\t * \r\n\t * Describes a triangle perimeter. See <RectanglePerimeter>\r\n\t * for a description of the parameters.\r\n\t */\r\n\tTrianglePerimeter: function (bounds, vertex, next, orthogonal)\r\n\t{\r\n\t\tvar direction = (vertex != null) ?\r\n\t\t\tvertex.style[mxConstants.STYLE_DIRECTION] : null;\r\n\t\tvar vertical = direction == mxConstants.DIRECTION_NORTH ||\r\n\t\t\tdirection == mxConstants.DIRECTION_SOUTH;\r\n\r\n\t\tvar x = bounds.x;\r\n\t\tvar y = bounds.y;\r\n\t\tvar w = bounds.width;\r\n\t\tvar h = bounds.height;\r\n\t\t\r\n\t\tvar cx = x + w / 2;\r\n\t\tvar cy = y + h / 2;\r\n\t\t\r\n\t\tvar start = new mxPoint(x, y);\r\n\t\tvar corner = new mxPoint(x + w, cy);\r\n\t\tvar end = new mxPoint(x, y + h);\r\n\t\t\r\n\t\tif (direction == mxConstants.DIRECTION_NORTH)\r\n\t\t{\r\n\t\t\tstart = end;\r\n\t\t\tcorner = new mxPoint(cx, y);\r\n\t\t\tend = new mxPoint(x + w, y + h);\r\n\t\t}\r\n\t\telse if (direction == mxConstants.DIRECTION_SOUTH)\r\n\t\t{\r\n\t\t\tcorner = new mxPoint(cx, y + h);\r\n\t\t\tend = new mxPoint(x + w, y);\r\n\t\t}\r\n\t\telse if (direction == mxConstants.DIRECTION_WEST)\r\n\t\t{\r\n\t\t\tstart = new mxPoint(x + w, y);\r\n\t\t\tcorner = new mxPoint(x, cy);\r\n\t\t\tend = new mxPoint(x + w, y + h);\r\n\t\t}\r\n\r\n\t\tvar dx = next.x - cx;\r\n\t\tvar dy = next.y - cy;\r\n\r\n\t\tvar alpha = (vertical) ? Math.atan2(dx, dy) : Math.atan2(dy, dx);\r\n\t\tvar t = (vertical) ? Math.atan2(w, h) : Math.atan2(h, w);\r\n\t\t\r\n\t\tvar base = false;\r\n\t\t\r\n\t\tif (direction == mxConstants.DIRECTION_NORTH ||\r\n\t\t\tdirection == mxConstants.DIRECTION_WEST)\r\n\t\t{\r\n\t\t\tbase = alpha > -t && alpha < t;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tbase = alpha < -Math.PI + t || alpha > Math.PI - t;\t\r\n\t\t}\r\n\r\n\t\tvar result = null;\t\t\t\r\n\r\n\t\tif (base)\r\n\t\t{\r\n\t\t\tif (orthogonal && ((vertical && next.x >= start.x && next.x <= end.x) ||\r\n\t\t\t\t(!vertical && next.y >= start.y && next.y <= end.y)))\r\n\t\t\t{\r\n\t\t\t\tif (vertical)\r\n\t\t\t\t{\r\n\t\t\t\t\tresult = new mxPoint(next.x, start.y);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tresult = new mxPoint(start.x, next.y);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tif (direction == mxConstants.DIRECTION_NORTH)\r\n\t\t\t\t{\r\n\t\t\t\t\tresult = new mxPoint(x + w / 2 + h * Math.tan(alpha) / 2,\r\n\t\t\t\t\t\ty + h);\r\n\t\t\t\t}\r\n\t\t\t\telse if (direction == mxConstants.DIRECTION_SOUTH)\r\n\t\t\t\t{\r\n\t\t\t\t\tresult = new mxPoint(x + w / 2 - h * Math.tan(alpha) / 2,\r\n\t\t\t\t\t\ty);\r\n\t\t\t\t}\r\n\t\t\t\telse if (direction == mxConstants.DIRECTION_WEST)\r\n\t\t\t\t{\r\n\t\t\t\t\tresult = new mxPoint(x + w, y + h / 2 +\r\n\t\t\t\t\t\tw * Math.tan(alpha) / 2);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tresult = new mxPoint(x, y + h / 2 -\r\n\t\t\t\t\t\tw * Math.tan(alpha) / 2);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tif (orthogonal)\r\n\t\t\t{\r\n\t\t\t\tvar pt = new mxPoint(cx, cy);\r\n\t\t\r\n\t\t\t\tif (next.y >= y && next.y <= y + h)\r\n\t\t\t\t{\r\n\t\t\t\t\tpt.x = (vertical) ? cx : (\r\n\t\t\t\t\t\t(direction == mxConstants.DIRECTION_WEST) ?\r\n\t\t\t\t\t\t\tx + w : x);\r\n\t\t\t\t\tpt.y = next.y;\r\n\t\t\t\t}\r\n\t\t\t\telse if (next.x >= x && next.x <= x + w)\r\n\t\t\t\t{\r\n\t\t\t\t\tpt.x = next.x;\r\n\t\t\t\t\tpt.y = (!vertical) ? cy : (\r\n\t\t\t\t\t\t(direction == mxConstants.DIRECTION_NORTH) ?\r\n\t\t\t\t\t\t\ty + h : y);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Compute angle\r\n\t\t\t\tdx = next.x - pt.x;\r\n\t\t\t\tdy = next.y - pt.y;\r\n\t\t\t\t\r\n\t\t\t\tcx = pt.x;\r\n\t\t\t\tcy = pt.y;\r\n\t\t\t}\r\n\r\n\t\t\tif ((vertical && next.x <= x + w / 2) ||\r\n\t\t\t\t(!vertical && next.y <= y + h / 2))\r\n\t\t\t{\r\n\t\t\t\tresult = mxUtils.intersection(next.x, next.y, cx, cy,\r\n\t\t\t\t\tstart.x, start.y, corner.x, corner.y);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tresult = mxUtils.intersection(next.x, next.y, cx, cy,\r\n\t\t\t\t\tcorner.x, corner.y, end.x, end.y);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (result == null)\r\n\t\t{\r\n\t\t\tresult = new mxPoint(cx, cy);\r\n\t\t}\r\n\t\t\r\n\t\treturn result;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: HexagonPerimeter\r\n\t * \r\n\t * Describes a hexagon perimeter. See <RectanglePerimeter>\r\n\t * for a description of the parameters.\r\n\t */\r\n\tHexagonPerimeter: function (bounds, vertex, next, orthogonal)\r\n\t{\r\n\t\tvar x = bounds.x;\r\n\t\tvar y = bounds.y;\r\n\t\tvar w = bounds.width;\r\n\t\tvar h = bounds.height;\r\n\r\n\t\tvar cx = bounds.getCenterX();\r\n\t\tvar cy = bounds.getCenterY();\r\n\t\tvar px = next.x;\r\n\t\tvar py = next.y;\r\n\t\tvar dx = px - cx;\r\n\t\tvar dy = py - cy;\r\n\t\tvar alpha = -Math.atan2(dy, dx);\r\n\t\tvar pi = Math.PI;\r\n\t\tvar pi2 = Math.PI / 2;\r\n\r\n\t\tvar result = new mxPoint(cx, cy);\r\n\r\n\t\tvar direction = (vertex != null) ? mxUtils.getValue(\r\n\t\t\t\tvertex.style, mxConstants.STYLE_DIRECTION,\r\n\t\t\t\tmxConstants.DIRECTION_EAST) : mxConstants.DIRECTION_EAST;\r\n\t\tvar vertical = direction == mxConstants.DIRECTION_NORTH\r\n\t\t\t\t|| direction == mxConstants.DIRECTION_SOUTH;\r\n\t\tvar a = new mxPoint();\r\n\t\tvar b = new mxPoint();\r\n\r\n\t\t//Only consider corrects quadrants for the orthogonal case.\r\n\t\tif ((px < x) && (py < y) || (px < x) && (py > y + h)\r\n\t\t\t\t|| (px > x + w) && (py < y) || (px > x + w) && (py > y + h))\r\n\t\t{\r\n\t\t\torthogonal = false;\r\n\t\t}\r\n\r\n\t\tif (orthogonal)\r\n\t\t{\r\n\t\t\tif (vertical)\r\n\t\t\t{\r\n\t\t\t\t//Special cases where intersects with hexagon corners\r\n\t\t\t\tif (px == cx)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (py <= y)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn new mxPoint(cx, y);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (py >= y + h)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn new mxPoint(cx, y + h);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if (px < x)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (py == y + h / 4)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn new mxPoint(x, y + h / 4);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (py == y + 3 * h / 4)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn new mxPoint(x, y + 3 * h / 4);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if (px > x + w)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (py == y + h / 4)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn new mxPoint(x + w, y + h / 4);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (py == y + 3 * h / 4)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn new mxPoint(x + w, y + 3 * h / 4);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if (px == x)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (py < cy)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn new mxPoint(x, y + h / 4);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (py > cy)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn new mxPoint(x, y + 3 * h / 4);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if (px == x + w)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (py < cy)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn new mxPoint(x + w, y + h / 4);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (py > cy)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn new mxPoint(x + w, y + 3 * h / 4);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (py == y)\r\n\t\t\t\t{\r\n\t\t\t\t\treturn new mxPoint(cx, y);\r\n\t\t\t\t}\r\n\t\t\t\telse if (py == y + h)\r\n\t\t\t\t{\r\n\t\t\t\t\treturn new mxPoint(cx, y + h);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (px < cx)\r\n\t\t\t\t{\r\n\t\t\t\t\tif ((py > y + h / 4) && (py < y + 3 * h / 4))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ta = new mxPoint(x, y);\r\n\t\t\t\t\t\tb = new mxPoint(x, y + h);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (py < y + h / 4)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ta = new mxPoint(x - Math.floor(0.5 * w), y\r\n\t\t\t\t\t\t\t\t+ Math.floor(0.5 * h));\r\n\t\t\t\t\t\tb = new mxPoint(x + w, y - Math.floor(0.25 * h));\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (py > y + 3 * h / 4)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ta = new mxPoint(x - Math.floor(0.5 * w), y\r\n\t\t\t\t\t\t\t\t+ Math.floor(0.5 * h));\r\n\t\t\t\t\t\tb = new mxPoint(x + w, y + Math.floor(1.25 * h));\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if (px > cx)\r\n\t\t\t\t{\r\n\t\t\t\t\tif ((py > y + h / 4) && (py < y + 3 * h / 4))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ta = new mxPoint(x + w, y);\r\n\t\t\t\t\t\tb = new mxPoint(x + w, y + h);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (py < y + h / 4)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ta = new mxPoint(x, y - Math.floor(0.25 * h));\r\n\t\t\t\t\t\tb = new mxPoint(x + Math.floor(1.5 * w), y\r\n\t\t\t\t\t\t\t\t+ Math.floor(0.5 * h));\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (py > y + 3 * h / 4)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ta = new mxPoint(x + Math.floor(1.5 * w), y\r\n\t\t\t\t\t\t\t\t+ Math.floor(0.5 * h));\r\n\t\t\t\t\t\tb = new mxPoint(x, y + Math.floor(1.25 * h));\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\t//Special cases where intersects with hexagon corners\r\n\t\t\t\tif (py == cy)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (px <= x)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn new mxPoint(x, y + h / 2);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (px >= x + w)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn new mxPoint(x + w, y + h / 2);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if (py < y)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (px == x + w / 4)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn new mxPoint(x + w / 4, y);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (px == x + 3 * w / 4)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn new mxPoint(x + 3 * w / 4, y);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if (py > y + h)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (px == x + w / 4)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn new mxPoint(x + w / 4, y + h);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (px == x + 3 * w / 4)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn new mxPoint(x + 3 * w / 4, y + h);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if (py == y)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (px < cx)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn new mxPoint(x + w / 4, y);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (px > cx)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn new mxPoint(x + 3 * w / 4, y);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if (py == y + h)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (px < cx)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn new mxPoint(x + w / 4, y + h);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (py > cy)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn new mxPoint(x + 3 * w / 4, y + h);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (px == x)\r\n\t\t\t\t{\r\n\t\t\t\t\treturn new mxPoint(x, cy);\r\n\t\t\t\t}\r\n\t\t\t\telse if (px == x + w)\r\n\t\t\t\t{\r\n\t\t\t\t\treturn new mxPoint(x + w, cy);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (py < cy)\r\n\t\t\t\t{\r\n\t\t\t\t\tif ((px > x + w / 4) && (px < x + 3 * w / 4))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ta = new mxPoint(x, y);\r\n\t\t\t\t\t\tb = new mxPoint(x + w, y);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (px < x + w / 4)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ta = new mxPoint(x - Math.floor(0.25 * w), y + h);\r\n\t\t\t\t\t\tb = new mxPoint(x + Math.floor(0.5 * w), y\r\n\t\t\t\t\t\t\t\t- Math.floor(0.5 * h));\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (px > x + 3 * w / 4)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ta = new mxPoint(x + Math.floor(0.5 * w), y\r\n\t\t\t\t\t\t\t\t- Math.floor(0.5 * h));\r\n\t\t\t\t\t\tb = new mxPoint(x + Math.floor(1.25 * w), y + h);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if (py > cy)\r\n\t\t\t\t{\r\n\t\t\t\t\tif ((px > x + w / 4) && (px < x + 3 * w / 4))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ta = new mxPoint(x, y + h);\r\n\t\t\t\t\t\tb = new mxPoint(x + w, y + h);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (px < x + w / 4)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ta = new mxPoint(x - Math.floor(0.25 * w), y);\r\n\t\t\t\t\t\tb = new mxPoint(x + Math.floor(0.5 * w), y\r\n\t\t\t\t\t\t\t\t+ Math.floor(1.5 * h));\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (px > x + 3 * w / 4)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ta = new mxPoint(x + Math.floor(0.5 * w), y\r\n\t\t\t\t\t\t\t\t+ Math.floor(1.5 * h));\r\n\t\t\t\t\t\tb = new mxPoint(x + Math.floor(1.25 * w), y);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tvar tx = cx;\r\n\t\t\tvar ty = cy;\r\n\r\n\t\t\tif (px >= x && px <= x + w)\r\n\t\t\t{\r\n\t\t\t\ttx = px;\r\n\t\t\t\t\r\n\t\t\t\tif (py < cy)\r\n\t\t\t\t{\r\n\t\t\t\t\tty = y + h;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tty = y;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if (py >= y && py <= y + h)\r\n\t\t\t{\r\n\t\t\t\tty = py;\r\n\t\t\t\t\r\n\t\t\t\tif (px < cx)\r\n\t\t\t\t{\r\n\t\t\t\t\ttx = x + w;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\ttx = x;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tresult = mxUtils.intersection(tx, ty, next.x, next.y, a.x, a.y, b.x, b.y);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tif (vertical)\r\n\t\t\t{\r\n\t\t\t\tvar beta = Math.atan2(h / 4, w / 2);\r\n\r\n\t\t\t\t//Special cases where intersects with hexagon corners\r\n\t\t\t\tif (alpha == beta)\r\n\t\t\t\t{\r\n\t\t\t\t\treturn new mxPoint(x + w, y + Math.floor(0.25 * h));\r\n\t\t\t\t}\r\n\t\t\t\telse if (alpha == pi2)\r\n\t\t\t\t{\r\n\t\t\t\t\treturn new mxPoint(x + Math.floor(0.5 * w), y);\r\n\t\t\t\t}\r\n\t\t\t\telse if (alpha == (pi - beta))\r\n\t\t\t\t{\r\n\t\t\t\t\treturn new mxPoint(x, y + Math.floor(0.25 * h));\r\n\t\t\t\t}\r\n\t\t\t\telse if (alpha == -beta)\r\n\t\t\t\t{\r\n\t\t\t\t\treturn new mxPoint(x + w, y + Math.floor(0.75 * h));\r\n\t\t\t\t}\r\n\t\t\t\telse if (alpha == (-pi2))\r\n\t\t\t\t{\r\n\t\t\t\t\treturn new mxPoint(x + Math.floor(0.5 * w), y + h);\r\n\t\t\t\t}\r\n\t\t\t\telse if (alpha == (-pi + beta))\r\n\t\t\t\t{\r\n\t\t\t\t\treturn new mxPoint(x, y + Math.floor(0.75 * h));\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif ((alpha < beta) && (alpha > -beta))\r\n\t\t\t\t{\r\n\t\t\t\t\ta = new mxPoint(x + w, y);\r\n\t\t\t\t\tb = new mxPoint(x + w, y + h);\r\n\t\t\t\t}\r\n\t\t\t\telse if ((alpha > beta) && (alpha < pi2))\r\n\t\t\t\t{\r\n\t\t\t\t\ta = new mxPoint(x, y - Math.floor(0.25 * h));\r\n\t\t\t\t\tb = new mxPoint(x + Math.floor(1.5 * w), y\r\n\t\t\t\t\t\t\t+ Math.floor(0.5 * h));\r\n\t\t\t\t}\r\n\t\t\t\telse if ((alpha > pi2) && (alpha < (pi - beta)))\r\n\t\t\t\t{\r\n\t\t\t\t\ta = new mxPoint(x - Math.floor(0.5 * w), y\r\n\t\t\t\t\t\t\t+ Math.floor(0.5 * h));\r\n\t\t\t\t\tb = new mxPoint(x + w, y - Math.floor(0.25 * h));\r\n\t\t\t\t}\r\n\t\t\t\telse if (((alpha > (pi - beta)) && (alpha <= pi))\r\n\t\t\t\t\t\t|| ((alpha < (-pi + beta)) && (alpha >= -pi)))\r\n\t\t\t\t{\r\n\t\t\t\t\ta = new mxPoint(x, y);\r\n\t\t\t\t\tb = new mxPoint(x, y + h);\r\n\t\t\t\t}\r\n\t\t\t\telse if ((alpha < -beta) && (alpha > -pi2))\r\n\t\t\t\t{\r\n\t\t\t\t\ta = new mxPoint(x + Math.floor(1.5 * w), y\r\n\t\t\t\t\t\t\t+ Math.floor(0.5 * h));\r\n\t\t\t\t\tb = new mxPoint(x, y + Math.floor(1.25 * h));\r\n\t\t\t\t}\r\n\t\t\t\telse if ((alpha < -pi2) && (alpha > (-pi + beta)))\r\n\t\t\t\t{\r\n\t\t\t\t\ta = new mxPoint(x - Math.floor(0.5 * w), y\r\n\t\t\t\t\t\t\t+ Math.floor(0.5 * h));\r\n\t\t\t\t\tb = new mxPoint(x + w, y + Math.floor(1.25 * h));\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tvar beta = Math.atan2(h / 2, w / 4);\r\n\r\n\t\t\t\t//Special cases where intersects with hexagon corners\r\n\t\t\t\tif (alpha == beta)\r\n\t\t\t\t{\r\n\t\t\t\t\treturn new mxPoint(x + Math.floor(0.75 * w), y);\r\n\t\t\t\t}\r\n\t\t\t\telse if (alpha == (pi - beta))\r\n\t\t\t\t{\r\n\t\t\t\t\treturn new mxPoint(x + Math.floor(0.25 * w), y);\r\n\t\t\t\t}\r\n\t\t\t\telse if ((alpha == pi) || (alpha == -pi))\r\n\t\t\t\t{\r\n\t\t\t\t\treturn new mxPoint(x, y + Math.floor(0.5 * h));\r\n\t\t\t\t}\r\n\t\t\t\telse if (alpha == 0)\r\n\t\t\t\t{\r\n\t\t\t\t\treturn new mxPoint(x + w, y + Math.floor(0.5 * h));\r\n\t\t\t\t}\r\n\t\t\t\telse if (alpha == -beta)\r\n\t\t\t\t{\r\n\t\t\t\t\treturn new mxPoint(x + Math.floor(0.75 * w), y + h);\r\n\t\t\t\t}\r\n\t\t\t\telse if (alpha == (-pi + beta))\r\n\t\t\t\t{\r\n\t\t\t\t\treturn new mxPoint(x + Math.floor(0.25 * w), y + h);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif ((alpha > 0) && (alpha < beta))\r\n\t\t\t\t{\r\n\t\t\t\t\ta = new mxPoint(x + Math.floor(0.5 * w), y\r\n\t\t\t\t\t\t\t- Math.floor(0.5 * h));\r\n\t\t\t\t\tb = new mxPoint(x + Math.floor(1.25 * w), y + h);\r\n\t\t\t\t}\r\n\t\t\t\telse if ((alpha > beta) && (alpha < (pi - beta)))\r\n\t\t\t\t{\r\n\t\t\t\t\ta = new mxPoint(x, y);\r\n\t\t\t\t\tb = new mxPoint(x + w, y);\r\n\t\t\t\t}\r\n\t\t\t\telse if ((alpha > (pi - beta)) && (alpha < pi))\r\n\t\t\t\t{\r\n\t\t\t\t\ta = new mxPoint(x - Math.floor(0.25 * w), y + h);\r\n\t\t\t\t\tb = new mxPoint(x + Math.floor(0.5 * w), y\r\n\t\t\t\t\t\t\t- Math.floor(0.5 * h));\r\n\t\t\t\t}\r\n\t\t\t\telse if ((alpha < 0) && (alpha > -beta))\r\n\t\t\t\t{\r\n\t\t\t\t\ta = new mxPoint(x + Math.floor(0.5 * w), y\r\n\t\t\t\t\t\t\t+ Math.floor(1.5 * h));\r\n\t\t\t\t\tb = new mxPoint(x + Math.floor(1.25 * w), y);\r\n\t\t\t\t}\r\n\t\t\t\telse if ((alpha < -beta) && (alpha > (-pi + beta)))\r\n\t\t\t\t{\r\n\t\t\t\t\ta = new mxPoint(x, y + h);\r\n\t\t\t\t\tb = new mxPoint(x + w, y + h);\r\n\t\t\t\t}\r\n\t\t\t\telse if ((alpha < (-pi + beta)) && (alpha > -pi))\r\n\t\t\t\t{\r\n\t\t\t\t\ta = new mxPoint(x - Math.floor(0.25 * w), y);\r\n\t\t\t\t\tb = new mxPoint(x + Math.floor(0.5 * w), y\r\n\t\t\t\t\t\t\t+ Math.floor(1.5 * h));\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tresult = mxUtils.intersection(cx, cy, next.x, next.y, a.x, a.y, b.x, b.y);\r\n\t\t}\r\n\t\t\r\n\t\tif (result == null)\r\n\t\t{\r\n\t\t\treturn new mxPoint(cx, cy);\r\n\t\t}\r\n\t\t\r\n\t\treturn result;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2017, JGraph Ltd\r\n * Copyright (c) 2006-2017, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxPrintPreview\r\n * \r\n * Implements printing of a diagram across multiple pages. The following opens\r\n * a print preview for an existing graph:\r\n * \r\n * (code)\r\n * var preview = new mxPrintPreview(graph);\r\n * preview.open();\r\n * (end)\r\n * \r\n * Use <mxUtils.getScaleForPageCount> as follows in order to print the graph\r\n * across a given number of pages:\r\n * \r\n * (code)\r\n * var pageCount = mxUtils.prompt('Enter page count', '1');\r\n * \r\n * if (pageCount != null)\r\n * {\r\n *   var scale = mxUtils.getScaleForPageCount(pageCount, graph);\r\n *   var preview = new mxPrintPreview(graph, scale);\r\n *   preview.open();\r\n * }\r\n * (end)\r\n * \r\n * Additional pages:\r\n * \r\n * To add additional pages before and after the output, <getCoverPages> and\r\n * <getAppendices> can be used, respectively.\r\n * \r\n * (code)\r\n * var preview = new mxPrintPreview(graph, 1);\r\n * \r\n * preview.getCoverPages = function(w, h)\r\n * {\r\n *   return [this.renderPage(w, h, 0, 0, mxUtils.bind(this, function(div)\r\n *   {\r\n *     div.innerHTML = '<div style=\"position:relative;margin:4px;\">Cover Page</p>'\r\n *   }))];\r\n * };\r\n * \r\n * preview.getAppendices = function(w, h)\r\n * {\r\n *   return [this.renderPage(w, h, 0, 0, mxUtils.bind(this, function(div)\r\n *   {\r\n *     div.innerHTML = '<div style=\"position:relative;margin:4px;\">Appendix</p>'\r\n *   }))];\r\n * };\r\n * \r\n * preview.open();\r\n * (end)\r\n * \r\n * CSS:\r\n * \r\n * The CSS from the original page is not carried over to the print preview.\r\n * To add CSS to the page, use the css argument in the <open> function or\r\n * override <writeHead> to add the respective link tags as follows:\r\n * \r\n * (code)\r\n * var writeHead = preview.writeHead;\r\n * preview.writeHead = function(doc, css)\r\n * {\r\n *   writeHead.apply(this, arguments);\r\n *   doc.writeln('<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\">');\r\n * };\r\n * (end)\r\n * \r\n * Padding:\r\n * \r\n * To add a padding to the page in the preview (but not the print output), use\r\n * the following code:\r\n * \r\n * (code)\r\n * preview.writeHead = function(doc)\r\n * {\r\n *   writeHead.apply(this, arguments);\r\n *   \r\n *   doc.writeln('<style type=\"text/css\">');\r\n *   doc.writeln('@media screen {');\r\n *   doc.writeln('  body > div { padding-top:30px;padding-left:40px;box-sizing:content-box; }');\r\n *   doc.writeln('}');\r\n *   doc.writeln('</style>');\r\n * };\r\n * (end)\r\n * \r\n * Headers:\r\n * \r\n * Apart from setting the title argument in the mxPrintPreview constructor you\r\n * can override <renderPage> as follows to add a header to any page:\r\n * \r\n * (code)\r\n * var oldRenderPage = mxPrintPreview.prototype.renderPage;\r\n * mxPrintPreview.prototype.renderPage = function(w, h, x, y, content, pageNumber)\r\n * {\r\n *   var div = oldRenderPage.apply(this, arguments);\r\n *   \r\n *   var header = document.createElement('div');\r\n *   header.style.position = 'absolute';\r\n *   header.style.top = '0px';\r\n *   header.style.width = '100%';\r\n *   header.style.textAlign = 'right';\r\n *   mxUtils.write(header, 'Your header here');\r\n *   div.firstChild.appendChild(header);\r\n *   \r\n *   return div;\r\n * };\r\n * (end)\r\n * \r\n * The pageNumber argument contains the number of the current page, starting at\r\n * 1. To display a header on the first page only, check pageNumber and add a\r\n * vertical offset in the constructor call for the height of the header.\r\n * \r\n * Page Format:\r\n * \r\n * For landscape printing, use <mxConstants.PAGE_FORMAT_A4_LANDSCAPE> as\r\n * the pageFormat in <mxUtils.getScaleForPageCount> and <mxPrintPreview>.\r\n * Keep in mind that one can not set the defaults for the print dialog\r\n * of the operating system from JavaScript so the user must manually choose\r\n * a page format that matches this setting.\r\n * \r\n * You can try passing the following CSS directive to <open> to set the\r\n * page format in the print dialog to landscape. However, this CSS\r\n * directive seems to be ignored in most major browsers, including IE.\r\n * \r\n * (code)\r\n * @page {\r\n *   size: landscape;\r\n * }\r\n * (end)\r\n * \r\n * Note that the print preview behaves differently in IE when used from the\r\n * filesystem or via HTTP so printing should always be tested via HTTP.\r\n * \r\n * If you are using a DOCTYPE in the source page you can override <getDoctype>\r\n * and provide the same DOCTYPE for the print preview if required. Here is\r\n * an example for IE8 standards mode.\r\n * \r\n * (code)\r\n * var preview = new mxPrintPreview(graph);\r\n * preview.getDoctype = function()\r\n * {\r\n *   return '<!--[if IE]><meta http-equiv=\"X-UA-Compatible\" content=\"IE=5,IE=8\" ><![endif]-->';\r\n * };\r\n * preview.open();\r\n * (end)\r\n * \r\n * Constructor: mxPrintPreview\r\n *\r\n * Constructs a new print preview for the given parameters.\r\n * \r\n * Parameters:\r\n * \r\n * graph - <mxGraph> to be previewed.\r\n * scale - Optional scale of the output. Default is 1 / <mxGraph.pageScale>.\r\n * border - Border in pixels along each side of every page. Note that the\r\n * actual print function in the browser will add another border for\r\n * printing.\r\n * pageFormat - <mxRectangle> that specifies the page format (in pixels).\r\n * This should match the page format of the printer. Default uses the\r\n * <mxGraph.pageFormat> of the given graph.\r\n * x0 - Optional left offset of the output. Default is 0.\r\n * y0 - Optional top offset of the output. Default is 0.\r\n * borderColor - Optional color of the page border. Default is no border.\r\n * Note that a border is sometimes useful to highlight the printed page\r\n * border in the print preview of the browser.\r\n * title - Optional string that is used for the window title. Default\r\n * is 'Printer-friendly version'.\r\n * pageSelector - Optional boolean that specifies if the page selector\r\n * should appear in the window with the print preview. Default is true.\r\n */\r\nfunction mxPrintPreview(graph, scale, pageFormat, border, x0, y0, borderColor, title, pageSelector)\r\n{\r\n\tthis.graph = graph;\r\n\tthis.scale = (scale != null) ? scale : 1 / graph.pageScale;\r\n\tthis.border = (border != null) ? border : 0;\r\n\tthis.pageFormat = mxRectangle.fromRectangle((pageFormat != null) ? pageFormat : graph.pageFormat);\r\n\tthis.title = (title != null) ? title : 'Printer-friendly version';\r\n\tthis.x0 = (x0 != null) ? x0 : 0;\r\n\tthis.y0 = (y0 != null) ? y0 : 0;\r\n\tthis.borderColor = borderColor;\r\n\tthis.pageSelector = (pageSelector != null) ? pageSelector : true;\r\n};\r\n\r\n/**\r\n * Variable: graph\r\n * \r\n * Reference to the <mxGraph> that should be previewed.\r\n */\r\nmxPrintPreview.prototype.graph = null;\r\n\r\n/**\r\n * Variable: pageFormat\r\n *\r\n * Holds the <mxRectangle> that defines the page format.\r\n */\r\nmxPrintPreview.prototype.pageFormat = null;\r\n\r\n/**\r\n * Variable: scale\r\n * \r\n * Holds the scale of the print preview.\r\n */\r\nmxPrintPreview.prototype.scale = null;\r\n\r\n/**\r\n * Variable: border\r\n * \r\n * The border inset around each side of every page in the preview. This is set\r\n * to 0 if autoOrigin is false.\r\n */\r\nmxPrintPreview.prototype.border = 0;\r\n\r\n/**\r\n * Variable: marginTop\r\n * \r\n * The margin at the top of the page (number). Default is 0.\r\n */\r\nmxPrintPreview.prototype.marginTop = 0;\r\n\r\n/**\r\n * Variable: marginBottom\r\n * \r\n * The margin at the bottom of the page (number). Default is 0.\r\n */\r\nmxPrintPreview.prototype.marginBottom = 0;\r\n\r\n/**\r\n * Variable: x0\r\n * \r\n * Holds the horizontal offset of the output.\r\n */\r\nmxPrintPreview.prototype.x0 = 0;\r\n\r\n/**\r\n * Variable: y0\r\n *\r\n * Holds the vertical offset of the output.\r\n */\r\nmxPrintPreview.prototype.y0 = 0;\r\n\r\n/**\r\n * Variable: autoOrigin\r\n * \r\n * Specifies if the origin should be automatically computed based on the top,\r\n * left corner of the actual diagram contents. The required offset will be added\r\n * to <x0> and <y0> in <open>. Default is true.\r\n */\r\nmxPrintPreview.prototype.autoOrigin = true;\r\n\r\n/**\r\n * Variable: printOverlays\r\n * \r\n * Specifies if overlays should be printed. Default is false.\r\n */\r\nmxPrintPreview.prototype.printOverlays = false;\r\n\r\n/**\r\n * Variable: printControls\r\n * \r\n * Specifies if controls (such as folding icons) should be printed. Default is\r\n * false.\r\n */\r\nmxPrintPreview.prototype.printControls = false;\r\n\r\n/**\r\n * Variable: printBackgroundImage\r\n * \r\n * Specifies if the background image should be printed. Default is false.\r\n */\r\nmxPrintPreview.prototype.printBackgroundImage = false;\r\n\r\n/**\r\n * Variable: backgroundColor\r\n * \r\n * Holds the color value for the page background color. Default is #ffffff.\r\n */\r\nmxPrintPreview.prototype.backgroundColor = '#ffffff';\r\n\r\n/**\r\n * Variable: borderColor\r\n * \r\n * Holds the color value for the page border.\r\n */\r\nmxPrintPreview.prototype.borderColor = null;\r\n\r\n/**\r\n * Variable: title\r\n * \r\n * Holds the title of the preview window.\r\n */\r\nmxPrintPreview.prototype.title = null;\r\n\r\n/**\r\n * Variable: pageSelector\r\n * \r\n * Boolean that specifies if the page selector should be\r\n * displayed. Default is true.\r\n */\r\nmxPrintPreview.prototype.pageSelector = null;\r\n\r\n/**\r\n * Variable: wnd\r\n * \r\n * Reference to the preview window.\r\n */\r\nmxPrintPreview.prototype.wnd = null;\r\n\r\n/**\r\n * Variable: targetWindow\r\n * \r\n * Assign any window here to redirect the rendering in <open>.\r\n */\r\nmxPrintPreview.prototype.targetWindow = null;\r\n\r\n/**\r\n * Variable: pageCount\r\n * \r\n * Holds the actual number of pages in the preview.\r\n */\r\nmxPrintPreview.prototype.pageCount = 0;\r\n\r\n/**\r\n * Variable: clipping\r\n * \r\n * Specifies is clipping should be used to avoid creating too many cell states\r\n * in large diagrams. The bounding box of the cells in the original diagram is\r\n * used if this is enabled. Default is true.\r\n */\r\nmxPrintPreview.prototype.clipping = true;\r\n\r\n/**\r\n * Function: getWindow\r\n * \r\n * Returns <wnd>.\r\n */\r\nmxPrintPreview.prototype.getWindow = function()\r\n{\r\n\treturn this.wnd;\r\n};\r\n\r\n/**\r\n * Function: getDocType\r\n * \r\n * Returns the string that should go before the HTML tag in the print preview\r\n * page. This implementation returns an X-UA meta tag for IE5 in quirks mode,\r\n * IE8 in IE8 standards mode and edge in IE9 standards mode.\r\n */\r\nmxPrintPreview.prototype.getDoctype = function()\r\n{\r\n\tvar dt = '';\r\n\t\r\n\tif (document.documentMode == 5)\r\n\t{\r\n\t\tdt = '<meta http-equiv=\"X-UA-Compatible\" content=\"IE=5\">';\r\n\t}\r\n\telse if (document.documentMode == 8)\r\n\t{\r\n\t\tdt = '<meta http-equiv=\"X-UA-Compatible\" content=\"IE=8\">';\r\n\t}\r\n\telse if (document.documentMode > 8)\r\n\t{\r\n\t\t// Comment needed to make standards doctype apply in IE\r\n\t\tdt = '<!--[if IE]><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><![endif]-->';\r\n\t}\r\n\t\r\n\treturn dt;\r\n};\r\n\r\n/**\r\n * Function: appendGraph\r\n * \r\n * Adds the given graph to the existing print preview.\r\n * \r\n * Parameters:\r\n * \r\n * css - Optional CSS string to be used in the head section.\r\n * targetWindow - Optional window that should be used for rendering. If\r\n * this is specified then no HEAD tag, CSS and BODY tag will be written.\r\n */\r\nmxPrintPreview.prototype.appendGraph = function(graph, scale, x0, y0, forcePageBreaks, keepOpen)\r\n{\r\n\tthis.graph = graph;\r\n\tthis.scale = (scale != null) ? scale : 1 / graph.pageScale;\r\n\tthis.x0 = x0;\r\n\tthis.y0 = y0;\r\n\tthis.open(null, null, forcePageBreaks, keepOpen);\r\n};\r\n\r\n/**\r\n * Function: open\r\n * \r\n * Shows the print preview window. The window is created here if it does\r\n * not exist.\r\n * \r\n * Parameters:\r\n * \r\n * css - Optional CSS string to be used in the head section.\r\n * targetWindow - Optional window that should be used for rendering. If\r\n * this is specified then no HEAD tag, CSS and BODY tag will be written.\r\n */\r\nmxPrintPreview.prototype.open = function(css, targetWindow, forcePageBreaks, keepOpen)\r\n{\r\n\t// Closing the window while the page is being rendered may cause an\r\n\t// exception in IE. This and any other exceptions are simply ignored.\r\n\tvar previousInitializeOverlay = this.graph.cellRenderer.initializeOverlay;\r\n\tvar div = null;\r\n\t\r\n\ttry\r\n\t{\r\n\t\t// Temporarily overrides the method to redirect rendering of overlays\r\n\t\t// to the draw pane so that they are visible in the printout\r\n\t\tif (this.printOverlays)\r\n\t\t{\r\n\t\t\tthis.graph.cellRenderer.initializeOverlay = function(state, overlay)\r\n\t\t\t{\r\n\t\t\t\toverlay.init(state.view.getDrawPane());\r\n\t\t\t};\r\n\t\t}\r\n\t\t\r\n\t\tif (this.printControls)\r\n\t\t{\r\n\t\t\tthis.graph.cellRenderer.initControl = function(state, control, handleEvents, clickHandler)\r\n\t\t\t{\r\n\t\t\t\tcontrol.dialect = state.view.graph.dialect;\r\n\t\t\t\tcontrol.init(state.view.getDrawPane());\r\n\t\t\t};\r\n\t\t}\r\n\t\t\r\n\t\tthis.wnd = (targetWindow != null) ? targetWindow : this.wnd;\r\n\t\tvar isNewWindow = false;\r\n\t\t\r\n\t\tif (this.wnd == null)\r\n\t\t{\r\n\t\t\tisNewWindow = true;\r\n\t\t\tthis.wnd = window.open();\r\n\t\t}\r\n\t\t\r\n\t\tvar doc = this.wnd.document;\r\n\t\t\r\n\t\tif (isNewWindow)\r\n\t\t{\r\n\t\t\tvar dt = this.getDoctype();\r\n\t\t\t\r\n\t\t\tif (dt != null && dt.length > 0)\r\n\t\t\t{\r\n\t\t\t\tdoc.writeln(dt);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (mxClient.IS_VML)\r\n\t\t\t{\r\n\t\t\t\tdoc.writeln('<html xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:o=\"urn:schemas-microsoft-com:office:office\">');\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tif (document.compatMode === 'CSS1Compat')\r\n\t\t\t\t{\r\n\t\t\t\t\tdoc.writeln('<!DOCTYPE html>');\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tdoc.writeln('<html>');\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tdoc.writeln('<head>');\r\n\t\t\tthis.writeHead(doc, css);\r\n\t\t\tdoc.writeln('</head>');\r\n\t\t\tdoc.writeln('<body class=\"mxPage\">');\r\n\t\t}\r\n\r\n\t\t// Computes the horizontal and vertical page count\r\n\t\tvar bounds = this.graph.getGraphBounds().clone();\r\n\t\tvar currentScale = this.graph.getView().getScale();\r\n\t\tvar sc = currentScale / this.scale;\r\n\t\tvar tr = this.graph.getView().getTranslate();\r\n\t\t\r\n\t\t// Uses the absolute origin with no offset for all printing\r\n\t\tif (!this.autoOrigin)\r\n\t\t{\r\n\t\t\tthis.x0 -= tr.x * this.scale;\r\n\t\t\tthis.y0 -= tr.y * this.scale;\r\n\t\t\tbounds.width += bounds.x;\r\n\t\t\tbounds.height += bounds.y;\r\n\t\t\tbounds.x = 0;\r\n\t\t\tbounds.y = 0;\r\n\t\t\tthis.border = 0;\r\n\t\t}\r\n\t\t\r\n\t\t// Store the available page area\r\n\t\tvar availableWidth = this.pageFormat.width - (this.border * 2);\r\n\t\tvar availableHeight = this.pageFormat.height - (this.border * 2);\r\n\t\r\n\t\t// Adds margins to page format\r\n\t\tthis.pageFormat.height += this.marginTop + this.marginBottom;\r\n\r\n\t\t// Compute the unscaled, untranslated bounds to find\r\n\t\t// the number of vertical and horizontal pages\r\n\t\tbounds.width /= sc;\r\n\t\tbounds.height /= sc;\r\n\r\n\t\tvar hpages = Math.max(1, Math.ceil((bounds.width + this.x0) / availableWidth));\r\n\t\tvar vpages = Math.max(1, Math.ceil((bounds.height + this.y0) / availableHeight));\r\n\t\tthis.pageCount = hpages * vpages;\r\n\t\t\r\n\t\tvar writePageSelector = mxUtils.bind(this, function()\r\n\t\t{\r\n\t\t\tif (this.pageSelector && (vpages > 1 || hpages > 1))\r\n\t\t\t{\r\n\t\t\t\tvar table = this.createPageSelector(vpages, hpages);\r\n\t\t\t\tdoc.body.appendChild(table);\r\n\t\t\t\t\r\n\t\t\t\t// Implements position: fixed in IE quirks mode\r\n\t\t\t\tif (mxClient.IS_IE && doc.documentMode == null || doc.documentMode == 5 || doc.documentMode == 8 || doc.documentMode == 7)\r\n\t\t\t\t{\r\n\t\t\t\t\ttable.style.position = 'absolute';\r\n\t\t\t\t\t\r\n\t\t\t\t\tvar update = function()\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ttable.style.top = ((doc.body.scrollTop || doc.documentElement.scrollTop) + 10) + 'px';\r\n\t\t\t\t\t};\r\n\t\t\t\t\t\r\n\t\t\t\t\tmxEvent.addListener(this.wnd, 'scroll', function(evt)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tupdate();\r\n\t\t\t\t\t});\r\n\t\t\t\t\t\r\n\t\t\t\t\tmxEvent.addListener(this.wnd, 'resize', function(evt)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tupdate();\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t});\r\n\t\t\r\n\t\tvar addPage = mxUtils.bind(this, function(div, addBreak)\r\n\t\t{\r\n\t\t\t// Border of the DIV (aka page) inside the document\r\n\t\t\tif (this.borderColor != null)\r\n\t\t\t{\r\n\t\t\t\tdiv.style.borderColor = this.borderColor;\r\n\t\t\t\tdiv.style.borderStyle = 'solid';\r\n\t\t\t\tdiv.style.borderWidth = '1px';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Needs to be assigned directly because IE doesn't support\r\n\t\t\t// child selectors, eg. body > div { background: white; }\r\n\t\t\tdiv.style.background = this.backgroundColor;\r\n\t\t\t\r\n\t\t\tif (forcePageBreaks || addBreak)\r\n\t\t\t{\r\n\t\t\t\tdiv.style.pageBreakAfter = 'always';\r\n\t\t\t}\r\n\r\n\t\t\t// NOTE: We are dealing with cross-window DOM here, which\r\n\t\t\t// is a problem in IE, so we copy the HTML markup instead.\r\n\t\t\t// The underlying problem is that the graph display markup\r\n\t\t\t// creation (in mxShape, mxGraphView) is hardwired to using\r\n\t\t\t// document.createElement and hence we must use this document\r\n\t\t\t// to create the complete page and then copy it over to the\r\n\t\t\t// new window.document. This can be fixed later by using the\r\n\t\t\t// ownerDocument of the container in mxShape and mxGraphView.\r\n\t\t\tif (isNewWindow && (mxClient.IS_IE || document.documentMode >= 11 || mxClient.IS_EDGE))\r\n\t\t\t{\r\n\t\t\t\t// For some obscure reason, removing the DIV from the\r\n\t\t\t\t// parent before fetching its outerHTML has missing\r\n\t\t\t\t// fillcolor properties and fill children, so the div\r\n\t\t\t\t// must be removed afterwards to keep the fillcolors.\r\n\t\t\t\tdoc.writeln(div.outerHTML);\r\n\t\t\t\tdiv.parentNode.removeChild(div);\r\n\t\t\t}\r\n\t\t\telse if (mxClient.IS_IE || document.documentMode >= 11 || mxClient.IS_EDGE)\r\n\t\t\t{\r\n\t\t\t\tvar clone = doc.createElement('div');\r\n\t\t\t\tclone.innerHTML = div.outerHTML;\r\n\t\t\t\tclone = clone.getElementsByTagName('div')[0];\r\n\t\t\t\tdoc.body.appendChild(clone);\r\n\t\t\t\tdiv.parentNode.removeChild(div);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tdiv.parentNode.removeChild(div);\r\n\t\t\t\tdoc.body.appendChild(div);\r\n\t\t\t}\r\n\r\n\t\t\tif (forcePageBreaks || addBreak)\r\n\t\t\t{\r\n\t\t\t\tthis.addPageBreak(doc);\r\n\t\t\t}\r\n\t\t});\r\n\t\t\r\n\t\tvar cov = this.getCoverPages(this.pageFormat.width, this.pageFormat.height);\r\n\t\t\r\n\t\tif (cov != null)\r\n\t\t{\r\n\t\t\tfor (var i = 0; i < cov.length; i++)\r\n\t\t\t{\r\n\t\t\t\taddPage(cov[i], true);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tvar apx = this.getAppendices(this.pageFormat.width, this.pageFormat.height);\r\n\t\t\r\n\t\t// Appends each page to the page output for printing, making\r\n\t\t// sure there will be a page break after each page (ie. div)\r\n\t\tfor (var i = 0; i < vpages; i++)\r\n\t\t{\r\n\t\t\tvar dy = i * availableHeight / this.scale - this.y0 / this.scale +\r\n\t\t\t\t\t(bounds.y - tr.y * currentScale) / currentScale;\r\n\t\t\t\r\n\t\t\tfor (var j = 0; j < hpages; j++)\r\n\t\t\t{\r\n\t\t\t\tif (this.wnd == null)\r\n\t\t\t\t{\r\n\t\t\t\t\treturn null;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tvar dx = j * availableWidth / this.scale - this.x0 / this.scale +\r\n\t\t\t\t\t\t(bounds.x - tr.x * currentScale) / currentScale;\r\n\t\t\t\tvar pageNum = i * hpages + j + 1;\r\n\t\t\t\tvar clip = new mxRectangle(dx, dy, availableWidth, availableHeight);\r\n\t\t\t\tdiv = this.renderPage(this.pageFormat.width, this.pageFormat.height, 0, 0, mxUtils.bind(this, function(div)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.addGraphFragment(-dx, -dy, this.scale, pageNum, div, clip);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (this.printBackgroundImage)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.insertBackgroundImage(div, -dx, -dy);\r\n\t\t\t\t\t}\r\n\t\t\t\t}), pageNum);\r\n\r\n\t\t\t\t// Gives the page a unique ID for later accessing the page\r\n\t\t\t\tdiv.setAttribute('id', 'mxPage-'+pageNum);\r\n\r\n\t\t\t\taddPage(div, apx != null || i < vpages - 1 || j < hpages - 1);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (apx != null)\r\n\t\t{\r\n\t\t\tfor (var i = 0; i < apx.length; i++)\r\n\t\t\t{\r\n\t\t\t\taddPage(apx[i], i < apx.length - 1);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (isNewWindow && !keepOpen)\r\n\t\t{\r\n\t\t\tthis.closeDocument();\r\n\t\t\twritePageSelector();\r\n\t\t}\r\n\t\t\r\n\t\tthis.wnd.focus();\r\n\t}\r\n\tcatch (e)\r\n\t{\r\n\t\t// Removes the DIV from the document in case of an error\r\n\t\tif (div != null && div.parentNode != null)\r\n\t\t{\r\n\t\t\tdiv.parentNode.removeChild(div);\r\n\t\t}\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tthis.graph.cellRenderer.initializeOverlay = previousInitializeOverlay;\r\n\t}\r\n\r\n\treturn this.wnd;\r\n};\r\n\r\n/**\r\n * Function: addPageBreak\r\n * \r\n * Adds a page break to the given document.\r\n */\r\nmxPrintPreview.prototype.addPageBreak = function(doc)\r\n{\r\n\tvar hr = doc.createElement('hr');\r\n\thr.className = 'mxPageBreak';\r\n\tdoc.body.appendChild(hr);\r\n};\r\n\r\n/**\r\n * Function: closeDocument\r\n * \r\n * Writes the closing tags for body and page after calling <writePostfix>.\r\n */\r\nmxPrintPreview.prototype.closeDocument = function()\r\n{\r\n\ttry\r\n\t{\r\n\t\tif (this.wnd != null && this.wnd.document != null)\r\n\t\t{\r\n\t\t\tvar doc = this.wnd.document;\r\n\t\t\t\r\n\t\t\tthis.writePostfix(doc);\r\n\t\t\tdoc.writeln('</body>');\r\n\t\t\tdoc.writeln('</html>');\r\n\t\t\tdoc.close();\r\n\t\t\t\r\n\t\t\t// Removes all event handlers in the print output\r\n\t\t\tmxEvent.release(doc.body);\r\n\t\t}\r\n\t}\r\n\tcatch (e)\r\n\t{\r\n\t\t// ignore any errors resulting from wnd no longer being available\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: writeHead\r\n * \r\n * Writes the HEAD section into the given document, without the opening\r\n * and closing HEAD tags.\r\n */\r\nmxPrintPreview.prototype.writeHead = function(doc, css)\r\n{\r\n\tif (this.title != null)\r\n\t{\r\n\t\tdoc.writeln('<title>' + this.title + '</title>');\r\n\t}\r\n\t\r\n\t// Adds required namespaces\r\n\tif (mxClient.IS_VML)\r\n\t{\r\n\t\tdoc.writeln('<style type=\"text/css\">v\\\\:*{behavior:url(#default#VML)}o\\\\:*{behavior:url(#default#VML)}</style>');\r\n\t}\r\n\r\n\t// Adds all required stylesheets\r\n\tmxClient.link('stylesheet', mxClient.basePath + '/css/common.css', doc);\r\n\r\n\t// Removes horizontal rules and page selector from print output\r\n\tdoc.writeln('<style type=\"text/css\">');\r\n\tdoc.writeln('@media print {');\r\n\tdoc.writeln('  * { -webkit-print-color-adjust: exact; }');\r\n\tdoc.writeln('  table.mxPageSelector { display: none; }');\r\n\tdoc.writeln('  hr.mxPageBreak { display: none; }');\r\n\tdoc.writeln('}');\r\n\tdoc.writeln('@media screen {');\r\n\t\r\n\t// NOTE: position: fixed is not supported in IE, so the page selector\r\n\t// position (absolute) needs to be updated in IE (see below)\r\n\tdoc.writeln('  table.mxPageSelector { position: fixed; right: 10px; top: 10px;' +\r\n\t\t\t'font-family: Arial; font-size:10pt; border: solid 1px darkgray;' +\r\n\t\t\t'background: white; border-collapse:collapse; }');\r\n\tdoc.writeln('  table.mxPageSelector td { border: solid 1px gray; padding:4px; }');\r\n\tdoc.writeln('  body.mxPage { background: gray; }');\r\n\tdoc.writeln('}');\r\n\t\r\n\tif (css != null)\r\n\t{\r\n\t\tdoc.writeln(css);\r\n\t}\r\n\t\r\n\tdoc.writeln('</style>');\r\n};\r\n\r\n/**\r\n * Function: writePostfix\r\n * \r\n * Called before closing the body of the page. This implementation is empty.\r\n */\r\nmxPrintPreview.prototype.writePostfix = function(doc)\r\n{\r\n\t// empty\r\n};\r\n\r\n/**\r\n * Function: createPageSelector\r\n * \r\n * Creates the page selector table.\r\n */\r\nmxPrintPreview.prototype.createPageSelector = function(vpages, hpages)\r\n{\r\n\tvar doc = this.wnd.document;\r\n\tvar table = doc.createElement('table');\r\n\ttable.className = 'mxPageSelector';\r\n\ttable.setAttribute('border', '0');\r\n\r\n\tvar tbody = doc.createElement('tbody');\r\n\t\r\n\tfor (var i = 0; i < vpages; i++)\r\n\t{\r\n\t\tvar row = doc.createElement('tr');\r\n\t\t\r\n\t\tfor (var j = 0; j < hpages; j++)\r\n\t\t{\r\n\t\t\tvar pageNum = i * hpages + j + 1;\r\n\t\t\tvar cell = doc.createElement('td');\r\n\t\t\tvar a = doc.createElement('a');\r\n\t\t\ta.setAttribute('href', '#mxPage-' + pageNum);\r\n\r\n\t\t\t// Workaround for FF where the anchor is appended to the URL of the original document\r\n\t\t\tif (mxClient.IS_NS && !mxClient.IS_SF && !mxClient.IS_GC)\r\n\t\t\t{\r\n\t\t\t\tvar js = 'var page = document.getElementById(\\'mxPage-' + pageNum + '\\');page.scrollIntoView(true);event.preventDefault();';\r\n\t\t\t\ta.setAttribute('onclick', js);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tmxUtils.write(a, pageNum, doc);\r\n\t\t\tcell.appendChild(a);\r\n\t\t\trow.appendChild(cell);\r\n\t\t}\r\n\t\t\r\n\t\ttbody.appendChild(row);\r\n\t}\r\n\t\r\n\ttable.appendChild(tbody);\r\n\t\r\n\treturn table;\r\n};\r\n\r\n/**\r\n * Function: renderPage\r\n * \r\n * Creates a DIV that prints a single page of the given\r\n * graph using the given scale and returns the DIV that\r\n * represents the page.\r\n * \r\n * Parameters:\r\n * \r\n * w - Width of the page in pixels.\r\n * h - Height of the page in pixels.\r\n * dx - Optional horizontal page offset in pixels (used internally).\r\n * dy - Optional vertical page offset in pixels (used internally).\r\n * content - Callback that adds the HTML content to the inner div of a page.\r\n * Takes the inner div as the argument.\r\n * pageNumber - Integer representing the page number.\r\n */\r\nmxPrintPreview.prototype.renderPage = function(w, h, dx, dy, content, pageNumber)\r\n{\r\n\tvar doc = this.wnd.document;\r\n\tvar div = document.createElement('div');\r\n\tvar arg = null;\r\n\r\n\ttry\r\n\t{\r\n\t\t// Workaround for ignored clipping in IE 9 standards\r\n\t\t// when printing with page breaks and HTML labels.\r\n\t\tif (dx != 0 || dy != 0)\r\n\t\t{\r\n\t\t\tdiv.style.position = 'relative';\r\n\t\t\tdiv.style.width = w + 'px';\r\n\t\t\tdiv.style.height = h + 'px';\r\n\t\t\tdiv.style.pageBreakInside = 'avoid';\r\n\t\t\t\r\n\t\t\tvar innerDiv = document.createElement('div');\r\n\t\t\tinnerDiv.style.position = 'relative';\r\n\t\t\tinnerDiv.style.top = this.border + 'px';\r\n\t\t\tinnerDiv.style.left = this.border + 'px';\r\n\t\t\tinnerDiv.style.width = (w - 2 * this.border) + 'px';\r\n\t\t\tinnerDiv.style.height = (h - 2 * this.border) + 'px';\r\n\t\t\tinnerDiv.style.overflow = 'hidden';\r\n\t\t\t\r\n\t\t\tvar viewport = document.createElement('div');\r\n\t\t\tviewport.style.position = 'relative';\r\n\t\t\tviewport.style.marginLeft = dx + 'px';\r\n\t\t\tviewport.style.marginTop = dy + 'px';\r\n\r\n\t\t\t// FIXME: IE8 standards output problems\r\n\t\t\tif (doc.documentMode == 8)\r\n\t\t\t{\r\n\t\t\t\tinnerDiv.style.position = 'absolute';\r\n\t\t\t\tviewport.style.position = 'absolute';\r\n\t\t\t}\r\n\t\t\r\n\t\t\tif (doc.documentMode == 10)\r\n\t\t\t{\r\n\t\t\t\tviewport.style.width = '100%';\r\n\t\t\t\tviewport.style.height = '100%';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tinnerDiv.appendChild(viewport);\r\n\t\t\tdiv.appendChild(innerDiv);\r\n\t\t\tdocument.body.appendChild(div);\r\n\t\t\targ = viewport;\r\n\t\t}\r\n\t\t// FIXME: IE10/11 too many pages\r\n\t\telse\r\n\t\t{\r\n\t\t\tdiv.style.width = w + 'px';\r\n\t\t\tdiv.style.height = h + 'px';\r\n\t\t\tdiv.style.overflow = 'hidden';\r\n\t\t\tdiv.style.pageBreakInside = 'avoid';\r\n\t\t\t\r\n\t\t\t// IE8 uses above branch currently\r\n\t\t\tif (doc.documentMode == 8)\r\n\t\t\t{\r\n\t\t\t\tdiv.style.position = 'relative';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar innerDiv = document.createElement('div');\r\n\t\t\tinnerDiv.style.width = (w - 2 * this.border) + 'px';\r\n\t\t\tinnerDiv.style.height = (h - 2 * this.border) + 'px';\r\n\t\t\tinnerDiv.style.overflow = 'hidden';\r\n\r\n\t\t\tif (mxClient.IS_IE && (doc.documentMode == null || doc.documentMode == 5 || doc.documentMode == 8 || doc.documentMode == 7))\r\n\t\t\t{\r\n\t\t\t\tinnerDiv.style.marginTop = this.border + 'px';\r\n\t\t\t\tinnerDiv.style.marginLeft = this.border + 'px';\t\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tinnerDiv.style.top = this.border + 'px';\r\n\t\t\t\tinnerDiv.style.left = this.border + 'px';\r\n\t\t\t}\r\n\t\r\n\t\t\tif (this.graph.dialect == mxConstants.DIALECT_VML)\r\n\t\t\t{\r\n\t\t\t\tinnerDiv.style.position = 'absolute';\r\n\t\t\t}\r\n\r\n\t\t\tdiv.appendChild(innerDiv);\r\n\t\t\tdocument.body.appendChild(div);\r\n\t\t\targ = innerDiv;\r\n\t\t}\r\n\t}\r\n\tcatch (e)\r\n\t{\r\n\t\tdiv.parentNode.removeChild(div);\r\n\t\tdiv = null;\r\n\t\t\r\n\t\tthrow e;\r\n\t}\r\n\r\n\tcontent(arg);\r\n\t \r\n\treturn div;\r\n};\r\n\r\n/**\r\n * Function: getRoot\r\n * \r\n * Returns the root cell for painting the graph.\r\n */\r\nmxPrintPreview.prototype.getRoot = function()\r\n{\r\n\tvar root = this.graph.view.currentRoot;\r\n\t\r\n\tif (root == null)\r\n\t{\r\n\t\troot = this.graph.getModel().getRoot();\r\n\t}\r\n\t\r\n\treturn root;\r\n};\r\n\r\n/**\r\n * Function: addGraphFragment\r\n * \r\n * Adds a graph fragment to the given div.\r\n * \r\n * Parameters:\r\n * \r\n * dx - Horizontal translation for the diagram.\r\n * dy - Vertical translation for the diagram.\r\n * scale - Scale for the diagram.\r\n * pageNumber - Number of the page to be rendered.\r\n * div - Div that contains the output.\r\n * clip - Contains the clipping rectangle as an <mxRectangle>.\r\n */\r\nmxPrintPreview.prototype.addGraphFragment = function(dx, dy, scale, pageNumber, div, clip)\r\n{\r\n\tvar view = this.graph.getView();\r\n\tvar previousContainer = this.graph.container;\r\n\tthis.graph.container = div;\r\n\t\r\n\tvar canvas = view.getCanvas();\r\n\tvar backgroundPane = view.getBackgroundPane();\r\n\tvar drawPane = view.getDrawPane();\r\n\tvar overlayPane = view.getOverlayPane();\r\n\r\n\tif (this.graph.dialect == mxConstants.DIALECT_SVG)\r\n\t{\r\n\t\tview.createSvg();\r\n\t\t\r\n\t\t// Uses CSS transform for scaling\r\n\t\tif (!mxClient.NO_FO)\r\n\t\t{\r\n\t\t\tvar g = view.getDrawPane().parentNode;\r\n\t\t\tvar prev = g.getAttribute('transform');\r\n\t\t\tg.setAttribute('transformOrigin', '0 0');\r\n\t\t\tg.setAttribute('transform', 'scale(' + scale + ',' + scale + ')' +\r\n\t\t\t\t'translate(' + dx + ',' + dy + ')');\r\n\t\t\t\r\n\t\t\tscale = 1;\r\n\t\t\tdx = 0;\r\n\t\t\tdy = 0;\r\n\t\t}\r\n\t}\r\n\telse if (this.graph.dialect == mxConstants.DIALECT_VML)\r\n\t{\r\n\t\tview.createVml();\r\n\t}\r\n\telse\r\n\t{\r\n\t\tview.createHtml();\r\n\t}\r\n\t\r\n\t// Disables events on the view\r\n\tvar eventsEnabled = view.isEventsEnabled();\r\n\tview.setEventsEnabled(false);\r\n\t\r\n\t// Disables the graph to avoid cursors\r\n\tvar graphEnabled = this.graph.isEnabled();\r\n\tthis.graph.setEnabled(false);\r\n\r\n\t// Resets the translation\r\n\tvar translate = view.getTranslate();\r\n\tview.translate = new mxPoint(dx, dy);\r\n\t\r\n\t// Redraws only states that intersect the clip\r\n\tvar redraw = this.graph.cellRenderer.redraw;\r\n\tvar states = view.states;\r\n\tvar s = view.scale;\r\n\r\n\t// Gets the transformed clip for intersection check below\r\n\tif (this.clipping)\r\n\t{\r\n\t\tvar tempClip = new mxRectangle((clip.x + translate.x) * s, (clip.y + translate.y) * s,\r\n\t\t\t\tclip.width * s / scale, clip.height * s / scale);\r\n\t\t\r\n\t\t// Checks clipping rectangle for speedup\r\n\t\t// Must create terminal states for edge clipping even if terminal outside of clip\r\n\t\tthis.graph.cellRenderer.redraw = function(state, force, rendering)\r\n\t\t{\r\n\t\t\tif (state != null)\r\n\t\t\t{\r\n\t\t\t\t// Gets original state from graph to find bounding box\r\n\t\t\t\tvar orig = states.get(state.cell);\r\n\t\t\t\t\r\n\t\t\t\tif (orig != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar bbox = view.getBoundingBox(orig, false);\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Stops rendering if outside clip for speedup\r\n\t\t\t\t\tif (bbox != null && !mxUtils.intersects(tempClip, bbox))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t//return;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tredraw.apply(this, arguments);\r\n\t\t};\r\n\t}\r\n\t\r\n\tvar temp = null;\r\n\t\r\n\ttry\r\n\t{\r\n\t\t// Creates the temporary cell states in the view and\r\n\t\t// draws them onto the temporary DOM nodes in the view\r\n\t\tvar cells = [this.getRoot()];\r\n\t\ttemp = new mxTemporaryCellStates(view, scale, cells, null, mxUtils.bind(this, function(state)\r\n\t\t{\r\n\t\t\treturn this.getLinkForCellState(state);\r\n\t\t}));\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\t// Removes overlay pane with selection handles\r\n\t\t// controls and icons from the print output\r\n\t\tif (mxClient.IS_IE)\r\n\t\t{\r\n\t\t\tview.overlayPane.innerHTML = '';\r\n\t\t\tview.canvas.style.overflow = 'hidden';\r\n\t\t\tview.canvas.style.position = 'relative';\r\n\t\t\tview.canvas.style.top = this.marginTop + 'px';\r\n\t\t\tview.canvas.style.width = clip.width + 'px';\r\n\t\t\tview.canvas.style.height = clip.height + 'px';\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Removes everything but the SVG node\r\n\t\t\tvar tmp = div.firstChild;\r\n\r\n\t\t\twhile (tmp != null)\r\n\t\t\t{\r\n\t\t\t\tvar next = tmp.nextSibling;\r\n\t\t\t\tvar name = tmp.nodeName.toLowerCase();\r\n\r\n\t\t\t\t// Note: Width and height are required in FF 11\r\n\t\t\t\tif (name == 'svg')\r\n\t\t\t\t{\r\n\t\t\t\t\ttmp.style.overflow = 'hidden';\r\n\t\t\t\t\ttmp.style.position = 'relative';\r\n\t\t\t\t\ttmp.style.top = this.marginTop + 'px';\r\n\t\t\t\t\ttmp.setAttribute('width', clip.width);\r\n\t\t\t\t\ttmp.setAttribute('height', clip.height);\r\n\t\t\t\t\ttmp.style.width = '';\r\n\t\t\t\t\ttmp.style.height = '';\r\n\t\t\t\t}\r\n\t\t\t\t// Tries to fetch all text labels and only text labels\r\n\t\t\t\telse if (tmp.style.cursor != 'default' && name != 'div')\r\n\t\t\t\t{\r\n\t\t\t\t\ttmp.parentNode.removeChild(tmp);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\ttmp = next;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Puts background image behind SVG output\r\n\t\tif (this.printBackgroundImage)\r\n\t\t{\r\n\t\t\tvar svgs = div.getElementsByTagName('svg');\r\n\t\t\t\r\n\t\t\tif (svgs.length > 0)\r\n\t\t\t{\r\n\t\t\t\tsvgs[0].style.position = 'absolute';\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Completely removes the overlay pane to remove more handles\r\n\t\tview.overlayPane.parentNode.removeChild(view.overlayPane);\r\n\r\n\t\t// Restores the state of the view\r\n\t\tthis.graph.setEnabled(graphEnabled);\r\n\t\tthis.graph.container = previousContainer;\r\n\t\tthis.graph.cellRenderer.redraw = redraw;\r\n\t\tview.canvas = canvas;\r\n\t\tview.backgroundPane = backgroundPane;\r\n\t\tview.drawPane = drawPane;\r\n\t\tview.overlayPane = overlayPane;\r\n\t\tview.translate = translate;\r\n\t\ttemp.destroy();\r\n\t\tview.setEventsEnabled(eventsEnabled);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getLinkForCellState\r\n * \r\n * Returns the link for the given cell state. This returns null.\r\n */\r\nmxPrintPreview.prototype.getLinkForCellState = function(state)\r\n{\r\n\treturn this.graph.getLinkForCell(state.cell);\r\n};\r\n\r\n/**\r\n * Function: insertBackgroundImage\r\n * \r\n * Inserts the background image into the given div.\r\n */\r\nmxPrintPreview.prototype.insertBackgroundImage = function(div, dx, dy)\r\n{\r\n\tvar bg = this.graph.backgroundImage;\r\n\t\r\n\tif (bg != null)\r\n\t{\r\n\t\tvar img = document.createElement('img');\r\n\t\timg.style.position = 'absolute';\r\n\t\timg.style.marginLeft = Math.round(dx * this.scale) + 'px';\r\n\t\timg.style.marginTop = Math.round(dy * this.scale) + 'px';\r\n\t\timg.setAttribute('width', Math.round(this.scale * bg.width));\r\n\t\timg.setAttribute('height', Math.round(this.scale * bg.height));\r\n\t\timg.src = bg.src;\r\n\t\t\r\n\t\tdiv.insertBefore(img, div.firstChild);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getCoverPages\r\n * \r\n * Returns the pages to be added before the print output. This returns null.\r\n */\r\nmxPrintPreview.prototype.getCoverPages = function()\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: getAppendices\r\n * \r\n * Returns the pages to be added after the print output. This returns null.\r\n */\r\nmxPrintPreview.prototype.getAppendices = function()\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: print\r\n * \r\n * Opens the print preview and shows the print dialog.\r\n * \r\n * Parameters:\r\n * \r\n * css - Optional CSS string to be used in the head section.\r\n */\r\nmxPrintPreview.prototype.print = function(css)\r\n{\r\n\tvar wnd = this.open(css);\r\n\t\r\n\tif (wnd != null)\r\n\t{\r\n\t\twnd.print();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: close\r\n * \r\n * Closes the print preview window.\r\n */\r\nmxPrintPreview.prototype.close = function()\r\n{\r\n\tif (this.wnd != null)\r\n\t{\r\n\t\tthis.wnd.close();\r\n\t\tthis.wnd = null;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxStylesheet\r\n * \r\n * Defines the appearance of the cells in a graph. See <putCellStyle> for an\r\n * example of creating a new cell style. It is recommended to use objects, not\r\n * arrays for holding cell styles. Existing styles can be cloned using\r\n * <mxUtils.clone> and turned into a string for debugging using\r\n * <mxUtils.toString>.\r\n *\r\n * Default Styles:\r\n * \r\n * The stylesheet contains two built-in styles, which are used if no style is\r\n * defined for a cell:\r\n *\r\n *   defaultVertex - Default style for vertices\r\n *   defaultEdge - Default style for edges\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * var vertexStyle = stylesheet.getDefaultVertexStyle();\r\n * vertexStyle[mxConstants.ROUNDED] = true;\r\n * var edgeStyle = stylesheet.getDefaultEdgeStyle();\r\n * edgeStyle[mxConstants.STYLE_EDGE] = mxEdgeStyle.EntityRelation;\r\n * (end)\r\n * \r\n * Modifies the built-in default styles.\r\n * \r\n * To avoid the default style for a cell, add a leading semicolon\r\n * to the style definition, eg.\r\n * \r\n * (code)\r\n * ;shadow=1\r\n * (end)\r\n * \r\n * Removing keys:\r\n * \r\n * For removing a key in a cell style of the form [stylename;|key=value;] the\r\n * special value none can be used, eg. highlight;fillColor=none\r\n * \r\n * See also the helper methods in mxUtils to modify strings of this format,\r\n * namely <mxUtils.setStyle>, <mxUtils.indexOfStylename>,\r\n * <mxUtils.addStylename>, <mxUtils.removeStylename>,\r\n * <mxUtils.removeAllStylenames> and <mxUtils.setStyleFlag>.\r\n * \r\n * Constructor: mxStylesheet\r\n * \r\n * Constructs a new stylesheet and assigns default styles.\r\n */\r\nfunction mxStylesheet()\r\n{\r\n\tthis.styles = new Object();\r\n\t\r\n\tthis.putDefaultVertexStyle(this.createDefaultVertexStyle());\r\n\tthis.putDefaultEdgeStyle(this.createDefaultEdgeStyle());\r\n};\r\n\r\n/**\r\n * Function: styles\r\n * \r\n * Maps from names to cell styles. Each cell style is a map of key,\r\n * value pairs.\r\n */\r\nmxStylesheet.prototype.styles;\r\n\r\n/**\r\n * Function: createDefaultVertexStyle\r\n * \r\n * Creates and returns the default vertex style.\r\n */\r\nmxStylesheet.prototype.createDefaultVertexStyle = function()\r\n{\r\n\tvar style = new Object();\r\n\t\r\n\tstyle[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE;\r\n\tstyle[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;\r\n\tstyle[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE;\r\n\tstyle[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER;\r\n\tstyle[mxConstants.STYLE_FILLCOLOR] = '#C3D9FF';\r\n\tstyle[mxConstants.STYLE_STROKECOLOR] = '#6482B9';\r\n\tstyle[mxConstants.STYLE_FONTCOLOR] = '#774400';\r\n\t\r\n\treturn style;\r\n};\r\n\r\n/**\r\n * Function: createDefaultEdgeStyle\r\n * \r\n * Creates and returns the default edge style.\r\n */\r\nmxStylesheet.prototype.createDefaultEdgeStyle = function()\r\n{\r\n\tvar style = new Object();\r\n\t\r\n\tstyle[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_CONNECTOR;\r\n\tstyle[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC;\r\n\tstyle[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE;\r\n\tstyle[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER;\r\n\tstyle[mxConstants.STYLE_STROKECOLOR] = '#6482B9';\r\n\tstyle[mxConstants.STYLE_FONTCOLOR] = '#446299';\r\n\t\r\n\treturn style;\r\n};\r\n\r\n/**\r\n * Function: putDefaultVertexStyle\r\n * \r\n * Sets the default style for vertices using defaultVertex as the\r\n * stylename.\r\n * \r\n * Parameters:\r\n * style - Key, value pairs that define the style.\r\n */\r\nmxStylesheet.prototype.putDefaultVertexStyle = function(style)\r\n{\r\n\tthis.putCellStyle('defaultVertex', style);\r\n};\r\n\r\n/**\r\n * Function: putDefaultEdgeStyle\r\n * \r\n * Sets the default style for edges using defaultEdge as the stylename.\r\n */\r\nmxStylesheet.prototype.putDefaultEdgeStyle = function(style)\r\n{\r\n\tthis.putCellStyle('defaultEdge', style);\r\n};\r\n\r\n/**\r\n * Function: getDefaultVertexStyle\r\n * \r\n * Returns the default style for vertices.\r\n */\r\nmxStylesheet.prototype.getDefaultVertexStyle = function()\r\n{\r\n\treturn this.styles['defaultVertex'];\r\n};\r\n\r\n/**\r\n * Function: getDefaultEdgeStyle\r\n * \r\n * Sets the default style for edges.\r\n */\r\nmxStylesheet.prototype.getDefaultEdgeStyle = function()\r\n{\r\n\treturn this.styles['defaultEdge'];\r\n};\r\n\r\n/**\r\n * Function: putCellStyle\r\n * \r\n * Stores the given map of key, value pairs under the given name in\r\n * <styles>.\r\n *\r\n * Example:\r\n * \r\n * The following example adds a new style called 'rounded' into an\r\n * existing stylesheet:\r\n * \r\n * (code)\r\n * var style = new Object();\r\n * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE;\r\n * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;\r\n * style[mxConstants.STYLE_ROUNDED] = true;\r\n * graph.getStylesheet().putCellStyle('rounded', style);\r\n * (end)\r\n * \r\n * In the above example, the new style is an object. The possible keys of\r\n * the object are all the constants in <mxConstants> that start with STYLE\r\n * and the values are either JavaScript objects, such as\r\n * <mxPerimeter.RightAngleRectanglePerimeter> (which is in fact a function)\r\n * or expressions, such as true. Note that not all keys will be\r\n * interpreted by all shapes (eg. the line shape ignores the fill color).\r\n * The final call to this method associates the style with a name in the\r\n * stylesheet. The style is used in a cell with the following code:\r\n * \r\n * (code)\r\n * model.setStyle(cell, 'rounded');\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * name - Name for the style to be stored.\r\n * style - Key, value pairs that define the style.\r\n */\r\nmxStylesheet.prototype.putCellStyle = function(name, style)\r\n{\r\n\tthis.styles[name] = style;\r\n};\r\n\r\n/**\r\n * Function: getCellStyle\r\n * \r\n * Returns the cell style for the specified stylename or the given\r\n * defaultStyle if no style can be found for the given stylename.\r\n * \r\n * Parameters:\r\n * \r\n * name - String of the form [(stylename|key=value);] that represents the\r\n * style.\r\n * defaultStyle - Default style to be returned if no style can be found.\r\n */\r\nmxStylesheet.prototype.getCellStyle = function(name, defaultStyle)\r\n{\r\n\tvar style = defaultStyle;\r\n\t\r\n\tif (name != null && name.length > 0)\r\n\t{\r\n\t\tvar pairs = name.split(';');\r\n\r\n\t\tif (style != null &&\r\n\t\t\tname.charAt(0) != ';')\r\n\t\t{\r\n\t\t\tstyle = mxUtils.clone(style);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tstyle = new Object();\r\n\t\t}\r\n\r\n\t\t// Parses each key, value pair into the existing style\r\n\t \tfor (var i = 0; i < pairs.length; i++)\r\n\t \t{\r\n\t \t\tvar tmp = pairs[i];\r\n\t \t\tvar pos = tmp.indexOf('=');\r\n\t \t\t\r\n\t \t\tif (pos >= 0)\r\n\t \t\t{\r\n\t\t \t\tvar key = tmp.substring(0, pos);\r\n\t\t \t\tvar value = tmp.substring(pos + 1);\r\n\r\n\t\t \t\tif (value == mxConstants.NONE)\r\n\t\t \t\t{\r\n\t\t \t\t\tdelete style[key];\r\n\t\t \t\t}\r\n\t\t \t\telse if (mxUtils.isNumeric(value))\r\n\t\t \t\t{\r\n\t\t \t\t\tstyle[key] = parseFloat(value);\r\n\t\t \t\t}\r\n\t\t \t\telse\r\n\t\t \t\t{\r\n\t\t\t \t\tstyle[key] = value;\r\n\t\t \t\t}\r\n\t\t\t}\r\n\t \t\telse\r\n\t \t\t{\r\n\t \t\t\t// Merges the entries from a named style\r\n\t\t\t\tvar tmpStyle = this.styles[tmp];\r\n\t\t\t\t\r\n\t\t\t\tif (tmpStyle != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tfor (var key in tmpStyle)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tstyle[key] = tmpStyle[key];\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t \t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn style;\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxCellState\r\n * \r\n * Represents the current state of a cell in a given <mxGraphView>.\r\n * \r\n * For edges, the edge label position is stored in <absoluteOffset>.\r\n * \r\n * The size for oversize labels can be retrieved using the boundingBox property\r\n * of the <text> field as shown below.\r\n * \r\n * (code)\r\n * var bbox = (state.text != null) ? state.text.boundingBox : null;\r\n * (end)\r\n * \r\n * Constructor: mxCellState\r\n * \r\n * Constructs a new object that represents the current state of the given\r\n * cell in the specified view.\r\n * \r\n * Parameters:\r\n * \r\n * view - <mxGraphView> that contains the state.\r\n * cell - <mxCell> that this state represents.\r\n * style - Array of key, value pairs that constitute the style.\r\n */\r\nfunction mxCellState(view, cell, style)\r\n{\r\n\tthis.view = view;\r\n\tthis.cell = cell;\r\n\tthis.style = (style != null) ? style : {};\r\n\t\r\n\tthis.origin = new mxPoint();\r\n\tthis.absoluteOffset = new mxPoint();\r\n};\r\n\r\n/**\r\n * Extends mxRectangle.\r\n */\r\nmxCellState.prototype = new mxRectangle();\r\nmxCellState.prototype.constructor = mxCellState;\r\n\r\n/**\r\n * Variable: view\r\n * \r\n * Reference to the enclosing <mxGraphView>.\r\n */\r\nmxCellState.prototype.view = null;\r\n\r\n/**\r\n * Variable: cell\r\n *\r\n * Reference to the <mxCell> that is represented by this state.\r\n */\r\nmxCellState.prototype.cell = null;\r\n\r\n/**\r\n * Variable: style\r\n * \r\n * Contains an array of key, value pairs that represent the style of the\r\n * cell.\r\n */\r\nmxCellState.prototype.style = null;\r\n\r\n/**\r\n * Variable: invalidStyle\r\n * \r\n * Specifies if the style is invalid. Default is false.\r\n */\r\nmxCellState.prototype.invalidStyle = false;\r\n\r\n/**\r\n * Variable: invalid\r\n * \r\n * Specifies if the state is invalid. Default is true.\r\n */\r\nmxCellState.prototype.invalid = true;\r\n\r\n/**\r\n * Variable: origin\r\n *\r\n * <mxPoint> that holds the origin for all child cells. Default is a new\r\n * empty <mxPoint>.\r\n */\r\nmxCellState.prototype.origin = null;\r\n\r\n/**\r\n * Variable: absolutePoints\r\n * \r\n * Holds an array of <mxPoints> that represent the absolute points of an\r\n * edge.\r\n */\r\nmxCellState.prototype.absolutePoints = null;\r\n\r\n/**\r\n * Variable: absoluteOffset\r\n *\r\n * <mxPoint> that holds the absolute offset. For edges, this is the\r\n * absolute coordinates of the label position. For vertices, this is the\r\n * offset of the label relative to the top, left corner of the vertex. \r\n */\r\nmxCellState.prototype.absoluteOffset = null;\r\n\r\n/**\r\n * Variable: visibleSourceState\r\n * \r\n * Caches the visible source terminal state.\r\n */\r\nmxCellState.prototype.visibleSourceState = null;\r\n\r\n/**\r\n * Variable: visibleTargetState\r\n * \r\n * Caches the visible target terminal state.\r\n */\r\nmxCellState.prototype.visibleTargetState = null;\r\n\r\n/**\r\n * Variable: terminalDistance\r\n * \r\n * Caches the distance between the end points for an edge.\r\n */\r\nmxCellState.prototype.terminalDistance = 0;\r\n\r\n/**\r\n * Variable: length\r\n *\r\n * Caches the length of an edge.\r\n */\r\nmxCellState.prototype.length = 0;\r\n\r\n/**\r\n * Variable: segments\r\n * \r\n * Array of numbers that represent the cached length of each segment of the\r\n * edge.\r\n */\r\nmxCellState.prototype.segments = null;\r\n\r\n/**\r\n * Variable: shape\r\n * \r\n * Holds the <mxShape> that represents the cell graphically.\r\n */\r\nmxCellState.prototype.shape = null;\r\n\r\n/**\r\n * Variable: text\r\n * \r\n * Holds the <mxText> that represents the label of the cell. Thi smay be\r\n * null if the cell has no label.\r\n */\r\nmxCellState.prototype.text = null;\r\n\r\n/**\r\n * Variable: unscaledWidth\r\n * \r\n * Holds the unscaled width of the state.\r\n */\r\nmxCellState.prototype.unscaledWidth = null;\r\n\r\n/**\r\n * Function: getPerimeterBounds\r\n * \r\n * Returns the <mxRectangle> that should be used as the perimeter of the\r\n * cell.\r\n * \r\n * Parameters:\r\n * \r\n * border - Optional border to be added around the perimeter bounds.\r\n * bounds - Optional <mxRectangle> to be used as the initial bounds.\r\n */\r\nmxCellState.prototype.getPerimeterBounds = function(border, bounds)\r\n{\r\n\tborder = border || 0;\r\n\tbounds = (bounds != null) ? bounds : new mxRectangle(this.x, this.y, this.width, this.height);\r\n\t\r\n\tif (this.shape != null && this.shape.stencil != null && this.shape.stencil.aspect == 'fixed')\r\n\t{\r\n\t\tvar aspect = this.shape.stencil.computeAspect(this.style, bounds.x, bounds.y, bounds.width, bounds.height);\r\n\t\t\r\n\t\tbounds.x = aspect.x;\r\n\t\tbounds.y = aspect.y;\r\n\t\tbounds.width = this.shape.stencil.w0 * aspect.width;\r\n\t\tbounds.height = this.shape.stencil.h0 * aspect.height;\r\n\t}\r\n\t\r\n\tif (border != 0)\r\n\t{\r\n\t\tbounds.grow(border);\r\n\t}\r\n\t\r\n\treturn bounds;\r\n};\r\n\r\n/**\r\n * Function: setAbsoluteTerminalPoint\r\n * \r\n * Sets the first or last point in <absolutePoints> depending on isSource.\r\n * \r\n * Parameters:\r\n * \r\n * point - <mxPoint> that represents the terminal point.\r\n * isSource - Boolean that specifies if the first or last point should\r\n * be assigned.\r\n */\r\nmxCellState.prototype.setAbsoluteTerminalPoint = function(point, isSource)\r\n{\r\n\tif (isSource)\r\n\t{\r\n\t\tif (this.absolutePoints == null)\r\n\t\t{\r\n\t\t\tthis.absolutePoints = [];\r\n\t\t}\r\n\t\t\r\n\t\tif (this.absolutePoints.length == 0)\r\n\t\t{\r\n\t\t\tthis.absolutePoints.push(point);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.absolutePoints[0] = point;\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tif (this.absolutePoints == null)\r\n\t\t{\r\n\t\t\tthis.absolutePoints = [];\r\n\t\t\tthis.absolutePoints.push(null);\r\n\t\t\tthis.absolutePoints.push(point);\r\n\t\t}\r\n\t\telse if (this.absolutePoints.length == 1)\r\n\t\t{\r\n\t\t\tthis.absolutePoints.push(point);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.absolutePoints[this.absolutePoints.length - 1] = point;\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setCursor\r\n * \r\n * Sets the given cursor on the shape and text shape.\r\n */\r\nmxCellState.prototype.setCursor = function(cursor)\r\n{\r\n\tif (this.shape != null)\r\n\t{\r\n\t\tthis.shape.setCursor(cursor);\r\n\t}\r\n\t\r\n\tif (this.text != null)\r\n\t{\r\n\t\tthis.text.setCursor(cursor);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getVisibleTerminal\r\n * \r\n * Returns the visible source or target terminal cell.\r\n * \r\n * Parameters:\r\n * \r\n * source - Boolean that specifies if the source or target cell should be\r\n * returned.\r\n */\r\nmxCellState.prototype.getVisibleTerminal = function(source)\r\n{\r\n\tvar tmp = this.getVisibleTerminalState(source);\r\n\t\r\n\treturn (tmp != null) ? tmp.cell : null;\r\n};\r\n\r\n/**\r\n * Function: getVisibleTerminalState\r\n * \r\n * Returns the visible source or target terminal state.\r\n * \r\n * Parameters:\r\n * \r\n * source - Boolean that specifies if the source or target state should be\r\n * returned.\r\n */\r\nmxCellState.prototype.getVisibleTerminalState = function(source)\r\n{\r\n\treturn (source) ? this.visibleSourceState : this.visibleTargetState;\r\n};\r\n\r\n/**\r\n * Function: setVisibleTerminalState\r\n * \r\n * Sets the visible source or target terminal state.\r\n * \r\n * Parameters:\r\n * \r\n * terminalState - <mxCellState> that represents the terminal.\r\n * source - Boolean that specifies if the source or target state should be set.\r\n */\r\nmxCellState.prototype.setVisibleTerminalState = function(terminalState, source)\r\n{\r\n\tif (source)\r\n\t{\r\n\t\tthis.visibleSourceState = terminalState;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.visibleTargetState = terminalState;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getCellBounds\r\n * \r\n * Returns the unscaled, untranslated bounds.\r\n */\r\nmxCellState.prototype.getCellBounds = function()\r\n{\r\n\treturn this.cellBounds;\r\n};\r\n\r\n/**\r\n * Function: getPaintBounds\r\n * \r\n * Returns the unscaled, untranslated paint bounds. This is the same as\r\n * <getCellBounds> but with a 90 degree rotation if the shape's\r\n * isPaintBoundsInverted returns true.\r\n */\r\nmxCellState.prototype.getPaintBounds = function()\r\n{\r\n\treturn this.paintBounds;\r\n};\r\n\r\n/**\r\n * Function: updateCachedBounds\r\n * \r\n * Updates the cellBounds and paintBounds.\r\n */\r\nmxCellState.prototype.updateCachedBounds = function()\r\n{\r\n\tvar tr = this.view.translate;\r\n\tvar s = this.view.scale;\r\n\tthis.cellBounds = new mxRectangle(this.x / s - tr.x, this.y / s - tr.y, this.width / s, this.height / s);\r\n\tthis.paintBounds = mxRectangle.fromRectangle(this.cellBounds);\r\n\t\r\n\tif (this.shape != null && this.shape.isPaintBoundsInverted())\r\n\t{\r\n\t\tthis.paintBounds.rotate90();\r\n\t}\r\n};\r\n\r\n/**\r\n * Destructor: setState\r\n * \r\n * Copies all fields from the given state to this state.\r\n */\r\nmxCellState.prototype.setState = function(state)\r\n{\r\n\tthis.view = state.view;\r\n\tthis.cell = state.cell;\r\n\tthis.style = state.style;\r\n\tthis.absolutePoints = state.absolutePoints;\r\n\tthis.origin = state.origin;\r\n\tthis.absoluteOffset = state.absoluteOffset;\r\n\tthis.boundingBox = state.boundingBox;\r\n\tthis.terminalDistance = state.terminalDistance;\r\n\tthis.segments = state.segments;\r\n\tthis.length = state.length;\r\n\tthis.x = state.x;\r\n\tthis.y = state.y;\r\n\tthis.width = state.width;\r\n\tthis.height = state.height;\r\n\tthis.unscaledWidth = state.unscaledWidth;\r\n};\r\n\r\n/**\r\n * Function: clone\r\n *\r\n * Returns a clone of this <mxPoint>.\r\n */\r\nmxCellState.prototype.clone = function()\r\n{\r\n \tvar clone = new mxCellState(this.view, this.cell, this.style);\r\n\r\n\t// Clones the absolute points\r\n\tif (this.absolutePoints != null)\r\n\t{\r\n\t\tclone.absolutePoints = [];\r\n\t\t\r\n\t\tfor (var i = 0; i < this.absolutePoints.length; i++)\r\n\t\t{\r\n\t\t\tclone.absolutePoints[i] = this.absolutePoints[i].clone();\r\n\t\t}\r\n\t}\r\n\r\n\tif (this.origin != null)\r\n\t{\r\n\t\tclone.origin = this.origin.clone();\r\n\t}\r\n\r\n\tif (this.absoluteOffset != null)\r\n\t{\r\n\t\tclone.absoluteOffset = this.absoluteOffset.clone();\r\n\t}\r\n\r\n\tif (this.boundingBox != null)\r\n\t{\r\n\t\tclone.boundingBox = this.boundingBox.clone();\r\n\t}\r\n\r\n\tclone.terminalDistance = this.terminalDistance;\r\n\tclone.segments = this.segments;\r\n\tclone.length = this.length;\r\n\tclone.x = this.x;\r\n\tclone.y = this.y;\r\n\tclone.width = this.width;\r\n\tclone.height = this.height;\r\n\tclone.unscaledWidth = this.unscaledWidth;\r\n\t\r\n\treturn clone;\r\n};\r\n\r\n/**\r\n * Destructor: destroy\r\n * \r\n * Destroys the state and all associated resources.\r\n */\r\nmxCellState.prototype.destroy = function()\r\n{\r\n\tthis.view.graph.cellRenderer.destroy(this);\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxGraphSelectionModel\r\n *\r\n * Implements the selection model for a graph. Here is a listener that handles\r\n * all removed selection cells.\r\n * \r\n * (code)\r\n * graph.getSelectionModel().addListener(mxEvent.CHANGE, function(sender, evt)\r\n * {\r\n *   var cells = evt.getProperty('added');\r\n *   \r\n *   for (var i = 0; i < cells.length; i++)\r\n *   {\r\n *     // Handle cells[i]...\r\n *   }\r\n * });\r\n * (end)\r\n * \r\n * Event: mxEvent.UNDO\r\n * \r\n * Fires after the selection was changed in <changeSelection>. The\r\n * <code>edit</code> property contains the <mxUndoableEdit> which contains the\r\n * <mxSelectionChange>.\r\n * \r\n * Event: mxEvent.CHANGE\r\n * \r\n * Fires after the selection changes by executing an <mxSelectionChange>. The\r\n * <code>added</code> and <code>removed</code> properties contain arrays of\r\n * cells that have been added to or removed from the selection, respectively.\r\n * The names are inverted due to historic reasons. This cannot be changed.\r\n * \r\n * Constructor: mxGraphSelectionModel\r\n *\r\n * Constructs a new graph selection model for the given <mxGraph>.\r\n * \r\n * Parameters:\r\n * \r\n * graph - Reference to the enclosing <mxGraph>.\r\n */\r\nfunction mxGraphSelectionModel(graph)\r\n{\r\n\tthis.graph = graph;\r\n\tthis.cells = [];\r\n};\r\n\r\n/**\r\n * Extends mxEventSource.\r\n */\r\nmxGraphSelectionModel.prototype = new mxEventSource();\r\nmxGraphSelectionModel.prototype.constructor = mxGraphSelectionModel;\r\n\r\n/**\r\n * Variable: doneResource\r\n * \r\n * Specifies the resource key for the status message after a long operation.\r\n * If the resource for this key does not exist then the value is used as\r\n * the status message. Default is 'done'.\r\n */\r\nmxGraphSelectionModel.prototype.doneResource = (mxClient.language != 'none') ? 'done' : '';\r\n\r\n/**\r\n * Variable: updatingSelectionResource\r\n *\r\n * Specifies the resource key for the status message while the selection is\r\n * being updated. If the resource for this key does not exist then the\r\n * value is used as the status message. Default is 'updatingSelection'.\r\n */\r\nmxGraphSelectionModel.prototype.updatingSelectionResource = (mxClient.language != 'none') ? 'updatingSelection' : '';\r\n\r\n/**\r\n * Variable: graph\r\n * \r\n * Reference to the enclosing <mxGraph>.\r\n */\r\nmxGraphSelectionModel.prototype.graph = null;\r\n\r\n/**\r\n * Variable: singleSelection\r\n *\r\n * Specifies if only one selected item at a time is allowed.\r\n * Default is false.\r\n */\r\nmxGraphSelectionModel.prototype.singleSelection = false;\r\n\r\n/**\r\n * Function: isSingleSelection\r\n *\r\n * Returns <singleSelection> as a boolean.\r\n */\r\nmxGraphSelectionModel.prototype.isSingleSelection = function()\r\n{\r\n\treturn this.singleSelection;\r\n};\r\n\r\n/**\r\n * Function: setSingleSelection\r\n *\r\n * Sets the <singleSelection> flag.\r\n * \r\n * Parameters:\r\n * \r\n * singleSelection - Boolean that specifies the new value for\r\n * <singleSelection>.\r\n */\r\nmxGraphSelectionModel.prototype.setSingleSelection = function(singleSelection)\r\n{\r\n\tthis.singleSelection = singleSelection;\r\n};\r\n\r\n/**\r\n * Function: isSelected\r\n *\r\n * Returns true if the given <mxCell> is selected.\r\n */\r\nmxGraphSelectionModel.prototype.isSelected = function(cell)\r\n{\r\n\tif (cell != null)\r\n\t{\r\n\t\treturn mxUtils.indexOf(this.cells, cell) >= 0;\r\n\t}\r\n\t\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: isEmpty\r\n *\r\n * Returns true if no cells are currently selected.\r\n */\r\nmxGraphSelectionModel.prototype.isEmpty = function()\r\n{\r\n\treturn this.cells.length == 0;\r\n};\r\n\r\n/**\r\n * Function: clear\r\n *\r\n * Clears the selection and fires a <change> event if the selection was not\r\n * empty.\r\n */\r\nmxGraphSelectionModel.prototype.clear = function()\r\n{\r\n\tthis.changeSelection(null, this.cells);\r\n};\r\n\r\n/**\r\n * Function: setCell\r\n *\r\n * Selects the specified <mxCell> using <setCells>.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> to be selected.\r\n */\r\nmxGraphSelectionModel.prototype.setCell = function(cell)\r\n{\r\n\tif (cell != null)\r\n\t{\r\n\t\tthis.setCells([cell]);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setCells\r\n *\r\n * Selects the given array of <mxCells> and fires a <change> event.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> to be selected.\r\n */\r\nmxGraphSelectionModel.prototype.setCells = function(cells)\r\n{\r\n\tif (cells != null)\r\n\t{\r\n\t\tif (this.singleSelection)\r\n\t\t{\r\n\t\t\tcells = [this.getFirstSelectableCell(cells)];\r\n\t\t}\r\n\t\r\n\t\tvar tmp = [];\r\n\t\t\r\n\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t{\r\n\t\t\tif (this.graph.isCellSelectable(cells[i]))\r\n\t\t\t{\r\n\t\t\t\ttmp.push(cells[i]);\r\n\t\t\t}\t\r\n\t\t}\r\n\r\n\t\tthis.changeSelection(tmp, this.cells);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getFirstSelectableCell\r\n *\r\n * Returns the first selectable cell in the given array of cells.\r\n */\r\nmxGraphSelectionModel.prototype.getFirstSelectableCell = function(cells)\r\n{\r\n\tif (cells != null)\r\n\t{\r\n\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t{\r\n\t\t\tif (this.graph.isCellSelectable(cells[i]))\r\n\t\t\t{\r\n\t\t\t\treturn cells[i];\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: addCell\r\n * \r\n * Adds the given <mxCell> to the selection and fires a <select> event.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> to add to the selection.\r\n */\r\nmxGraphSelectionModel.prototype.addCell = function(cell)\r\n{\r\n\tif (cell != null)\r\n\t{\r\n\t\tthis.addCells([cell]);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: addCells\r\n * \r\n * Adds the given array of <mxCells> to the selection and fires a <select>\r\n * event.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> to add to the selection.\r\n */\r\nmxGraphSelectionModel.prototype.addCells = function(cells)\r\n{\r\n\tif (cells != null)\r\n\t{\r\n\t\tvar remove = null;\r\n\t\t\r\n\t\tif (this.singleSelection)\r\n\t\t{\r\n\t\t\tremove = this.cells;\r\n\t\t\tcells = [this.getFirstSelectableCell(cells)];\r\n\t\t}\r\n\r\n\t\tvar tmp = [];\r\n\t\t\r\n\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t{\r\n\t\t\tif (!this.isSelected(cells[i]) &&\r\n\t\t\t\tthis.graph.isCellSelectable(cells[i]))\r\n\t\t\t{\r\n\t\t\t\ttmp.push(cells[i]);\r\n\t\t\t}\t\r\n\t\t}\r\n\r\n\t\tthis.changeSelection(tmp, remove);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: removeCell\r\n *\r\n * Removes the specified <mxCell> from the selection and fires a <select>\r\n * event for the remaining cells.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> to remove from the selection.\r\n */\r\nmxGraphSelectionModel.prototype.removeCell = function(cell)\r\n{\r\n\tif (cell != null)\r\n\t{\r\n\t\tthis.removeCells([cell]);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: removeCells\r\n */\r\nmxGraphSelectionModel.prototype.removeCells = function(cells)\r\n{\r\n\tif (cells != null)\r\n\t{\r\n\t\tvar tmp = [];\r\n\t\t\r\n\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t{\r\n\t\t\tif (this.isSelected(cells[i]))\r\n\t\t\t{\r\n\t\t\t\ttmp.push(cells[i]);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tthis.changeSelection(null, tmp);\t\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: changeSelection\r\n *\r\n * Inner callback to add the specified <mxCell> to the selection. No event\r\n * is fired in this implementation.\r\n * \r\n * Paramters:\r\n * \r\n * cell - <mxCell> to add to the selection.\r\n */\r\nmxGraphSelectionModel.prototype.changeSelection = function(added, removed)\r\n{\r\n\tif ((added != null &&\r\n\t\tadded.length > 0 &&\r\n\t\tadded[0] != null) ||\r\n\t\t(removed != null &&\r\n\t\tremoved.length > 0 &&\r\n\t\tremoved[0] != null))\r\n\t{\r\n\t\tvar change = new mxSelectionChange(this, added, removed);\r\n\t\tchange.execute();\r\n\t\tvar edit = new mxUndoableEdit(this, false);\r\n\t\tedit.add(change);\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: cellAdded\r\n *\r\n * Inner callback to add the specified <mxCell> to the selection. No event\r\n * is fired in this implementation.\r\n * \r\n * Paramters:\r\n * \r\n * cell - <mxCell> to add to the selection.\r\n */\r\nmxGraphSelectionModel.prototype.cellAdded = function(cell)\r\n{\r\n\tif (cell != null &&\r\n\t\t!this.isSelected(cell))\r\n\t{\r\n\t\tthis.cells.push(cell);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: cellRemoved\r\n *\r\n * Inner callback to remove the specified <mxCell> from the selection. No\r\n * event is fired in this implementation.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> to remove from the selection.\r\n */\r\nmxGraphSelectionModel.prototype.cellRemoved = function(cell)\r\n{\r\n\tif (cell != null)\r\n\t{\r\n\t\tvar index = mxUtils.indexOf(this.cells, cell);\r\n\t\t\r\n\t\tif (index >= 0)\r\n\t\t{\r\n\t\t\tthis.cells.splice(index, 1);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Class: mxSelectionChange\r\n *\r\n * Action to change the current root in a view.\r\n *\r\n * Constructor: mxCurrentRootChange\r\n *\r\n * Constructs a change of the current root in the given view.\r\n */\r\nfunction mxSelectionChange(selectionModel, added, removed)\r\n{\r\n\tthis.selectionModel = selectionModel;\r\n\tthis.added = (added != null) ? added.slice() : null;\r\n\tthis.removed = (removed != null) ? removed.slice() : null;\r\n};\r\n\r\n/**\r\n * Function: execute\r\n *\r\n * Changes the current root of the view.\r\n */\r\nmxSelectionChange.prototype.execute = function()\r\n{\r\n\tvar t0 = mxLog.enter('mxSelectionChange.execute');\r\n\twindow.status = mxResources.get(\r\n\t\tthis.selectionModel.updatingSelectionResource) ||\r\n\t\tthis.selectionModel.updatingSelectionResource;\r\n\r\n\tif (this.removed != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.removed.length; i++)\r\n\t\t{\r\n\t\t\tthis.selectionModel.cellRemoved(this.removed[i]);\r\n\t\t}\r\n\t}\r\n\r\n\tif (this.added != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.added.length; i++)\r\n\t\t{\r\n\t\t\tthis.selectionModel.cellAdded(this.added[i]);\r\n\t\t}\r\n\t}\r\n\t\r\n\tvar tmp = this.added;\r\n\tthis.added = this.removed;\r\n\tthis.removed = tmp;\r\n\r\n\twindow.status = mxResources.get(this.selectionModel.doneResource) ||\r\n\t\tthis.selectionModel.doneResource;\r\n\tmxLog.leave('mxSelectionChange.execute', t0);\r\n\t\r\n\tthis.selectionModel.fireEvent(new mxEventObject(mxEvent.CHANGE,\r\n\t\t\t'added', this.added, 'removed', this.removed));\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxCellEditor\r\n *\r\n * In-place editor for the graph. To control this editor, use\r\n * <mxGraph.invokesStopCellEditing>, <mxGraph.enterStopsCellEditing> and\r\n * <mxGraph.escapeEnabled>. If <mxGraph.enterStopsCellEditing> is true then\r\n * ctrl-enter or shift-enter can be used to create a linefeed. The F2 and\r\n * escape keys can always be used to stop editing.\r\n * \r\n * To customize the location of the textbox in the graph, override\r\n * <getEditorBounds> as follows:\r\n * \r\n * (code)\r\n * graph.cellEditor.getEditorBounds = function(state)\r\n * {\r\n *   var result = mxCellEditor.prototype.getEditorBounds.apply(this, arguments);\r\n *   \r\n *   if (this.graph.getModel().isEdge(state.cell))\r\n *   {\r\n *     result.x = state.getCenterX() - result.width / 2;\r\n *     result.y = state.getCenterY() - result.height / 2;\r\n *   }\r\n *   \r\n *   return result;\r\n * };\r\n * (end)\r\n * \r\n * Note that this hook is only called if <autoSize> is false. If <autoSize> is true,\r\n * then <mxShape.getLabelBounds> is used to compute the current bounds of the textbox.\r\n * \r\n * The textarea uses the mxCellEditor CSS class. You can modify this class in\r\n * your custom CSS. Note: You should modify the CSS after loading the client\r\n * in the page.\r\n *\r\n * Example:\r\n * \r\n * To only allow numeric input in the in-place editor, use the following code.\r\n *\r\n * (code)\r\n * var text = graph.cellEditor.textarea;\r\n * \r\n * mxEvent.addListener(text, 'keydown', function (evt)\r\n * {\r\n *   if (!(evt.keyCode >= 48 && evt.keyCode <= 57) &&\r\n *       !(evt.keyCode >= 96 && evt.keyCode <= 105))\r\n *   {\r\n *     mxEvent.consume(evt);\r\n *   }\r\n * }); \r\n * (end)\r\n * \r\n * Placeholder:\r\n * \r\n * To implement a placeholder for cells without a label, use the\r\n * <emptyLabelText> variable.\r\n * \r\n * Resize in Chrome:\r\n * \r\n * Resize of the textarea is disabled by default. If you want to enable\r\n * this feature extend <init> and set this.textarea.style.resize = ''.\r\n * \r\n * To start editing on a key press event, the container of the graph\r\n * should have focus or a focusable parent should be used to add the\r\n * key press handler as follows.\r\n * \r\n * (code)\r\n * mxEvent.addListener(graph.container, 'keypress', mxUtils.bind(this, function(evt)\r\n * {\r\n *   if (!graph.isEditing() && !graph.isSelectionEmpty() && evt.which !== 0 &&\r\n *       !mxEvent.isAltDown(evt) && !mxEvent.isControlDown(evt) && !mxEvent.isMetaDown(evt))\r\n *   {\r\n *     graph.startEditing();\r\n *     \r\n *     if (mxClient.IS_FF)\r\n *     {\r\n *       graph.cellEditor.textarea.value = String.fromCharCode(evt.which);\r\n *     }\r\n *   }\r\n * }));\r\n * (end)\r\n * \r\n * To allow focus for a DIV, and hence to receive key press events, some browsers\r\n * require it to have a valid tabindex attribute. In this case the following\r\n * code may be used to keep the container focused.\r\n * \r\n * (code)\r\n * var graphFireMouseEvent = graph.fireMouseEvent;\r\n * graph.fireMouseEvent = function(evtName, me, sender)\r\n * {\r\n *   if (evtName == mxEvent.MOUSE_DOWN)\r\n *   {\r\n *     this.container.focus();\r\n *   }\r\n *   \r\n *   graphFireMouseEvent.apply(this, arguments);\r\n * };\r\n * (end)\r\n *\r\n * Constructor: mxCellEditor\r\n *\r\n * Constructs a new in-place editor for the specified graph.\r\n * \r\n * Parameters:\r\n * \r\n * graph - Reference to the enclosing <mxGraph>.\r\n */\r\nfunction mxCellEditor(graph)\r\n{\r\n\tthis.graph = graph;\r\n\t\r\n\t// Stops editing after zoom changes\r\n\tthis.zoomHandler = mxUtils.bind(this, function()\r\n\t{\r\n\t\tif (this.graph.isEditing())\r\n\t\t{\r\n\t\t\tthis.resize();\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.graph.view.addListener(mxEvent.SCALE, this.zoomHandler);\r\n\tthis.graph.view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.zoomHandler);\r\n\t\r\n\t// Adds handling of deleted cells while editing\r\n\tthis.changeHandler = mxUtils.bind(this, function(sender)\r\n\t{\r\n\t\tif (this.editingCell != null && this.graph.getView().getState(this.editingCell) == null)\r\n\t\t{\r\n\t\t\tthis.stopEditing(true);\r\n\t\t}\r\n\t});\r\n\r\n\tthis.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);\r\n};\r\n\r\n/**\r\n * Variable: graph\r\n * \r\n * Reference to the enclosing <mxGraph>.\r\n */\r\nmxCellEditor.prototype.graph = null;\r\n\r\n/**\r\n * Variable: textarea\r\n *\r\n * Holds the DIV that is used for text editing. Note that this may be null before the first\r\n * edit. Instantiated in <init>.\r\n */\r\nmxCellEditor.prototype.textarea = null;\r\n\r\n/**\r\n * Variable: editingCell\r\n * \r\n * Reference to the <mxCell> that is currently being edited.\r\n */\r\nmxCellEditor.prototype.editingCell = null;\r\n\r\n/**\r\n * Variable: trigger\r\n * \r\n * Reference to the event that was used to start editing.\r\n */\r\nmxCellEditor.prototype.trigger = null;\r\n\r\n/**\r\n * Variable: modified\r\n * \r\n * Specifies if the label has been modified.\r\n */\r\nmxCellEditor.prototype.modified = false;\r\n\r\n/**\r\n * Variable: autoSize\r\n * \r\n * Specifies if the textarea should be resized while the text is being edited.\r\n * Default is true.\r\n */\r\nmxCellEditor.prototype.autoSize = true;\r\n\r\n/**\r\n * Variable: selectText\r\n * \r\n * Specifies if the text should be selected when editing starts. Default is\r\n * true.\r\n */\r\nmxCellEditor.prototype.selectText = true;\r\n\r\n/**\r\n * Variable: emptyLabelText\r\n * \r\n * Text to be displayed for empty labels. Default is '' or '<br>' in Firefox as\r\n * a workaround for the missing cursor bug for empty content editable. This can\r\n * be set to eg. \"[Type Here]\" to easier visualize editing of empty labels. The\r\n * value is only displayed before the first keystroke and is never used as the\r\n * actual editing value.\r\n */\r\nmxCellEditor.prototype.emptyLabelText = (mxClient.IS_FF) ? '<br>' : '';\r\n\r\n/**\r\n * Variable: escapeCancelsEditing\r\n * \r\n * If true, pressing the escape key will stop editing and not accept the new\r\n * value. Change this to false to accept the new value on escape, and cancel\r\n * editing on Shift+Escape instead. Default is true.\r\n */\r\nmxCellEditor.prototype.escapeCancelsEditing = true;\r\n\r\n/**\r\n * Variable: textNode\r\n * \r\n * Reference to the label DOM node that has been hidden.\r\n */\r\nmxCellEditor.prototype.textNode = '';\r\n\r\n/**\r\n * Variable: zIndex\r\n * \r\n * Specifies the zIndex for the textarea. Default is 5.\r\n */\r\nmxCellEditor.prototype.zIndex = 5;\r\n\r\n/**\r\n * Variable: minResize\r\n * \r\n * Defines the minimum width and height to be used in <resize>. Default is 0x20px.\r\n */\r\nmxCellEditor.prototype.minResize = new mxRectangle(0, 20);\r\n\r\n/**\r\n * Variable: wordWrapPadding\r\n * \r\n * Correction factor for word wrapping width. Default is 2 in quirks, 0 in IE\r\n * 11 and 1 in all other browsers and modes.\r\n */\r\nmxCellEditor.prototype.wordWrapPadding = (mxClient.IS_QUIRKS) ? 2 : (!mxClient.IS_IE11) ? 1 : 0;\r\n\r\n/**\r\n * Variable: blurEnabled\r\n *\r\n * If <focusLost> should be called if <textarea> loses the focus. Default is false.\r\n */\r\nmxCellEditor.prototype.blurEnabled = false;\r\n\r\n/**\r\n * Variable: initialValue\r\n * \r\n * Holds the initial editing value to check if the current value was modified.\r\n */\r\nmxCellEditor.prototype.initialValue = null;\r\n\r\n/**\r\n * Function: init\r\n *\r\n * Creates the <textarea> and installs the event listeners. The key handler\r\n * updates the <modified> state.\r\n */\r\nmxCellEditor.prototype.init = function ()\r\n{\r\n\tthis.textarea = document.createElement('div');\r\n\tthis.textarea.className = 'mxCellEditor mxPlainTextEditor';\r\n\tthis.textarea.contentEditable = true;\r\n\t\r\n\t// Workaround for selection outside of DIV if height is 0\r\n\tif (mxClient.IS_GC)\r\n\t{\r\n\t\tthis.textarea.style.minHeight = '1em';\r\n\t}\r\n\r\n\tthis.textarea.style.position = ((this.isLegacyEditor())) ? 'absolute' : 'relative';\r\n\tthis.installListeners(this.textarea);\r\n};\r\n\r\n/**\r\n * Function: applyValue\r\n * \r\n * Called in <stopEditing> if cancel is false to invoke <mxGraph.labelChanged>.\r\n */\r\nmxCellEditor.prototype.applyValue = function(state, value)\r\n{\r\n\tthis.graph.labelChanged(state.cell, value, this.trigger);\r\n};\r\n\r\n/**\r\n * Function: getInitialValue\r\n * \r\n * Gets the initial editing value for the given cell.\r\n */\r\nmxCellEditor.prototype.getInitialValue = function(state, trigger)\r\n{\r\n\tvar result = mxUtils.htmlEntities(this.graph.getEditingValue(state.cell, trigger), false);\r\n\t\r\n    // Workaround for trailing line breaks being ignored in the editor\r\n\tif (!mxClient.IS_QUIRKS && document.documentMode != 8 && document.documentMode != 9 &&\r\n\t\tdocument.documentMode != 10)\r\n\t{\r\n\t\tresult = mxUtils.replaceTrailingNewlines(result, '<div><br></div>');\r\n\t}\r\n    \r\n    return result.replace(/\\n/g, '<br>');\r\n};\r\n\r\n/**\r\n * Function: getCurrentValue\r\n * \r\n * Returns the current editing value.\r\n */\r\nmxCellEditor.prototype.getCurrentValue = function(state)\r\n{\r\n\treturn mxUtils.extractTextWithWhitespace(this.textarea.childNodes);\r\n};\r\n\r\n/**\r\n * Function: isCancelEditingKeyEvent\r\n * \r\n * Returns true if <escapeCancelsEditing> is true and shift, control and meta\r\n * are not pressed.\r\n */\r\nmxCellEditor.prototype.isCancelEditingKeyEvent = function(evt)\r\n{\r\n\treturn this.escapeCancelsEditing || mxEvent.isShiftDown(evt) || mxEvent.isControlDown(evt) || mxEvent.isMetaDown(evt);\r\n};\r\n\r\n/**\r\n * Function: installListeners\r\n * \r\n * Installs listeners for focus, change and standard key event handling.\r\n */\r\nmxCellEditor.prototype.installListeners = function(elt)\r\n{\r\n\t// Applies value if text is dragged\r\n\t// LATER: Gesture mouse events ignored for starting move\r\n\tmxEvent.addListener(elt, 'dragstart', mxUtils.bind(this, function(evt)\r\n\t{\r\n\t\tthis.graph.stopEditing(false);\r\n\t\tmxEvent.consume(evt);\r\n\t}));\r\n\r\n\t// Applies value if focus is lost\r\n\tmxEvent.addListener(elt, 'blur', mxUtils.bind(this, function(evt)\r\n\t{\r\n\t\tif (this.blurEnabled)\r\n\t\t{\r\n\t\t\tthis.focusLost(evt);\r\n\t\t}\r\n\t}));\r\n\r\n\t// Updates modified state and handles placeholder text\r\n\tmxEvent.addListener(elt, 'keydown', mxUtils.bind(this, function(evt)\r\n\t{\r\n\t\tif (!mxEvent.isConsumed(evt))\r\n\t\t{\r\n\t\t\tif (this.isStopEditingEvent(evt))\r\n\t\t\t{\r\n\t\t\t\tthis.graph.stopEditing(false);\r\n\t\t\t\tmxEvent.consume(evt);\r\n\t\t\t}\r\n\t\t\telse if (evt.keyCode == 27 /* Escape */)\r\n\t\t\t{\r\n\t\t\t\tthis.graph.stopEditing(this.isCancelEditingKeyEvent(evt));\r\n\t\t\t\tmxEvent.consume(evt);\r\n\t\t\t}\r\n\t\t}\r\n\t}));\r\n\r\n\t// Keypress only fires if printable key was pressed and handles removing the empty placeholder\r\n\tvar keypressHandler = mxUtils.bind(this, function(evt)\r\n\t{\r\n\t\tif (this.editingCell != null)\r\n\t\t{\r\n\t\t\t// Clears the initial empty label on the first keystroke\r\n\t\t\t// and workaround for FF which fires keypress for delete and backspace\r\n\t\t\tif (this.clearOnChange && elt.innerHTML == this.getEmptyLabelText() &&\r\n\t\t\t\t(!mxClient.IS_FF || (evt.keyCode != 8 /* Backspace */ && evt.keyCode != 46 /* Delete */)))\r\n\t\t\t{\r\n\t\t\t\tthis.clearOnChange = false;\r\n\t\t\t\telt.innerHTML = '';\r\n\t\t\t}\r\n\t\t}\r\n\t});\r\n\r\n\tmxEvent.addListener(elt, 'keypress', keypressHandler);\r\n\tmxEvent.addListener(elt, 'paste', keypressHandler);\r\n\t\r\n\t// Handler for updating the empty label text value after a change\r\n\tvar keyupHandler = mxUtils.bind(this, function(evt)\r\n\t{\r\n\t\tif (this.editingCell != null)\r\n\t\t{\r\n\t\t\t// Uses an optional text value for sempty labels which is cleared\r\n\t\t\t// when the first keystroke appears. This makes it easier to see\r\n\t\t\t// that a label is being edited even if the label is empty.\r\n\t\t\t// In Safari and FF, an empty text is represented by <BR> which isn't enough to force a valid size\r\n\t\t\tif (this.textarea.innerHTML.length == 0 || this.textarea.innerHTML == '<br>')\r\n\t\t\t{\r\n\t\t\t\tthis.textarea.innerHTML = this.getEmptyLabelText();\r\n\t\t\t\tthis.clearOnChange = this.textarea.innerHTML.length > 0;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.clearOnChange = false;\r\n\t\t\t}\r\n\t\t}\r\n\t});\r\n\r\n\tmxEvent.addListener(elt, (!mxClient.IS_IE11 && !mxClient.IS_IE) ? 'input' : 'keyup', keyupHandler);\r\n\tmxEvent.addListener(elt, 'cut', keyupHandler);\r\n\tmxEvent.addListener(elt, 'paste', keyupHandler);\r\n\r\n\t// Adds automatic resizing of the textbox while typing using input, keyup and/or DOM change events\r\n\tvar evtName = (!mxClient.IS_IE11 && !mxClient.IS_IE) ? 'input' : 'keydown';\r\n\t\r\n\tvar resizeHandler = mxUtils.bind(this, function(evt)\r\n\t{\r\n\t\tif (this.editingCell != null && this.autoSize && !mxEvent.isConsumed(evt))\r\n\t\t{\r\n\t\t\t// Asynchronous is needed for keydown and shows better results for input events overall\r\n\t\t\t// (ie non-blocking and cases where the offsetWidth/-Height was wrong at this time)\r\n\t\t\tif (this.resizeThread != null)\r\n\t\t\t{\r\n\t\t\t\twindow.clearTimeout(this.resizeThread);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.resizeThread = window.setTimeout(mxUtils.bind(this, function()\r\n\t\t\t{\r\n\t\t\t\tthis.resizeThread = null;\r\n\t\t\t\tthis.resize();\r\n\t\t\t}), 0);\r\n\t\t}\r\n\t});\r\n\t\r\n\tmxEvent.addListener(elt, evtName, resizeHandler);\r\n\tmxEvent.addListener(window, 'resize', resizeHandler);\r\n\r\n\tif (document.documentMode >= 9)\r\n\t{\r\n\t\tmxEvent.addListener(elt, 'DOMNodeRemoved', resizeHandler);\r\n\t\tmxEvent.addListener(elt, 'DOMNodeInserted', resizeHandler);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tmxEvent.addListener(elt, 'cut', resizeHandler);\r\n\t\tmxEvent.addListener(elt, 'paste', resizeHandler);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isStopEditingEvent\r\n * \r\n * Returns true if the given keydown event should stop cell editing. This\r\n * returns true if F2 is pressed of if <mxGraph.enterStopsCellEditing> is true\r\n * and enter is pressed without control or shift.\r\n */\r\nmxCellEditor.prototype.isStopEditingEvent = function(evt)\r\n{\r\n\treturn evt.keyCode == 113 /* F2 */ || (this.graph.isEnterStopsCellEditing() &&\r\n\t\tevt.keyCode == 13 /* Enter */ && !mxEvent.isControlDown(evt) &&\r\n\t\t!mxEvent.isShiftDown(evt));\r\n};\r\n\r\n/**\r\n * Function: isEventSource\r\n * \r\n * Returns true if this editor is the source for the given native event.\r\n */\r\nmxCellEditor.prototype.isEventSource = function(evt)\r\n{\r\n\treturn mxEvent.getSource(evt) == this.textarea;\r\n};\r\n\r\n/**\r\n * Function: resize\r\n * \r\n * Returns <modified>.\r\n */\r\nmxCellEditor.prototype.resize = function()\r\n{\r\n\tvar state = this.graph.getView().getState(this.editingCell);\r\n\t\r\n\tif (state == null)\r\n\t{\r\n\t\tthis.stopEditing(true);\r\n\t}\r\n\telse if (this.textarea != null)\r\n\t{\r\n\t\tvar isEdge = this.graph.getModel().isEdge(state.cell);\r\n \t\tvar scale = this.graph.getView().scale;\r\n \t\tvar m = null;\r\n\t\t\r\n\t\tif (!this.autoSize || (state.style[mxConstants.STYLE_OVERFLOW] == 'fill'))\r\n\t\t{\r\n\t\t\t// Specifies the bounds of the editor box\r\n\t\t\tthis.bounds = this.getEditorBounds(state);\r\n\t\t\tthis.textarea.style.width = Math.round(this.bounds.width / scale) + 'px';\r\n\t\t\tthis.textarea.style.height = Math.round(this.bounds.height / scale) + 'px';\r\n\t\t\t\r\n\t\t\t// FIXME: Offset when scaled\r\n\t\t\tif (document.documentMode == 8 || mxClient.IS_QUIRKS)\r\n\t\t\t{\r\n\t\t\t\tthis.textarea.style.left = Math.round(this.bounds.x) + 'px';\r\n\t\t\t\tthis.textarea.style.top = Math.round(this.bounds.y) + 'px';\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.textarea.style.left = Math.max(0, Math.round(this.bounds.x + 1)) + 'px';\r\n\t\t\t\tthis.textarea.style.top = Math.max(0, Math.round(this.bounds.y + 1)) + 'px';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Installs native word wrapping and avoids word wrap for empty label placeholder\r\n\t\t\tif (this.graph.isWrapping(state.cell) && (this.bounds.width >= 2 || this.bounds.height >= 2) &&\r\n\t\t\t\tthis.textarea.innerHTML != this.getEmptyLabelText())\r\n\t\t\t{\r\n\t\t\t\tthis.textarea.style.wordWrap = mxConstants.WORD_WRAP;\r\n\t\t\t\tthis.textarea.style.whiteSpace = 'normal';\r\n\t\t\t\t\r\n\t\t\t\tif (state.style[mxConstants.STYLE_OVERFLOW] != 'fill')\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.textarea.style.width = Math.round(this.bounds.width / scale) + this.wordWrapPadding + 'px';\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.textarea.style.whiteSpace = 'nowrap';\r\n\t\t\t\t\r\n\t\t\t\tif (state.style[mxConstants.STYLE_OVERFLOW] != 'fill')\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.textarea.style.width = '';\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t \t{\r\n\t \t\tvar lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);\r\n\t\t\tm = (state.text != null) ? state.text.margin : null;\r\n\t\t\t\r\n\t\t\tif (m == null)\r\n\t\t\t{\r\n\t\t\t\tm = mxUtils.getAlignmentAsPoint(mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER),\r\n\t\t\t\t\t\tmxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE));\r\n\t\t\t}\r\n\t\t\t\r\n\t \t\tif (isEdge)\r\n\t\t\t{\r\n\t\t\t\tthis.bounds = new mxRectangle(state.absoluteOffset.x, state.absoluteOffset.y, 0, 0);\r\n\t\t\t\t\r\n\t\t\t\tif (lw != null)\r\n\t\t\t \t{\r\n\t\t\t\t\tvar tmp = (parseFloat(lw) + 2) * scale;\r\n\t\t\t\t\tthis.bounds.width = tmp;\r\n\t\t\t\t\tthis.bounds.x += m.x * tmp;\r\n\t\t\t \t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tvar bds = mxRectangle.fromRectangle(state);\r\n\t\t\t\tvar hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);\r\n\t\t\t\tvar vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);\r\n\r\n\t\t\t\tbds = (state.shape != null && hpos == mxConstants.ALIGN_CENTER && vpos == mxConstants.ALIGN_MIDDLE) ? state.shape.getLabelBounds(bds) : bds;\r\n\t\t\t \t\r\n\t\t\t \tif (lw != null)\r\n\t\t\t \t{\r\n\t\t\t \t\tbds.width = parseFloat(lw) * scale;\r\n\t\t\t \t}\r\n\t\t\t \t\r\n\t\t\t \tif (!state.view.graph.cellRenderer.legacySpacing || state.style[mxConstants.STYLE_OVERFLOW] != 'width')\r\n\t\t\t \t{\r\n\t\t\t\t\tvar spacing = parseInt(state.style[mxConstants.STYLE_SPACING] || 2) * scale;\r\n\t\t\t\t\tvar spacingTop = (parseInt(state.style[mxConstants.STYLE_SPACING_TOP] || 0) + mxText.prototype.baseSpacingTop) * scale + spacing;\r\n\t\t\t\t\tvar spacingRight = (parseInt(state.style[mxConstants.STYLE_SPACING_RIGHT] || 0) + mxText.prototype.baseSpacingRight) * scale + spacing;\r\n\t\t\t\t\tvar spacingBottom = (parseInt(state.style[mxConstants.STYLE_SPACING_BOTTOM] || 0) + mxText.prototype.baseSpacingBottom) * scale + spacing;\r\n\t\t\t\t\tvar spacingLeft = (parseInt(state.style[mxConstants.STYLE_SPACING_LEFT] || 0) + mxText.prototype.baseSpacingLeft) * scale + spacing;\r\n\t\t\t\t\t\r\n\t\t\t\t\tvar hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);\r\n\t\t\t\t\tvar vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);\r\n\r\n\t\t\t\t\tbds = new mxRectangle(bds.x + spacingLeft, bds.y + spacingTop,\r\n\t\t\t\t\t\tbds.width - ((hpos == mxConstants.ALIGN_CENTER && lw == null) ? (spacingLeft + spacingRight) : 0),\r\n\t\t\t\t\t\tbds.height - ((vpos == mxConstants.ALIGN_MIDDLE) ? (spacingTop + spacingBottom) : 0));\r\n\t\t\t \t}\r\n\r\n\t\t\t\tthis.bounds = new mxRectangle(bds.x + state.absoluteOffset.x, bds.y + state.absoluteOffset.y, bds.width, bds.height);\r\n\t\t\t}\r\n\r\n\t\t\t// Needed for word wrap inside text blocks with oversize lines to match the final result where\r\n\t \t\t// the width of the longest line is used as the reference for text alignment in the cell\r\n\t \t\t// TODO: Fix word wrapping preview for edge labels in helloworld.html\r\n\t\t\tif (this.graph.isWrapping(state.cell) && (this.bounds.width >= 2 || this.bounds.height >= 2) &&\r\n\t\t\t\tthis.textarea.innerHTML != this.getEmptyLabelText())\r\n\t\t\t{\r\n\t\t\t\tthis.textarea.style.wordWrap = mxConstants.WORD_WRAP;\r\n\t\t\t\tthis.textarea.style.whiteSpace = 'normal';\r\n\t\t\t\t\r\n\t\t \t\t// Forces automatic reflow if text is removed from an oversize label and normal word wrap\r\n\t\t\t\tvar tmp = Math.round(this.bounds.width / ((document.documentMode == 8) ? scale : scale)) + this.wordWrapPadding;\r\n\r\n\t\t\t\tif (this.textarea.style.position != 'relative')\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.textarea.style.width = tmp + 'px';\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (this.textarea.scrollWidth > tmp)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.textarea.style.width = this.textarea.scrollWidth + 'px';\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.textarea.style.maxWidth = tmp + 'px';\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\t// KNOWN: Trailing cursor in IE9 quirks mode is not visible\r\n\t\t\t\tthis.textarea.style.whiteSpace = 'nowrap';\r\n\t\t\t\tthis.textarea.style.width = '';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// LATER: Keep in visible area, add fine tuning for pixel precision\r\n\t\t\t// Workaround for wrong measuring in IE8 standards\r\n\t\t\tif (document.documentMode == 8)\r\n\t\t\t{\r\n\t\t\t\tthis.textarea.style.zoom = '1';\r\n\t\t\t\tthis.textarea.style.height = 'auto';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar ow = this.textarea.scrollWidth;\r\n\t\t\tvar oh = this.textarea.scrollHeight;\r\n\t\t\t\r\n\t\t\t// TODO: Update CSS width and height if smaller than minResize or remove minResize\r\n\t\t\t//if (this.minResize != null)\r\n\t\t\t//{\r\n\t\t\t//\tow = Math.max(ow, this.minResize.width);\r\n\t\t\t//\toh = Math.max(oh, this.minResize.height);\r\n\t\t\t//}\r\n\t\t\t\r\n\t\t\t// LATER: Keep in visible area, add fine tuning for pixel precision\r\n\t\t\tif (document.documentMode == 8)\r\n\t\t\t{\r\n\t\t\t\t// LATER: Scaled wrapping and position is wrong in IE8\r\n\t\t\t\tthis.textarea.style.left = Math.max(0, Math.ceil((this.bounds.x - m.x * (this.bounds.width - (ow + 1) * scale) + ow * (scale - 1) * 0 + (m.x + 0.5) * 2) / scale)) + 'px';\r\n\t\t\t\tthis.textarea.style.top = Math.max(0, Math.ceil((this.bounds.y - m.y * (this.bounds.height - (oh + 0.5) * scale) + oh * (scale - 1) * 0 + Math.abs(m.y + 0.5) * 1) / scale)) + 'px';\r\n\t\t\t\t// Workaround for wrong event handling width and height\r\n\t\t\t\tthis.textarea.style.width = Math.round(ow * scale) + 'px';\r\n\t\t\t\tthis.textarea.style.height = Math.round(oh * scale) + 'px';\r\n\t\t\t}\r\n\t\t\telse if (mxClient.IS_QUIRKS)\r\n\t\t\t{\t\t\t\r\n\t\t\t\tthis.textarea.style.left = Math.max(0, Math.ceil(this.bounds.x - m.x * (this.bounds.width - (ow + 1) * scale) + ow * (scale - 1) * 0 + (m.x + 0.5) * 2)) + 'px';\r\n\t\t\t\tthis.textarea.style.top = Math.max(0, Math.ceil(this.bounds.y - m.y * (this.bounds.height - (oh + 0.5) * scale) + oh * (scale - 1) * 0 + Math.abs(m.y + 0.5) * 1)) + 'px';\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.textarea.style.left = Math.max(0, Math.round(this.bounds.x - m.x * (this.bounds.width - 2)) + 1) + 'px';\r\n\t\t\t\tthis.textarea.style.top = Math.max(0, Math.round(this.bounds.y - m.y * (this.bounds.height - 4) + ((m.y == -1) ? 3 : 0)) + 1) + 'px';\r\n\t\t\t}\r\n\t \t}\r\n\r\n\t\tif (mxClient.IS_VML)\r\n\t\t{\r\n\t\t\tthis.textarea.style.zoom = scale;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tmxUtils.setPrefixedStyle(this.textarea.style, 'transformOrigin', '0px 0px');\r\n\t\t\tmxUtils.setPrefixedStyle(this.textarea.style, 'transform',\r\n\t\t\t\t'scale(' + scale + ',' + scale + ')' + ((m == null) ? '' :\r\n\t\t\t\t' translate(' + (m.x * 100) + '%,' + (m.y * 100) + '%)'));\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: focusLost\r\n *\r\n * Called if the textarea has lost focus.\r\n */\r\nmxCellEditor.prototype.focusLost = function()\r\n{\r\n\tthis.stopEditing(!this.graph.isInvokesStopCellEditing());\r\n};\r\n\r\n/**\r\n * Function: getBackgroundColor\r\n * \r\n * Returns the background color for the in-place editor. This implementation\r\n * always returns null.\r\n */\r\nmxCellEditor.prototype.getBackgroundColor = function(state)\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: isLegacyEditor\r\n * \r\n * Returns true if max-width is not supported or if the SVG root element in\r\n * in the graph does not have CSS position absolute. In these cases the text\r\n * editor must use CSS position absolute to avoid an offset but it will have\r\n * a less accurate line wrapping width during the text editing preview. This\r\n * implementation returns true for IE8- and quirks mode or if the CSS position\r\n * of the SVG element is not absolute.\r\n */\r\nmxCellEditor.prototype.isLegacyEditor = function()\r\n{\r\n\tif (mxClient.IS_VML)\r\n\t{\r\n\t\treturn true;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar absoluteRoot = false;\r\n\t\t\r\n\t\tif (mxClient.IS_SVG)\r\n\t\t{\r\n\t\t\tvar root = this.graph.view.getDrawPane().ownerSVGElement;\r\n\t\t\t\r\n\t\t\tif (root != null)\r\n\t\t\t{\r\n\t\t\t\tabsoluteRoot = mxUtils.getCurrentStyle(root).position == 'absolute';\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn !absoluteRoot;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: startEditing\r\n *\r\n * Starts the editor for the given cell.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> to start editing.\r\n * trigger - Optional mouse event that triggered the editor.\r\n */\r\nmxCellEditor.prototype.startEditing = function(cell, trigger)\r\n{\r\n\tthis.stopEditing(true);\r\n\t\r\n\t// Creates new textarea instance\r\n\tif (this.textarea == null)\r\n\t{\r\n\t\tthis.init();\r\n\t}\r\n\t\r\n\tif (this.graph.tooltipHandler != null)\r\n\t{\r\n\t\tthis.graph.tooltipHandler.hideTooltip();\r\n\t}\r\n\t\r\n\tvar state = this.graph.getView().getState(cell);\r\n\t\r\n\tif (state != null)\r\n\t{\r\n\t\t// Configures the style of the in-place editor\r\n\t\tvar scale = this.graph.getView().scale;\r\n\t\tvar size = mxUtils.getValue(state.style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE);\r\n\t\tvar family = mxUtils.getValue(state.style, mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILY);\r\n\t\tvar color = mxUtils.getValue(state.style, mxConstants.STYLE_FONTCOLOR, 'black');\r\n\t\tvar align = mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT);\r\n\t\tvar bold = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &\r\n\t\t\t\tmxConstants.FONT_BOLD) == mxConstants.FONT_BOLD;\r\n\t\tvar italic = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &\r\n\t\t\t\tmxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC;\r\n\t\tvar uline = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &\r\n\t\t\t\tmxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE;\r\n\t\t\r\n\t\tthis.textarea.style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? Math.round(size * mxConstants.LINE_HEIGHT) + 'px' : mxConstants.LINE_HEIGHT;\r\n\t\tthis.textarea.style.backgroundColor = this.getBackgroundColor(state);\r\n\t\tthis.textarea.style.textDecoration = (uline) ? 'underline' : '';\r\n\t\tthis.textarea.style.fontWeight = (bold) ? 'bold' : 'normal';\r\n\t\tthis.textarea.style.fontStyle = (italic) ? 'italic' : '';\r\n\t\tthis.textarea.style.fontSize = Math.round(size) + 'px';\r\n\t\tthis.textarea.style.zIndex = this.zIndex;\r\n\t\t// Quotes are workaround for font name \"m+\"\r\n\t\tthis.textarea.style.fontFamily = '\"' + family + '\"';\r\n\t\tthis.textarea.style.textAlign = align;\r\n\t\tthis.textarea.style.outline = 'none';\r\n\t\tthis.textarea.style.color = color;\r\n\t\t\r\n\t\tvar dir = this.textDirection = mxUtils.getValue(state.style, mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION);\r\n\t\t\r\n\t\tif (dir == mxConstants.TEXT_DIRECTION_AUTO)\r\n\t\t{\r\n\t\t\tif (state != null && state.text != null && state.text.dialect != mxConstants.DIALECT_STRICTHTML &&\r\n\t\t\t\t!mxUtils.isNode(state.text.value))\r\n\t\t\t{\r\n\t\t\t\tdir = state.text.getAutoDirection();\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (dir == mxConstants.TEXT_DIRECTION_LTR || dir == mxConstants.TEXT_DIRECTION_RTL)\r\n\t\t{\r\n\t\t\tthis.textarea.setAttribute('dir', dir);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.textarea.removeAttribute('dir');\r\n\t\t}\r\n\r\n\t\t// Sets the initial editing value\r\n\t\tthis.textarea.innerHTML = this.getInitialValue(state, trigger) || '';\r\n\t\tthis.initialValue = this.textarea.innerHTML;\r\n\r\n\t\t// Uses an optional text value for empty labels which is cleared\r\n\t\t// when the first keystroke appears. This makes it easier to see\r\n\t\t// that a label is being edited even if the label is empty.\r\n\t\tif (this.textarea.innerHTML.length == 0 || this.textarea.innerHTML == '<br>')\r\n\t\t{\r\n\t\t\tthis.textarea.innerHTML = this.getEmptyLabelText();\r\n\t\t\tthis.clearOnChange = true;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.clearOnChange = this.textarea.innerHTML == this.getEmptyLabelText();\r\n\t\t}\r\n\r\n\t\tthis.graph.container.appendChild(this.textarea);\r\n\t\t\r\n\t\t// Update this after firing all potential events that could update the cleanOnChange flag\r\n\t\tthis.editingCell = cell;\r\n\t\tthis.trigger = trigger;\r\n\t\tthis.textNode = null;\r\n\r\n\t\tif (state.text != null && this.isHideLabel(state))\r\n\t\t{\r\n\t\t\tthis.textNode = state.text.node;\r\n\t\t\tthis.textNode.style.visibility = 'hidden';\r\n\t\t}\r\n\r\n\t\t// Workaround for initial offsetHeight not ready for heading in markup\r\n\t\tif (this.autoSize && (this.graph.model.isEdge(state.cell) || state.style[mxConstants.STYLE_OVERFLOW] != 'fill'))\r\n\t\t{\r\n\t\t\twindow.setTimeout(mxUtils.bind(this, function()\r\n\t\t\t{\r\n\t\t\t\tthis.resize();\r\n\t\t\t}), 0);\r\n\t\t}\r\n\t\t\r\n\t\tthis.resize();\r\n\t\t\r\n\t\t// Workaround for NS_ERROR_FAILURE in FF\r\n\t\ttry\r\n\t\t{\r\n\t\t\t// Prefers blinking cursor over no selected text if empty\r\n\t\t\tthis.textarea.focus();\r\n\t\t\t\r\n\t\t\tif (this.isSelectText() && this.textarea.innerHTML.length > 0 &&\r\n\t\t\t\t(this.textarea.innerHTML != this.getEmptyLabelText() || !this.clearOnChange))\r\n\t\t\t{\r\n\t\t\t\tdocument.execCommand('selectAll', false, null);\r\n\t\t\t}\r\n\t\t}\r\n\t\tcatch (e)\r\n\t\t{\r\n\t\t\t// ignore\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isSelectText\r\n * \r\n * Returns <selectText>.\r\n */\r\nmxCellEditor.prototype.isSelectText = function()\r\n{\r\n\treturn this.selectText;\r\n};\r\n\r\n/**\r\n * Function: isSelectText\r\n * \r\n * Returns <selectText>.\r\n */\r\nmxCellEditor.prototype.clearSelection = function()\r\n{\r\n\tvar selection = null;\r\n\t\r\n\tif (window.getSelection)\r\n\t{\r\n\t\tselection = window.getSelection();\r\n\t}\r\n\telse if (document.selection)\r\n\t{\r\n\t\tselection = document.selection;\r\n\t}\r\n\t\r\n\tif (selection != null)\r\n\t{\r\n\t\tif (selection.empty)\r\n\t\t{\r\n\t\t\tselection.empty();\r\n\t\t}\r\n\t\telse if (selection.removeAllRanges)\r\n\t\t{\r\n\t\t\tselection.removeAllRanges();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: stopEditing\r\n *\r\n * Stops the editor and applies the value if cancel is false.\r\n */\r\nmxCellEditor.prototype.stopEditing = function(cancel)\r\n{\r\n\tcancel = cancel || false;\r\n\t\r\n\tif (this.editingCell != null)\r\n\t{\r\n\t\tif (this.textNode != null)\r\n\t\t{\r\n\t\t\tthis.textNode.style.visibility = 'visible';\r\n\t\t\tthis.textNode = null;\r\n\t\t}\r\n\r\n\t\tvar state = (!cancel) ? this.graph.view.getState(this.editingCell) : null;\r\n\r\n\t\tvar initial = this.initialValue;\r\n\t\tthis.initialValue = null;\r\n\t\tthis.editingCell = null;\r\n\t\tthis.trigger = null;\r\n\t\tthis.bounds = null;\r\n\t\tthis.textarea.blur();\r\n\t\tthis.clearSelection();\r\n\t\t\r\n\t\tif (this.textarea.parentNode != null)\r\n\t\t{\r\n\t\t\tthis.textarea.parentNode.removeChild(this.textarea);\r\n\t\t}\r\n\t\t\r\n\t\tif (this.clearOnChange && this.textarea.innerHTML == this.getEmptyLabelText())\r\n\t\t{\r\n\t\t\tthis.textarea.innerHTML = '';\r\n\t\t\tthis.clearOnChange = false;\r\n\t\t}\r\n\t\t\r\n\t\tif (state != null && this.textarea.innerHTML != initial)\r\n\t\t{\r\n\t\t\tthis.prepareTextarea();\r\n\t\t\tvar value = this.getCurrentValue(state);\r\n\t\t\t\r\n\t\t\tif (value != null)\r\n\t\t\t{\r\n\t\t\t\tthis.applyValue(state, value);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Forces new instance on next edit for undo history reset\r\n\t\tmxEvent.release(this.textarea);\r\n\t\tthis.textarea = null;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: prepareTextarea\r\n * \r\n * Prepares the textarea for getting its value in <stopEditing>.\r\n * This implementation removes the extra trailing linefeed in Firefox.\r\n */\r\nmxCellEditor.prototype.prepareTextarea = function()\r\n{\r\n\tif (mxClient.IS_FF && this.textarea.lastChild != null &&\r\n\t\tthis.textarea.lastChild.nodeName == 'BR')\r\n\t{\r\n\t\tthis.textarea.removeChild(this.textarea.lastChild);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isHideLabel\r\n * \r\n * Returns true if the label should be hidden while the cell is being\r\n * edited.\r\n */\r\nmxCellEditor.prototype.isHideLabel = function(state)\r\n{\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: getMinimumSize\r\n * \r\n * Returns the minimum width and height for editing the given state.\r\n */\r\nmxCellEditor.prototype.getMinimumSize = function(state)\r\n{\r\n\tvar scale = this.graph.getView().scale;\r\n\t\r\n\treturn new mxRectangle(0, 0, (state.text == null) ? 30 : state.text.size * scale + 20,\r\n\t\t\t(this.textarea.style.textAlign == 'left') ? 120 : 40);\r\n};\r\n\r\n/**\r\n * Function: getEditorBounds\r\n * \r\n * Returns the <mxRectangle> that defines the bounds of the editor.\r\n */\r\nmxCellEditor.prototype.getEditorBounds = function(state)\r\n{\r\n\tvar isEdge = this.graph.getModel().isEdge(state.cell);\r\n\tvar scale = this.graph.getView().scale;\r\n\tvar minSize = this.getMinimumSize(state);\r\n\tvar minWidth = minSize.width;\r\n \tvar minHeight = minSize.height;\r\n \tvar result = null;\r\n \t\r\n \tif (!isEdge && state.view.graph.cellRenderer.legacySpacing && state.style[mxConstants.STYLE_OVERFLOW] == 'fill')\r\n \t{\r\n \t\tresult = state.shape.getLabelBounds(mxRectangle.fromRectangle(state));\r\n \t}\r\n \telse\r\n \t{\r\n\t\tvar spacing = parseInt(state.style[mxConstants.STYLE_SPACING] || 0) * scale;\r\n\t\tvar spacingTop = (parseInt(state.style[mxConstants.STYLE_SPACING_TOP] || 0) + mxText.prototype.baseSpacingTop) * scale + spacing;\r\n\t\tvar spacingRight = (parseInt(state.style[mxConstants.STYLE_SPACING_RIGHT] || 0) + mxText.prototype.baseSpacingRight) * scale + spacing;\r\n\t\tvar spacingBottom = (parseInt(state.style[mxConstants.STYLE_SPACING_BOTTOM] || 0) + mxText.prototype.baseSpacingBottom) * scale + spacing;\r\n\t\tvar spacingLeft = (parseInt(state.style[mxConstants.STYLE_SPACING_LEFT] || 0) + mxText.prototype.baseSpacingLeft) * scale + spacing;\r\n\t\r\n\t \tresult = new mxRectangle(state.x, state.y,\r\n\t \t\t Math.max(minWidth, state.width - spacingLeft - spacingRight),\r\n\t \t\t Math.max(minHeight, state.height - spacingTop - spacingBottom));\r\n\t\tvar hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);\r\n\t\tvar vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);\r\n\t\t\r\n\t\tresult = (state.shape != null && hpos == mxConstants.ALIGN_CENTER && vpos == mxConstants.ALIGN_MIDDLE) ? state.shape.getLabelBounds(result) : result;\r\n\t\r\n\t\tif (isEdge)\r\n\t\t{\r\n\t\t\tresult.x = state.absoluteOffset.x;\r\n\t\t\tresult.y = state.absoluteOffset.y;\r\n\t\r\n\t\t\tif (state.text != null && state.text.boundingBox != null)\r\n\t\t\t{\r\n\t\t\t\t// Workaround for label containing just spaces in which case\r\n\t\t\t\t// the bounding box location contains negative numbers \r\n\t\t\t\tif (state.text.boundingBox.x > 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tresult.x = state.text.boundingBox.x;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (state.text.boundingBox.y > 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tresult.y = state.text.boundingBox.y;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (state.text != null && state.text.boundingBox != null)\r\n\t\t{\r\n\t\t\tresult.x = Math.min(result.x, state.text.boundingBox.x);\r\n\t\t\tresult.y = Math.min(result.y, state.text.boundingBox.y);\r\n\t\t}\r\n\t\r\n\t\tresult.x += spacingLeft;\r\n\t\tresult.y += spacingTop;\r\n\t\r\n\t\tif (state.text != null && state.text.boundingBox != null)\r\n\t\t{\r\n\t\t\tif (!isEdge)\r\n\t\t\t{\r\n\t\t\t\tresult.width = Math.max(result.width, state.text.boundingBox.width);\r\n\t\t\t\tresult.height = Math.max(result.height, state.text.boundingBox.height);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tresult.width = Math.max(minWidth, state.text.boundingBox.width);\r\n\t\t\t\tresult.height = Math.max(minHeight, state.text.boundingBox.height);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Applies the horizontal and vertical label positions\r\n\t\tif (this.graph.getModel().isVertex(state.cell))\r\n\t\t{\r\n\t\t\tvar horizontal = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);\r\n\t\r\n\t\t\tif (horizontal == mxConstants.ALIGN_LEFT)\r\n\t\t\t{\r\n\t\t\t\tresult.x -= state.width;\r\n\t\t\t}\r\n\t\t\telse if (horizontal == mxConstants.ALIGN_RIGHT)\r\n\t\t\t{\r\n\t\t\t\tresult.x += state.width;\r\n\t\t\t}\r\n\t\r\n\t\t\tvar vertical = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);\r\n\t\r\n\t\t\tif (vertical == mxConstants.ALIGN_TOP)\r\n\t\t\t{\r\n\t\t\t\tresult.y -= state.height;\r\n\t\t\t}\r\n\t\t\telse if (vertical == mxConstants.ALIGN_BOTTOM)\r\n\t\t\t{\r\n\t\t\t\tresult.y += state.height;\r\n\t\t\t}\r\n\t\t}\r\n \t}\r\n \t\r\n \treturn new mxRectangle(Math.round(result.x), Math.round(result.y), Math.round(result.width), Math.round(result.height));\r\n};\r\n\r\n/**\r\n * Function: getEmptyLabelText\r\n *\r\n * Returns the initial label value to be used of the label of the given\r\n * cell is empty. This label is displayed and cleared on the first keystroke.\r\n * This implementation returns <emptyLabelText>.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> for which a text for an empty editing box should be\r\n * returned.\r\n */\r\nmxCellEditor.prototype.getEmptyLabelText = function (cell)\r\n{\r\n\treturn this.emptyLabelText;\r\n};\r\n\r\n/**\r\n * Function: getEditingCell\r\n *\r\n * Returns the cell that is currently being edited or null if no cell is\r\n * being edited.\r\n */\r\nmxCellEditor.prototype.getEditingCell = function ()\r\n{\r\n\treturn this.editingCell;\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n *\r\n * Destroys the editor and removes all associated resources.\r\n */\r\nmxCellEditor.prototype.destroy = function ()\r\n{\r\n\tif (this.textarea != null)\r\n\t{\r\n\t\tmxEvent.release(this.textarea);\r\n\t\t\r\n\t\tif (this.textarea.parentNode != null)\r\n\t\t{\r\n\t\t\tthis.textarea.parentNode.removeChild(this.textarea);\r\n\t\t}\r\n\t\t\r\n\t\tthis.textarea = null;\r\n\r\n\t}\r\n\t\t\t\r\n\tif (this.changeHandler != null)\r\n\t{\r\n\t\tthis.graph.getModel().removeListener(this.changeHandler);\r\n\t\tthis.changeHandler = null;\r\n\t}\r\n\r\n\tif (this.zoomHandler)\r\n\t{\r\n\t\tthis.graph.view.removeListener(this.zoomHandler);\r\n\t\tthis.zoomHandler = null;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2017, JGraph Ltd\r\n * Copyright (c) 2006-2017, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxCellRenderer\r\n * \r\n * Renders cells into a document object model. The <defaultShapes> is a global\r\n * map of shapename, constructor pairs that is used in all instances. You can\r\n * get a list of all available shape names using the following code.\r\n * \r\n * In general the cell renderer is in charge of creating, redrawing and\r\n * destroying the shape and label associated with a cell state, as well as\r\n * some other graphical objects, namely controls and overlays. The shape\r\n * hieararchy in the display (ie. the hierarchy in which the DOM nodes\r\n * appear in the document) does not reflect the cell hierarchy. The shapes\r\n * are a (flat) sequence of shapes and labels inside the draw pane of the\r\n * graph view, with some exceptions, namely the HTML labels being placed\r\n * directly inside the graph container for certain browsers.\r\n * \r\n * (code)\r\n * mxLog.show();\r\n * for (var i in mxCellRenderer.defaultShapes)\r\n * {\r\n *   mxLog.debug(i);\r\n * }\r\n * (end)\r\n *\r\n * Constructor: mxCellRenderer\r\n * \r\n * Constructs a new cell renderer with the following built-in shapes:\r\n * arrow, rectangle, ellipse, rhombus, image, line, label, cylinder,\r\n * swimlane, connector, actor and cloud.\r\n */\r\nfunction mxCellRenderer() { };\r\n\r\n/**\r\n * Variable: defaultShapes\r\n * \r\n * Static array that contains the globally registered shapes which are\r\n * known to all instances of this class. For adding new shapes you should\r\n * use the static <mxCellRenderer.registerShape> function.\r\n */\r\nmxCellRenderer.defaultShapes = new Object();\r\n\r\n/**\r\n * Variable: defaultEdgeShape\r\n * \r\n * Defines the default shape for edges. Default is <mxConnector>.\r\n */\r\nmxCellRenderer.prototype.defaultEdgeShape = mxConnector;\r\n\r\n/**\r\n * Variable: defaultVertexShape\r\n * \r\n * Defines the default shape for vertices. Default is <mxRectangleShape>.\r\n */\r\nmxCellRenderer.prototype.defaultVertexShape = mxRectangleShape;\r\n\r\n/**\r\n * Variable: defaultTextShape\r\n * \r\n * Defines the default shape for labels. Default is <mxText>.\r\n */\r\nmxCellRenderer.prototype.defaultTextShape = mxText;\r\n\r\n/**\r\n * Variable: legacyControlPosition\r\n * \r\n * Specifies if the folding icon should ignore the horizontal\r\n * orientation of a swimlane. Default is true.\r\n */\r\nmxCellRenderer.prototype.legacyControlPosition = true;\r\n\r\n/**\r\n * Variable: legacySpacing\r\n * \r\n * Specifies if spacing and label position should be ignored if overflow is\r\n * fill or width. Default is true for backwards compatiblity.\r\n */\r\nmxCellRenderer.prototype.legacySpacing = true;\r\n\r\n/**\r\n * Variable: antiAlias\r\n * \r\n * Anti-aliasing option for new shapes. Default is true.\r\n */\r\nmxCellRenderer.prototype.antiAlias = true;\r\n\r\n/**\r\n * Variable: minSvgStrokeWidth\r\n * \r\n * Minimum stroke width for SVG output.\r\n */\r\nmxCellRenderer.prototype.minSvgStrokeWidth = 1;\r\n\r\n/**\r\n * Variable: forceControlClickHandler\r\n * \r\n * Specifies if the enabled state of the graph should be ignored in the control\r\n * click handler (to allow folding in disabled graphs). Default is false.\r\n */\r\nmxCellRenderer.prototype.forceControlClickHandler = false;\r\n\r\n/**\r\n * Function: registerShape\r\n * \r\n * Registers the given constructor under the specified key in this instance\r\n * of the renderer.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * mxCellRenderer.registerShape(mxConstants.SHAPE_RECTANGLE, mxRectangleShape);\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * key - String representing the shape name.\r\n * shape - Constructor of the <mxShape> subclass.\r\n */\r\nmxCellRenderer.registerShape = function(key, shape)\r\n{\r\n\tmxCellRenderer.defaultShapes[key] = shape;\r\n};\r\n\r\n// Adds default shapes into the default shapes array\r\nmxCellRenderer.registerShape(mxConstants.SHAPE_RECTANGLE, mxRectangleShape);\r\nmxCellRenderer.registerShape(mxConstants.SHAPE_ELLIPSE, mxEllipse);\r\nmxCellRenderer.registerShape(mxConstants.SHAPE_RHOMBUS, mxRhombus);\r\nmxCellRenderer.registerShape(mxConstants.SHAPE_CYLINDER, mxCylinder);\r\nmxCellRenderer.registerShape(mxConstants.SHAPE_CONNECTOR, mxConnector);\r\nmxCellRenderer.registerShape(mxConstants.SHAPE_ACTOR, mxActor);\r\nmxCellRenderer.registerShape(mxConstants.SHAPE_TRIANGLE, mxTriangle);\r\nmxCellRenderer.registerShape(mxConstants.SHAPE_HEXAGON, mxHexagon);\r\nmxCellRenderer.registerShape(mxConstants.SHAPE_CLOUD, mxCloud);\r\nmxCellRenderer.registerShape(mxConstants.SHAPE_LINE, mxLine);\r\nmxCellRenderer.registerShape(mxConstants.SHAPE_ARROW, mxArrow);\r\nmxCellRenderer.registerShape(mxConstants.SHAPE_ARROW_CONNECTOR, mxArrowConnector);\r\nmxCellRenderer.registerShape(mxConstants.SHAPE_DOUBLE_ELLIPSE, mxDoubleEllipse);\r\nmxCellRenderer.registerShape(mxConstants.SHAPE_SWIMLANE, mxSwimlane);\r\nmxCellRenderer.registerShape(mxConstants.SHAPE_IMAGE, mxImageShape);\r\nmxCellRenderer.registerShape(mxConstants.SHAPE_LABEL, mxLabel);\r\n\r\n/**\r\n * Function: initializeShape\r\n * \r\n * Initializes the shape in the given state by calling its init method with\r\n * the correct container after configuring it using <configureShape>.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> for which the shape should be initialized.\r\n */\r\nmxCellRenderer.prototype.initializeShape = function(state)\r\n{\r\n\tstate.shape.dialect = state.view.graph.dialect;\r\n\tthis.configureShape(state);\r\n\tstate.shape.init(state.view.getDrawPane());\r\n};\r\n\r\n/**\r\n * Function: createShape\r\n * \r\n * Creates and returns the shape for the given cell state.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> for which the shape should be created.\r\n */\r\nmxCellRenderer.prototype.createShape = function(state)\r\n{\r\n\tvar shape = null;\r\n\t\r\n\tif (state.style != null)\r\n\t{\r\n\t\t// Checks if there is a stencil for the name and creates\r\n\t\t// a shape instance for the stencil if one exists\r\n\t\tvar stencil = mxStencilRegistry.getStencil(state.style[mxConstants.STYLE_SHAPE]);\r\n\t\t\r\n\t\tif (stencil != null)\r\n\t\t{\r\n\t\t\tshape = new mxShape(stencil);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar ctor = this.getShapeConstructor(state);\r\n\t\t\tshape = new ctor();\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn shape;\r\n};\r\n\r\n/**\r\n * Function: createIndicatorShape\r\n * \r\n * Creates the indicator shape for the given cell state.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> for which the indicator shape should be created.\r\n */\r\nmxCellRenderer.prototype.createIndicatorShape = function(state)\r\n{\r\n\tstate.shape.indicatorShape = this.getShape(state.view.graph.getIndicatorShape(state));\r\n};\r\n\r\n/**\r\n * Function: getShape\r\n * \r\n * Returns the shape for the given name from <defaultShapes>.\r\n */\r\nmxCellRenderer.prototype.getShape = function(name)\r\n{\r\n\treturn (name != null) ? mxCellRenderer.defaultShapes[name] : null;\r\n};\r\n\r\n/**\r\n * Function: getShapeConstructor\r\n * \r\n * Returns the constructor to be used for creating the shape.\r\n */\r\nmxCellRenderer.prototype.getShapeConstructor = function(state)\r\n{\r\n\tvar ctor = this.getShape(state.style[mxConstants.STYLE_SHAPE]);\r\n\t\r\n\tif (ctor == null)\r\n\t{\r\n\t\tctor = (state.view.graph.getModel().isEdge(state.cell)) ?\r\n\t\t\tthis.defaultEdgeShape : this.defaultVertexShape;\r\n\t}\r\n\t\r\n\treturn ctor;\r\n};\r\n\r\n/**\r\n * Function: configureShape\r\n * \r\n * Configures the shape for the given cell state.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> for which the shape should be configured.\r\n */\r\nmxCellRenderer.prototype.configureShape = function(state)\r\n{\r\n\tstate.shape.apply(state);\r\n\tstate.shape.image = state.view.graph.getImage(state);\r\n\tstate.shape.indicatorColor = state.view.graph.getIndicatorColor(state);\r\n\tstate.shape.indicatorStrokeColor = state.style[mxConstants.STYLE_INDICATOR_STROKECOLOR];\r\n\tstate.shape.indicatorGradientColor = state.view.graph.getIndicatorGradientColor(state);\r\n\tstate.shape.indicatorDirection = state.style[mxConstants.STYLE_INDICATOR_DIRECTION];\r\n\tstate.shape.indicatorImage = state.view.graph.getIndicatorImage(state);\r\n\r\n\tthis.postConfigureShape(state);\r\n};\r\n\r\n/**\r\n * Function: postConfigureShape\r\n * \r\n * Replaces any reserved words used for attributes, eg. inherit,\r\n * indicated or swimlane for colors in the shape for the given state.\r\n * This implementation resolves these keywords on the fill, stroke\r\n * and gradient color keys.\r\n */\r\nmxCellRenderer.prototype.postConfigureShape = function(state)\r\n{\r\n\tif (state.shape != null)\r\n\t{\r\n\t\tthis.resolveColor(state, 'indicatorColor', mxConstants.STYLE_FILLCOLOR);\r\n\t\tthis.resolveColor(state, 'indicatorGradientColor', mxConstants.STYLE_GRADIENTCOLOR);\r\n\t\tthis.resolveColor(state, 'fill', mxConstants.STYLE_FILLCOLOR);\r\n\t\tthis.resolveColor(state, 'stroke', mxConstants.STYLE_STROKECOLOR);\r\n\t\tthis.resolveColor(state, 'gradient', mxConstants.STYLE_GRADIENTCOLOR);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: checkPlaceholderStyles\r\n * \r\n * Resolves special keywords 'inherit', 'indicated' and 'swimlane' and sets\r\n * the respective color on the shape.\r\n */\r\nmxCellRenderer.prototype.checkPlaceholderStyles = function(state)\r\n{\r\n\t// LATER: Check if the color has actually changed\r\n\tif (state.style != null)\r\n\t{\r\n\t\tvar values = ['inherit', 'swimlane', 'indicated'];\r\n\t\tvar styles = [mxConstants.STYLE_FILLCOLOR, mxConstants.STYLE_STROKECOLOR, mxConstants.STYLE_GRADIENTCOLOR];\r\n\t\t\r\n\t\tfor (var i = 0; i < styles.length; i++)\r\n\t\t{\r\n\t\t\tif (mxUtils.indexOf(values, state.style[styles[i]]) >= 0)\r\n\t\t\t{\r\n\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: resolveColor\r\n * \r\n * Resolves special keywords 'inherit', 'indicated' and 'swimlane' and sets\r\n * the respective color on the shape.\r\n */\r\nmxCellRenderer.prototype.resolveColor = function(state, field, key)\r\n{\r\n\tvar value = state.shape[field];\r\n\tvar graph = state.view.graph;\r\n\tvar referenced = null;\r\n\t\r\n\tif (value == 'inherit')\r\n\t{\r\n\t\treferenced = graph.model.getParent(state.cell);\r\n\t}\r\n\telse if (value == 'swimlane')\r\n\t{\r\n\t\tstate.shape[field] = (key == mxConstants.STYLE_STROKECOLOR) ? '#000000' : '#ffffff';\r\n\t\t\r\n\t\tif (graph.model.getTerminal(state.cell, false) != null)\r\n\t\t{\r\n\t\t\treferenced = graph.model.getTerminal(state.cell, false);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treferenced = state.cell;\r\n\t\t}\r\n\t\t\r\n\t\treferenced = graph.getSwimlane(referenced);\r\n\t\tkey = graph.swimlaneIndicatorColorAttribute;\r\n\t}\r\n\telse if (value == 'indicated')\r\n\t{\r\n\t\tstate.shape[field] = state.shape.indicatorColor;\r\n\t}\r\n\t\r\n\tif (referenced != null)\r\n\t{\r\n\t\tvar rstate = graph.getView().getState(referenced);\r\n\t\tstate.shape[field] = null;\r\n\r\n\t\tif (rstate != null)\r\n\t\t{\r\n\t\t\tif (rstate.shape != null && field != 'indicatorColor')\r\n\t\t\t{\r\n\t\t\t\tstate.shape[field] = rstate.shape[field];\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tstate.shape[field] = rstate.style[key];\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getLabelValue\r\n * \r\n * Returns the value to be used for the label.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> for which the label should be created.\r\n */\r\nmxCellRenderer.prototype.getLabelValue = function(state)\r\n{\r\n\treturn state.view.graph.getLabel(state.cell);\r\n};\r\n\r\n/**\r\n * Function: createLabel\r\n * \r\n * Creates the label for the given cell state.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> for which the label should be created.\r\n */\r\nmxCellRenderer.prototype.createLabel = function(state, value)\r\n{\r\n\tvar graph = state.view.graph;\r\n\tvar isEdge = graph.getModel().isEdge(state.cell);\r\n\t\r\n\tif (state.style[mxConstants.STYLE_FONTSIZE] > 0 || state.style[mxConstants.STYLE_FONTSIZE] == null)\r\n\t{\r\n\t\t// Avoids using DOM node for empty labels\r\n\t\tvar isForceHtml = (graph.isHtmlLabel(state.cell) || (value != null && mxUtils.isNode(value)));\r\n\r\n\t\tstate.text = new this.defaultTextShape(value, new mxRectangle(),\r\n\t\t\t\t(state.style[mxConstants.STYLE_ALIGN] || mxConstants.ALIGN_CENTER),\r\n\t\t\t\tgraph.getVerticalAlign(state),\r\n\t\t\t\tstate.style[mxConstants.STYLE_FONTCOLOR],\r\n\t\t\t\tstate.style[mxConstants.STYLE_FONTFAMILY],\r\n\t\t\t\tstate.style[mxConstants.STYLE_FONTSIZE],\r\n\t\t\t\tstate.style[mxConstants.STYLE_FONTSTYLE],\r\n\t\t\t\tstate.style[mxConstants.STYLE_SPACING],\r\n\t\t\t\tstate.style[mxConstants.STYLE_SPACING_TOP],\r\n\t\t\t\tstate.style[mxConstants.STYLE_SPACING_RIGHT],\r\n\t\t\t\tstate.style[mxConstants.STYLE_SPACING_BOTTOM],\r\n\t\t\t\tstate.style[mxConstants.STYLE_SPACING_LEFT],\r\n\t\t\t\tstate.style[mxConstants.STYLE_HORIZONTAL],\r\n\t\t\t\tstate.style[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR],\r\n\t\t\t\tstate.style[mxConstants.STYLE_LABEL_BORDERCOLOR],\r\n\t\t\t\tgraph.isWrapping(state.cell) && graph.isHtmlLabel(state.cell),\r\n\t\t\t\tgraph.isLabelClipped(state.cell),\r\n\t\t\t\tstate.style[mxConstants.STYLE_OVERFLOW],\r\n\t\t\t\tstate.style[mxConstants.STYLE_LABEL_PADDING],\r\n\t\t\t\tmxUtils.getValue(state.style, mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION));\r\n\t\tstate.text.opacity = mxUtils.getValue(state.style, mxConstants.STYLE_TEXT_OPACITY, 100);\r\n\t\tstate.text.dialect = (isForceHtml) ? mxConstants.DIALECT_STRICTHTML : state.view.graph.dialect;\r\n\t\tstate.text.style = state.style;\r\n\t\tstate.text.state = state;\r\n\t\tthis.initializeLabel(state, state.text);\r\n\t\t\r\n\t\t// Workaround for touch devices routing all events for a mouse gesture\r\n\t\t// (down, move, up) via the initial DOM node. IE additionally redirects\r\n\t\t// the event via the initial DOM node but the event source is the node\r\n\t\t// under the mouse, so we need to check if this is the case and force\r\n\t\t// getCellAt for the subsequent mouseMoves and the final mouseUp.\r\n\t\tvar forceGetCell = false;\r\n\t\t\r\n\t\tvar getState = function(evt)\r\n\t\t{\r\n\t\t\tvar result = state;\r\n\r\n\t\t\tif (mxClient.IS_TOUCH || forceGetCell)\r\n\t\t\t{\r\n\t\t\t\tvar x = mxEvent.getClientX(evt);\r\n\t\t\t\tvar y = mxEvent.getClientY(evt);\r\n\t\t\t\t\r\n\t\t\t\t// Dispatches the drop event to the graph which\r\n\t\t\t\t// consumes and executes the source function\r\n\t\t\t\tvar pt = mxUtils.convertPoint(graph.container, x, y);\r\n\t\t\t\tresult = graph.view.getState(graph.getCellAt(pt.x, pt.y));\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\treturn result;\r\n\t\t};\r\n\t\t\r\n\t\t// TODO: Add handling for special touch device gestures\r\n\t\tmxEvent.addGestureListeners(state.text.node,\r\n\t\t\tmxUtils.bind(this, function(evt)\r\n\t\t\t{\r\n\t\t\t\tif (this.isLabelEvent(state, evt))\r\n\t\t\t\t{\r\n\t\t\t\t\tgraph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));\r\n\t\t\t\t\tforceGetCell = graph.dialect != mxConstants.DIALECT_SVG &&\r\n\t\t\t\t\t\tmxEvent.getSource(evt).nodeName == 'IMG';\r\n\t\t\t\t}\r\n\t\t\t}),\r\n\t\t\tmxUtils.bind(this, function(evt)\r\n\t\t\t{\r\n\t\t\t\tif (this.isLabelEvent(state, evt))\r\n\t\t\t\t{\r\n\t\t\t\t\tgraph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));\r\n\t\t\t\t}\r\n\t\t\t}),\r\n\t\t\tmxUtils.bind(this, function(evt)\r\n\t\t\t{\r\n\t\t\t\tif (this.isLabelEvent(state, evt))\r\n\t\t\t\t{\r\n\t\t\t\t\tgraph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, getState(evt)));\r\n\t\t\t\t\tforceGetCell = false;\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t);\r\n\r\n\t\t// Uses double click timeout in mxGraph for quirks mode\r\n\t\tif (graph.nativeDblClickEnabled)\r\n\t\t{\r\n\t\t\tmxEvent.addListener(state.text.node, 'dblclick',\r\n\t\t\t\tmxUtils.bind(this, function(evt)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (this.isLabelEvent(state, evt))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tgraph.dblClick(evt, state.cell);\r\n\t\t\t\t\t\tmxEvent.consume(evt);\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: initializeLabel\r\n * \r\n * Initiailzes the label with a suitable container.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose label should be initialized.\r\n */\r\nmxCellRenderer.prototype.initializeLabel = function(state, shape)\r\n{\r\n\tif (mxClient.IS_SVG && mxClient.NO_FO && shape.dialect != mxConstants.DIALECT_SVG)\r\n\t{\r\n\t\tshape.init(state.view.graph.container);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tshape.init(state.view.getDrawPane());\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createCellOverlays\r\n * \r\n * Creates the actual shape for showing the overlay for the given cell state.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> for which the overlay should be created.\r\n */\r\nmxCellRenderer.prototype.createCellOverlays = function(state)\r\n{\r\n\tvar graph = state.view.graph;\r\n\tvar overlays = graph.getCellOverlays(state.cell);\r\n\tvar dict = null;\r\n\t\r\n\tif (overlays != null)\r\n\t{\r\n\t\tdict = new mxDictionary();\r\n\t\t\r\n\t\tfor (var i = 0; i < overlays.length; i++)\r\n\t\t{\r\n\t\t\tvar shape = (state.overlays != null) ? state.overlays.remove(overlays[i]) : null;\r\n\t\t\t\r\n\t\t\tif (shape == null)\r\n\t\t\t{\r\n\t\t\t\tvar tmp = new mxImageShape(new mxRectangle(), overlays[i].image.src);\r\n\t\t\t\ttmp.dialect = state.view.graph.dialect;\r\n\t\t\t\ttmp.preserveImageAspect = false;\r\n\t\t\t\ttmp.overlay = overlays[i];\r\n\t\t\t\tthis.initializeOverlay(state, tmp);\r\n\t\t\t\tthis.installCellOverlayListeners(state, overlays[i], tmp);\r\n\t\r\n\t\t\t\tif (overlays[i].cursor != null)\r\n\t\t\t\t{\r\n\t\t\t\t\ttmp.node.style.cursor = overlays[i].cursor;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tdict.put(overlays[i], tmp);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tdict.put(overlays[i], shape);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\t// Removes unused\r\n\tif (state.overlays != null)\r\n\t{\r\n\t\tstate.overlays.visit(function(id, shape)\r\n\t\t{\r\n\t\t\tshape.destroy();\r\n\t\t});\r\n\t}\r\n\t\r\n\tstate.overlays = dict;\r\n};\r\n\r\n/**\r\n * Function: initializeOverlay\r\n * \r\n * Initializes the given overlay.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> for which the overlay should be created.\r\n * overlay - <mxImageShape> that represents the overlay.\r\n */\r\nmxCellRenderer.prototype.initializeOverlay = function(state, overlay)\r\n{\r\n\toverlay.init(state.view.getOverlayPane());\r\n};\r\n\r\n/**\r\n * Function: installOverlayListeners\r\n * \r\n * Installs the listeners for the given <mxCellState>, <mxCellOverlay> and\r\n * <mxShape> that represents the overlay.\r\n */\r\nmxCellRenderer.prototype.installCellOverlayListeners = function(state, overlay, shape)\r\n{\r\n\tvar graph  = state.view.graph;\r\n\t\r\n\tmxEvent.addListener(shape.node, 'click', function (evt)\r\n\t{\r\n\t\tif (graph.isEditing())\r\n\t\t{\r\n\t\t\tgraph.stopEditing(!graph.isInvokesStopCellEditing());\r\n\t\t}\r\n\t\t\r\n\t\toverlay.fireEvent(new mxEventObject(mxEvent.CLICK,\r\n\t\t\t\t'event', evt, 'cell', state.cell));\r\n\t});\r\n\t\r\n\tmxEvent.addGestureListeners(shape.node,\r\n\t\tfunction (evt)\r\n\t\t{\r\n\t\t\tmxEvent.consume(evt);\r\n\t\t},\r\n\t\tfunction (evt)\r\n\t\t{\r\n\t\t\tgraph.fireMouseEvent(mxEvent.MOUSE_MOVE,\r\n\t\t\t\tnew mxMouseEvent(evt, state));\r\n\t\t});\r\n\t\r\n\tif (mxClient.IS_TOUCH)\r\n\t{\r\n\t\tmxEvent.addListener(shape.node, 'touchend', function (evt)\r\n\t\t{\r\n\t\t\toverlay.fireEvent(new mxEventObject(mxEvent.CLICK,\r\n\t\t\t\t\t'event', evt, 'cell', state.cell));\r\n\t\t});\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createControl\r\n * \r\n * Creates the control for the given cell state.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> for which the control should be created.\r\n */\r\nmxCellRenderer.prototype.createControl = function(state)\r\n{\r\n\tvar graph = state.view.graph;\r\n\tvar image = graph.getFoldingImage(state);\r\n\t\r\n\tif (graph.foldingEnabled && image != null)\r\n\t{\r\n\t\tif (state.control == null)\r\n\t\t{\r\n\t\t\tvar b = new mxRectangle(0, 0, image.width, image.height);\r\n\t\t\tstate.control = new mxImageShape(b, image.src);\r\n\t\t\tstate.control.preserveImageAspect = false;\r\n\t\t\tstate.control.dialect = graph.dialect;\r\n\r\n\t\t\tthis.initControl(state, state.control, true, this.createControlClickHandler(state));\r\n\t\t}\r\n\t}\r\n\telse if (state.control != null)\r\n\t{\r\n\t\tstate.control.destroy();\r\n\t\tstate.control = null;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createControlClickHandler\r\n * \r\n * Hook for creating the click handler for the folding icon.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose control click handler should be returned.\r\n */\r\nmxCellRenderer.prototype.createControlClickHandler = function(state)\r\n{\r\n\tvar graph = state.view.graph;\r\n\t\r\n\treturn mxUtils.bind(this, function (evt)\r\n\t{\r\n\t\tif (this.forceControlClickHandler || graph.isEnabled())\r\n\t\t{\r\n\t\t\tvar collapse = !graph.isCellCollapsed(state.cell);\r\n\t\t\tgraph.foldCells(collapse, false, [state.cell], null, evt);\r\n\t\t\tmxEvent.consume(evt);\r\n\t\t}\r\n\t});\r\n};\r\n\r\n/**\r\n * Function: initControl\r\n * \r\n * Initializes the given control and returns the corresponding DOM node.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> for which the control should be initialized.\r\n * control - <mxShape> to be initialized.\r\n * handleEvents - Boolean indicating if mousedown and mousemove should fire events via the graph.\r\n * clickHandler - Optional function to implement clicks on the control.\r\n */\r\nmxCellRenderer.prototype.initControl = function(state, control, handleEvents, clickHandler)\r\n{\r\n\tvar graph = state.view.graph;\r\n\t\r\n\t// In the special case where the label is in HTML and the display is SVG the image\r\n\t// should go into the graph container directly in order to be clickable. Otherwise\r\n\t// it is obscured by the HTML label that overlaps the cell.\r\n\tvar isForceHtml = graph.isHtmlLabel(state.cell) && mxClient.NO_FO &&\r\n\t\tgraph.dialect == mxConstants.DIALECT_SVG;\r\n\r\n\tif (isForceHtml)\r\n\t{\r\n\t\tcontrol.dialect = mxConstants.DIALECT_PREFERHTML;\r\n\t\tcontrol.init(graph.container);\r\n\t\tcontrol.node.style.zIndex = 1;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tcontrol.init(state.view.getOverlayPane());\r\n\t}\r\n\r\n\tvar node = control.innerNode || control.node;\r\n\t\r\n\t// Workaround for missing click event on iOS is to check tolerance below\r\n\tif (clickHandler != null && !mxClient.IS_IOS)\r\n\t{\r\n\t\tif (graph.isEnabled())\r\n\t\t{\r\n\t\t\tnode.style.cursor = 'pointer';\r\n\t\t}\r\n\t\t\r\n\t\tmxEvent.addListener(node, 'click', clickHandler);\r\n\t}\r\n\t\r\n\tif (handleEvents)\r\n\t{\r\n\t\tvar first = null;\r\n\r\n\t\tmxEvent.addGestureListeners(node,\r\n\t\t\tfunction (evt)\r\n\t\t\t{\r\n\t\t\t\tfirst = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));\r\n\t\t\t\tgraph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));\r\n\t\t\t\tmxEvent.consume(evt);\r\n\t\t\t},\r\n\t\t\tfunction (evt)\r\n\t\t\t{\r\n\t\t\t\tgraph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, state));\r\n\t\t\t},\r\n\t\t\tfunction (evt)\r\n\t\t\t{\r\n\t\t\t\tgraph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, state));\r\n\t\t\t\tmxEvent.consume(evt);\r\n\t\t\t});\r\n\t\t\r\n\t\t// Uses capture phase for event interception to stop bubble phase\r\n\t\tif (clickHandler != null && mxClient.IS_IOS)\r\n\t\t{\r\n\t\t\tnode.addEventListener('touchend', function(evt)\r\n\t\t\t{\r\n\t\t\t\tif (first != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar tol = graph.tolerance;\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (Math.abs(first.x - mxEvent.getClientX(evt)) < tol &&\r\n\t\t\t\t\t\tMath.abs(first.y - mxEvent.getClientY(evt)) < tol)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tclickHandler.call(clickHandler, evt);\r\n\t\t\t\t\t\tmxEvent.consume(evt);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}, true);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn node;\r\n};\r\n\r\n/**\r\n * Function: isShapeEvent\r\n * \r\n * Returns true if the event is for the shape of the given state. This\r\n * implementation always returns true.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose shape fired the event.\r\n * evt - Mouse event which was fired.\r\n */\r\nmxCellRenderer.prototype.isShapeEvent = function(state, evt)\r\n{\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: isLabelEvent\r\n * \r\n * Returns true if the event is for the label of the given state. This\r\n * implementation always returns true.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose label fired the event.\r\n * evt - Mouse event which was fired.\r\n */\r\nmxCellRenderer.prototype.isLabelEvent = function(state, evt)\r\n{\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: installListeners\r\n * \r\n * Installs the event listeners for the given cell state.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> for which the event listeners should be isntalled.\r\n */\r\nmxCellRenderer.prototype.installListeners = function(state)\r\n{\r\n\tvar graph = state.view.graph;\r\n\r\n\t// Workaround for touch devices routing all events for a mouse\r\n\t// gesture (down, move, up) via the initial DOM node. Same for\r\n\t// HTML images in all IE versions (VML images are working).\r\n\tvar getState = function(evt)\r\n\t{\r\n\t\tvar result = state;\r\n\t\t\r\n\t\tif ((graph.dialect != mxConstants.DIALECT_SVG && mxEvent.getSource(evt).nodeName == 'IMG') || mxClient.IS_TOUCH)\r\n\t\t{\r\n\t\t\tvar x = mxEvent.getClientX(evt);\r\n\t\t\tvar y = mxEvent.getClientY(evt);\r\n\t\t\t\r\n\t\t\t// Dispatches the drop event to the graph which\r\n\t\t\t// consumes and executes the source function\r\n\t\t\tvar pt = mxUtils.convertPoint(graph.container, x, y);\r\n\t\t\tresult = graph.view.getState(graph.getCellAt(pt.x, pt.y));\r\n\t\t}\r\n\t\t\r\n\t\treturn result;\r\n\t};\r\n\r\n\tmxEvent.addGestureListeners(state.shape.node,\r\n\t\tmxUtils.bind(this, function(evt)\r\n\t\t{\r\n\t\t\tif (this.isShapeEvent(state, evt))\r\n\t\t\t{\r\n\t\t\t\tgraph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));\r\n\t\t\t}\r\n\t\t}),\r\n\t\tmxUtils.bind(this, function(evt)\r\n\t\t{\r\n\t\t\tif (this.isShapeEvent(state, evt))\r\n\t\t\t{\r\n\t\t\t\tgraph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));\r\n\t\t\t}\r\n\t\t}),\r\n\t\tmxUtils.bind(this, function(evt)\r\n\t\t{\r\n\t\t\tif (this.isShapeEvent(state, evt))\r\n\t\t\t{\r\n\t\t\t\tgraph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, getState(evt)));\r\n\t\t\t}\r\n\t\t})\r\n\t);\r\n\t\r\n\t// Uses double click timeout in mxGraph for quirks mode\r\n\tif (graph.nativeDblClickEnabled)\r\n\t{\r\n\t\tmxEvent.addListener(state.shape.node, 'dblclick',\r\n\t\t\tmxUtils.bind(this, function(evt)\r\n\t\t\t{\r\n\t\t\t\tif (this.isShapeEvent(state, evt))\r\n\t\t\t\t{\r\n\t\t\t\t\tgraph.dblClick(evt, state.cell);\r\n\t\t\t\t\tmxEvent.consume(evt);\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: redrawLabel\r\n * \r\n * Redraws the label for the given cell state.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose label should be redrawn.\r\n */\r\nmxCellRenderer.prototype.redrawLabel = function(state, forced)\r\n{\r\n\tvar graph = state.view.graph;\r\n\tvar value = this.getLabelValue(state);\r\n\tvar wrapping = graph.isWrapping(state.cell);\r\n\tvar clipping = graph.isLabelClipped(state.cell);\r\n\tvar isForceHtml = (state.view.graph.isHtmlLabel(state.cell) || (value != null && mxUtils.isNode(value)));\r\n\tvar dialect = (isForceHtml) ? mxConstants.DIALECT_STRICTHTML : state.view.graph.dialect;\r\n\tvar overflow = state.style[mxConstants.STYLE_OVERFLOW] || 'visible';\r\n\r\n\tif (state.text != null && (state.text.wrap != wrapping || state.text.clipped != clipping ||\r\n\t\tstate.text.overflow != overflow || state.text.dialect != dialect))\r\n\t{\r\n\t\tstate.text.destroy();\r\n\t\tstate.text = null;\r\n\t}\r\n\t\r\n\tif (state.text == null && value != null && (mxUtils.isNode(value) || value.length > 0))\r\n\t{\r\n\t\tthis.createLabel(state, value);\r\n\t}\r\n\telse if (state.text != null && (value == null || value.length == 0))\r\n\t{\r\n\t\tstate.text.destroy();\r\n\t\tstate.text = null;\r\n\t}\r\n\r\n\tif (state.text != null)\r\n\t{\r\n\t\t// Forced is true if the style has changed, so to get the updated\r\n\t\t// result in getLabelBounds we apply the new style to the shape\r\n\t\tif (forced)\r\n\t\t{\r\n\t\t\t// Checks if a full repaint is needed\r\n\t\t\tif (state.text.lastValue != null && this.isTextShapeInvalid(state, state.text))\r\n\t\t\t{\r\n\t\t\t\t// Forces a full repaint\r\n\t\t\t\tstate.text.lastValue = null;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tstate.text.resetStyles();\r\n\t\t\tstate.text.apply(state);\r\n\t\t\t\r\n\t\t\t// Special case where value is obtained via hook in graph\r\n\t\t\tstate.text.valign = graph.getVerticalAlign(state);\r\n\t\t}\r\n\t\t\r\n\t\tvar bounds = this.getLabelBounds(state);\r\n\t\tvar nextScale = this.getTextScale(state);\r\n\t\t\r\n\t\tif (forced || state.text.value != value || state.text.isWrapping != wrapping ||\r\n\t\t\tstate.text.overflow != overflow || state.text.isClipping != clipping ||\r\n\t\t\tstate.text.scale != nextScale || state.text.dialect != dialect ||\r\n\t\t\t!state.text.bounds.equals(bounds))\r\n\t\t{\r\n\t\t\t// Forces an update of the text bounding box\r\n\t\t\tif (state.text.bounds.width != 0 && state.unscaledWidth != null &&\r\n\t\t\t\tMath.round((state.text.bounds.width /\r\n\t\t\t\tstate.text.scale * nextScale) - bounds.width) != 0)\r\n\t\t\t{\r\n\t\t\t\tstate.unscaledWidth = null;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tstate.text.dialect = dialect;\r\n\t\t\tstate.text.value = value;\r\n\t\t\tstate.text.bounds = bounds;\r\n\t\t\tstate.text.scale = nextScale;\r\n\t\t\tstate.text.wrap = wrapping;\r\n\t\t\tstate.text.clipped = clipping;\r\n\t\t\tstate.text.overflow = overflow;\r\n\t\t\t\r\n\t\t\t// Preserves visible state\r\n\t\t\tvar vis = state.text.node.style.visibility;\r\n\t\t\tthis.redrawLabelShape(state.text);\r\n\t\t\tstate.text.node.style.visibility = vis;\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isTextShapeInvalid\r\n * \r\n * Returns true if the style for the text shape has changed.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose label should be checked.\r\n * shape - <mxText> shape to be checked.\r\n */\r\nmxCellRenderer.prototype.isTextShapeInvalid = function(state, shape)\r\n{\r\n\tfunction check(property, stylename, defaultValue)\r\n\t{\r\n\t\tvar result = false;\r\n\t\t\r\n\t\t// Workaround for spacing added to directional spacing\r\n\t\tif (stylename == 'spacingTop' || stylename == 'spacingRight' ||\r\n\t\t\tstylename == 'spacingBottom' || stylename == 'spacingLeft')\r\n\t\t{\r\n\t\t\tresult = parseFloat(shape[property]) - parseFloat(shape.spacing) !=\r\n\t\t\t\t(state.style[stylename] || defaultValue);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tresult = shape[property] != (state.style[stylename] || defaultValue);\r\n\t\t}\r\n\t\t\r\n\t\treturn result;\r\n\t};\r\n\r\n\treturn check('fontStyle', mxConstants.STYLE_FONTSTYLE, mxConstants.DEFAULT_FONTSTYLE) ||\r\n\t\tcheck('family', mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILY) ||\r\n\t\tcheck('size', mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE) ||\r\n\t\tcheck('color', mxConstants.STYLE_FONTCOLOR, 'black') ||\r\n\t\tcheck('align', mxConstants.STYLE_ALIGN, '') ||\r\n\t\tcheck('valign', mxConstants.STYLE_VERTICAL_ALIGN, '') ||\r\n\t\tcheck('spacing', mxConstants.STYLE_SPACING, 2) ||\r\n\t\tcheck('spacingTop', mxConstants.STYLE_SPACING_TOP, 0) ||\r\n\t\tcheck('spacingRight', mxConstants.STYLE_SPACING_RIGHT, 0) ||\r\n\t\tcheck('spacingBottom', mxConstants.STYLE_SPACING_BOTTOM, 0) ||\r\n\t\tcheck('spacingLeft', mxConstants.STYLE_SPACING_LEFT, 0) ||\r\n\t\tcheck('horizontal', mxConstants.STYLE_HORIZONTAL, true) ||\r\n\t\tcheck('background', mxConstants.STYLE_LABEL_BACKGROUNDCOLOR) ||\r\n\t\tcheck('border', mxConstants.STYLE_LABEL_BORDERCOLOR) ||\r\n\t\tcheck('opacity', mxConstants.STYLE_TEXT_OPACITY, 100) ||\r\n\t\tcheck('textDirection', mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION);\r\n};\r\n\r\n/**\r\n * Function: redrawLabelShape\r\n * \r\n * Called to invoked redraw on the given text shape.\r\n * \r\n * Parameters:\r\n * \r\n * shape - <mxText> shape to be redrawn.\r\n */\r\nmxCellRenderer.prototype.redrawLabelShape = function(shape)\r\n{\r\n\tshape.redraw();\r\n};\r\n\r\n/**\r\n * Function: getTextScale\r\n * \r\n * Returns the scaling used for the label of the given state\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose label scale should be returned.\r\n */\r\nmxCellRenderer.prototype.getTextScale = function(state)\r\n{\r\n\treturn state.view.scale;\r\n};\r\n\r\n/**\r\n * Function: getLabelBounds\r\n * \r\n * Returns the bounds to be used to draw the label of the given state.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose label bounds should be returned.\r\n */\r\nmxCellRenderer.prototype.getLabelBounds = function(state)\r\n{\r\n\tvar graph = state.view.graph;\r\n\tvar scale = state.view.scale;\r\n\tvar isEdge = graph.getModel().isEdge(state.cell);\r\n\tvar bounds = new mxRectangle(state.absoluteOffset.x, state.absoluteOffset.y);\r\n\r\n\tif (isEdge)\r\n\t{\r\n\t\tvar spacing = state.text.getSpacing();\r\n\t\tbounds.x += spacing.x * scale;\r\n\t\tbounds.y += spacing.y * scale;\r\n\t\t\r\n\t\tvar geo = graph.getCellGeometry(state.cell);\r\n\t\t\r\n\t\tif (geo != null)\r\n\t\t{\r\n\t\t\tbounds.width = Math.max(0, geo.width * scale);\r\n\t\t\tbounds.height = Math.max(0, geo.height * scale);\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\t// Inverts label position\r\n\t\tif (state.text.isPaintBoundsInverted())\r\n\t\t{\r\n\t\t\tvar tmp = bounds.x;\r\n\t\t\tbounds.x = bounds.y;\r\n\t\t\tbounds.y = tmp;\r\n\t\t}\r\n\t\t\r\n\t\tbounds.x += state.x;\r\n\t\tbounds.y += state.y;\r\n\t\t\r\n\t\t// Minimum of 1 fixes alignment bug in HTML labels\r\n\t\tbounds.width = Math.max(1, state.width);\r\n\t\tbounds.height = Math.max(1, state.height);\r\n\r\n\t\tvar sc = mxUtils.getValue(state.style, mxConstants.STYLE_STROKECOLOR, mxConstants.NONE);\r\n\t\t\r\n\t\tif (sc != mxConstants.NONE && sc != '')\r\n\t\t{\r\n\t\t\tvar s = parseFloat(mxUtils.getValue(state.style, mxConstants.STYLE_STROKEWIDTH, 1)) * scale;\r\n\t\t\tvar dx = 1 + Math.floor((s - 1) / 2);\r\n\t\t\tvar dh = Math.floor(s + 1);\r\n\t\t\t\r\n\t\t\tbounds.x += dx;\r\n\t\t\tbounds.y += dx;\r\n\t\t\tbounds.width -= dh;\r\n\t\t\tbounds.height -= dh;\r\n\t\t}\r\n\t}\r\n\r\n\tif (state.text.isPaintBoundsInverted())\r\n\t{\r\n\t\t// Rotates around center of state\r\n\t\tvar t = (state.width - state.height) / 2;\r\n\t\tbounds.x += t;\r\n\t\tbounds.y -= t;\r\n\t\tvar tmp = bounds.width;\r\n\t\tbounds.width = bounds.height;\r\n\t\tbounds.height = tmp;\r\n\t}\r\n\t\r\n\t// Shape can modify its label bounds\r\n\tif (state.shape != null)\r\n\t{\r\n\t\tvar hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);\r\n\t\tvar vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);\r\n\t\t\r\n\t\tif (hpos == mxConstants.ALIGN_CENTER && vpos == mxConstants.ALIGN_MIDDLE)\r\n\t\t{\r\n\t\t\tbounds = state.shape.getLabelBounds(bounds);\r\n\t\t}\r\n\t}\r\n\t\r\n\t// Label width style overrides actual label width\r\n\tvar lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);\r\n\t\r\n\tif (lw != null)\r\n\t{\r\n\t\tbounds.width = parseFloat(lw) * scale;\r\n\t}\r\n\t\r\n\tif (!isEdge)\r\n\t{\r\n\t\tthis.rotateLabelBounds(state, bounds);\r\n\t}\r\n\t\r\n\treturn bounds;\r\n};\r\n\r\n/**\r\n * Function: rotateLabelBounds\r\n * \r\n * Adds the shape rotation to the given label bounds and\r\n * applies the alignment and offsets.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose label bounds should be rotated.\r\n * bounds - <mxRectangle> the rectangle to be rotated.\r\n */\r\nmxCellRenderer.prototype.rotateLabelBounds = function(state, bounds)\r\n{\r\n\tbounds.y -= state.text.margin.y * bounds.height;\r\n\tbounds.x -= state.text.margin.x * bounds.width;\r\n\t\r\n\tif (!this.legacySpacing || (state.style[mxConstants.STYLE_OVERFLOW] != 'fill' && state.style[mxConstants.STYLE_OVERFLOW] != 'width'))\r\n\t{\r\n\t\tvar s = state.view.scale;\r\n\t\tvar spacing = state.text.getSpacing();\r\n\t\tbounds.x += spacing.x * s;\r\n\t\tbounds.y += spacing.y * s;\r\n\t\t\r\n\t\tvar hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);\r\n\t\tvar vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);\r\n\t\tvar lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);\r\n\t\t\r\n\t\tbounds.width = Math.max(0, bounds.width - ((hpos == mxConstants.ALIGN_CENTER && lw == null) ? (state.text.spacingLeft * s + state.text.spacingRight * s) : 0));\r\n\t\tbounds.height = Math.max(0, bounds.height - ((vpos == mxConstants.ALIGN_MIDDLE) ? (state.text.spacingTop * s + state.text.spacingBottom * s) : 0));\r\n\t}\r\n\r\n\tvar theta = state.text.getTextRotation();\r\n\r\n\t// Only needed if rotated around another center\r\n\tif (theta != 0 && state != null && state.view.graph.model.isVertex(state.cell))\r\n\t{\r\n\t\tvar cx = state.getCenterX();\r\n\t\tvar cy = state.getCenterY();\r\n\t\t\r\n\t\tif (bounds.x != cx || bounds.y != cy)\r\n\t\t{\r\n\t\t\tvar rad = theta * (Math.PI / 180);\r\n\t\t\tpt = mxUtils.getRotatedPoint(new mxPoint(bounds.x, bounds.y),\r\n\t\t\t\t\tMath.cos(rad), Math.sin(rad), new mxPoint(cx, cy));\r\n\t\t\t\r\n\t\t\tbounds.x = pt.x;\r\n\t\t\tbounds.y = pt.y;\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: redrawCellOverlays\r\n * \r\n * Redraws the overlays for the given cell state.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose overlays should be redrawn.\r\n */\r\nmxCellRenderer.prototype.redrawCellOverlays = function(state, forced)\r\n{\r\n\tthis.createCellOverlays(state);\r\n\r\n\tif (state.overlays != null)\r\n\t{\r\n\t\tvar rot = mxUtils.mod(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0), 90);\r\n        var rad = mxUtils.toRadians(rot);\r\n        var cos = Math.cos(rad);\r\n        var sin = Math.sin(rad);\r\n\t\t\r\n\t\tstate.overlays.visit(function(id, shape)\r\n\t\t{\r\n\t\t\tvar bounds = shape.overlay.getBounds(state);\r\n\t\t\r\n\t\t\tif (!state.view.graph.getModel().isEdge(state.cell))\r\n\t\t\t{\r\n\t\t\t\tif (state.shape != null && rot != 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar cx = bounds.getCenterX();\r\n\t\t\t\t\tvar cy = bounds.getCenterY();\r\n\r\n\t\t\t\t\tvar point = mxUtils.getRotatedPoint(new mxPoint(cx, cy), cos, sin,\r\n\t\t\t        \t\tnew mxPoint(state.getCenterX(), state.getCenterY()));\r\n\r\n\t\t\t        cx = point.x;\r\n\t\t\t        cy = point.y;\r\n\t\t\t        bounds.x = Math.round(cx - bounds.width / 2);\r\n\t\t\t        bounds.y = Math.round(cy - bounds.height / 2);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (forced || shape.bounds == null || shape.scale != state.view.scale ||\r\n\t\t\t\t!shape.bounds.equals(bounds))\r\n\t\t\t{\r\n\t\t\t\tshape.bounds = bounds;\r\n\t\t\t\tshape.scale = state.view.scale;\r\n\t\t\t\tshape.redraw();\r\n\t\t\t}\r\n\t\t});\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: redrawControl\r\n * \r\n * Redraws the control for the given cell state.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose control should be redrawn.\r\n */\r\nmxCellRenderer.prototype.redrawControl = function(state, forced)\r\n{\r\n\tvar image = state.view.graph.getFoldingImage(state);\r\n\t\r\n\tif (state.control != null && image != null)\r\n\t{\r\n\t\tvar bounds = this.getControlBounds(state, image.width, image.height);\r\n\t\tvar r = (this.legacyControlPosition) ?\r\n\t\t\t\tmxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0) :\r\n\t\t\t\tstate.shape.getTextRotation();\r\n\t\tvar s = state.view.scale;\r\n\t\t\r\n\t\tif (forced || state.control.scale != s || !state.control.bounds.equals(bounds) ||\r\n\t\t\tstate.control.rotation != r)\r\n\t\t{\r\n\t\t\tstate.control.rotation = r;\r\n\t\t\tstate.control.bounds = bounds;\r\n\t\t\tstate.control.scale = s;\r\n\t\t\t\r\n\t\t\tstate.control.redraw();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getControlBounds\r\n * \r\n * Returns the bounds to be used to draw the control (folding icon) of the\r\n * given state.\r\n */\r\nmxCellRenderer.prototype.getControlBounds = function(state, w, h)\r\n{\r\n\tif (state.control != null)\r\n\t{\r\n\t\tvar s = state.view.scale;\r\n\t\tvar cx = state.getCenterX();\r\n\t\tvar cy = state.getCenterY();\r\n\t\r\n\t\tif (!state.view.graph.getModel().isEdge(state.cell))\r\n\t\t{\r\n\t\t\tcx = state.x + w * s;\r\n\t\t\tcy = state.y + h * s;\r\n\t\t\t\r\n\t\t\tif (state.shape != null)\r\n\t\t\t{\r\n\t\t\t\t// TODO: Factor out common code\r\n\t\t\t\tvar rot = state.shape.getShapeRotation();\r\n\t\t\t\t\r\n\t\t\t\tif (this.legacyControlPosition)\r\n\t\t\t\t{\r\n\t\t\t\t\trot = mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tif (state.shape.isPaintBoundsInverted())\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar t = (state.width - state.height) / 2;\r\n\t\t\t\t\t\tcx += t;\r\n\t\t\t\t\t\tcy -= t;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (rot != 0)\r\n\t\t\t\t{\r\n\t\t\t        var rad = mxUtils.toRadians(rot);\r\n\t\t\t        var cos = Math.cos(rad);\r\n\t\t\t        var sin = Math.sin(rad);\r\n\t\t\t        \r\n\t\t\t        var point = mxUtils.getRotatedPoint(new mxPoint(cx, cy), cos, sin,\r\n\t\t\t        \t\tnew mxPoint(state.getCenterX(), state.getCenterY()));\r\n\t\t\t        cx = point.x;\r\n\t\t\t        cy = point.y;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn (state.view.graph.getModel().isEdge(state.cell)) ? \r\n\t\t\tnew mxRectangle(Math.round(cx - w / 2 * s), Math.round(cy - h / 2 * s), Math.round(w * s), Math.round(h * s))\r\n\t\t\t: new mxRectangle(Math.round(cx - w / 2 * s), Math.round(cy - h / 2 * s), Math.round(w * s), Math.round(h * s));\r\n\t}\r\n\t\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: insertStateAfter\r\n * \r\n * Inserts the given array of <mxShapes> after the given nodes in the DOM.\r\n * \r\n * Parameters:\r\n * \r\n * shapes - Array of <mxShapes> to be inserted.\r\n * node - Node in <drawPane> after which the shapes should be inserted.\r\n * htmlNode - Node in the graph container after which the shapes should be inserted that\r\n * will not go into the <drawPane> (eg. HTML labels without foreignObjects).\r\n */\r\nmxCellRenderer.prototype.insertStateAfter = function(state, node, htmlNode)\r\n{\r\n\tvar shapes = this.getShapesForState(state);\r\n\t\r\n\tfor (var i = 0; i < shapes.length; i++)\r\n\t{\r\n\t\tif (shapes[i] != null && shapes[i].node != null)\r\n\t\t{\r\n\t\t\tvar html = shapes[i].node.parentNode != state.view.getDrawPane() &&\r\n\t\t\t\tshapes[i].node.parentNode != state.view.getOverlayPane();\r\n\t\t\tvar temp = (html) ? htmlNode : node;\r\n\t\t\t\r\n\t\t\tif (temp != null && temp.nextSibling != shapes[i].node)\r\n\t\t\t{\r\n\t\t\t\tif (temp.nextSibling == null)\r\n\t\t\t\t{\r\n\t\t\t\t\ttemp.parentNode.appendChild(shapes[i].node);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\ttemp.parentNode.insertBefore(shapes[i].node, temp.nextSibling);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if (temp == null)\r\n\t\t\t{\r\n\t\t\t\t// Special case: First HTML node should be first sibling after canvas\r\n\t\t\t\tif (shapes[i].node.parentNode == state.view.graph.container)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar canvas = state.view.canvas;\r\n\t\t\t\t\t\r\n\t\t\t\t\twhile (canvas != null && canvas.parentNode != state.view.graph.container)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tcanvas = canvas.parentNode;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (canvas != null && canvas.nextSibling != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (canvas.nextSibling != shapes[i].node)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tshapes[i].node.parentNode.insertBefore(shapes[i].node, canvas.nextSibling);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tshapes[i].node.parentNode.appendChild(shapes[i].node);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if (shapes[i].node.parentNode.firstChild != null && shapes[i].node.parentNode.firstChild != shapes[i].node)\r\n\t\t\t\t{\r\n\t\t\t\t\t// Inserts the node as the first child of the parent to implement the order\r\n\t\t\t\t\tshapes[i].node.parentNode.insertBefore(shapes[i].node, shapes[i].node.parentNode.firstChild);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (html)\r\n\t\t\t{\r\n\t\t\t\thtmlNode = shapes[i].node;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tnode = shapes[i].node;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn [node, htmlNode];\r\n};\r\n\r\n/**\r\n * Function: getShapesForState\r\n * \r\n * Returns the <mxShapes> for the given cell state in the order in which they should\r\n * appear in the DOM.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose shapes should be returned.\r\n */\r\nmxCellRenderer.prototype.getShapesForState = function(state)\r\n{\r\n\treturn [state.shape, state.text, state.control];\r\n};\r\n\r\n/**\r\n * Function: redraw\r\n * \r\n * Updates the bounds or points and scale of the shapes for the given cell\r\n * state. This is called in mxGraphView.validatePoints as the last step of\r\n * updating all cells.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> for which the shapes should be updated.\r\n * force - Optional boolean that specifies if the cell should be reconfiured\r\n * and redrawn without any additional checks.\r\n * rendering - Optional boolean that specifies if the cell should actually\r\n * be drawn into the DOM. If this is false then redraw and/or reconfigure\r\n * will not be called on the shape.\r\n */\r\nmxCellRenderer.prototype.redraw = function(state, force, rendering)\r\n{\r\n\tvar shapeChanged = this.redrawShape(state, force, rendering);\r\n\t\r\n\tif (state.shape != null && (rendering == null || rendering))\r\n\t{\r\n\t\tthis.redrawLabel(state, shapeChanged);\r\n\t\tthis.redrawCellOverlays(state, shapeChanged);\r\n\t\tthis.redrawControl(state, shapeChanged);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: redrawShape\r\n * \r\n * Redraws the shape for the given cell state.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose label should be redrawn.\r\n */\r\nmxCellRenderer.prototype.redrawShape = function(state, force, rendering)\r\n{\r\n\tvar model = state.view.graph.model;\r\n\tvar shapeChanged = false;\r\n\r\n\t// Forces creation of new shape if shape style has changed\r\n\tif (state.shape != null && state.shape.style != null && state.style != null &&\r\n\t\tstate.shape.style[mxConstants.STYLE_SHAPE] != state.style[mxConstants.STYLE_SHAPE])\r\n\t{\r\n\t\tstate.shape.destroy();\r\n\t\tstate.shape = null;\r\n\t}\r\n\t\r\n\tif (state.shape == null && state.view.graph.container != null &&\r\n\t\tstate.cell != state.view.currentRoot &&\r\n\t\t(model.isVertex(state.cell) || model.isEdge(state.cell)))\r\n\t{\r\n\t\tstate.shape = this.createShape(state);\r\n\t\t\r\n\t\tif (state.shape != null)\r\n\t\t{\r\n\t\t\tstate.shape.minSvgStrokeWidth = this.minSvgStrokeWidth;\r\n\t\t\tstate.shape.antiAlias = this.antiAlias;\r\n\t\r\n\t\t\tthis.createIndicatorShape(state);\r\n\t\t\tthis.initializeShape(state);\r\n\t\t\tthis.createCellOverlays(state);\r\n\t\t\tthis.installListeners(state);\r\n\t\t\t\r\n\t\t\t// Forces a refresh of the handler if one exists\r\n\t\t\tstate.view.graph.selectionCellsHandler.updateHandler(state);\r\n\t\t}\r\n\t}\r\n\telse if (!force && state.shape != null && (!mxUtils.equalEntries(state.shape.style,\r\n\t\tstate.style) || this.checkPlaceholderStyles(state)))\r\n\t{\r\n\t\tstate.shape.resetStyles();\r\n\t\tthis.configureShape(state);\r\n\t\t// LATER: Ignore update for realtime to fix reset of current gesture\r\n\t\tstate.view.graph.selectionCellsHandler.updateHandler(state);\r\n\t\tforce = true;\r\n\t}\r\n\r\n\tif (state.shape != null)\r\n\t{\r\n\t\t// Handles changes of the collapse icon\r\n\t\tthis.createControl(state);\r\n\t\t\r\n\t\t// Redraws the cell if required, ignores changes to bounds if points are\r\n\t\t// defined as the bounds are updated for the given points inside the shape\r\n\t\tif (force || this.isShapeInvalid(state, state.shape))\r\n\t\t{\r\n\t\t\tif (state.absolutePoints != null)\r\n\t\t\t{\r\n\t\t\t\tstate.shape.points = state.absolutePoints.slice();\r\n\t\t\t\tstate.shape.bounds = null;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tstate.shape.points = null;\r\n\t\t\t\tstate.shape.bounds = new mxRectangle(state.x, state.y, state.width, state.height);\r\n\t\t\t}\r\n\r\n\t\t\tstate.shape.scale = state.view.scale;\r\n\t\t\t\r\n\t\t\tif (rendering == null || rendering)\r\n\t\t\t{\r\n\t\t\t\tthis.doRedrawShape(state);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tstate.shape.updateBoundingBox();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tshapeChanged = true;\r\n\t\t}\r\n\t}\r\n\r\n\treturn shapeChanged;\r\n};\r\n\r\n/**\r\n * Function: doRedrawShape\r\n * \r\n * Invokes redraw on the shape of the given state.\r\n */\r\nmxCellRenderer.prototype.doRedrawShape = function(state)\r\n{\r\n\tstate.shape.redraw();\r\n};\r\n\r\n/**\r\n * Function: isShapeInvalid\r\n * \r\n * Returns true if the given shape must be repainted.\r\n */\r\nmxCellRenderer.prototype.isShapeInvalid = function(state, shape)\r\n{\r\n\treturn shape.bounds == null || shape.scale != state.view.scale ||\r\n\t\t(state.absolutePoints == null && !shape.bounds.equals(state)) ||\r\n\t\t(state.absolutePoints != null && !mxUtils.equalPoints(shape.points, state.absolutePoints))\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Destroys the shapes associated with the given cell state.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> for which the shapes should be destroyed.\r\n */\r\nmxCellRenderer.prototype.destroy = function(state)\r\n{\r\n\tif (state.shape != null)\r\n\t{\r\n\t\tif (state.text != null)\r\n\t\t{\t\t\r\n\t\t\tstate.text.destroy();\r\n\t\t\tstate.text = null;\r\n\t\t}\r\n\t\t\r\n\t\tif (state.overlays != null)\r\n\t\t{\r\n\t\t\tstate.overlays.visit(function(id, shape)\r\n\t\t\t{\r\n\t\t\t\tshape.destroy();\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\tstate.overlays = null;\r\n\t\t}\r\n\r\n\t\tif (state.control != null)\r\n\t\t{\r\n\t\t\tstate.control.destroy();\r\n\t\t\tstate.control = null;\r\n\t\t}\r\n\t\t\r\n\t\tstate.shape.destroy();\r\n\t\tstate.shape = null;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nvar mxEdgeStyle =\r\n{\r\n\t/**\r\n\t * Class: mxEdgeStyle\r\n\t * \r\n\t * Provides various edge styles to be used as the values for\r\n\t * <mxConstants.STYLE_EDGE> in a cell style.\r\n\t *\r\n\t * Example:\r\n\t * \r\n\t * (code)\r\n\t * var style = stylesheet.getDefaultEdgeStyle();\r\n\t * style[mxConstants.STYLE_EDGE] = mxEdgeStyle.ElbowConnector;\r\n\t * (end)\r\n\t * \r\n\t * Sets the default edge style to <ElbowConnector>.\r\n\t * \r\n\t * Custom edge style:\r\n\t * \r\n\t * To write a custom edge style, a function must be added to the mxEdgeStyle\r\n\t * object as follows:\r\n\t * \r\n\t * (code)\r\n\t * mxEdgeStyle.MyStyle = function(state, source, target, points, result)\r\n\t * {\r\n\t *   if (source != null && target != null)\r\n\t *   {\r\n\t *     var pt = new mxPoint(target.getCenterX(), source.getCenterY());\r\n\t * \r\n\t *     if (mxUtils.contains(source, pt.x, pt.y))\r\n\t *     {\r\n\t *       pt.y = source.y + source.height;\r\n\t *     }\r\n\t * \r\n\t *     result.push(pt);\r\n\t *   }\r\n\t * };\r\n\t * (end)\r\n\t * \r\n\t * In the above example, a right angle is created using a point on the\r\n\t * horizontal center of the target vertex and the vertical center of the source\r\n\t * vertex. The code checks if that point intersects the source vertex and makes\r\n\t * the edge straight if it does. The point is then added into the result array,\r\n\t * which acts as the return value of the function.\r\n\t *\r\n\t * The new edge style should then be registered in the <mxStyleRegistry> as follows:\r\n\t * (code)\r\n\t * mxStyleRegistry.putValue('myEdgeStyle', mxEdgeStyle.MyStyle);\r\n\t * (end)\r\n\t * \r\n\t * The custom edge style above can now be used in a specific edge as follows:\r\n\t * \r\n\t * (code)\r\n\t * model.setStyle(edge, 'edgeStyle=myEdgeStyle');\r\n\t * (end)\r\n\t * \r\n\t * Note that the key of the <mxStyleRegistry> entry for the function should\r\n\t * be used in string values, unless <mxGraphView.allowEval> is true, in\r\n\t * which case you can also use mxEdgeStyle.MyStyle for the value in the\r\n\t * cell style above.\r\n\t * \r\n\t * Or it can be used for all edges in the graph as follows:\r\n\t * \r\n\t * (code)\r\n\t * var style = graph.getStylesheet().getDefaultEdgeStyle();\r\n\t * style[mxConstants.STYLE_EDGE] = mxEdgeStyle.MyStyle;\r\n\t * (end)\r\n\t * \r\n\t * Note that the object can be used directly when programmatically setting\r\n\t * the value, but the key in the <mxStyleRegistry> should be used when\r\n\t * setting the value via a key, value pair in a cell style.\r\n\t * \r\n\t * Function: EntityRelation\r\n\t * \r\n\t * Implements an entity relation style for edges (as used in database\r\n\t * schema diagrams). At the time the function is called, the result\r\n\t * array contains a placeholder (null) for the first absolute point,\r\n\t * that is, the point where the edge and source terminal are connected.\r\n\t * The implementation of the style then adds all intermediate waypoints\r\n\t * except for the last point, that is, the connection point between the\r\n\t * edge and the target terminal. The first ant the last point in the\r\n\t * result array are then replaced with mxPoints that take into account\r\n\t * the terminal's perimeter and next point on the edge.\r\n\t *\r\n\t * Parameters:\r\n\t * \r\n\t * state - <mxCellState> that represents the edge to be updated.\r\n\t * source - <mxCellState> that represents the source terminal.\r\n\t * target - <mxCellState> that represents the target terminal.\r\n\t * points - List of relative control points.\r\n\t * result - Array of <mxPoints> that represent the actual points of the\r\n\t * edge.\r\n\t */\r\n\t EntityRelation: function (state, source, target, points, result)\r\n\t {\r\n\t\tvar view = state.view;\r\n\t \tvar graph = view.graph;\r\n\t \tvar segment = mxUtils.getValue(state.style,\r\n\t \t\t\tmxConstants.STYLE_SEGMENT,\r\n\t \t\t\tmxConstants.ENTITY_SEGMENT) * view.scale;\r\n\t \t\r\n\t\tvar pts = state.absolutePoints;\r\n\t\tvar p0 = pts[0];\r\n\t\tvar pe = pts[pts.length-1];\r\n\r\n\t \tvar isSourceLeft = false;\r\n\r\n\t\tif (p0 != null)\r\n\t\t{\r\n\t\t\tsource = new mxCellState();\r\n\t\t\tsource.x = p0.x;\r\n\t\t\tsource.y = p0.y;\r\n\t\t}\r\n\t\telse if (source != null)\r\n\t\t{\r\n\t\t\tvar constraint = mxUtils.getPortConstraints(source, state, true, mxConstants.DIRECTION_MASK_NONE);\r\n\t\t\t\r\n\t\t\tif (constraint != mxConstants.DIRECTION_MASK_NONE && constraint != mxConstants.DIRECTION_MASK_WEST +\r\n\t\t\t\tmxConstants.DIRECTION_MASK_EAST)\r\n\t\t\t{\r\n\t\t\t\tisSourceLeft = constraint == mxConstants.DIRECTION_MASK_WEST;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t \tvar sourceGeometry = graph.getCellGeometry(source.cell);\r\n\t\t\r\n\t\t\t \tif (sourceGeometry.relative)\r\n\t\t\t \t{\r\n\t\t\t \t\tisSourceLeft = sourceGeometry.x <= 0.5;\r\n\t\t\t \t}\r\n\t\t\t \telse if (target != null)\r\n\t\t\t \t{\r\n\t\t\t \t\tisSourceLeft = target.x + target.width < source.x;\r\n\t\t\t \t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\t \t\r\n\t \tvar isTargetLeft = true;\r\n\r\n\t\tif (pe != null)\r\n\t\t{\r\n\t\t\ttarget = new mxCellState();\r\n\t\t\ttarget.x = pe.x;\r\n\t\t\ttarget.y = pe.y;\r\n\t\t}\r\n\t\telse if (target != null)\r\n\t \t{\r\n\t\t\tvar constraint = mxUtils.getPortConstraints(target, state, false, mxConstants.DIRECTION_MASK_NONE);\r\n\r\n\t\t\tif (constraint != mxConstants.DIRECTION_MASK_NONE && constraint != mxConstants.DIRECTION_MASK_WEST +\r\n\t\t\t\tmxConstants.DIRECTION_MASK_EAST)\r\n\t\t\t{\r\n\t\t\t\tisTargetLeft = constraint == mxConstants.DIRECTION_MASK_WEST;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t \tvar targetGeometry = graph.getCellGeometry(target.cell);\r\n\t\r\n\t\t\t \tif (targetGeometry.relative)\r\n\t\t\t \t{\r\n\t\t\t \t\tisTargetLeft = targetGeometry.x <= 0.5;\r\n\t\t\t \t}\r\n\t\t\t \telse if (source != null)\r\n\t\t\t \t{\r\n\t\t\t \t\tisTargetLeft = source.x + source.width < target.x;\r\n\t\t\t \t}\r\n\t\t\t}\r\n\t \t}\r\n\t\t\r\n\t\tif (source != null && target != null)\r\n\t\t{\r\n\t\t\tvar x0 = (isSourceLeft) ? source.x : source.x + source.width;\r\n\t\t\tvar y0 = view.getRoutingCenterY(source);\r\n\t\t\t\r\n\t\t\tvar xe = (isTargetLeft) ? target.x : target.x + target.width;\r\n\t\t\tvar ye = view.getRoutingCenterY(target);\r\n\t\r\n\t\t\tvar seg = segment;\r\n\t\r\n\t\t\tvar dx = (isSourceLeft) ? -seg : seg;\r\n\t\t\tvar dep = new mxPoint(x0 + dx, y0);\r\n\t\t\t\t\t\r\n\t\t\tdx = (isTargetLeft) ? -seg : seg;\r\n\t\t\tvar arr = new mxPoint(xe + dx, ye);\r\n\t\r\n\t\t\t// Adds intermediate points if both go out on same side\r\n\t\t\tif (isSourceLeft == isTargetLeft)\r\n\t\t\t{\r\n\t\t\t\tvar x = (isSourceLeft) ?\r\n\t\t\t\t\tMath.min(x0, xe)-segment :\r\n\t\t\t\t\tMath.max(x0, xe)+segment;\r\n\t\r\n\t\t\t\tresult.push(new mxPoint(x, y0));\r\n\t\t\t\tresult.push(new mxPoint(x, ye));\r\n\t\t\t}\r\n\t\t\telse if ((dep.x < arr.x) == isSourceLeft)\r\n\t\t\t{\r\n\t\t\t\tvar midY = y0 + (ye - y0) / 2;\r\n\t\r\n\t\t\t\tresult.push(dep);\r\n\t\t\t\tresult.push(new mxPoint(dep.x, midY));\r\n\t\t\t\tresult.push(new mxPoint(arr.x, midY));\r\n\t\t\t\tresult.push(arr);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tresult.push(dep);\r\n\t\t\t\tresult.push(arr);\r\n\t\t\t}\r\n\t\t}\r\n\t },\r\n\r\n\t /**\r\n\t * Function: Loop\r\n\t * \r\n\t * Implements a self-reference, aka. loop.\r\n\t */\r\n\tLoop: function (state, source, target, points, result)\r\n\t{\r\n\t\tvar pts = state.absolutePoints;\r\n\t\t\r\n\t\tvar p0 = pts[0];\r\n\t\tvar pe = pts[pts.length-1];\r\n\r\n\t\tif (p0 != null && pe != null)\r\n\t\t{\r\n\t\t\tif (points != null && points.length > 0)\r\n\t\t\t{\r\n\t\t\t\tfor (var i = 0; i < points.length; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar pt = points[i];\r\n\t\t\t\t\tpt = state.view.transformControlPoint(state, pt);\r\n\t\t\t\t\tresult.push(new mxPoint(pt.x, pt.y));\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tif (source != null)\r\n\t\t{\r\n\t\t\tvar view = state.view;\r\n\t\t\tvar graph = view.graph;\r\n\t\t\tvar pt = (points != null && points.length > 0) ? points[0] : null;\r\n\r\n\t\t\tif (pt != null)\r\n\t\t\t{\r\n\t\t\t\tpt = view.transformControlPoint(state, pt);\r\n\t\t\t\t\t\r\n\t\t\t\tif (mxUtils.contains(source, pt.x, pt.y))\r\n\t\t\t\t{\r\n\t\t\t\t\tpt = null;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar x = 0;\r\n\t\t\tvar dx = 0;\r\n\t\t\tvar y = 0;\r\n\t\t\tvar dy = 0;\r\n\t\t\t\r\n\t\t \tvar seg = mxUtils.getValue(state.style, mxConstants.STYLE_SEGMENT,\r\n\t\t \t\tgraph.gridSize) * view.scale;\r\n\t\t\tvar dir = mxUtils.getValue(state.style, mxConstants.STYLE_DIRECTION,\r\n\t\t\t\tmxConstants.DIRECTION_WEST);\r\n\t\t\t\r\n\t\t\tif (dir == mxConstants.DIRECTION_NORTH ||\r\n\t\t\t\tdir == mxConstants.DIRECTION_SOUTH)\r\n\t\t\t{\r\n\t\t\t\tx = view.getRoutingCenterX(source);\r\n\t\t\t\tdx = seg;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\ty = view.getRoutingCenterY(source);\r\n\t\t\t\tdy = seg;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (pt == null ||\r\n\t\t\t\tpt.x < source.x ||\r\n\t\t\t\tpt.x > source.x + source.width)\r\n\t\t\t{\r\n\t\t\t\tif (pt != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tx = pt.x;\r\n\t\t\t\t\tdy = Math.max(Math.abs(y - pt.y), dy);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tif (dir == mxConstants.DIRECTION_NORTH)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ty = source.y - 2 * dx;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (dir == mxConstants.DIRECTION_SOUTH)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ty = source.y + source.height + 2 * dx;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (dir == mxConstants.DIRECTION_EAST)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tx = source.x - 2 * dy;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tx = source.x + source.width + 2 * dy;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if (pt != null)\r\n\t\t\t{\r\n\t\t\t\tx = view.getRoutingCenterX(source);\r\n\t\t\t\tdx = Math.max(Math.abs(x - pt.x), dy);\r\n\t\t\t\ty = pt.y;\r\n\t\t\t\tdy = 0;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tresult.push(new mxPoint(x - dx, y - dy));\r\n\t\t\tresult.push(new mxPoint(x + dx, y + dy));\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: ElbowConnector\r\n\t * \r\n\t * Uses either <SideToSide> or <TopToBottom> depending on the horizontal\r\n\t * flag in the cell style. <SideToSide> is used if horizontal is true or\r\n\t * unspecified. See <EntityRelation> for a description of the\r\n\t * parameters.\r\n\t */\r\n\tElbowConnector: function (state, source, target, points, result)\r\n\t{\r\n\t\tvar pt = (points != null && points.length > 0) ? points[0] : null;\r\n\r\n\t\tvar vertical = false;\r\n\t\tvar horizontal = false;\r\n\t\t\r\n\t\tif (source != null && target != null)\r\n\t\t{\r\n\t\t\tif (pt != null)\r\n\t\t\t{\r\n\t\t\t\tvar left = Math.min(source.x, target.x);\r\n\t\t\t\tvar right = Math.max(source.x + source.width,\r\n\t\t\t\t\ttarget.x + target.width);\r\n\t\r\n\t\t\t\tvar top = Math.min(source.y, target.y);\r\n\t\t\t\tvar bottom = Math.max(source.y + source.height,\r\n\t\t\t\t\ttarget.y + target.height);\r\n\r\n\t\t\t\tpt = state.view.transformControlPoint(state, pt);\r\n\t\t\t\t\t\r\n\t\t\t\tvertical = pt.y < top || pt.y > bottom;\r\n\t\t\t\thorizontal = pt.x < left || pt.x > right;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tvar left = Math.max(source.x, target.x);\r\n\t\t\t\tvar right = Math.min(source.x + source.width,\r\n\t\t\t\t\ttarget.x + target.width);\r\n\t\t\t\t\t\r\n\t\t\t\tvertical = left == right;\r\n\t\t\t\t\r\n\t\t\t\tif (!vertical)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar top = Math.max(source.y, target.y);\r\n\t\t\t\t\tvar bottom = Math.min(source.y + source.height,\r\n\t\t\t\t\t\ttarget.y + target.height);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\thorizontal = top == bottom;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (!horizontal && (vertical ||\r\n\t\t\tstate.style[mxConstants.STYLE_ELBOW] == mxConstants.ELBOW_VERTICAL))\r\n\t\t{\r\n\t\t\tmxEdgeStyle.TopToBottom(state, source, target, points, result);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tmxEdgeStyle.SideToSide(state, source, target, points, result);\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Function: SideToSide\r\n\t * \r\n\t * Implements a vertical elbow edge. See <EntityRelation> for a description\r\n\t * of the parameters.\r\n\t */\r\n\tSideToSide: function (state, source, target, points, result)\r\n\t{\r\n\t\tvar view = state.view;\r\n\t\tvar pt = (points != null && points.length > 0) ? points[0] : null;\r\n\t\tvar pts = state.absolutePoints;\r\n\t\tvar p0 = pts[0];\r\n\t\tvar pe = pts[pts.length-1];\r\n\t\t\r\n\t\tif (pt != null)\r\n\t\t{\r\n\t\t\tpt = view.transformControlPoint(state, pt);\r\n\t\t}\r\n\t\t\r\n\t\tif (p0 != null)\r\n\t\t{\r\n\t\t\tsource = new mxCellState();\r\n\t\t\tsource.x = p0.x;\r\n\t\t\tsource.y = p0.y;\r\n\t\t}\r\n\t\t\r\n\t\tif (pe != null)\r\n\t\t{\r\n\t\t\ttarget = new mxCellState();\r\n\t\t\ttarget.x = pe.x;\r\n\t\t\ttarget.y = pe.y;\r\n\t\t}\r\n\t\t\r\n\t\tif (source != null && target != null)\r\n\t\t{\r\n\t\t\tvar l = Math.max(source.x, target.x);\r\n\t\t\tvar r = Math.min(source.x + source.width,\r\n\t\t\t\t\t\t\t target.x + target.width);\r\n\t\r\n\t\t\tvar x = (pt != null) ? pt.x : Math.round(r + (l - r) / 2);\r\n\t\r\n\t\t\tvar y1 = view.getRoutingCenterY(source);\r\n\t\t\tvar y2 = view.getRoutingCenterY(target);\r\n\t\r\n\t\t\tif (pt != null)\r\n\t\t\t{\r\n\t\t\t\tif (pt.y >= source.y && pt.y <= source.y + source.height)\r\n\t\t\t\t{\r\n\t\t\t\t\ty1 = pt.y;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (pt.y >= target.y && pt.y <= target.y + target.height)\r\n\t\t\t\t{\r\n\t\t\t\t\ty2 = pt.y;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (!mxUtils.contains(target, x, y1) &&\r\n\t\t\t\t!mxUtils.contains(source, x, y1))\r\n\t\t\t{\r\n\t\t\t\tresult.push(new mxPoint(x,  y1));\r\n\t\t\t}\r\n\t\r\n\t\t\tif (!mxUtils.contains(target, x, y2) &&\r\n\t\t\t\t!mxUtils.contains(source, x, y2))\r\n\t\t\t{\r\n\t\t\t\tresult.push(new mxPoint(x, y2));\r\n\t\t\t}\r\n\t\r\n\t\t\tif (result.length == 1)\r\n\t\t\t{\r\n\t\t\t\tif (pt != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (!mxUtils.contains(target, x, pt.y) &&\r\n\t\t\t\t\t\t!mxUtils.contains(source, x, pt.y))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tresult.push(new mxPoint(x, pt.y));\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\t\r\n\t\t\t\t\tvar t = Math.max(source.y, target.y);\r\n\t\t\t\t\tvar b = Math.min(source.y + source.height,\r\n\t\t\t\t\t\t\t target.y + target.height);\r\n\t\t\t\t\t\t \r\n\t\t\t\t\tresult.push(new mxPoint(x, t + (b - t) / 2));\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Function: TopToBottom\r\n\t * \r\n\t * Implements a horizontal elbow edge. See <EntityRelation> for a\r\n\t * description of the parameters.\r\n\t */\r\n\tTopToBottom: function(state, source, target, points, result)\r\n\t{\r\n\t\tvar view = state.view;\r\n\t\tvar pt = (points != null && points.length > 0) ? points[0] : null;\r\n\t\tvar pts = state.absolutePoints;\r\n\t\tvar p0 = pts[0];\r\n\t\tvar pe = pts[pts.length-1];\r\n\t\t\r\n\t\tif (pt != null)\r\n\t\t{\r\n\t\t\tpt = view.transformControlPoint(state, pt);\r\n\t\t}\r\n\t\t\r\n\t\tif (p0 != null)\r\n\t\t{\r\n\t\t\tsource = new mxCellState();\r\n\t\t\tsource.x = p0.x;\r\n\t\t\tsource.y = p0.y;\r\n\t\t}\r\n\t\t\r\n\t\tif (pe != null)\r\n\t\t{\r\n\t\t\ttarget = new mxCellState();\r\n\t\t\ttarget.x = pe.x;\r\n\t\t\ttarget.y = pe.y;\r\n\t\t}\r\n\r\n\t\tif (source != null && target != null)\r\n\t\t{\r\n\t\t\tvar t = Math.max(source.y, target.y);\r\n\t\t\tvar b = Math.min(source.y + source.height,\r\n\t\t\t\t\t\t\t target.y + target.height);\r\n\t\r\n\t\t\tvar x = view.getRoutingCenterX(source);\r\n\t\t\t\r\n\t\t\tif (pt != null &&\r\n\t\t\t\tpt.x >= source.x &&\r\n\t\t\t\tpt.x <= source.x + source.width)\r\n\t\t\t{\r\n\t\t\t\tx = pt.x;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar y = (pt != null) ? pt.y : Math.round(b + (t - b) / 2);\r\n\t\t\t\r\n\t\t\tif (!mxUtils.contains(target, x, y) &&\r\n\t\t\t\t!mxUtils.contains(source, x, y))\r\n\t\t\t{\r\n\t\t\t\tresult.push(new mxPoint(x, y));\t\t\t\t\t\t\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (pt != null &&\r\n\t\t\t\tpt.x >= target.x &&\r\n\t\t\t\tpt.x <= target.x + target.width)\r\n\t\t\t{\r\n\t\t\t\tx = pt.x;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tx = view.getRoutingCenterX(target);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (!mxUtils.contains(target, x, y) &&\r\n\t\t\t\t!mxUtils.contains(source, x, y))\r\n\t\t\t{\r\n\t\t\t\tresult.push(new mxPoint(x, y));\t\t\t\t\t\t\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (result.length == 1)\r\n\t\t\t{\r\n\t\t\t\tif (pt != null && result.length == 1)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (!mxUtils.contains(target, pt.x, y) &&\r\n\t\t\t\t\t\t!mxUtils.contains(source, pt.x, y))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tresult.push(new mxPoint(pt.x, y));\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tvar l = Math.max(source.x, target.x);\r\n\t\t\t\t\tvar r = Math.min(source.x + source.width,\r\n\t\t\t\t\t\t\t target.x + target.width);\r\n\t\t\t\t\t\t \r\n\t\t\t\t\tresult.push(new mxPoint(l + (r - l) / 2, y));\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Function: SegmentConnector\r\n\t * \r\n\t * Implements an orthogonal edge style. Use <mxEdgeSegmentHandler>\r\n\t * as an interactive handler for this style.\r\n\t */\r\n\tSegmentConnector: function(state, source, target, hints, result)\r\n\t{\r\n\t\t// Creates array of all way- and terminalpoints\r\n\t\tvar pts = state.absolutePoints;\r\n\t\tvar tol = Math.max(1, state.view.scale);\r\n\t\t\r\n\t\t// Whether the first segment outgoing from the source end is horizontal\r\n\t\tvar lastPushed = (result.length > 0) ? result[0] : null;\r\n\t\tvar horizontal = true;\r\n\t\tvar hint = null;\r\n\t\t\r\n\t\t// Adds waypoints only if outside of tolerance\r\n\t\tfunction pushPoint(pt)\r\n\t\t{\r\n\t\t\tif (lastPushed == null || Math.abs(lastPushed.x - pt.x) >= tol || Math.abs(lastPushed.y - pt.y) >= tol)\r\n\t\t\t{\r\n\t\t\t\tresult.push(pt);\r\n\t\t\t\tlastPushed = pt;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\treturn lastPushed;\r\n\t\t};\r\n\r\n\t\t// Adds the first point\r\n\t\tvar pt = pts[0];\r\n\t\t\r\n\t\tif (pt == null && source != null)\r\n\t\t{\r\n\t\t\tpt = new mxPoint(state.view.getRoutingCenterX(source), state.view.getRoutingCenterY(source));\r\n\t\t}\r\n\t\telse if (pt != null)\r\n\t\t{\r\n\t\t\tpt = pt.clone();\r\n\t\t}\r\n\t\t\r\n\t\tpt.x = Math.round(pt.x);\r\n\t\tpt.y = Math.round(pt.y);\r\n\t\t\r\n\t\tvar lastInx = pts.length - 1;\r\n\r\n\t\t// Adds the waypoints\r\n\t\tif (hints != null && hints.length > 0)\r\n\t\t{\r\n\t\t\t// Converts all hints and removes nulls\r\n\t\t\tvar newHints = [];\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < hints.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar tmp = state.view.transformControlPoint(state, hints[i]);\r\n\t\t\t\t\r\n\t\t\t\tif (tmp != null)\r\n\t\t\t\t{\r\n\t\t\t\t\ttmp.x = Math.round(tmp.x);\r\n\t\t\t\t\ttmp.y = Math.round(tmp.y);\r\n\t\t\t\t\tnewHints.push(tmp);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (newHints.length == 0)\r\n\t\t\t{\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\thints = newHints;\r\n\t\t\t\r\n\t\t\t// Aligns source and target hint to fixed points\r\n\t\t\tif (pt != null && hints[0] != null)\r\n\t\t\t{\r\n\t\t\t\tif (Math.abs(hints[0].x - pt.x) < tol)\r\n\t\t\t\t{\r\n\t\t\t\t\thints[0].x = pt.x;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (Math.abs(hints[0].y - pt.y) < tol)\r\n\t\t\t\t{\r\n\t\t\t\t\thints[0].y = pt.y;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar pe = pts[lastInx];\r\n\t\t\t\r\n\t\t\tif (pe != null && hints[hints.length - 1] != null)\r\n\t\t\t{\r\n\t\t\t\tif (Math.abs(hints[hints.length - 1].x - pe.x) < tol)\r\n\t\t\t\t{\r\n\t\t\t\t\thints[hints.length - 1].x = pe.x;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (Math.abs(hints[hints.length - 1].y - pe.y) < tol)\r\n\t\t\t\t{\r\n\t\t\t\t\thints[hints.length - 1].y = pe.y;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\thint = hints[0];\r\n\r\n\t\t\tvar currentTerm = source;\r\n\t\t\tvar currentPt = pts[0];\r\n\t\t\tvar hozChan = false;\r\n\t\t\tvar vertChan = false;\r\n\t\t\tvar currentHint = hint;\r\n\t\t\t\r\n\t\t\tif (currentPt != null)\r\n\t\t\t{\r\n\t\t\t\tcurrentPt.x = Math.round(currentPt.x);\r\n\t\t\t\tcurrentPt.y = Math.round(currentPt.y);\r\n\t\t\t\tcurrentTerm = null;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Check for alignment with fixed points and with channels\r\n\t\t\t// at source and target segments only\r\n\t\t\tfor (var i = 0; i < 2; i++)\r\n\t\t\t{\r\n\t\t\t\tvar fixedVertAlign = currentPt != null && currentPt.x == currentHint.x;\r\n\t\t\t\tvar fixedHozAlign = currentPt != null && currentPt.y == currentHint.y;\r\n\t\t\t\t\r\n\t\t\t\tvar inHozChan = currentTerm != null && (currentHint.y >= currentTerm.y &&\r\n\t\t\t\t\t\tcurrentHint.y <= currentTerm.y + currentTerm.height);\r\n\t\t\t\tvar inVertChan = currentTerm != null && (currentHint.x >= currentTerm.x &&\r\n\t\t\t\t\t\tcurrentHint.x <= currentTerm.x + currentTerm.width);\r\n\r\n\t\t\t\thozChan = fixedHozAlign || (currentPt == null && inHozChan);\r\n\t\t\t\tvertChan = fixedVertAlign || (currentPt == null && inVertChan);\r\n\t\t\t\t\r\n\t\t\t\t// If the current hint falls in both the hor and vert channels in the case\r\n\t\t\t\t// of a floating port, or if the hint is exactly co-incident with a \r\n\t\t\t\t// fixed point, ignore the source and try to work out the orientation\r\n\t\t\t\t// from the target end\r\n\t\t\t\tif (i==0 && ((hozChan && vertChan) || (fixedVertAlign && fixedHozAlign)))\r\n\t\t\t\t{\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tif (currentPt != null && (!fixedHozAlign && !fixedVertAlign) && (inHozChan || inVertChan)) \r\n\t\t\t\t\t{\r\n\t\t\t\t\t\thorizontal = inHozChan ? false : true;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\r\n\t\t\t\t\tif (vertChan || hozChan)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\thorizontal = hozChan;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (i == 1)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t// Work back from target end\r\n\t\t\t\t\t\t\thorizontal = hints.length % 2 == 0 ? hozChan : vertChan;\r\n\t\t\t\t\t\t}\r\n\t\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tcurrentTerm = target;\r\n\t\t\t\tcurrentPt = pts[lastInx];\r\n\t\t\t\t\r\n\t\t\t\tif (currentPt != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tcurrentPt.x = Math.round(currentPt.x);\r\n\t\t\t\t\tcurrentPt.y = Math.round(currentPt.y);\r\n\t\t\t\t\tcurrentTerm = null;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tcurrentHint = hints[hints.length - 1];\r\n\t\t\t\t\r\n\t\t\t\tif (fixedVertAlign && fixedHozAlign)\r\n\t\t\t\t{\r\n\t\t\t\t\thints = hints.slice(1);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (horizontal && ((pts[0] != null && pts[0].y != hint.y) ||\r\n\t\t\t\t(pts[0] == null && source != null &&\r\n\t\t\t\t(hint.y < source.y || hint.y > source.y + source.height))))\r\n\t\t\t{\r\n\t\t\t\tpushPoint(new mxPoint(pt.x, hint.y));\r\n\t\t\t}\r\n\t\t\telse if (!horizontal && ((pts[0] != null && pts[0].x != hint.x) ||\r\n\t\t\t\t\t(pts[0] == null && source != null &&\r\n\t\t\t\t\t(hint.x < source.x || hint.x > source.x + source.width))))\r\n\t\t\t{\r\n\t\t\t\tpushPoint(new mxPoint(hint.x, pt.y));\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (horizontal)\r\n\t\t\t{\r\n\t\t\t\tpt.y = hint.y;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tpt.x = hint.x;\r\n\t\t\t}\r\n\t\t\r\n\t\t\tfor (var i = 0; i < hints.length; i++)\r\n\t\t\t{\r\n\t\t\t\thorizontal = !horizontal;\r\n\t\t\t\thint = hints[i];\r\n\t\t\t\t\r\n//\t\t\t\tmxLog.show();\r\n//\t\t\t\tmxLog.debug('hint', i, hint.x, hint.y);\r\n\t\t\t\t\r\n\t\t\t\tif (horizontal)\r\n\t\t\t\t{\r\n\t\t\t\t\tpt.y = hint.y;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tpt.x = hint.x;\r\n\t\t\t\t}\r\n\t\t\r\n\t\t\t\tpushPoint(pt.clone());\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\thint = pt;\r\n\t\t\t// FIXME: First click in connect preview toggles orientation\r\n\t\t\thorizontal = true;\r\n\t\t}\r\n\r\n\t\t// Adds the last point\r\n\t\tpt = pts[lastInx];\r\n\r\n\t\tif (pt == null && target != null)\r\n\t\t{\r\n\t\t\tpt = new mxPoint(state.view.getRoutingCenterX(target), state.view.getRoutingCenterY(target));\r\n\t\t}\r\n\t\t\r\n\t\tif (pt != null)\r\n\t\t{\r\n\t\t\tpt.x = Math.round(pt.x);\r\n\t\t\tpt.y = Math.round(pt.y);\r\n\t\t\t\r\n\t\t\tif (hint != null)\r\n\t\t\t{\r\n\t\t\t\tif (horizontal && ((pts[lastInx] != null && pts[lastInx].y != hint.y) ||\r\n\t\t\t\t\t(pts[lastInx] == null && target != null &&\r\n\t\t\t\t\t(hint.y < target.y || hint.y > target.y + target.height))))\r\n\t\t\t\t{\r\n\t\t\t\t\tpushPoint(new mxPoint(pt.x, hint.y));\r\n\t\t\t\t}\r\n\t\t\t\telse if (!horizontal && ((pts[lastInx] != null && pts[lastInx].x != hint.x) ||\r\n\t\t\t\t\t\t(pts[lastInx] == null && target != null &&\r\n\t\t\t\t\t\t(hint.x < target.x || hint.x > target.x + target.width))))\r\n\t\t\t\t{\r\n\t\t\t\t\tpushPoint(new mxPoint(hint.x, pt.y));\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Removes bends inside the source terminal for floating ports\r\n\t\tif (pts[0] == null && source != null)\r\n\t\t{\r\n\t\t\twhile (result.length > 1 && result[1] != null &&\r\n\t\t\t\tmxUtils.contains(source, result[1].x, result[1].y))\r\n\t\t\t{\r\n\t\t\t\tresult.splice(1, 1);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Removes bends inside the target terminal\r\n\t\tif (pts[lastInx] == null && target != null)\r\n\t\t{\r\n\t\t\twhile (result.length > 1 && result[result.length - 1] != null &&\r\n\t\t\t\tmxUtils.contains(target, result[result.length - 1].x, result[result.length - 1].y))\r\n\t\t\t{\r\n\t\t\t\tresult.splice(result.length - 1, 1);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Removes last point if inside tolerance with end point\r\n\t\tif (pe != null && result[result.length - 1] != null &&\r\n\t\t\tMath.abs(pe.x - result[result.length - 1].x) < tol &&\r\n\t\t\tMath.abs(pe.y - result[result.length - 1].y) < tol)\r\n\t\t{\r\n\t\t\tresult.splice(result.length - 1, 1);\r\n\t\t\t\r\n\t\t\t// Lines up second last point in result with end point\r\n\t\t\tif (result[result.length - 1] != null)\r\n\t\t\t{\r\n\t\t\t\tif (Math.abs(result[result.length - 1].x - pe.x) < tol)\r\n\t\t\t\t{\r\n\t\t\t\t\tresult[result.length - 1].x = pe.x;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (Math.abs(result[result.length - 1].y - pe.y) < tol)\r\n\t\t\t\t{\r\n\t\t\t\t\tresult[result.length - 1].y = pe.y;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\t\r\n\torthBuffer: 10,\r\n\t\r\n\torthPointsFallback: true,\r\n\r\n\tdirVectors: [ [ -1, 0 ],\r\n\t\t\t[ 0, -1 ], [ 1, 0 ], [ 0, 1 ], [ -1, 0 ], [ 0, -1 ], [ 1, 0 ] ],\r\n\r\n\twayPoints1: [ [ 0, 0], [ 0, 0],  [ 0, 0], [ 0, 0], [ 0, 0],  [ 0, 0],\r\n\t              [ 0, 0],  [ 0, 0], [ 0, 0],  [ 0, 0], [ 0, 0],  [ 0, 0] ],\r\n\r\n\troutePatterns: [\r\n\t\t[ [ 513, 2308, 2081, 2562 ], [ 513, 1090, 514, 2184, 2114, 2561 ],\r\n\t\t\t[ 513, 1090, 514, 2564, 2184, 2562 ],\r\n\t\t\t[ 513, 2308, 2561, 1090, 514, 2568, 2308 ] ],\r\n\t[ [ 514, 1057, 513, 2308, 2081, 2562 ], [ 514, 2184, 2114, 2561 ],\r\n\t\t\t[ 514, 2184, 2562, 1057, 513, 2564, 2184 ],\r\n\t\t\t[ 514, 1057, 513, 2568, 2308, 2561 ] ],\r\n\t[ [ 1090, 514, 1057, 513, 2308, 2081, 2562 ], [ 2114, 2561 ],\r\n\t\t\t[ 1090, 2562, 1057, 513, 2564, 2184 ],\r\n\t\t\t[ 1090, 514, 1057, 513, 2308, 2561, 2568 ] ],\r\n\t[ [ 2081, 2562 ], [ 1057, 513, 1090, 514, 2184, 2114, 2561 ],\r\n\t\t\t[ 1057, 513, 1090, 514, 2184, 2562, 2564 ],\r\n\t\t\t[ 1057, 2561, 1090, 514, 2568, 2308 ] ] ],\r\n\t\r\n\tinlineRoutePatterns: [\r\n\t\t\t[ null, [ 2114, 2568 ], null, null ],\r\n\t\t\t[ null, [ 514, 2081, 2114, 2568 ] , null, null ],\r\n\t\t\t[ null, [ 2114, 2561 ], null, null ],\r\n\t\t\t[ [ 2081, 2562 ], [ 1057, 2114, 2568 ],\r\n\t\t\t\t\t[ 2184, 2562 ],\r\n\t\t\t\t\tnull ] ],\r\n\tvertexSeperations: [],\r\n\r\n\tlimits: [\r\n\t       [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ],\r\n\t       [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ],\r\n\r\n\tLEFT_MASK: 32,\r\n\r\n\tTOP_MASK: 64,\r\n\r\n\tRIGHT_MASK: 128,\r\n\r\n\tBOTTOM_MASK: 256,\r\n\r\n\tLEFT: 1,\r\n\r\n\tTOP: 2,\r\n\r\n\tRIGHT: 4,\r\n\r\n\tBOTTOM: 8,\r\n\r\n\t// TODO remove magic numbers\r\n\tSIDE_MASK: 480,\r\n\t//mxEdgeStyle.LEFT_MASK | mxEdgeStyle.TOP_MASK | mxEdgeStyle.RIGHT_MASK\r\n\t//| mxEdgeStyle.BOTTOM_MASK,\r\n\r\n\tCENTER_MASK: 512,\r\n\r\n\tSOURCE_MASK: 1024,\r\n\r\n\tTARGET_MASK: 2048,\r\n\r\n\tVERTEX_MASK: 3072,\r\n\t// mxEdgeStyle.SOURCE_MASK | mxEdgeStyle.TARGET_MASK,\r\n\t\r\n\tgetJettySize: function(state, source, target, points, isSource)\r\n\t{\r\n\t\tvar value = mxUtils.getValue(state.style, (isSource) ? mxConstants.STYLE_SOURCE_JETTY_SIZE :\r\n\t\t\tmxConstants.STYLE_TARGET_JETTY_SIZE, mxUtils.getValue(state.style,\r\n\t\t\t\t\tmxConstants.STYLE_JETTY_SIZE, mxEdgeStyle.orthBuffer));\r\n\t\t\r\n\t\tif (value == 'auto')\r\n\t\t{\r\n\t\t\t// Computes the automatic jetty size\r\n\t\t\tvar type = mxUtils.getValue(state.style, (isSource) ? mxConstants.STYLE_STARTARROW : mxConstants.STYLE_ENDARROW, mxConstants.NONE);\r\n\t\t\t\r\n\t\t\tif (type != mxConstants.NONE)\r\n\t\t\t{\r\n\t\t\t\tvar size = mxUtils.getNumber(state.style, (isSource) ? mxConstants.STYLE_STARTSIZE : mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE);\r\n\t\t\t\tvalue = Math.max(2, Math.ceil((size + mxEdgeStyle.orthBuffer) / mxEdgeStyle.orthBuffer)) * mxEdgeStyle.orthBuffer;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tvalue = 2 * mxEdgeStyle.orthBuffer;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn value;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: OrthConnector\r\n\t * \r\n\t * Implements a local orthogonal router between the given\r\n\t * cells.\r\n\t * \r\n\t * Parameters:\r\n\t * \r\n\t * state - <mxCellState> that represents the edge to be updated.\r\n\t * source - <mxCellState> that represents the source terminal.\r\n\t * target - <mxCellState> that represents the target terminal.\r\n\t * points - List of relative control points.\r\n\t * result - Array of <mxPoints> that represent the actual points of the\r\n\t * edge.\r\n\t * \r\n\t */\r\n\tOrthConnector: function(state, source, target, points, result)\r\n\t{\r\n\t\tvar graph = state.view.graph;\r\n\t\tvar sourceEdge = source == null ? false : graph.getModel().isEdge(source.cell);\r\n\t\tvar targetEdge = target == null ? false : graph.getModel().isEdge(target.cell);\r\n\r\n\t\tvar pts = state.absolutePoints;\r\n\t\tvar p0 = pts[0];\r\n\t\tvar pe = pts[pts.length-1];\r\n\r\n\t\tvar sourceX = source != null ? source.x : p0.x;\r\n\t\tvar sourceY = source != null ? source.y : p0.y;\r\n\t\tvar sourceWidth = source != null ? source.width : 0;\r\n\t\tvar sourceHeight = source != null ? source.height : 0;\r\n\t\t\r\n\t\tvar targetX = target != null ? target.x : pe.x;\r\n\t\tvar targetY = target != null ? target.y : pe.y;\r\n\t\tvar targetWidth = target != null ? target.width : 0;\r\n\t\tvar targetHeight = target != null ? target.height : 0;\r\n\r\n\t\tvar scaledSourceBuffer = state.view.scale * mxEdgeStyle.getJettySize(state, source, target, points, true);\r\n\t\tvar scaledTargetBuffer = state.view.scale * mxEdgeStyle.getJettySize(state, source, target, points, false);\r\n\t\t\r\n\t\t// Workaround for loop routing within buffer zone\r\n\t\tif (source != null && target == source)\r\n\t\t{\r\n\t\t\tscaledTargetBuffer = Math.max(scaledSourceBuffer, scaledTargetBuffer);\r\n\t\t\tscaledSourceBuffer = scaledTargetBuffer;\r\n\t\t}\r\n\t\t\r\n\t\tvar totalBuffer = scaledTargetBuffer + scaledSourceBuffer;\r\n\t\tvar tooShort = false;\r\n\t\t\r\n\t\t// Checks minimum distance for fixed points and falls back to segment connector\r\n\t\tif (p0 != null && pe != null)\r\n\t\t{\r\n\t\t\tvar dx = pe.x - p0.x;\r\n\t\t\tvar dy = pe.y - p0.y;\r\n\t\t\t\r\n\t\t\ttooShort = dx * dx + dy * dy < totalBuffer * totalBuffer;\r\n\t\t}\r\n\r\n\t\tif (tooShort || (mxEdgeStyle.orthPointsFallback && (points != null &&\r\n\t\t\tpoints.length > 0)) || sourceEdge || targetEdge)\r\n\t\t{\r\n\t\t\tmxEdgeStyle.SegmentConnector(state, source, target, points, result);\r\n\t\t\t\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Determine the side(s) of the source and target vertices\r\n\t\t// that the edge may connect to\r\n\t\t// portConstraint [source, target]\r\n\t\tvar portConstraint = [mxConstants.DIRECTION_MASK_ALL, mxConstants.DIRECTION_MASK_ALL];\r\n\t\tvar rotation = 0;\r\n\t\t\r\n\t\tif (source != null)\r\n\t\t{\r\n\t\t\tportConstraint[0] = mxUtils.getPortConstraints(source, state, true, \r\n\t\t\t\t\tmxConstants.DIRECTION_MASK_ALL);\r\n\t\t\trotation = mxUtils.getValue(source.style, mxConstants.STYLE_ROTATION, 0);\r\n\t\t\t\r\n\t\t\tif (rotation != 0)\r\n\t\t\t{\r\n\t\t\t\tvar newRect = mxUtils.getBoundingBox(new mxRectangle(sourceX, sourceY, sourceWidth, sourceHeight), rotation);\r\n\t\t\t\tsourceX = newRect.x; \r\n\t\t\t\tsourceY = newRect.y;\r\n\t\t\t\tsourceWidth = newRect.width;\r\n\t\t\t\tsourceHeight = newRect.height;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (target != null)\r\n\t\t{\r\n\t\t\tportConstraint[1] = mxUtils.getPortConstraints(target, state, false,\r\n\t\t\t\tmxConstants.DIRECTION_MASK_ALL);\r\n\t\t\trotation = mxUtils.getValue(target.style, mxConstants.STYLE_ROTATION, 0);\r\n\r\n\t\t\tif (rotation != 0)\r\n\t\t\t{\r\n\t\t\t\tvar newRect = mxUtils.getBoundingBox(new mxRectangle(targetX, targetY, targetWidth, targetHeight), rotation);\r\n\t\t\t\ttargetX = newRect.x;\r\n\t\t\t\ttargetY = newRect.y;\r\n\t\t\t\ttargetWidth = newRect.width;\r\n\t\t\t\ttargetHeight = newRect.height;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Avoids floating point number errors\r\n\t\tsourceX = Math.round(sourceX * 10) / 10;\r\n\t\tsourceY = Math.round(sourceY * 10) / 10;\r\n\t\tsourceWidth = Math.round(sourceWidth * 10) / 10;\r\n\t\tsourceHeight = Math.round(sourceHeight * 10) / 10;\r\n\t\t\r\n\t\ttargetX = Math.round(targetX * 10) / 10;\r\n\t\ttargetY = Math.round(targetY * 10) / 10;\r\n\t\ttargetWidth = Math.round(targetWidth * 10) / 10;\r\n\t\ttargetHeight = Math.round(targetHeight * 10) / 10;\r\n\t\t\r\n\t\tvar dir = [0, 0];\r\n\r\n\t\t// Work out which faces of the vertices present against each other\r\n\t\t// in a way that would allow a 3-segment connection if port constraints\r\n\t\t// permitted.\r\n\t\t// geo -> [source, target] [x, y, width, height]\r\n\t\tvar geo = [ [sourceX, sourceY, sourceWidth, sourceHeight] ,\r\n\t\t            [targetX, targetY, targetWidth, targetHeight] ];\r\n\t\tvar buffer = [scaledSourceBuffer, scaledTargetBuffer];\r\n\r\n\t\tfor (var i = 0; i < 2; i++)\r\n\t\t{\r\n\t\t\tmxEdgeStyle.limits[i][1] = geo[i][0] - buffer[i];\r\n\t\t\tmxEdgeStyle.limits[i][2] = geo[i][1] - buffer[i];\r\n\t\t\tmxEdgeStyle.limits[i][4] = geo[i][0] + geo[i][2] + buffer[i];\r\n\t\t\tmxEdgeStyle.limits[i][8] = geo[i][1] + geo[i][3] + buffer[i];\r\n\t\t}\r\n\t\t\r\n\t\t// Work out which quad the target is in\r\n\t\tvar sourceCenX = geo[0][0] + geo[0][2] / 2.0;\r\n\t\tvar sourceCenY = geo[0][1] + geo[0][3] / 2.0;\r\n\t\tvar targetCenX = geo[1][0] + geo[1][2] / 2.0;\r\n\t\tvar targetCenY = geo[1][1] + geo[1][3] / 2.0;\r\n\t\t\r\n\t\tvar dx = sourceCenX - targetCenX;\r\n\t\tvar dy = sourceCenY - targetCenY;\r\n\r\n\t\tvar quad = 0;\r\n\r\n\t\tif (dx < 0)\r\n\t\t{\r\n\t\t\tif (dy < 0)\r\n\t\t\t{\r\n\t\t\t\tquad = 2;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tquad = 1;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tif (dy <= 0)\r\n\t\t\t{\r\n\t\t\t\tquad = 3;\r\n\t\t\t\t\r\n\t\t\t\t// Special case on x = 0 and negative y\r\n\t\t\t\tif (dx == 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tquad = 2;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Check for connection constraints\r\n\t\tvar currentTerm = null;\r\n\t\t\r\n\t\tif (source != null)\r\n\t\t{\r\n\t\t\tcurrentTerm = p0;\r\n\t\t}\r\n\r\n\t\tvar constraint = [ [0.5, 0.5] , [0.5, 0.5] ];\r\n\r\n\t\tfor (var i = 0; i < 2; i++)\r\n\t\t{\r\n\t\t\tif (currentTerm != null)\r\n\t\t\t{\r\n\t\t\t\tconstraint[i][0] = (currentTerm.x - geo[i][0]) / geo[i][2];\r\n\t\t\t\t\r\n\t\t\t\tif (Math.abs(currentTerm.x - geo[i][0]) <= 1)\r\n\t\t\t\t{\r\n\t\t\t\t\tdir[i] = mxConstants.DIRECTION_MASK_WEST;\r\n\t\t\t\t}\r\n\t\t\t\telse if (Math.abs(currentTerm.x - geo[i][0] - geo[i][2]) <= 1)\r\n\t\t\t\t{\r\n\t\t\t\t\tdir[i] = mxConstants.DIRECTION_MASK_EAST;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tconstraint[i][1] = (currentTerm.y - geo[i][1]) / geo[i][3];\r\n\r\n\t\t\t\tif (Math.abs(currentTerm.y - geo[i][1]) <= 1)\r\n\t\t\t\t{\r\n\t\t\t\t\tdir[i] = mxConstants.DIRECTION_MASK_NORTH;\r\n\t\t\t\t}\r\n\t\t\t\telse if (Math.abs(currentTerm.y - geo[i][1] - geo[i][3]) <= 1)\r\n\t\t\t\t{\r\n\t\t\t\t\tdir[i] = mxConstants.DIRECTION_MASK_SOUTH;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tcurrentTerm = null;\r\n\t\t\t\r\n\t\t\tif (target != null)\r\n\t\t\t{\r\n\t\t\t\tcurrentTerm = pe;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tvar sourceTopDist = geo[0][1] - (geo[1][1] + geo[1][3]);\r\n\t\tvar sourceLeftDist = geo[0][0] - (geo[1][0] + geo[1][2]);\r\n\t\tvar sourceBottomDist = geo[1][1] - (geo[0][1] + geo[0][3]);\r\n\t\tvar sourceRightDist = geo[1][0] - (geo[0][0] + geo[0][2]);\r\n\r\n\t\tmxEdgeStyle.vertexSeperations[1] = Math.max(sourceLeftDist - totalBuffer, 0);\r\n\t\tmxEdgeStyle.vertexSeperations[2] = Math.max(sourceTopDist - totalBuffer, 0);\r\n\t\tmxEdgeStyle.vertexSeperations[4] = Math.max(sourceBottomDist - totalBuffer, 0);\r\n\t\tmxEdgeStyle.vertexSeperations[3] = Math.max(sourceRightDist - totalBuffer, 0);\r\n\t\t\t\t\r\n\t\t//==============================================================\r\n\t\t// Start of source and target direction determination\r\n\r\n\t\t// Work through the preferred orientations by relative positioning\r\n\t\t// of the vertices and list them in preferred and available order\r\n\t\t\r\n\t\tvar dirPref = [];\r\n\t\tvar horPref = [];\r\n\t\tvar vertPref = [];\r\n\r\n\t\thorPref[0] = (sourceLeftDist >= sourceRightDist) ? mxConstants.DIRECTION_MASK_WEST\r\n\t\t\t\t: mxConstants.DIRECTION_MASK_EAST;\r\n\t\tvertPref[0] = (sourceTopDist >= sourceBottomDist) ? mxConstants.DIRECTION_MASK_NORTH\r\n\t\t\t\t: mxConstants.DIRECTION_MASK_SOUTH;\r\n\r\n\t\thorPref[1] = mxUtils.reversePortConstraints(horPref[0]);\r\n\t\tvertPref[1] = mxUtils.reversePortConstraints(vertPref[0]);\r\n\t\t\r\n\t\tvar preferredHorizDist = sourceLeftDist >= sourceRightDist ? sourceLeftDist\r\n\t\t\t\t: sourceRightDist;\r\n\t\tvar preferredVertDist = sourceTopDist >= sourceBottomDist ? sourceTopDist\r\n\t\t\t\t: sourceBottomDist;\r\n\r\n\t\tvar prefOrdering = [ [0, 0] , [0, 0] ];\r\n\t\tvar preferredOrderSet = false;\r\n\r\n\t\t// If the preferred port isn't available, switch it\r\n\t\tfor (var i = 0; i < 2; i++)\r\n\t\t{\r\n\t\t\tif (dir[i] != 0x0)\r\n\t\t\t{\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tif ((horPref[i] & portConstraint[i]) == 0)\r\n\t\t\t{\r\n\t\t\t\thorPref[i] = mxUtils.reversePortConstraints(horPref[i]);\r\n\t\t\t}\r\n\r\n\t\t\tif ((vertPref[i] & portConstraint[i]) == 0)\r\n\t\t\t{\r\n\t\t\t\tvertPref[i] = mxUtils\r\n\t\t\t\t\t\t.reversePortConstraints(vertPref[i]);\r\n\t\t\t}\r\n\r\n\t\t\tprefOrdering[i][0] = vertPref[i];\r\n\t\t\tprefOrdering[i][1] = horPref[i];\r\n\t\t}\r\n\r\n\t\tif (preferredVertDist > 0\r\n\t\t\t\t&& preferredHorizDist > 0)\r\n\t\t{\r\n\t\t\t// Possibility of two segment edge connection\r\n\t\t\tif (((horPref[0] & portConstraint[0]) > 0)\r\n\t\t\t\t\t&& ((vertPref[1] & portConstraint[1]) > 0))\r\n\t\t\t{\r\n\t\t\t\tprefOrdering[0][0] = horPref[0];\r\n\t\t\t\tprefOrdering[0][1] = vertPref[0];\r\n\t\t\t\tprefOrdering[1][0] = vertPref[1];\r\n\t\t\t\tprefOrdering[1][1] = horPref[1];\r\n\t\t\t\tpreferredOrderSet = true;\r\n\t\t\t}\r\n\t\t\telse if (((vertPref[0] & portConstraint[0]) > 0)\r\n\t\t\t\t\t&& ((horPref[1] & portConstraint[1]) > 0))\r\n\t\t\t{\r\n\t\t\t\tprefOrdering[0][0] = vertPref[0];\r\n\t\t\t\tprefOrdering[0][1] = horPref[0];\r\n\t\t\t\tprefOrdering[1][0] = horPref[1];\r\n\t\t\t\tprefOrdering[1][1] = vertPref[1];\r\n\t\t\t\tpreferredOrderSet = true;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (preferredVertDist > 0 && !preferredOrderSet)\r\n\t\t{\r\n\t\t\tprefOrdering[0][0] = vertPref[0];\r\n\t\t\tprefOrdering[0][1] = horPref[0];\r\n\t\t\tprefOrdering[1][0] = vertPref[1];\r\n\t\t\tprefOrdering[1][1] = horPref[1];\r\n\t\t\tpreferredOrderSet = true;\r\n\r\n\t\t}\r\n\t\t\r\n\t\tif (preferredHorizDist > 0 && !preferredOrderSet)\r\n\t\t{\r\n\t\t\tprefOrdering[0][0] = horPref[0];\r\n\t\t\tprefOrdering[0][1] = vertPref[0];\r\n\t\t\tprefOrdering[1][0] = horPref[1];\r\n\t\t\tprefOrdering[1][1] = vertPref[1];\r\n\t\t\tpreferredOrderSet = true;\r\n\t\t}\r\n\r\n\t\t// The source and target prefs are now an ordered list of\r\n\t\t// the preferred port selections\r\n\t\t// It the list can contain gaps, compact it\r\n\r\n\t\tfor (var i = 0; i < 2; i++)\r\n\t\t{\r\n\t\t\tif (dir[i] != 0x0)\r\n\t\t\t{\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tif ((prefOrdering[i][0] & portConstraint[i]) == 0)\r\n\t\t\t{\r\n\t\t\t\tprefOrdering[i][0] = prefOrdering[i][1];\r\n\t\t\t}\r\n\r\n\t\t\tdirPref[i] = prefOrdering[i][0] & portConstraint[i];\r\n\t\t\tdirPref[i] |= (prefOrdering[i][1] & portConstraint[i]) << 8;\r\n\t\t\tdirPref[i] |= (prefOrdering[1 - i][i] & portConstraint[i]) << 16;\r\n\t\t\tdirPref[i] |= (prefOrdering[1 - i][1 - i] & portConstraint[i]) << 24;\r\n\r\n\t\t\tif ((dirPref[i] & 0xF) == 0)\r\n\t\t\t{\r\n\t\t\t\tdirPref[i] = dirPref[i] << 8;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif ((dirPref[i] & 0xF00) == 0)\r\n\t\t\t{\r\n\t\t\t\tdirPref[i] = (dirPref[i] & 0xF) | dirPref[i] >> 8;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif ((dirPref[i] & 0xF0000) == 0)\r\n\t\t\t{\r\n\t\t\t\tdirPref[i] = (dirPref[i] & 0xFFFF)\r\n\t\t\t\t\t\t| ((dirPref[i] & 0xF000000) >> 8);\r\n\t\t\t}\r\n\r\n\t\t\tdir[i] = dirPref[i] & 0xF;\r\n\r\n\t\t\tif (portConstraint[i] == mxConstants.DIRECTION_MASK_WEST\r\n\t\t\t\t\t|| portConstraint[i] == mxConstants.DIRECTION_MASK_NORTH\r\n\t\t\t\t\t|| portConstraint[i] == mxConstants.DIRECTION_MASK_EAST\r\n\t\t\t\t\t|| portConstraint[i] == mxConstants.DIRECTION_MASK_SOUTH)\r\n\t\t\t{\r\n\t\t\t\tdir[i] = portConstraint[i];\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t//==============================================================\r\n\t\t// End of source and target direction determination\r\n\r\n\t\tvar sourceIndex = dir[0] == mxConstants.DIRECTION_MASK_EAST ? 3\r\n\t\t\t\t: dir[0];\r\n\t\tvar targetIndex = dir[1] == mxConstants.DIRECTION_MASK_EAST ? 3\r\n\t\t\t\t: dir[1];\r\n\r\n\t\tsourceIndex -= quad;\r\n\t\ttargetIndex -= quad;\r\n\r\n\t\tif (sourceIndex < 1)\r\n\t\t{\r\n\t\t\tsourceIndex += 4;\r\n\t\t}\r\n\t\t\r\n\t\tif (targetIndex < 1)\r\n\t\t{\r\n\t\t\ttargetIndex += 4;\r\n\t\t}\r\n\r\n\t\tvar routePattern = mxEdgeStyle.routePatterns[sourceIndex - 1][targetIndex - 1];\r\n\r\n\t\tmxEdgeStyle.wayPoints1[0][0] = geo[0][0];\r\n\t\tmxEdgeStyle.wayPoints1[0][1] = geo[0][1];\r\n\r\n\t\tswitch (dir[0])\r\n\t\t{\r\n\t\t\tcase mxConstants.DIRECTION_MASK_WEST:\r\n\t\t\t\tmxEdgeStyle.wayPoints1[0][0] -= scaledSourceBuffer;\r\n\t\t\t\tmxEdgeStyle.wayPoints1[0][1] += constraint[0][1] * geo[0][3];\r\n\t\t\t\tbreak;\r\n\t\t\tcase mxConstants.DIRECTION_MASK_SOUTH:\r\n\t\t\t\tmxEdgeStyle.wayPoints1[0][0] += constraint[0][0] * geo[0][2];\r\n\t\t\t\tmxEdgeStyle.wayPoints1[0][1] += geo[0][3] + scaledSourceBuffer;\r\n\t\t\t\tbreak;\r\n\t\t\tcase mxConstants.DIRECTION_MASK_EAST:\r\n\t\t\t\tmxEdgeStyle.wayPoints1[0][0] += geo[0][2] + scaledSourceBuffer;\r\n\t\t\t\tmxEdgeStyle.wayPoints1[0][1] += constraint[0][1] * geo[0][3];\r\n\t\t\t\tbreak;\r\n\t\t\tcase mxConstants.DIRECTION_MASK_NORTH:\r\n\t\t\t\tmxEdgeStyle.wayPoints1[0][0] += constraint[0][0] * geo[0][2];\r\n\t\t\t\tmxEdgeStyle.wayPoints1[0][1] -= scaledSourceBuffer;\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\r\n\t\tvar currentIndex = 0;\r\n\r\n\t\t// Orientation, 0 horizontal, 1 vertical\r\n\t\tvar lastOrientation = (dir[0] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) > 0 ? 0\r\n\t\t\t\t: 1;\r\n\t\tvar initialOrientation = lastOrientation;\r\n\t\tvar currentOrientation = 0;\r\n\r\n\t\tfor (var i = 0; i < routePattern.length; i++)\r\n\t\t{\r\n\t\t\tvar nextDirection = routePattern[i] & 0xF;\r\n\r\n\t\t\t// Rotate the index of this direction by the quad\r\n\t\t\t// to get the real direction\r\n\t\t\tvar directionIndex = nextDirection == mxConstants.DIRECTION_MASK_EAST ? 3\r\n\t\t\t\t\t: nextDirection;\r\n\r\n\t\t\tdirectionIndex += quad;\r\n\r\n\t\t\tif (directionIndex > 4)\r\n\t\t\t{\r\n\t\t\t\tdirectionIndex -= 4;\r\n\t\t\t}\r\n\r\n\t\t\tvar direction = mxEdgeStyle.dirVectors[directionIndex - 1];\r\n\r\n\t\t\tcurrentOrientation = (directionIndex % 2 > 0) ? 0 : 1;\r\n\t\t\t// Only update the current index if the point moved\r\n\t\t\t// in the direction of the current segment move,\r\n\t\t\t// otherwise the same point is moved until there is \r\n\t\t\t// a segment direction change\r\n\t\t\tif (currentOrientation != lastOrientation)\r\n\t\t\t{\r\n\t\t\t\tcurrentIndex++;\r\n\t\t\t\t// Copy the previous way point into the new one\r\n\t\t\t\t// We can't base the new position on index - 1\r\n\t\t\t\t// because sometime elbows turn out not to exist,\r\n\t\t\t\t// then we'd have to rewind.\r\n\t\t\t\tmxEdgeStyle.wayPoints1[currentIndex][0] = mxEdgeStyle.wayPoints1[currentIndex - 1][0];\r\n\t\t\t\tmxEdgeStyle.wayPoints1[currentIndex][1] = mxEdgeStyle.wayPoints1[currentIndex - 1][1];\r\n\t\t\t}\r\n\r\n\t\t\tvar tar = (routePattern[i] & mxEdgeStyle.TARGET_MASK) > 0;\r\n\t\t\tvar sou = (routePattern[i] & mxEdgeStyle.SOURCE_MASK) > 0;\r\n\t\t\tvar side = (routePattern[i] & mxEdgeStyle.SIDE_MASK) >> 5;\r\n\t\t\tside = side << quad;\r\n\r\n\t\t\tif (side > 0xF)\r\n\t\t\t{\r\n\t\t\t\tside = side >> 4;\r\n\t\t\t}\r\n\r\n\t\t\tvar center = (routePattern[i] & mxEdgeStyle.CENTER_MASK) > 0;\r\n\r\n\t\t\tif ((sou || tar) && side < 9)\r\n\t\t\t{\r\n\t\t\t\tvar limit = 0;\r\n\t\t\t\tvar souTar = sou ? 0 : 1;\r\n\r\n\t\t\t\tif (center && currentOrientation == 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tlimit = geo[souTar][0] + constraint[souTar][0] * geo[souTar][2];\r\n\t\t\t\t}\r\n\t\t\t\telse if (center)\r\n\t\t\t\t{\r\n\t\t\t\t\tlimit = geo[souTar][1] + constraint[souTar][1] * geo[souTar][3];\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tlimit = mxEdgeStyle.limits[souTar][side];\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (currentOrientation == 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar lastX = mxEdgeStyle.wayPoints1[currentIndex][0];\r\n\t\t\t\t\tvar deltaX = (limit - lastX) * direction[0];\r\n\r\n\t\t\t\t\tif (deltaX > 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tmxEdgeStyle.wayPoints1[currentIndex][0] += direction[0]\r\n\t\t\t\t\t\t\t\t* deltaX;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tvar lastY = mxEdgeStyle.wayPoints1[currentIndex][1];\r\n\t\t\t\t\tvar deltaY = (limit - lastY) * direction[1];\r\n\r\n\t\t\t\t\tif (deltaY > 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tmxEdgeStyle.wayPoints1[currentIndex][1] += direction[1]\r\n\t\t\t\t\t\t\t\t* deltaY;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\telse if (center)\r\n\t\t\t{\r\n\t\t\t\t// Which center we're travelling to depend on the current direction\r\n\t\t\t\tmxEdgeStyle.wayPoints1[currentIndex][0] += direction[0]\r\n\t\t\t\t\t\t* Math.abs(mxEdgeStyle.vertexSeperations[directionIndex] / 2);\r\n\t\t\t\tmxEdgeStyle.wayPoints1[currentIndex][1] += direction[1]\r\n\t\t\t\t\t\t* Math.abs(mxEdgeStyle.vertexSeperations[directionIndex] / 2);\r\n\t\t\t}\r\n\r\n\t\t\tif (currentIndex > 0\r\n\t\t\t\t\t&& mxEdgeStyle.wayPoints1[currentIndex][currentOrientation] == mxEdgeStyle.wayPoints1[currentIndex - 1][currentOrientation])\r\n\t\t\t{\r\n\t\t\t\tcurrentIndex--;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tlastOrientation = currentOrientation;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tfor (var i = 0; i <= currentIndex; i++)\r\n\t\t{\r\n\t\t\tif (i == currentIndex)\r\n\t\t\t{\r\n\t\t\t\t// Last point can cause last segment to be in\r\n\t\t\t\t// same direction as jetty/approach. If so,\r\n\t\t\t\t// check the number of points is consistent\r\n\t\t\t\t// with the relative orientation of source and target\r\n\t\t\t\t// jx. Same orientation requires an even\r\n\t\t\t\t// number of turns (points), different requires\r\n\t\t\t\t// odd.\r\n\t\t\t\tvar targetOrientation = (dir[1] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) > 0 ? 0\r\n\t\t\t\t\t\t: 1;\r\n\t\t\t\tvar sameOrient = targetOrientation == initialOrientation ? 0 : 1;\r\n\r\n\t\t\t\t// (currentIndex + 1) % 2 is 0 for even number of points,\r\n\t\t\t\t// 1 for odd\r\n\t\t\t\tif (sameOrient != (currentIndex + 1) % 2)\r\n\t\t\t\t{\r\n\t\t\t\t\t// The last point isn't required\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tresult.push(new mxPoint(Math.round(mxEdgeStyle.wayPoints1[i][0]), Math.round(mxEdgeStyle.wayPoints1[i][1])));\r\n\t\t}\r\n\t\t\r\n\t\t// Removes duplicates\r\n\t\tvar index = 1;\r\n\t\t\r\n\t\twhile (index < result.length)\r\n\t\t{\r\n\t\t\tif (result[index - 1] == null || result[index] == null ||\r\n\t\t\t\tresult[index - 1].x != result[index].x ||\r\n\t\t\t\tresult[index - 1].y != result[index].y)\r\n\t\t\t{\r\n\t\t\t\tindex++;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tresult.splice(index, 1);\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\t\r\n\tgetRoutePattern: function(dir, quad, dx, dy)\r\n\t{\r\n\t\tvar sourceIndex = dir[0] == mxConstants.DIRECTION_MASK_EAST ? 3\r\n\t\t\t\t: dir[0];\r\n\t\tvar targetIndex = dir[1] == mxConstants.DIRECTION_MASK_EAST ? 3\r\n\t\t\t\t: dir[1];\r\n\r\n\t\tsourceIndex -= quad;\r\n\t\ttargetIndex -= quad;\r\n\r\n\t\tif (sourceIndex < 1)\r\n\t\t{\r\n\t\t\tsourceIndex += 4;\r\n\t\t}\r\n\t\tif (targetIndex < 1)\r\n\t\t{\r\n\t\t\ttargetIndex += 4;\r\n\t\t}\r\n\r\n\t\tvar result = routePatterns[sourceIndex - 1][targetIndex - 1];\r\n\r\n\t\tif (dx == 0 || dy == 0)\r\n\t\t{\r\n\t\t\tif (inlineRoutePatterns[sourceIndex - 1][targetIndex - 1] != null)\r\n\t\t\t{\r\n\t\t\t\tresult = inlineRoutePatterns[sourceIndex - 1][targetIndex - 1];\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn result;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nvar mxStyleRegistry =\r\n{\r\n\t/**\r\n\t * Class: mxStyleRegistry\r\n\t *\r\n\t * Singleton class that acts as a global converter from string to object values\r\n\t * in a style. This is currently only used to perimeters and edge styles.\r\n\t * \r\n\t * Variable: values\r\n\t *\r\n\t * Maps from strings to objects.\r\n\t */\r\n\tvalues: [],\r\n\r\n\t/**\r\n\t * Function: putValue\r\n\t *\r\n\t * Puts the given object into the registry under the given name.\r\n\t */\r\n\tputValue: function(name, obj)\r\n\t{\r\n\t\tmxStyleRegistry.values[name] = obj;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: getValue\r\n\t *\r\n\t * Returns the value associated with the given name.\r\n\t */\r\n\tgetValue: function(name)\r\n\t{\r\n\t\treturn mxStyleRegistry.values[name];\r\n\t},\r\n\t\r\n\t/**\r\n\t * Function: getName\r\n\t * \r\n\t * Returns the name for the given value.\r\n\t */\r\n\tgetName: function(value)\r\n\t{\r\n\t\tfor (var key in mxStyleRegistry.values)\r\n\t\t{\r\n\t\t\tif (mxStyleRegistry.values[key] == value)\r\n\t\t\t{\r\n\t\t\t\treturn key;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn null;\r\n\t}\r\n\r\n};\r\n\r\nmxStyleRegistry.putValue(mxConstants.EDGESTYLE_ELBOW, mxEdgeStyle.ElbowConnector);\r\nmxStyleRegistry.putValue(mxConstants.EDGESTYLE_ENTITY_RELATION, mxEdgeStyle.EntityRelation);\r\nmxStyleRegistry.putValue(mxConstants.EDGESTYLE_LOOP, mxEdgeStyle.Loop);\r\nmxStyleRegistry.putValue(mxConstants.EDGESTYLE_SIDETOSIDE, mxEdgeStyle.SideToSide);\r\nmxStyleRegistry.putValue(mxConstants.EDGESTYLE_TOPTOBOTTOM, mxEdgeStyle.TopToBottom);\r\nmxStyleRegistry.putValue(mxConstants.EDGESTYLE_ORTHOGONAL, mxEdgeStyle.OrthConnector);\r\nmxStyleRegistry.putValue(mxConstants.EDGESTYLE_SEGMENT, mxEdgeStyle.SegmentConnector);\r\n\r\nmxStyleRegistry.putValue(mxConstants.PERIMETER_ELLIPSE, mxPerimeter.EllipsePerimeter);\r\nmxStyleRegistry.putValue(mxConstants.PERIMETER_RECTANGLE, mxPerimeter.RectanglePerimeter);\r\nmxStyleRegistry.putValue(mxConstants.PERIMETER_RHOMBUS, mxPerimeter.RhombusPerimeter);\r\nmxStyleRegistry.putValue(mxConstants.PERIMETER_TRIANGLE, mxPerimeter.TrianglePerimeter);\r\nmxStyleRegistry.putValue(mxConstants.PERIMETER_HEXAGON, mxPerimeter.HexagonPerimeter);\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxGraphView\r\n *\r\n * Extends <mxEventSource> to implement a view for a graph. This class is in\r\n * charge of computing the absolute coordinates for the relative child\r\n * geometries, the points for perimeters and edge styles and keeping them\r\n * cached in <mxCellStates> for faster retrieval. The states are updated\r\n * whenever the model or the view state (translate, scale) changes. The scale\r\n * and translate are honoured in the bounds.\r\n * \r\n * Event: mxEvent.UNDO\r\n * \r\n * Fires after the root was changed in <setCurrentRoot>. The <code>edit</code>\r\n * property contains the <mxUndoableEdit> which contains the\r\n * <mxCurrentRootChange>.\r\n * \r\n * Event: mxEvent.SCALE_AND_TRANSLATE\r\n * \r\n * Fires after the scale and translate have been changed in <scaleAndTranslate>.\r\n * The <code>scale</code>, <code>previousScale</code>, <code>translate</code>\r\n * and <code>previousTranslate</code> properties contain the new and previous\r\n * scale and translate, respectively.\r\n * \r\n * Event: mxEvent.SCALE\r\n * \r\n * Fires after the scale was changed in <setScale>. The <code>scale</code> and\r\n * <code>previousScale</code> properties contain the new and previous scale.\r\n * \r\n * Event: mxEvent.TRANSLATE\r\n * \r\n * Fires after the translate was changed in <setTranslate>. The\r\n * <code>translate</code> and <code>previousTranslate</code> properties contain\r\n * the new and previous value for translate.\r\n * \r\n * Event: mxEvent.DOWN and mxEvent.UP\r\n * \r\n * Fire if the current root is changed by executing an <mxCurrentRootChange>.\r\n * The event name depends on the location of the root in the cell hierarchy\r\n * with respect to the current root. The <code>root</code> and\r\n * <code>previous</code> properties contain the new and previous root,\r\n * respectively.\r\n * \r\n * Constructor: mxGraphView\r\n *\r\n * Constructs a new view for the given <mxGraph>.\r\n * \r\n * Parameters:\r\n * \r\n * graph - Reference to the enclosing <mxGraph>.\r\n */\r\nfunction mxGraphView(graph)\r\n{\r\n\tthis.graph = graph;\r\n\tthis.translate = new mxPoint();\r\n\tthis.graphBounds = new mxRectangle();\r\n\tthis.states = new mxDictionary();\r\n};\r\n\r\n/**\r\n * Extends mxEventSource.\r\n */\r\nmxGraphView.prototype = new mxEventSource();\r\nmxGraphView.prototype.constructor = mxGraphView;\r\n\r\n/**\r\n *\r\n */\r\nmxGraphView.prototype.EMPTY_POINT = new mxPoint();\r\n\r\n/**\r\n * Variable: doneResource\r\n * \r\n * Specifies the resource key for the status message after a long operation.\r\n * If the resource for this key does not exist then the value is used as\r\n * the status message. Default is 'done'.\r\n */\r\nmxGraphView.prototype.doneResource = (mxClient.language != 'none') ? 'done' : '';\r\n\r\n/**\r\n * Function: updatingDocumentResource\r\n *\r\n * Specifies the resource key for the status message while the document is\r\n * being updated. If the resource for this key does not exist then the\r\n * value is used as the status message. Default is 'updatingDocument'.\r\n */\r\nmxGraphView.prototype.updatingDocumentResource = (mxClient.language != 'none') ? 'updatingDocument' : '';\r\n\r\n/**\r\n * Variable: allowEval\r\n * \r\n * Specifies if string values in cell styles should be evaluated using\r\n * <mxUtils.eval>. This will only be used if the string values can't be mapped\r\n * to objects using <mxStyleRegistry>. Default is false. NOTE: Enabling this\r\n * switch carries a possible security risk.\r\n */\r\nmxGraphView.prototype.allowEval = false;\r\n\r\n/**\r\n * Variable: captureDocumentGesture\r\n * \r\n * Specifies if a gesture should be captured when it goes outside of the\r\n * graph container. Default is true.\r\n */\r\nmxGraphView.prototype.captureDocumentGesture = true;\r\n\r\n/**\r\n * Variable: optimizeVmlReflows\r\n * \r\n * Specifies if the <canvas> should be hidden while rendering in IE8 standards\r\n * mode and quirks mode. This will significantly improve rendering performance.\r\n * Default is true.\r\n */\r\nmxGraphView.prototype.optimizeVmlReflows = true;\r\n\r\n/**\r\n * Variable: rendering\r\n * \r\n * Specifies if shapes should be created, updated and destroyed using the\r\n * methods of <mxCellRenderer> in <graph>. Default is true.\r\n */\r\nmxGraphView.prototype.rendering = true;\r\n\r\n/**\r\n * Variable: graph\r\n *\r\n * Reference to the enclosing <mxGraph>.\r\n */\r\nmxGraphView.prototype.graph = null;\r\n\r\n/**\r\n * Variable: currentRoot\r\n *\r\n * <mxCell> that acts as the root of the displayed cell hierarchy.\r\n */\r\nmxGraphView.prototype.currentRoot = null;\r\n\r\n/**\r\n * Variable: graphBounds\r\n *\r\n * <mxRectangle> that caches the scales, translated bounds of the current view.\r\n */\r\nmxGraphView.prototype.graphBounds = null;\r\n\r\n/**\r\n * Variable: scale\r\n * \r\n * Specifies the scale. Default is 1 (100%).\r\n */\r\nmxGraphView.prototype.scale = 1;\r\n\t\r\n/**\r\n * Variable: translate\r\n *\r\n * <mxPoint> that specifies the current translation. Default is a new\r\n * empty <mxPoint>.\r\n */\r\nmxGraphView.prototype.translate = null;\r\n\r\n/**\r\n * Variable: states\r\n * \r\n * <mxDictionary> that maps from cell IDs to <mxCellStates>.\r\n */\r\nmxGraphView.prototype.states = null;\r\n\r\n/**\r\n * Variable: updateStyle\r\n * \r\n * Specifies if the style should be updated in each validation step. If this\r\n * is false then the style is only updated if the state is created or if the\r\n * style of the cell was changed. Default is false.\r\n */\r\nmxGraphView.prototype.updateStyle = false;\r\n\r\n/**\r\n * Variable: lastNode\r\n * \r\n * During validation, this contains the last DOM node that was processed.\r\n */\r\nmxGraphView.prototype.lastNode = null;\r\n\r\n/**\r\n * Variable: lastHtmlNode\r\n * \r\n * During validation, this contains the last HTML DOM node that was processed.\r\n */\r\nmxGraphView.prototype.lastHtmlNode = null;\r\n\r\n/**\r\n * Variable: lastForegroundNode\r\n * \r\n * During validation, this contains the last edge's DOM node that was processed.\r\n */\r\nmxGraphView.prototype.lastForegroundNode = null;\r\n\r\n/**\r\n * Variable: lastForegroundHtmlNode\r\n * \r\n * During validation, this contains the last edge HTML DOM node that was processed.\r\n */\r\nmxGraphView.prototype.lastForegroundHtmlNode = null;\r\n\r\n/**\r\n * Function: getGraphBounds\r\n *\r\n * Returns <graphBounds>.\r\n */\r\nmxGraphView.prototype.getGraphBounds = function()\r\n{\r\n\treturn this.graphBounds;\r\n};\r\n\r\n/**\r\n * Function: setGraphBounds\r\n *\r\n * Sets <graphBounds>.\r\n */\r\nmxGraphView.prototype.setGraphBounds = function(value)\r\n{\r\n\tthis.graphBounds = value;\r\n};\r\n\r\n/**\r\n * Function: getBounds\r\n * \r\n * Returns the union of all <mxCellStates> for the given array of <mxCells>.\r\n *\r\n * Parameters:\r\n *\r\n * cells - Array of <mxCells> whose bounds should be returned.\r\n */\r\nmxGraphView.prototype.getBounds = function(cells)\r\n{\r\n\tvar result = null;\r\n\t\r\n\tif (cells != null && cells.length > 0)\r\n\t{\r\n\t\tvar model = this.graph.getModel();\r\n\t\t\r\n\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t{\r\n\t\t\tif (model.isVertex(cells[i]) || model.isEdge(cells[i]))\r\n\t\t\t{\r\n\t\t\t\tvar state = this.getState(cells[i]);\r\n\t\t\t\r\n\t\t\t\tif (state != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (result == null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tresult = mxRectangle.fromRectangle(state);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tresult.add(state);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: setCurrentRoot\r\n *\r\n * Sets and returns the current root and fires an <undo> event before\r\n * calling <mxGraph.sizeDidChange>.\r\n *\r\n * Parameters:\r\n *\r\n * root - <mxCell> that specifies the root of the displayed cell hierarchy.\r\n */\r\nmxGraphView.prototype.setCurrentRoot = function(root)\r\n{\r\n\tif (this.currentRoot != root)\r\n\t{\r\n\t\tvar change = new mxCurrentRootChange(this, root);\r\n\t\tchange.execute();\r\n\t\tvar edit = new mxUndoableEdit(this, true);\r\n\t\tedit.add(change);\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));\r\n\t\tthis.graph.sizeDidChange();\r\n\t}\r\n\t\r\n\treturn root;\r\n};\r\n\r\n/**\r\n * Function: scaleAndTranslate\r\n *\r\n * Sets the scale and translation and fires a <scale> and <translate> event\r\n * before calling <revalidate> followed by <mxGraph.sizeDidChange>.\r\n *\r\n * Parameters:\r\n *\r\n * scale - Decimal value that specifies the new scale (1 is 100%).\r\n * dx - X-coordinate of the translation.\r\n * dy - Y-coordinate of the translation.\r\n */\r\nmxGraphView.prototype.scaleAndTranslate = function(scale, dx, dy)\r\n{\r\n\tvar previousScale = this.scale;\r\n\tvar previousTranslate = new mxPoint(this.translate.x, this.translate.y);\r\n\t\r\n\tif (this.scale != scale || this.translate.x != dx || this.translate.y != dy)\r\n\t{\r\n\t\tthis.scale = scale;\r\n\t\t\r\n\t\tthis.translate.x = dx;\r\n\t\tthis.translate.y = dy;\r\n\r\n\t\tif (this.isEventsEnabled())\r\n\t\t{\r\n\t\t\tthis.viewStateChanged();\r\n\t\t}\r\n\t}\r\n\t\r\n\tthis.fireEvent(new mxEventObject(mxEvent.SCALE_AND_TRANSLATE,\r\n\t\t'scale', scale, 'previousScale', previousScale,\r\n\t\t'translate', this.translate, 'previousTranslate', previousTranslate));\r\n};\r\n\r\n/**\r\n * Function: getScale\r\n * \r\n * Returns the <scale>.\r\n */\r\nmxGraphView.prototype.getScale = function()\r\n{\r\n\treturn this.scale;\r\n};\r\n\r\n/**\r\n * Function: setScale\r\n *\r\n * Sets the scale and fires a <scale> event before calling <revalidate> followed\r\n * by <mxGraph.sizeDidChange>.\r\n *\r\n * Parameters:\r\n *\r\n * value - Decimal value that specifies the new scale (1 is 100%).\r\n */\r\nmxGraphView.prototype.setScale = function(value)\r\n{\r\n\tvar previousScale = this.scale;\r\n\t\r\n\tif (this.scale != value)\r\n\t{\r\n\t\tthis.scale = value;\r\n\r\n\t\tif (this.isEventsEnabled())\r\n\t\t{\r\n\t\t\tthis.viewStateChanged();\r\n\t\t}\r\n\t}\r\n\t\r\n\tthis.fireEvent(new mxEventObject(mxEvent.SCALE,\r\n\t\t'scale', value, 'previousScale', previousScale));\r\n};\r\n\r\n/**\r\n * Function: getTranslate\r\n * \r\n * Returns the <translate>.\r\n */\r\nmxGraphView.prototype.getTranslate = function()\r\n{\r\n\treturn this.translate;\r\n};\r\n\r\n/**\r\n * Function: setTranslate\r\n *\r\n * Sets the translation and fires a <translate> event before calling\r\n * <revalidate> followed by <mxGraph.sizeDidChange>. The translation is the\r\n * negative of the origin.\r\n *\r\n * Parameters:\r\n *\r\n * dx - X-coordinate of the translation.\r\n * dy - Y-coordinate of the translation.\r\n */\r\nmxGraphView.prototype.setTranslate = function(dx, dy)\r\n{\r\n\tvar previousTranslate = new mxPoint(this.translate.x, this.translate.y);\r\n\t\r\n\tif (this.translate.x != dx || this.translate.y != dy)\r\n\t{\r\n\t\tthis.translate.x = dx;\r\n\t\tthis.translate.y = dy;\r\n\r\n\t\tif (this.isEventsEnabled())\r\n\t\t{\r\n\t\t\tthis.viewStateChanged();\r\n\t\t}\r\n\t}\r\n\t\r\n\tthis.fireEvent(new mxEventObject(mxEvent.TRANSLATE,\r\n\t\t'translate', this.translate, 'previousTranslate', previousTranslate));\r\n};\r\n\r\n/**\r\n * Function: viewStateChanged\r\n * \r\n * Invoked after <scale> and/or <translate> has changed.\r\n */\r\nmxGraphView.prototype.viewStateChanged = function()\r\n{\r\n\tthis.revalidate();\r\n\tthis.graph.sizeDidChange();\r\n};\r\n\r\n/**\r\n * Function: refresh\r\n *\r\n * Clears the view if <currentRoot> is not null and revalidates.\r\n */\r\nmxGraphView.prototype.refresh = function()\r\n{\r\n\tif (this.currentRoot != null)\r\n\t{\r\n\t\tthis.clear();\r\n\t}\r\n\t\r\n\tthis.revalidate();\r\n};\r\n\r\n/**\r\n * Function: revalidate\r\n *\r\n * Revalidates the complete view with all cell states.\r\n */\r\nmxGraphView.prototype.revalidate = function()\r\n{\r\n\tthis.invalidate();\r\n\tthis.validate();\r\n};\r\n\r\n/**\r\n * Function: clear\r\n *\r\n * Removes the state of the given cell and all descendants if the given\r\n * cell is not the current root.\r\n * \r\n * Parameters:\r\n * \r\n * cell - Optional <mxCell> for which the state should be removed. Default\r\n * is the root of the model.\r\n * force - Boolean indicating if the current root should be ignored for\r\n * recursion.\r\n */\r\nmxGraphView.prototype.clear = function(cell, force, recurse)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\tcell = cell || model.getRoot();\r\n\tforce = (force != null) ? force : false;\r\n\trecurse = (recurse != null) ? recurse : true;\r\n\t\r\n\tthis.removeState(cell);\r\n\t\r\n\tif (recurse && (force || cell != this.currentRoot))\r\n\t{\r\n\t\tvar childCount = model.getChildCount(cell);\r\n\t\t\r\n\t\tfor (var i = 0; i < childCount; i++)\r\n\t\t{\r\n\t\t\tthis.clear(model.getChildAt(cell, i), force);\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.invalidate(cell);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: invalidate\r\n * \r\n * Invalidates the state of the given cell, all its descendants and\r\n * connected edges.\r\n * \r\n * Parameters:\r\n * \r\n * cell - Optional <mxCell> to be invalidated. Default is the root of the\r\n * model.\r\n */\r\nmxGraphView.prototype.invalidate = function(cell, recurse, includeEdges)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\tcell = cell || model.getRoot();\r\n\trecurse = (recurse != null) ? recurse : true;\r\n\tincludeEdges = (includeEdges != null) ? includeEdges : true;\r\n\t\r\n\tvar state = this.getState(cell);\r\n\t\r\n\tif (state != null)\r\n\t{\r\n\t\tstate.invalid = true;\r\n\t}\r\n\t\r\n\t// Avoids infinite loops for invalid graphs\r\n\tif (!cell.invalidating)\r\n\t{\r\n\t\tcell.invalidating = true;\r\n\t\t\r\n\t\t// Recursively invalidates all descendants\r\n\t\tif (recurse)\r\n\t\t{\r\n\t\t\tvar childCount = model.getChildCount(cell);\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < childCount; i++)\r\n\t\t\t{\r\n\t\t\t\tvar child = model.getChildAt(cell, i);\r\n\t\t\t\tthis.invalidate(child, recurse, includeEdges);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Propagates invalidation to all connected edges\r\n\t\tif (includeEdges)\r\n\t\t{\r\n\t\t\tvar edgeCount = model.getEdgeCount(cell);\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < edgeCount; i++)\r\n\t\t\t{\r\n\t\t\t\tthis.invalidate(model.getEdgeAt(cell, i), recurse, includeEdges);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tdelete cell.invalidating;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: validate\r\n * \r\n * Calls <validateCell> and <validateCellState> and updates the <graphBounds>\r\n * using <getBoundingBox>. Finally the background is validated using\r\n * <validateBackground>.\r\n * \r\n * Parameters:\r\n * \r\n * cell - Optional <mxCell> to be used as the root of the validation.\r\n * Default is <currentRoot> or the root of the model.\r\n */\r\nmxGraphView.prototype.validate = function(cell)\r\n{\r\n\tvar t0 = mxLog.enter('mxGraphView.validate');\r\n\twindow.status = mxResources.get(this.updatingDocumentResource) ||\r\n\t\tthis.updatingDocumentResource;\r\n\t\r\n\tthis.resetValidationState();\r\n\t\r\n\t// Improves IE rendering speed by minimizing reflows\r\n\tvar prevDisplay = null;\r\n\t\r\n\tif (this.optimizeVmlReflows && this.canvas != null && this.textDiv == null &&\r\n\t\t((document.documentMode == 8 && !mxClient.IS_EM) || mxClient.IS_QUIRKS))\r\n\t{\r\n\t\t// Placeholder keeps scrollbar positions when canvas is hidden\r\n\t\tthis.placeholder = document.createElement('div');\r\n\t\tthis.placeholder.style.position = 'absolute';\r\n\t\tthis.placeholder.style.width = this.canvas.clientWidth + 'px';\r\n\t\tthis.placeholder.style.height = this.canvas.clientHeight + 'px';\r\n\t\tthis.canvas.parentNode.appendChild(this.placeholder);\r\n\r\n\t\tprevDisplay = this.drawPane.style.display;\r\n\t\tthis.canvas.style.display = 'none';\r\n\t\t\r\n\t\t// Creates temporary DIV used for text measuring in mxText.updateBoundingBox\r\n\t\tthis.textDiv = document.createElement('div');\r\n\t\tthis.textDiv.style.position = 'absolute';\r\n\t\tthis.textDiv.style.whiteSpace = 'nowrap';\r\n\t\tthis.textDiv.style.visibility = 'hidden';\r\n\t\tthis.textDiv.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';\r\n\t\tthis.textDiv.style.zoom = '1';\r\n\t\t\r\n\t\tdocument.body.appendChild(this.textDiv);\r\n\t}\r\n\t\r\n\tvar graphBounds = this.getBoundingBox(this.validateCellState(\r\n\t\tthis.validateCell(cell || ((this.currentRoot != null) ?\r\n\t\t\tthis.currentRoot : this.graph.getModel().getRoot()))));\r\n\tthis.setGraphBounds((graphBounds != null) ? graphBounds : this.getEmptyBounds());\r\n\tthis.validateBackground();\r\n\t\r\n\tif (prevDisplay != null)\r\n\t{\r\n\t\tthis.canvas.style.display = prevDisplay;\r\n\t\tthis.textDiv.parentNode.removeChild(this.textDiv);\r\n\t\t\r\n\t\tif (this.placeholder != null)\r\n\t\t{\r\n\t\t\tthis.placeholder.parentNode.removeChild(this.placeholder);\r\n\t\t}\r\n\t\t\t\t\r\n\t\t// Textdiv cannot be reused\r\n\t\tthis.textDiv = null;\r\n\t}\r\n\t\r\n\tthis.resetValidationState();\r\n\t\r\n\twindow.status = mxResources.get(this.doneResource) ||\r\n\t\tthis.doneResource;\r\n\tmxLog.leave('mxGraphView.validate', t0);\r\n};\r\n\r\n/**\r\n * Function: getEmptyBounds\r\n * \r\n * Returns the bounds for an empty graph. This returns a rectangle at\r\n * <translate> with the size of 0 x 0.\r\n */\r\nmxGraphView.prototype.getEmptyBounds = function()\r\n{\r\n\treturn new mxRectangle(this.translate.x * this.scale, this.translate.y * this.scale);\r\n};\r\n\r\n/**\r\n * Function: getBoundingBox\r\n * \r\n * Returns the bounding box of the shape and the label for the given\r\n * <mxCellState> and its children if recurse is true.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose bounding box should be returned.\r\n * recurse - Optional boolean indicating if the children should be included.\r\n * Default is true.\r\n */\r\nmxGraphView.prototype.getBoundingBox = function(state, recurse)\r\n{\r\n\trecurse = (recurse != null) ? recurse : true;\r\n\tvar bbox = null;\r\n\t\r\n\tif (state != null)\r\n\t{\r\n\t\tif (state.shape != null && state.shape.boundingBox != null)\r\n\t\t{\r\n\t\t\tbbox = state.shape.boundingBox.clone();\r\n\t\t}\r\n\t\t\r\n\t\t// Adds label bounding box to graph bounds\r\n\t\tif (state.text != null && state.text.boundingBox != null)\r\n\t\t{\r\n\t\t\tif (bbox != null)\r\n\t\t\t{\r\n\t\t\t\tbbox.add(state.text.boundingBox);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tbbox = state.text.boundingBox.clone();\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (recurse)\r\n\t\t{\r\n\t\t\tvar model = this.graph.getModel();\r\n\t\t\tvar childCount = model.getChildCount(state.cell);\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < childCount; i++)\r\n\t\t\t{\r\n\t\t\t\tvar bounds = this.getBoundingBox(this.getState(model.getChildAt(state.cell, i)));\r\n\t\t\t\t\r\n\t\t\t\tif (bounds != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (bbox == null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tbbox = bounds;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tbbox.add(bounds);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn bbox;\r\n};\r\n\r\n/**\r\n * Function: createBackgroundPageShape\r\n *\r\n * Creates and returns the shape used as the background page.\r\n * \r\n * Parameters:\r\n * \r\n * bounds - <mxRectangle> that represents the bounds of the shape.\r\n */\r\nmxGraphView.prototype.createBackgroundPageShape = function(bounds)\r\n{\r\n\treturn new mxRectangleShape(bounds, 'white', 'black');\r\n};\r\n\r\n/**\r\n * Function: validateBackground\r\n *\r\n * Calls <validateBackgroundImage> and <validateBackgroundPage>.\r\n */\r\nmxGraphView.prototype.validateBackground = function()\r\n{\r\n\tthis.validateBackgroundImage();\r\n\tthis.validateBackgroundPage();\r\n};\r\n\r\n/**\r\n * Function: validateBackgroundImage\r\n * \r\n * Validates the background image.\r\n */\r\nmxGraphView.prototype.validateBackgroundImage = function()\r\n{\r\n\tvar bg = this.graph.getBackgroundImage();\r\n\t\r\n\tif (bg != null)\r\n\t{\r\n\t\tif (this.backgroundImage == null || this.backgroundImage.image != bg.src)\r\n\t\t{\r\n\t\t\tif (this.backgroundImage != null)\r\n\t\t\t{\r\n\t\t\t\tthis.backgroundImage.destroy();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar bounds = new mxRectangle(0, 0, 1, 1);\r\n\t\t\t\r\n\t\t\tthis.backgroundImage = new mxImageShape(bounds, bg.src);\r\n\t\t\tthis.backgroundImage.dialect = this.graph.dialect;\r\n\t\t\tthis.backgroundImage.init(this.backgroundPane);\r\n\t\t\tthis.backgroundImage.redraw();\r\n\r\n\t\t\t// Workaround for ignored event on background in IE8 standards mode\r\n\t\t\tif (document.documentMode == 8 && !mxClient.IS_EM)\r\n\t\t\t{\r\n\t\t\t\tmxEvent.addGestureListeners(this.backgroundImage.node,\r\n\t\t\t\t\tmxUtils.bind(this, function(evt)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));\r\n\t\t\t\t\t}),\r\n\t\t\t\t\tmxUtils.bind(this, function(evt)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));\r\n\t\t\t\t\t}),\r\n\t\t\t\t\tmxUtils.bind(this, function(evt)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));\r\n\t\t\t\t\t})\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tthis.redrawBackgroundImage(this.backgroundImage, bg);\r\n\t}\r\n\telse if (this.backgroundImage != null)\r\n\t{\r\n\t\tthis.backgroundImage.destroy();\r\n\t\tthis.backgroundImage = null;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: validateBackgroundPage\r\n * \r\n * Validates the background page.\r\n */\r\nmxGraphView.prototype.validateBackgroundPage = function()\r\n{\r\n\tif (this.graph.pageVisible)\r\n\t{\r\n\t\tvar bounds = this.getBackgroundPageBounds();\r\n\t\t\r\n\t\tif (this.backgroundPageShape == null)\r\n\t\t{\r\n\t\t\tthis.backgroundPageShape = this.createBackgroundPageShape(bounds);\r\n\t\t\tthis.backgroundPageShape.scale = this.scale;\r\n\t\t\tthis.backgroundPageShape.isShadow = true;\r\n\t\t\tthis.backgroundPageShape.dialect = this.graph.dialect;\r\n\t\t\tthis.backgroundPageShape.init(this.backgroundPane);\r\n\t\t\tthis.backgroundPageShape.redraw();\r\n\t\t\t\r\n\t\t\t// Adds listener for double click handling on background\r\n\t\t\tif (this.graph.nativeDblClickEnabled)\r\n\t\t\t{\r\n\t\t\t\tmxEvent.addListener(this.backgroundPageShape.node, 'dblclick', mxUtils.bind(this, function(evt)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.graph.dblClick(evt);\r\n\t\t\t\t}));\r\n\t\t\t}\r\n\r\n\t\t\t// Adds basic listeners for graph event dispatching outside of the\r\n\t\t\t// container and finishing the handling of a single gesture\r\n\t\t\tmxEvent.addGestureListeners(this.backgroundPageShape.node,\r\n\t\t\t\tmxUtils.bind(this, function(evt)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));\r\n\t\t\t\t}),\r\n\t\t\t\tmxUtils.bind(this, function(evt)\r\n\t\t\t\t{\r\n\t\t\t\t\t// Hides the tooltip if mouse is outside container\r\n\t\t\t\t\tif (this.graph.tooltipHandler != null && this.graph.tooltipHandler.isHideOnHover())\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.graph.tooltipHandler.hide();\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (this.graph.isMouseDown && !mxEvent.isConsumed(evt))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));\r\n\t\t\t\t\t}\r\n\t\t\t\t}),\r\n\t\t\t\tmxUtils.bind(this, function(evt)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));\r\n\t\t\t\t})\r\n\t\t\t);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.backgroundPageShape.scale = this.scale;\r\n\t\t\tthis.backgroundPageShape.bounds = bounds;\r\n\t\t\tthis.backgroundPageShape.redraw();\r\n\t\t}\r\n\t}\r\n\telse if (this.backgroundPageShape != null)\r\n\t{\r\n\t\tthis.backgroundPageShape.destroy();\r\n\t\tthis.backgroundPageShape = null;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getBackgroundPageBounds\r\n * \r\n * Returns the bounds for the background page.\r\n */\r\nmxGraphView.prototype.getBackgroundPageBounds = function()\r\n{\r\n\tvar fmt = this.graph.pageFormat;\r\n\tvar ps = this.scale * this.graph.pageScale;\r\n\tvar bounds = new mxRectangle(this.scale * this.translate.x, this.scale * this.translate.y,\r\n\t\t\tfmt.width * ps, fmt.height * ps);\r\n\t\r\n\treturn bounds;\r\n};\r\n\r\n/**\r\n * Function: redrawBackgroundImage\r\n *\r\n * Updates the bounds and redraws the background image.\r\n * \r\n * Example:\r\n * \r\n * If the background image should not be scaled, this can be replaced with\r\n * the following.\r\n * \r\n * (code)\r\n * mxGraphView.prototype.redrawBackground = function(backgroundImage, bg)\r\n * {\r\n *   backgroundImage.bounds.x = this.translate.x;\r\n *   backgroundImage.bounds.y = this.translate.y;\r\n *   backgroundImage.bounds.width = bg.width;\r\n *   backgroundImage.bounds.height = bg.height;\r\n *\r\n *   backgroundImage.redraw();\r\n * };\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * backgroundImage - <mxImageShape> that represents the background image.\r\n * bg - <mxImage> that specifies the image and its dimensions.\r\n */\r\nmxGraphView.prototype.redrawBackgroundImage = function(backgroundImage, bg)\r\n{\r\n\tbackgroundImage.scale = this.scale;\r\n\tbackgroundImage.bounds.x = this.scale * this.translate.x;\r\n\tbackgroundImage.bounds.y = this.scale * this.translate.y;\r\n\tbackgroundImage.bounds.width = this.scale * bg.width;\r\n\tbackgroundImage.bounds.height = this.scale * bg.height;\r\n\r\n\tbackgroundImage.redraw();\r\n};\r\n\r\n/**\r\n * Function: validateCell\r\n * \r\n * Recursively creates the cell state for the given cell if visible is true and\r\n * the given cell is visible. If the cell is not visible but the state exists\r\n * then it is removed using <removeState>.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose <mxCellState> should be created.\r\n * visible - Optional boolean indicating if the cell should be visible. Default\r\n * is true.\r\n */\r\nmxGraphView.prototype.validateCell = function(cell, visible)\r\n{\r\n\tvisible = (visible != null) ? visible : true;\r\n\t\r\n\tif (cell != null)\r\n\t{\r\n\t\tvisible = visible && this.graph.isCellVisible(cell);\r\n\t\tvar state = this.getState(cell, visible);\r\n\t\t\r\n\t\tif (state != null && !visible)\r\n\t\t{\r\n\t\t\tthis.removeState(cell);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar model = this.graph.getModel();\r\n\t\t\tvar childCount = model.getChildCount(cell);\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < childCount; i++)\r\n\t\t\t{\r\n\t\t\t\tthis.validateCell(model.getChildAt(cell, i), visible &&\r\n\t\t\t\t\t(!this.isCellCollapsed(cell) || cell == this.currentRoot));\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn cell;\r\n};\r\n\r\n/**\r\n * Function: validateCellState\r\n * \r\n * Validates and repaints the <mxCellState> for the given <mxCell>.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose <mxCellState> should be validated.\r\n * recurse - Optional boolean indicating if the children of the cell should be\r\n * validated. Default is true.\r\n */\r\nmxGraphView.prototype.validateCellState = function(cell, recurse)\r\n{\r\n\trecurse = (recurse != null) ? recurse : true;\r\n\tvar state = null;\r\n\t\r\n\tif (cell != null)\r\n\t{\r\n\t\tstate = this.getState(cell);\r\n\t\t\r\n\t\tif (state != null)\r\n\t\t{\r\n\t\t\tvar model = this.graph.getModel();\r\n\t\t\t\r\n\t\t\tif (state.invalid)\r\n\t\t\t{\r\n\t\t\t\tstate.invalid = false;\r\n\t\t\t\t\r\n\t\t\t\tif (state.style == null || state.invalidStyle)\r\n\t\t\t\t{\r\n\t\t\t\t\tstate.style = this.graph.getCellStyle(state.cell);\r\n\t\t\t\t\tstate.invalidStyle = false;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (cell != this.currentRoot)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.validateCellState(model.getParent(cell), false);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tstate.setVisibleTerminalState(this.validateCellState(this.getVisibleTerminal(cell, true), false), true);\r\n\t\t\t\tstate.setVisibleTerminalState(this.validateCellState(this.getVisibleTerminal(cell, false), false), false);\r\n\t\t\t\t\r\n\t\t\t\tthis.updateCellState(state);\r\n\t\t\t\t\r\n\t\t\t\t// Repaint happens immediately after the cell is validated\r\n\t\t\t\tif (cell != this.currentRoot && !state.invalid)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.graph.cellRenderer.redraw(state, false, this.isRendering());\r\n\r\n\t\t\t\t\t// Handles changes to invertex paintbounds after update of rendering shape\r\n\t\t\t\t\tstate.updateCachedBounds();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (recurse && !state.invalid)\r\n\t\t\t{\r\n\t\t\t\t// Updates order in DOM if recursively traversing\r\n\t\t\t\tif (state.shape != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.stateValidated(state);\r\n\t\t\t\t}\r\n\t\t\t\r\n\t\t\t\tvar childCount = model.getChildCount(cell);\r\n\t\t\t\t\r\n\t\t\t\tfor (var i = 0; i < childCount; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.validateCellState(model.getChildAt(cell, i));\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn state;\r\n};\r\n\r\n/**\r\n * Function: updateCellState\r\n * \r\n * Updates the given <mxCellState>.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> to be updated.\r\n */\r\nmxGraphView.prototype.updateCellState = function(state)\r\n{\r\n\tstate.absoluteOffset.x = 0;\r\n\tstate.absoluteOffset.y = 0;\r\n\tstate.origin.x = 0;\r\n\tstate.origin.y = 0;\r\n\tstate.length = 0;\r\n\t\r\n\tif (state.cell != this.currentRoot)\r\n\t{\r\n\t\tvar model = this.graph.getModel();\r\n\t\tvar pState = this.getState(model.getParent(state.cell)); \r\n\t\t\r\n\t\tif (pState != null && pState.cell != this.currentRoot)\r\n\t\t{\r\n\t\t\tstate.origin.x += pState.origin.x;\r\n\t\t\tstate.origin.y += pState.origin.y;\r\n\t\t}\r\n\t\t\r\n\t\tvar offset = this.graph.getChildOffsetForCell(state.cell);\r\n\t\t\r\n\t\tif (offset != null)\r\n\t\t{\r\n\t\t\tstate.origin.x += offset.x;\r\n\t\t\tstate.origin.y += offset.y;\r\n\t\t}\r\n\t\t\r\n\t\tvar geo = this.graph.getCellGeometry(state.cell);\t\t\t\t\r\n\t\r\n\t\tif (geo != null)\r\n\t\t{\r\n\t\t\tif (!model.isEdge(state.cell))\r\n\t\t\t{\r\n\t\t\t\toffset = geo.offset || this.EMPTY_POINT;\r\n\t\r\n\t\t\t\tif (geo.relative && pState != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (model.isEdge(pState.cell))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar origin = this.getPoint(pState, geo);\r\n\r\n\t\t\t\t\t\tif (origin != null)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tstate.origin.x += (origin.x / this.scale) - pState.origin.x - this.translate.x;\r\n\t\t\t\t\t\t\tstate.origin.y += (origin.y / this.scale) - pState.origin.y - this.translate.y;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tstate.origin.x += geo.x * pState.width / this.scale + offset.x;\r\n\t\t\t\t\t\tstate.origin.y += geo.y * pState.height / this.scale + offset.y;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tstate.absoluteOffset.x = this.scale * offset.x;\r\n\t\t\t\t\tstate.absoluteOffset.y = this.scale * offset.y;\r\n\t\t\t\t\tstate.origin.x += geo.x;\r\n\t\t\t\t\tstate.origin.y += geo.y;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\r\n\t\t\tstate.x = this.scale * (this.translate.x + state.origin.x);\r\n\t\t\tstate.y = this.scale * (this.translate.y + state.origin.y);\r\n\t\t\tstate.width = this.scale * geo.width;\r\n\t\t\tstate.unscaledWidth = geo.width;\r\n\t\t\tstate.height = this.scale * geo.height;\r\n\t\t\t\r\n\t\t\tif (model.isVertex(state.cell))\r\n\t\t\t{\r\n\t\t\t\tthis.updateVertexState(state, geo);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (model.isEdge(state.cell))\r\n\t\t\t{\r\n\t\t\t\tthis.updateEdgeState(state, geo);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tstate.updateCachedBounds();\r\n};\r\n\r\n/**\r\n * Function: isCellCollapsed\r\n * \r\n * Returns true if the children of the given cell should not be visible in the\r\n * view. This implementation uses <mxGraph.isCellVisible> but it can be\r\n * overidden to use a separate condition.\r\n */\r\nmxGraphView.prototype.isCellCollapsed = function(cell)\r\n{\r\n\treturn this.graph.isCellCollapsed(cell);\r\n};\r\n\r\n/**\r\n * Function: updateVertexState\r\n * \r\n * Validates the given cell state.\r\n */\r\nmxGraphView.prototype.updateVertexState = function(state, geo)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\tvar pState = this.getState(model.getParent(state.cell));\r\n\t\r\n\tif (geo.relative && pState != null && !model.isEdge(pState.cell))\r\n\t{\r\n\t\tvar alpha = mxUtils.toRadians(pState.style[mxConstants.STYLE_ROTATION] || '0');\r\n\t\t\r\n\t\tif (alpha != 0)\r\n\t\t{\r\n\t\t\tvar cos = Math.cos(alpha);\r\n\t\t\tvar sin = Math.sin(alpha);\r\n\r\n\t\t\tvar ct = new mxPoint(state.getCenterX(), state.getCenterY());\r\n\t\t\tvar cx = new mxPoint(pState.getCenterX(), pState.getCenterY());\r\n\t\t\tvar pt = mxUtils.getRotatedPoint(ct, cos, sin, cx);\r\n\t\t\tstate.x = pt.x - state.width / 2;\r\n\t\t\tstate.y = pt.y - state.height / 2;\r\n\t\t}\r\n\t}\r\n\t\r\n\tthis.updateVertexLabelOffset(state);\r\n};\r\n\r\n/**\r\n * Function: updateEdgeState\r\n * \r\n * Validates the given cell state.\r\n */\r\nmxGraphView.prototype.updateEdgeState = function(state, geo)\r\n{\r\n\tvar source = state.getVisibleTerminalState(true);\r\n\tvar target = state.getVisibleTerminalState(false);\r\n\t\r\n\t// This will remove edges with no terminals and no terminal points\r\n\t// as such edges are invalid and produce NPEs in the edge styles.\r\n\t// Also removes connected edges that have no visible terminals.\r\n\tif ((this.graph.model.getTerminal(state.cell, true) != null && source == null) ||\r\n\t\t(source == null && geo.getTerminalPoint(true) == null) ||\r\n\t\t(this.graph.model.getTerminal(state.cell, false) != null && target == null) ||\r\n\t\t(target == null && geo.getTerminalPoint(false) == null))\r\n\t{\r\n\t\tthis.clear(state.cell, true);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.updateFixedTerminalPoints(state, source, target);\r\n\t\tthis.updatePoints(state, geo.points, source, target);\r\n\t\tthis.updateFloatingTerminalPoints(state, source, target);\r\n\t\t\r\n\t\tvar pts = state.absolutePoints;\r\n\t\t\r\n\t\tif (state.cell != this.currentRoot && (pts == null || pts.length < 2 ||\r\n\t\t\tpts[0] == null || pts[pts.length - 1] == null))\r\n\t\t{\r\n\t\t\t// This will remove edges with invalid points from the list of states in the view.\r\n\t\t\t// Happens if the one of the terminals and the corresponding terminal point is null.\r\n\t\t\tthis.clear(state.cell, true);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.updateEdgeBounds(state);\r\n\t\t\tthis.updateEdgeLabelOffset(state);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: updateVertexLabelOffset\r\n * \r\n * Updates the absoluteOffset of the given vertex cell state. This takes\r\n * into account the label position styles.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose absolute offset should be updated.\r\n */\r\nmxGraphView.prototype.updateVertexLabelOffset = function(state)\r\n{\r\n\tvar h = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);\r\n\r\n\tif (h == mxConstants.ALIGN_LEFT)\r\n\t{\r\n\t\tvar lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);\r\n\t\t\r\n\t\tif (lw != null)\r\n\t\t{\r\n\t\t\tlw *= this.scale;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tlw = state.width;\r\n\t\t}\r\n\t\t\r\n\t\tstate.absoluteOffset.x -= lw;\r\n\t}\r\n\telse if (h == mxConstants.ALIGN_RIGHT)\r\n\t{\r\n\t\tstate.absoluteOffset.x += state.width;\r\n\t}\r\n\telse if (h == mxConstants.ALIGN_CENTER)\r\n\t{\r\n\t\tvar lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);\r\n\t\t\r\n\t\tif (lw != null)\r\n\t\t{\r\n\t\t\t// Aligns text block with given width inside the vertex width\r\n\t\t\tvar align = mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER);\r\n\t\t\tvar dx = 0;\r\n\t\t\t\r\n\t\t\tif (align == mxConstants.ALIGN_CENTER)\r\n\t\t\t{\r\n\t\t\t\tdx = 0.5;\r\n\t\t\t}\r\n\t\t\telse if (align == mxConstants.ALIGN_RIGHT)\r\n\t\t\t{\r\n\t\t\t\tdx = 1;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (dx != 0)\r\n\t\t\t{\r\n\t\t\t\tstate.absoluteOffset.x -= (lw * this.scale - state.width) * dx;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\tvar v = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);\r\n\t\r\n\tif (v == mxConstants.ALIGN_TOP)\r\n\t{\r\n\t\tstate.absoluteOffset.y -= state.height;\r\n\t}\r\n\telse if (v == mxConstants.ALIGN_BOTTOM)\r\n\t{\r\n\t\tstate.absoluteOffset.y += state.height;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: resetValidationState\r\n *\r\n * Resets the current validation state.\r\n */\r\nmxGraphView.prototype.resetValidationState = function()\r\n{\r\n\tthis.lastNode = null;\r\n\tthis.lastHtmlNode = null;\r\n\tthis.lastForegroundNode = null;\r\n\tthis.lastForegroundHtmlNode = null;\r\n};\r\n\r\n/**\r\n * Function: stateValidated\r\n * \r\n * Invoked when a state has been processed in <validatePoints>. This is used\r\n * to update the order of the DOM nodes of the shape.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> that represents the cell state.\r\n */\r\nmxGraphView.prototype.stateValidated = function(state)\r\n{\r\n\tvar fg = (this.graph.getModel().isEdge(state.cell) && this.graph.keepEdgesInForeground) ||\r\n\t\t(this.graph.getModel().isVertex(state.cell) && this.graph.keepEdgesInBackground);\r\n\tvar htmlNode = (fg) ? this.lastForegroundHtmlNode || this.lastHtmlNode : this.lastHtmlNode;\r\n\tvar node = (fg) ? this.lastForegroundNode || this.lastNode : this.lastNode;\r\n\tvar result = this.graph.cellRenderer.insertStateAfter(state, node, htmlNode);\r\n\r\n\tif (fg)\r\n\t{\r\n\t\tthis.lastForegroundHtmlNode = result[1];\r\n\t\tthis.lastForegroundNode = result[0];\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.lastHtmlNode = result[1];\r\n\t\tthis.lastNode = result[0];\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: updateFixedTerminalPoints\r\n *\r\n * Sets the initial absolute terminal points in the given state before the edge\r\n * style is computed.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCellState> whose initial terminal points should be updated.\r\n * source - <mxCellState> which represents the source terminal.\r\n * target - <mxCellState> which represents the target terminal.\r\n */\r\nmxGraphView.prototype.updateFixedTerminalPoints = function(edge, source, target)\r\n{\r\n\tthis.updateFixedTerminalPoint(edge, source, true,\r\n\t\tthis.graph.getConnectionConstraint(edge, source, true));\r\n\tthis.updateFixedTerminalPoint(edge, target, false,\r\n\t\tthis.graph.getConnectionConstraint(edge, target, false));\r\n};\r\n\r\n/**\r\n * Function: updateFixedTerminalPoint\r\n *\r\n * Sets the fixed source or target terminal point on the given edge.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCellState> whose terminal point should be updated.\r\n * terminal - <mxCellState> which represents the actual terminal.\r\n * source - Boolean that specifies if the terminal is the source.\r\n * constraint - <mxConnectionConstraint> that specifies the connection.\r\n */\r\nmxGraphView.prototype.updateFixedTerminalPoint = function(edge, terminal, source, constraint)\r\n{\r\n\tedge.setAbsoluteTerminalPoint(this.getFixedTerminalPoint(edge, terminal, source, constraint), source);\r\n};\r\n\r\n/**\r\n * Function: getFixedTerminalPoint\r\n *\r\n * Returns the fixed source or target terminal point for the given edge.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCellState> whose terminal point should be returned.\r\n * terminal - <mxCellState> which represents the actual terminal.\r\n * source - Boolean that specifies if the terminal is the source.\r\n * constraint - <mxConnectionConstraint> that specifies the connection.\r\n */\r\nmxGraphView.prototype.getFixedTerminalPoint = function(edge, terminal, source, constraint)\r\n{\r\n\tvar pt = null;\r\n\t\r\n\tif (constraint != null)\r\n\t{\r\n\t\tpt = this.graph.getConnectionPoint(terminal, constraint);\r\n\t}\r\n\t\r\n\tif (pt == null && terminal == null)\r\n\t{\r\n\t\tvar s = this.scale;\r\n\t\tvar tr = this.translate;\r\n\t\tvar orig = edge.origin;\r\n\t\tvar geo = this.graph.getCellGeometry(edge.cell);\r\n\t\tpt = geo.getTerminalPoint(source);\r\n\t\t\r\n\t\tif (pt != null)\r\n\t\t{\r\n\t\t\tpt = new mxPoint(s * (tr.x + pt.x + orig.x),\r\n\t\t\t\t\t\t\t s * (tr.y + pt.y + orig.y));\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn pt;\r\n};\r\n\r\n/**\r\n * Function: updateBoundsFromStencil\r\n * \r\n * Updates the bounds of the given cell state to reflect the bounds of the stencil\r\n * if it has a fixed aspect and returns the previous bounds as an <mxRectangle> if\r\n * the bounds have been modified or null otherwise.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCellState> whose bounds should be updated.\r\n */\r\nmxGraphView.prototype.updateBoundsFromStencil = function(state)\r\n{\r\n\tvar previous = null;\r\n\t\r\n\tif (state != null && state.shape != null && state.shape.stencil != null && state.shape.stencil.aspect == 'fixed')\r\n\t{\r\n\t\tprevious = mxRectangle.fromRectangle(state);\r\n\t\tvar asp = state.shape.stencil.computeAspect(state.style, state.x, state.y, state.width, state.height);\r\n\t\tstate.setRect(asp.x, asp.y, state.shape.stencil.w0 * asp.width, state.shape.stencil.h0 * asp.height);\r\n\t}\r\n\t\r\n\treturn previous;\r\n};\r\n\r\n/**\r\n * Function: updatePoints\r\n *\r\n * Updates the absolute points in the given state using the specified array\r\n * of <mxPoints> as the relative points.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCellState> whose absolute points should be updated.\r\n * points - Array of <mxPoints> that constitute the relative points.\r\n * source - <mxCellState> that represents the source terminal.\r\n * target - <mxCellState> that represents the target terminal.\r\n */\r\nmxGraphView.prototype.updatePoints = function(edge, points, source, target)\r\n{\r\n\tif (edge != null)\r\n\t{\r\n\t\tvar pts = [];\r\n\t\tpts.push(edge.absolutePoints[0]);\r\n\t\tvar edgeStyle = this.getEdgeStyle(edge, points, source, target);\r\n\t\t\r\n\t\tif (edgeStyle != null)\r\n\t\t{\r\n\t\t\tvar src = this.getTerminalPort(edge, source, true);\r\n\t\t\tvar trg = this.getTerminalPort(edge, target, false);\r\n\t\t\t\r\n\t\t\t// Uses the stencil bounds for routing and restores after routing\r\n\t\t\tvar srcBounds = this.updateBoundsFromStencil(src);\r\n\t\t\tvar trgBounds = this.updateBoundsFromStencil(trg);\r\n\r\n\t\t\tedgeStyle(edge, src, trg, points, pts);\r\n\t\t\t\r\n\t\t\t// Restores previous bounds\r\n\t\t\tif (srcBounds != null)\r\n\t\t\t{\r\n\t\t\t\tsrc.setRect(srcBounds.x, srcBounds.y, srcBounds.width, srcBounds.height);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (trgBounds != null)\r\n\t\t\t{\r\n\t\t\t\ttrg.setRect(trgBounds.x, trgBounds.y, trgBounds.width, trgBounds.height);\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (points != null)\r\n\t\t{\r\n\t\t\tfor (var i = 0; i < points.length; i++)\r\n\t\t\t{\r\n\t\t\t\tif (points[i] != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar pt = mxUtils.clone(points[i]);\r\n\t\t\t\t\tpts.push(this.transformControlPoint(edge, pt));\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tvar tmp = edge.absolutePoints;\r\n\t\tpts.push(tmp[tmp.length-1]);\r\n\r\n\t\tedge.absolutePoints = pts;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: transformControlPoint\r\n *\r\n * Transforms the given control point to an absolute point.\r\n */\r\nmxGraphView.prototype.transformControlPoint = function(state, pt)\r\n{\r\n\tif (state != null && pt != null)\r\n\t{\r\n\t\tvar orig = state.origin;\r\n\t\t\r\n\t    return new mxPoint(this.scale * (pt.x + this.translate.x + orig.x),\r\n\t    \tthis.scale * (pt.y + this.translate.y + orig.y));\r\n\t}\r\n\t\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: isLoopStyleEnabled\r\n * \r\n * Returns true if the given edge should be routed with <mxGraph.defaultLoopStyle>\r\n * or the <mxConstants.STYLE_LOOP> defined for the given edge. This implementation\r\n * returns true if the given edge is a loop and does not have connections constraints\r\n * associated.\r\n */\r\nmxGraphView.prototype.isLoopStyleEnabled = function(edge, points, source, target)\r\n{\r\n\tvar sc = this.graph.getConnectionConstraint(edge, source, true);\r\n\tvar tc = this.graph.getConnectionConstraint(edge, target, false);\r\n\t\r\n\tif ((points == null || points.length < 2) &&\r\n\t\t(!mxUtils.getValue(edge.style, mxConstants.STYLE_ORTHOGONAL_LOOP, false) ||\r\n\t\t((sc == null || sc.point == null) && (tc == null || tc.point == null))))\r\n\t{\r\n\t\treturn source != null && source == target;\r\n\t}\r\n\t\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: getEdgeStyle\r\n * \r\n * Returns the edge style function to be used to render the given edge state.\r\n */\r\nmxGraphView.prototype.getEdgeStyle = function(edge, points, source, target)\r\n{\r\n\tvar edgeStyle = this.isLoopStyleEnabled(edge, points, source, target) ?\r\n\t\tmxUtils.getValue(edge.style, mxConstants.STYLE_LOOP, this.graph.defaultLoopStyle) :\r\n\t\t(!mxUtils.getValue(edge.style, mxConstants.STYLE_NOEDGESTYLE, false) ?\r\n\t\tedge.style[mxConstants.STYLE_EDGE] : null);\r\n\r\n\t// Converts string values to objects\r\n\tif (typeof(edgeStyle) == \"string\")\r\n\t{\r\n\t\tvar tmp = mxStyleRegistry.getValue(edgeStyle);\r\n\t\t\r\n\t\tif (tmp == null && this.isAllowEval())\r\n\t\t{\r\n \t\t\ttmp = mxUtils.eval(edgeStyle);\r\n\t\t}\r\n\t\t\r\n\t\tedgeStyle = tmp;\r\n\t}\r\n\t\r\n\tif (typeof(edgeStyle) == \"function\")\r\n\t{\r\n\t\treturn edgeStyle;\r\n\t}\r\n\t\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: updateFloatingTerminalPoints\r\n *\r\n * Updates the terminal points in the given state after the edge style was\r\n * computed for the edge.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose terminal points should be updated.\r\n * source - <mxCellState> that represents the source terminal.\r\n * target - <mxCellState> that represents the target terminal.\r\n */\r\nmxGraphView.prototype.updateFloatingTerminalPoints = function(state, source, target)\r\n{\r\n\tvar pts = state.absolutePoints;\r\n\tvar p0 = pts[0];\r\n\tvar pe = pts[pts.length - 1];\r\n\r\n\tif (pe == null && target != null)\r\n\t{\r\n\t\tthis.updateFloatingTerminalPoint(state, target, source, false);\r\n\t}\r\n\t\r\n\tif (p0 == null && source != null)\r\n\t{\r\n\t\tthis.updateFloatingTerminalPoint(state, source, target, true);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: updateFloatingTerminalPoint\r\n *\r\n * Updates the absolute terminal point in the given state for the given\r\n * start and end state, where start is the source if source is true.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCellState> whose terminal point should be updated.\r\n * start - <mxCellState> for the terminal on \"this\" side of the edge.\r\n * end - <mxCellState> for the terminal on the other side of the edge.\r\n * source - Boolean indicating if start is the source terminal state.\r\n */\r\nmxGraphView.prototype.updateFloatingTerminalPoint = function(edge, start, end, source)\r\n{\r\n\tedge.setAbsoluteTerminalPoint(this.getFloatingTerminalPoint(edge, start, end, source), source);\r\n};\r\n\r\n/**\r\n * Function: getFloatingTerminalPoint\r\n * \r\n * Returns the floating terminal point for the given edge, start and end\r\n * state, where start is the source if source is true.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCellState> whose terminal point should be returned.\r\n * start - <mxCellState> for the terminal on \"this\" side of the edge.\r\n * end - <mxCellState> for the terminal on the other side of the edge.\r\n * source - Boolean indicating if start is the source terminal state.\r\n */\r\nmxGraphView.prototype.getFloatingTerminalPoint = function(edge, start, end, source)\r\n{\r\n\tstart = this.getTerminalPort(edge, start, source);\r\n\tvar next = this.getNextPoint(edge, end, source);\r\n\t\r\n\tvar orth = this.graph.isOrthogonal(edge);\r\n\tvar alpha = mxUtils.toRadians(Number(start.style[mxConstants.STYLE_ROTATION] || '0'));\r\n\tvar center = new mxPoint(start.getCenterX(), start.getCenterY());\r\n\t\r\n\tif (alpha != 0)\r\n\t{\r\n\t\tvar cos = Math.cos(-alpha);\r\n\t\tvar sin = Math.sin(-alpha);\r\n\t\tnext = mxUtils.getRotatedPoint(next, cos, sin, center);\r\n\t}\r\n\t\r\n\tvar border = parseFloat(edge.style[mxConstants.STYLE_PERIMETER_SPACING] || 0);\r\n\tborder += parseFloat(edge.style[(source) ?\r\n\t\tmxConstants.STYLE_SOURCE_PERIMETER_SPACING :\r\n\t\tmxConstants.STYLE_TARGET_PERIMETER_SPACING] || 0);\r\n\tvar pt = this.getPerimeterPoint(start, next, alpha == 0 && orth, border);\r\n\r\n\tif (alpha != 0)\r\n\t{\r\n\t\tvar cos = Math.cos(alpha);\r\n\t\tvar sin = Math.sin(alpha);\r\n\t\tpt = mxUtils.getRotatedPoint(pt, cos, sin, center);\r\n\t}\r\n\t\r\n\treturn pt;\r\n};\r\n\r\n/**\r\n * Function: getTerminalPort\r\n * \r\n * Returns an <mxCellState> that represents the source or target terminal or\r\n * port for the given edge.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> that represents the state of the edge.\r\n * terminal - <mxCellState> that represents the terminal.\r\n * source - Boolean indicating if the given terminal is the source terminal.\r\n */\r\nmxGraphView.prototype.getTerminalPort = function(state, terminal, source)\r\n{\r\n\tvar key = (source) ? mxConstants.STYLE_SOURCE_PORT :\r\n\t\tmxConstants.STYLE_TARGET_PORT;\r\n\tvar id = mxUtils.getValue(state.style, key);\r\n\t\r\n\tif (id != null)\r\n\t{\r\n\t\tvar tmp = this.getState(this.graph.getModel().getCell(id));\r\n\t\t\r\n\t\t// Only uses ports where a cell state exists\r\n\t\tif (tmp != null)\r\n\t\t{\r\n\t\t\tterminal = tmp;\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn terminal;\r\n};\r\n\r\n/**\r\n * Function: getPerimeterPoint\r\n *\r\n * Returns an <mxPoint> that defines the location of the intersection point between\r\n * the perimeter and the line between the center of the shape and the given point.\r\n * \r\n * Parameters:\r\n * \r\n * terminal - <mxCellState> for the source or target terminal.\r\n * next - <mxPoint> that lies outside of the given terminal.\r\n * orthogonal - Boolean that specifies if the orthogonal projection onto\r\n * the perimeter should be returned. If this is false then the intersection\r\n * of the perimeter and the line between the next and the center point is\r\n * returned.\r\n * border - Optional border between the perimeter and the shape.\r\n */\r\nmxGraphView.prototype.getPerimeterPoint = function(terminal, next, orthogonal, border)\r\n{\r\n\tvar point = null;\r\n\t\r\n\tif (terminal != null)\r\n\t{\r\n\t\tvar perimeter = this.getPerimeterFunction(terminal);\r\n\t\t\r\n\t\tif (perimeter != null && next != null)\r\n\t\t{\r\n\t\t\tvar bounds = this.getPerimeterBounds(terminal, border);\r\n\r\n\t\t\tif (bounds.width > 0 || bounds.height > 0)\r\n\t\t\t{\r\n\t\t\t\tpoint = new mxPoint(next.x, next.y);\r\n\t\t\t\tvar flipH = false;\r\n\t\t\t\tvar flipV = false;\t\r\n\t\t\t\t\r\n\t\t\t\tif (this.graph.model.isVertex(terminal.cell))\r\n\t\t\t\t{\r\n\t\t\t\t\tflipH = mxUtils.getValue(terminal.style, mxConstants.STYLE_FLIPH, 0) == 1;\r\n\t\t\t\t\tflipV = mxUtils.getValue(terminal.style, mxConstants.STYLE_FLIPV, 0) == 1;\t\r\n\t\r\n\t\t\t\t\t// Legacy support for stencilFlipH/V\r\n\t\t\t\t\tif (terminal.shape != null && terminal.shape.stencil != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tflipH = (mxUtils.getValue(terminal.style, 'stencilFlipH', 0) == 1) || flipH;\r\n\t\t\t\t\t\tflipV = (mxUtils.getValue(terminal.style, 'stencilFlipV', 0) == 1) || flipV;\r\n\t\t\t\t\t}\r\n\t\r\n\t\t\t\t\tif (flipH)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tpoint.x = 2 * bounds.getCenterX() - point.x;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (flipV)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tpoint.y = 2 * bounds.getCenterY() - point.y;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tpoint = perimeter(bounds, terminal, point, orthogonal);\r\n\r\n\t\t\t\tif (point != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (flipH)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tpoint.x = 2 * bounds.getCenterX() - point.x;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (flipV)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tpoint.y = 2 * bounds.getCenterY() - point.y;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (point == null)\r\n\t\t{\r\n\t\t\tpoint = this.getPoint(terminal);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn point;\r\n};\r\n\r\n/**\r\n * Function: getRoutingCenterX\r\n * \r\n * Returns the x-coordinate of the center point for automatic routing.\r\n */\r\nmxGraphView.prototype.getRoutingCenterX = function (state)\r\n{\r\n\tvar f = (state.style != null) ? parseFloat(state.style\r\n\t\t[mxConstants.STYLE_ROUTING_CENTER_X]) || 0 : 0;\r\n\r\n\treturn state.getCenterX() + f * state.width;\r\n};\r\n\r\n/**\r\n * Function: getRoutingCenterY\r\n * \r\n * Returns the y-coordinate of the center point for automatic routing.\r\n */\r\nmxGraphView.prototype.getRoutingCenterY = function (state)\r\n{\r\n\tvar f = (state.style != null) ? parseFloat(state.style\r\n\t\t[mxConstants.STYLE_ROUTING_CENTER_Y]) || 0 : 0;\r\n\r\n\treturn state.getCenterY() + f * state.height;\r\n};\r\n\r\n/**\r\n * Function: getPerimeterBounds\r\n *\r\n * Returns the perimeter bounds for the given terminal, edge pair as an\r\n * <mxRectangle>.\r\n * \r\n * If you have a model where each terminal has a relative child that should\r\n * act as the graphical endpoint for a connection from/to the terminal, then\r\n * this method can be replaced as follows:\r\n * \r\n * (code)\r\n * var oldGetPerimeterBounds = mxGraphView.prototype.getPerimeterBounds;\r\n * mxGraphView.prototype.getPerimeterBounds = function(terminal, edge, isSource)\r\n * {\r\n *   var model = this.graph.getModel();\r\n *   var childCount = model.getChildCount(terminal.cell);\r\n * \r\n *   if (childCount > 0)\r\n *   {\r\n *     var child = model.getChildAt(terminal.cell, 0);\r\n *     var geo = model.getGeometry(child);\r\n *\r\n *     if (geo != null &&\r\n *         geo.relative)\r\n *     {\r\n *       var state = this.getState(child);\r\n *       \r\n *       if (state != null)\r\n *       {\r\n *         terminal = state;\r\n *       }\r\n *     }\r\n *   }\r\n *   \r\n *   return oldGetPerimeterBounds.apply(this, arguments);\r\n * };\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * terminal - <mxCellState> that represents the terminal.\r\n * border - Number that adds a border between the shape and the perimeter.\r\n */\r\nmxGraphView.prototype.getPerimeterBounds = function(terminal, border)\r\n{\r\n\tborder = (border != null) ? border : 0;\r\n\r\n\tif (terminal != null)\r\n\t{\r\n\t\tborder += parseFloat(terminal.style[mxConstants.STYLE_PERIMETER_SPACING] || 0);\r\n\t}\r\n\r\n\treturn terminal.getPerimeterBounds(border * this.scale);\r\n};\r\n\r\n/**\r\n * Function: getPerimeterFunction\r\n *\r\n * Returns the perimeter function for the given state.\r\n */\r\nmxGraphView.prototype.getPerimeterFunction = function(state)\r\n{\r\n\tvar perimeter = state.style[mxConstants.STYLE_PERIMETER];\r\n\r\n\t// Converts string values to objects\r\n\tif (typeof(perimeter) == \"string\")\r\n\t{\r\n\t\tvar tmp = mxStyleRegistry.getValue(perimeter);\r\n\t\t\r\n\t\tif (tmp == null && this.isAllowEval())\r\n\t\t{\r\n \t\t\ttmp = mxUtils.eval(perimeter);\r\n\t\t}\r\n\r\n\t\tperimeter = tmp;\r\n\t}\r\n\t\r\n\tif (typeof(perimeter) == \"function\")\r\n\t{\r\n\t\treturn perimeter;\r\n\t}\r\n\t\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: getNextPoint\r\n *\r\n * Returns the nearest point in the list of absolute points or the center\r\n * of the opposite terminal.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCellState> that represents the edge.\r\n * opposite - <mxCellState> that represents the opposite terminal.\r\n * source - Boolean indicating if the next point for the source or target\r\n * should be returned.\r\n */\r\nmxGraphView.prototype.getNextPoint = function(edge, opposite, source)\r\n{\r\n\tvar pts = edge.absolutePoints;\r\n\tvar point = null;\r\n\t\r\n\tif (pts != null && pts.length >= 2)\r\n\t{\r\n\t\tvar count = pts.length;\r\n\t\tpoint = pts[(source) ? Math.min(1, count - 1) : Math.max(0, count - 2)];\r\n\t}\r\n\t\r\n\tif (point == null && opposite != null)\r\n\t{\r\n\t\tpoint = new mxPoint(opposite.getCenterX(), opposite.getCenterY());\r\n\t}\r\n\t\r\n\treturn point;\r\n};\r\n\r\n/**\r\n * Function: getVisibleTerminal\r\n *\r\n * Returns the nearest ancestor terminal that is visible. The edge appears\r\n * to be connected to this terminal on the display. The result of this method\r\n * is cached in <mxCellState.getVisibleTerminalState>.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCell> whose visible terminal should be returned.\r\n * source - Boolean that specifies if the source or target terminal\r\n * should be returned.\r\n */\r\nmxGraphView.prototype.getVisibleTerminal = function(edge, source)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\tvar result = model.getTerminal(edge, source);\r\n\tvar best = result;\r\n\t\r\n\twhile (result != null && result != this.currentRoot)\r\n\t{\r\n\t\tif (!this.graph.isCellVisible(best) || this.isCellCollapsed(result))\r\n\t\t{\r\n\t\t\tbest = result;\r\n\t\t}\r\n\t\t\r\n\t\tresult = model.getParent(result);\r\n\t}\r\n\r\n\t// Checks if the result is valid for the current view state\r\n\tif (best != null && (!model.contains(best) ||\r\n\t\tmodel.getParent(best) == model.getRoot() ||\r\n\t\tbest == this.currentRoot))\r\n\t{\r\n\t\tbest = null;\r\n\t}\r\n\t\r\n\treturn best;\r\n};\r\n\r\n/**\r\n * Function: updateEdgeBounds\r\n *\r\n * Updates the given state using the bounding box of t\r\n * he absolute points.\r\n * Also updates <mxCellState.terminalDistance>, <mxCellState.length> and\r\n * <mxCellState.segments>.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose bounds should be updated.\r\n */\r\nmxGraphView.prototype.updateEdgeBounds = function(state)\r\n{\r\n\tvar points = state.absolutePoints;\r\n\tvar p0 = points[0];\r\n\tvar pe = points[points.length - 1];\r\n\t\r\n\tif (p0.x != pe.x || p0.y != pe.y)\r\n\t{\r\n\t\tvar dx = pe.x - p0.x;\r\n\t\tvar dy = pe.y - p0.y;\r\n\t\tstate.terminalDistance = Math.sqrt(dx * dx + dy * dy);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tstate.terminalDistance = 0;\r\n\t}\r\n\t\r\n\tvar length = 0;\r\n\tvar segments = [];\r\n\tvar pt = p0;\r\n\t\r\n\tif (pt != null)\r\n\t{\r\n\t\tvar minX = pt.x;\r\n\t\tvar minY = pt.y;\r\n\t\tvar maxX = minX;\r\n\t\tvar maxY = minY;\r\n\t\t\r\n\t\tfor (var i = 1; i < points.length; i++)\r\n\t\t{\r\n\t\t\tvar tmp = points[i];\r\n\t\t\t\r\n\t\t\tif (tmp != null)\r\n\t\t\t{\r\n\t\t\t\tvar dx = pt.x - tmp.x;\r\n\t\t\t\tvar dy = pt.y - tmp.y;\r\n\t\t\t\t\r\n\t\t\t\tvar segment = Math.sqrt(dx * dx + dy * dy);\r\n\t\t\t\tsegments.push(segment);\r\n\t\t\t\tlength += segment;\r\n\t\t\t\t\r\n\t\t\t\tpt = tmp;\r\n\t\t\t\t\r\n\t\t\t\tminX = Math.min(pt.x, minX);\r\n\t\t\t\tminY = Math.min(pt.y, minY);\r\n\t\t\t\tmaxX = Math.max(pt.x, maxX);\r\n\t\t\t\tmaxY = Math.max(pt.y, maxY);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tstate.length = length;\r\n\t\tstate.segments = segments;\r\n\t\t\r\n\t\tvar markerSize = 1; // TODO: include marker size\r\n\t\t\r\n\t\tstate.x = minX;\r\n\t\tstate.y = minY;\r\n\t\tstate.width = Math.max(markerSize, maxX - minX);\r\n\t\tstate.height = Math.max(markerSize, maxY - minY);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getPoint\r\n *\r\n * Returns the absolute point on the edge for the given relative\r\n * <mxGeometry> as an <mxPoint>. The edge is represented by the given\r\n * <mxCellState>.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> that represents the state of the parent edge.\r\n * geometry - <mxGeometry> that represents the relative location.\r\n */\r\nmxGraphView.prototype.getPoint = function(state, geometry)\r\n{\r\n\tvar x = state.getCenterX();\r\n\tvar y = state.getCenterY();\r\n\t\r\n\tif (state.segments != null && (geometry == null || geometry.relative))\r\n\t{\r\n\t\tvar gx = (geometry != null) ? geometry.x / 2 : 0;\r\n\t\tvar pointCount = state.absolutePoints.length;\r\n\t\tvar dist = Math.round((gx + 0.5) * state.length);\r\n\t\tvar segment = state.segments[0];\r\n\t\tvar length = 0;\t\t\t\t\r\n\t\tvar index = 1;\r\n\r\n\t\twhile (dist >= Math.round(length + segment) && index < pointCount - 1)\r\n\t\t{\r\n\t\t\tlength += segment;\r\n\t\t\tsegment = state.segments[index++];\r\n\t\t}\r\n\r\n\t\tvar factor = (segment == 0) ? 0 : (dist - length) / segment;\r\n\t\tvar p0 = state.absolutePoints[index-1];\r\n\t\tvar pe = state.absolutePoints[index];\r\n\r\n\t\tif (p0 != null && pe != null)\r\n\t\t{\r\n\t\t\tvar gy = 0;\r\n\t\t\tvar offsetX = 0;\r\n\t\t\tvar offsetY = 0;\r\n\r\n\t\t\tif (geometry != null)\r\n\t\t\t{\r\n\t\t\t\tgy = geometry.y;\r\n\t\t\t\tvar offset = geometry.offset;\r\n\t\t\t\t\r\n\t\t\t\tif (offset != null)\r\n\t\t\t\t{\r\n\t\t\t\t\toffsetX = offset.x;\r\n\t\t\t\t\toffsetY = offset.y;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tvar dx = pe.x - p0.x;\r\n\t\t\tvar dy = pe.y - p0.y;\r\n\t\t\tvar nx = (segment == 0) ? 0 : dy / segment;\r\n\t\t\tvar ny = (segment == 0) ? 0 : dx / segment;\r\n\t\t\t\r\n\t\t\tx = p0.x + dx * factor + (nx * gy + offsetX) * this.scale;\r\n\t\t\ty = p0.y + dy * factor - (ny * gy - offsetY) * this.scale;\r\n\t\t}\r\n\t}\r\n\telse if (geometry != null)\r\n\t{\r\n\t\tvar offset = geometry.offset;\r\n\t\t\r\n\t\tif (offset != null)\r\n\t\t{\r\n\t\t\tx += offset.x;\r\n\t\t\ty += offset.y;\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn new mxPoint(x, y);\t\t\r\n};\r\n\r\n/**\r\n * Function: getRelativePoint\r\n *\r\n * Gets the relative point that describes the given, absolute label\r\n * position for the given edge state.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> that represents the state of the parent edge.\r\n * x - Specifies the x-coordinate of the absolute label location.\r\n * y - Specifies the y-coordinate of the absolute label location.\r\n */\r\nmxGraphView.prototype.getRelativePoint = function(edgeState, x, y)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\tvar geometry = model.getGeometry(edgeState.cell);\r\n\t\r\n\tif (geometry != null)\r\n\t{\r\n\t\tvar pointCount = edgeState.absolutePoints.length;\r\n\t\t\r\n\t\tif (geometry.relative && pointCount > 1)\r\n\t\t{\r\n\t\t\tvar totalLength = edgeState.length;\r\n\t\t\tvar segments = edgeState.segments;\r\n\r\n\t\t\t// Works which line segment the point of the label is closest to\r\n\t\t\tvar p0 = edgeState.absolutePoints[0];\r\n\t\t\tvar pe = edgeState.absolutePoints[1];\r\n\t\t\tvar minDist = mxUtils.ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y);\r\n\r\n\t\t\tvar index = 0;\r\n\t\t\tvar tmp = 0;\r\n\t\t\tvar length = 0;\r\n\t\t\t\r\n\t\t\tfor (var i = 2; i < pointCount; i++)\r\n\t\t\t{\r\n\t\t\t\ttmp += segments[i - 2];\r\n\t\t\t\tpe = edgeState.absolutePoints[i];\r\n\t\t\t\tvar dist = mxUtils.ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y);\r\n\r\n\t\t\t\tif (dist <= minDist)\r\n\t\t\t\t{\r\n\t\t\t\t\tminDist = dist;\r\n\t\t\t\t\tindex = i - 1;\r\n\t\t\t\t\tlength = tmp;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tp0 = pe;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar seg = segments[index];\r\n\t\t\tp0 = edgeState.absolutePoints[index];\r\n\t\t\tpe = edgeState.absolutePoints[index + 1];\r\n\t\t\t\r\n\t\t\tvar x2 = p0.x;\r\n\t\t\tvar y2 = p0.y;\r\n\t\t\t\r\n\t\t\tvar x1 = pe.x;\r\n\t\t\tvar y1 = pe.y;\r\n\t\t\t\r\n\t\t\tvar px = x;\r\n\t\t\tvar py = y;\r\n\t\t\t\r\n\t\t\tvar xSegment = x2 - x1;\r\n\t\t\tvar ySegment = y2 - y1;\r\n\t\t\t\r\n\t\t\tpx -= x1;\r\n\t\t\tpy -= y1;\r\n\t\t\tvar projlenSq = 0;\r\n\t\t\t\r\n\t\t\tpx = xSegment - px;\r\n\t\t\tpy = ySegment - py;\r\n\t\t\tvar dotprod = px * xSegment + py * ySegment;\r\n\r\n\t\t\tif (dotprod <= 0.0)\r\n\t\t\t{\r\n\t\t\t\tprojlenSq = 0;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tprojlenSq = dotprod * dotprod\r\n\t\t\t\t\t\t/ (xSegment * xSegment + ySegment * ySegment);\r\n\t\t\t}\r\n\r\n\t\t\tvar projlen = Math.sqrt(projlenSq);\r\n\r\n\t\t\tif (projlen > seg)\r\n\t\t\t{\r\n\t\t\t\tprojlen = seg;\r\n\t\t\t}\r\n\r\n\t\t\tvar yDistance = Math.sqrt(mxUtils.ptSegDistSq(p0.x, p0.y, pe\r\n\t\t\t\t\t.x, pe.y, x, y));\r\n\t\t\tvar direction = mxUtils.relativeCcw(p0.x, p0.y, pe.x, pe.y, x, y);\r\n\r\n\t\t\tif (direction == -1)\r\n\t\t\t{\r\n\t\t\t\tyDistance = -yDistance;\r\n\t\t\t}\r\n\r\n\t\t\t// Constructs the relative point for the label\r\n\t\t\treturn new mxPoint(((totalLength / 2 - length - projlen) / totalLength) * -2,\r\n\t\t\t\t\t\tyDistance / this.scale);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn new mxPoint();\r\n};\r\n\r\n/**\r\n * Function: updateEdgeLabelOffset\r\n *\r\n * Updates <mxCellState.absoluteOffset> for the given state. The absolute\r\n * offset is normally used for the position of the edge label. Is is\r\n * calculated from the geometry as an absolute offset from the center\r\n * between the two endpoints if the geometry is absolute, or as the\r\n * relative distance between the center along the line and the absolute\r\n * orthogonal distance if the geometry is relative.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose absolute offset should be updated.\r\n */\r\nmxGraphView.prototype.updateEdgeLabelOffset = function(state)\r\n{\r\n\tvar points = state.absolutePoints;\r\n\t\r\n\tstate.absoluteOffset.x = state.getCenterX();\r\n\tstate.absoluteOffset.y = state.getCenterY();\r\n\r\n\tif (points != null && points.length > 0 && state.segments != null)\r\n\t{\r\n\t\tvar geometry = this.graph.getCellGeometry(state.cell);\r\n\t\t\r\n\t\tif (geometry.relative)\r\n\t\t{\r\n\t\t\tvar offset = this.getPoint(state, geometry);\r\n\t\t\t\r\n\t\t\tif (offset != null)\r\n\t\t\t{\r\n\t\t\t\tstate.absoluteOffset = offset;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar p0 = points[0];\r\n\t\t\tvar pe = points[points.length - 1];\r\n\t\t\t\r\n\t\t\tif (p0 != null && pe != null)\r\n\t\t\t{\r\n\t\t\t\tvar dx = pe.x - p0.x;\r\n\t\t\t\tvar dy = pe.y - p0.y;\r\n\t\t\t\tvar x0 = 0;\r\n\t\t\t\tvar y0 = 0;\r\n\r\n\t\t\t\tvar off = geometry.offset;\r\n\t\t\t\t\r\n\t\t\t\tif (off != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tx0 = off.x;\r\n\t\t\t\t\ty0 = off.y;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tvar x = p0.x + dx / 2 + x0 * this.scale;\r\n\t\t\t\tvar y = p0.y + dy / 2 + y0 * this.scale;\r\n\t\t\t\t\r\n\t\t\t\tstate.absoluteOffset.x = x;\r\n\t\t\t\tstate.absoluteOffset.y = y;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getState\r\n *\r\n * Returns the <mxCellState> for the given cell. If create is true, then\r\n * the state is created if it does not yet exist.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> for which the <mxCellState> should be returned.\r\n * create - Optional boolean indicating if a new state should be created\r\n * if it does not yet exist. Default is false.\r\n */\r\nmxGraphView.prototype.getState = function(cell, create)\r\n{\r\n\tcreate = create || false;\r\n\tvar state = null;\r\n\t\r\n\tif (cell != null)\r\n\t{\r\n\t\tstate = this.states.get(cell);\r\n\t\t\r\n\t\tif (create && (state == null || this.updateStyle) && this.graph.isCellVisible(cell))\r\n\t\t{\r\n\t\t\tif (state == null)\r\n\t\t\t{\r\n\t\t\t\tstate = this.createState(cell);\r\n\t\t\t\tthis.states.put(cell, state);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tstate.style = this.graph.getCellStyle(cell);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn state;\r\n};\r\n\r\n/**\r\n * Function: isRendering\r\n *\r\n * Returns <rendering>.\r\n */\r\nmxGraphView.prototype.isRendering = function()\r\n{\r\n\treturn this.rendering;\r\n};\r\n\r\n/**\r\n * Function: setRendering\r\n *\r\n * Sets <rendering>.\r\n */\r\nmxGraphView.prototype.setRendering = function(value)\r\n{\r\n\tthis.rendering = value;\r\n};\r\n\r\n/**\r\n * Function: isAllowEval\r\n *\r\n * Returns <allowEval>.\r\n */\r\nmxGraphView.prototype.isAllowEval = function()\r\n{\r\n\treturn this.allowEval;\r\n};\r\n\r\n/**\r\n * Function: setAllowEval\r\n *\r\n * Sets <allowEval>.\r\n */\r\nmxGraphView.prototype.setAllowEval = function(value)\r\n{\r\n\tthis.allowEval = value;\r\n};\r\n\r\n/**\r\n * Function: getStates\r\n *\r\n * Returns <states>.\r\n */\r\nmxGraphView.prototype.getStates = function()\r\n{\r\n\treturn this.states;\r\n};\r\n\r\n/**\r\n * Function: setStates\r\n *\r\n * Sets <states>.\r\n */\r\nmxGraphView.prototype.setStates = function(value)\r\n{\r\n\tthis.states = value;\r\n};\r\n\r\n/**\r\n * Function: getCellStates\r\n *\r\n * Returns the <mxCellStates> for the given array of <mxCells>. The array\r\n * contains all states that are not null, that is, the returned array may\r\n * have less elements than the given array. If no argument is given, then\r\n * this returns <states>.\r\n */\r\nmxGraphView.prototype.getCellStates = function(cells)\r\n{\r\n\tif (cells == null)\r\n\t{\r\n\t\treturn this.states;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar result = [];\r\n\t\t\r\n\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t{\r\n\t\t\tvar state = this.getState(cells[i]);\r\n\t\t\t\r\n\t\t\tif (state != null)\r\n\t\t\t{\r\n\t\t\t\tresult.push(state);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn result;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: removeState\r\n *\r\n * Removes and returns the <mxCellState> for the given cell.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> for which the <mxCellState> should be removed.\r\n */\r\nmxGraphView.prototype.removeState = function(cell)\r\n{\r\n\tvar state = null;\r\n\t\r\n\tif (cell != null)\r\n\t{\r\n\t\tstate = this.states.remove(cell);\r\n\t\t\r\n\t\tif (state != null)\r\n\t\t{\r\n\t\t\tthis.graph.cellRenderer.destroy(state);\r\n\t\t\tstate.invalid = true;\r\n\t\t\tstate.destroy();\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn state;\r\n};\r\n\r\n/**\r\n * Function: createState\r\n *\r\n * Creates and returns an <mxCellState> for the given cell and initializes\r\n * it using <mxCellRenderer.initialize>.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> for which a new <mxCellState> should be created.\r\n */\r\nmxGraphView.prototype.createState = function(cell)\r\n{\r\n\treturn new mxCellState(this, cell, this.graph.getCellStyle(cell));\r\n};\r\n\r\n/**\r\n * Function: getCanvas\r\n *\r\n * Returns the DOM node that contains the background-, draw- and\r\n * overlay- and decoratorpanes.\r\n */\r\nmxGraphView.prototype.getCanvas = function()\r\n{\r\n\treturn this.canvas;\r\n};\r\n\r\n/**\r\n * Function: getBackgroundPane\r\n *\r\n * Returns the DOM node that represents the background layer.\r\n */\r\nmxGraphView.prototype.getBackgroundPane = function()\r\n{\r\n\treturn this.backgroundPane;\r\n};\r\n\r\n/**\r\n * Function: getDrawPane\r\n *\r\n * Returns the DOM node that represents the main drawing layer.\r\n */\r\nmxGraphView.prototype.getDrawPane = function()\r\n{\r\n\treturn this.drawPane;\r\n};\r\n\r\n/**\r\n * Function: getOverlayPane\r\n *\r\n * Returns the DOM node that represents the layer above the drawing layer.\r\n */\r\nmxGraphView.prototype.getOverlayPane = function()\r\n{\r\n\treturn this.overlayPane;\r\n};\r\n\r\n/**\r\n * Function: getDecoratorPane\r\n *\r\n * Returns the DOM node that represents the topmost drawing layer.\r\n */\r\nmxGraphView.prototype.getDecoratorPane = function()\r\n{\r\n\treturn this.decoratorPane;\r\n};\r\n\r\n/**\r\n * Function: isContainerEvent\r\n * \r\n * Returns true if the event origin is one of the drawing panes or\r\n * containers of the view.\r\n */\r\nmxGraphView.prototype.isContainerEvent = function(evt)\r\n{\r\n\tvar source = mxEvent.getSource(evt);\r\n\r\n\treturn (source == this.graph.container ||\r\n\t\tsource.parentNode == this.backgroundPane ||\r\n\t\t(source.parentNode != null &&\r\n\t\tsource.parentNode.parentNode == this.backgroundPane) ||\r\n\t\tsource == this.canvas.parentNode ||\r\n\t\tsource == this.canvas ||\r\n\t\tsource == this.backgroundPane ||\r\n\t\tsource == this.drawPane ||\r\n\t\tsource == this.overlayPane ||\r\n\t\tsource == this.decoratorPane);\r\n};\r\n\r\n/**\r\n * Function: isScrollEvent\r\n * \r\n * Returns true if the event origin is one of the scrollbars of the\r\n * container in IE. Such events are ignored.\r\n */\r\n mxGraphView.prototype.isScrollEvent = function(evt)\r\n{\r\n\tvar offset = mxUtils.getOffset(this.graph.container);\r\n\tvar pt = new mxPoint(evt.clientX - offset.x, evt.clientY - offset.y);\r\n\r\n\tvar outWidth = this.graph.container.offsetWidth;\r\n\tvar inWidth = this.graph.container.clientWidth;\r\n\r\n\tif (outWidth > inWidth && pt.x > inWidth + 2 && pt.x <= outWidth)\r\n\t{\r\n\t\treturn true;\r\n\t}\r\n\r\n\tvar outHeight = this.graph.container.offsetHeight;\r\n\tvar inHeight = this.graph.container.clientHeight;\r\n\t\r\n\tif (outHeight > inHeight && pt.y > inHeight + 2 && pt.y <= outHeight)\r\n\t{\r\n\t\treturn true;\r\n\t}\r\n\t\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: init\r\n *\r\n * Initializes the graph event dispatch loop for the specified container\r\n * and invokes <create> to create the required DOM nodes for the display.\r\n */\r\nmxGraphView.prototype.init = function()\r\n{\r\n\tthis.installListeners();\r\n\t\r\n\t// Creates the DOM nodes for the respective display dialect\r\n\tvar graph = this.graph;\r\n\t\r\n\tif (graph.dialect == mxConstants.DIALECT_SVG)\r\n\t{\r\n\t\tthis.createSvg();\r\n\t}\r\n\telse if (graph.dialect == mxConstants.DIALECT_VML)\r\n\t{\r\n\t\tthis.createVml();\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.createHtml();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: installListeners\r\n *\r\n * Installs the required listeners in the container.\r\n */\r\nmxGraphView.prototype.installListeners = function()\r\n{\r\n\tvar graph = this.graph;\r\n\tvar container = graph.container;\r\n\t\r\n\tif (container != null)\r\n\t{\r\n\t\t// Support for touch device gestures (eg. pinch to zoom)\r\n\t\t// Double-tap handling is implemented in mxGraph.fireMouseEvent\r\n\t\tif (mxClient.IS_TOUCH)\r\n\t\t{\r\n\t\t\tmxEvent.addListener(container, 'gesturestart', mxUtils.bind(this, function(evt)\r\n\t\t\t{\r\n\t\t\t\tgraph.fireGestureEvent(evt);\r\n\t\t\t\tmxEvent.consume(evt);\r\n\t\t\t}));\r\n\t\t\t\r\n\t\t\tmxEvent.addListener(container, 'gesturechange', mxUtils.bind(this, function(evt)\r\n\t\t\t{\r\n\t\t\t\tgraph.fireGestureEvent(evt);\r\n\t\t\t\tmxEvent.consume(evt);\r\n\t\t\t}));\r\n\r\n\t\t\tmxEvent.addListener(container, 'gestureend', mxUtils.bind(this, function(evt)\r\n\t\t\t{\r\n\t\t\t\tgraph.fireGestureEvent(evt);\r\n\t\t\t\tmxEvent.consume(evt);\r\n\t\t\t}));\r\n\t\t}\r\n\t\t\r\n\t\t// Adds basic listeners for graph event dispatching\r\n\t\tmxEvent.addGestureListeners(container, mxUtils.bind(this, function(evt)\r\n\t\t{\r\n\t\t\t// Condition to avoid scrollbar events starting a rubberband selection\r\n\t\t\tif (this.isContainerEvent(evt) && ((!mxClient.IS_IE && !mxClient.IS_IE11 && !mxClient.IS_GC &&\r\n\t\t\t\t!mxClient.IS_OP && !mxClient.IS_SF) || !this.isScrollEvent(evt)))\r\n\t\t\t{\r\n\t\t\t\tgraph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));\r\n\t\t\t}\r\n\t\t}),\r\n\t\tmxUtils.bind(this, function(evt)\r\n\t\t{\r\n\t\t\tif (this.isContainerEvent(evt))\r\n\t\t\t{\r\n\t\t\t\tgraph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));\r\n\t\t\t}\r\n\t\t}),\r\n\t\tmxUtils.bind(this, function(evt)\r\n\t\t{\r\n\t\t\tif (this.isContainerEvent(evt))\r\n\t\t\t{\r\n\t\t\t\tgraph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));\r\n\t\t\t}\r\n\t\t}));\r\n\t\t\r\n\t\t// Adds listener for double click handling on background, this does always\r\n\t\t// use native event handler, we assume that the DOM of the background\r\n\t\t// does not change during the double click\r\n\t\tmxEvent.addListener(container, 'dblclick', mxUtils.bind(this, function(evt)\r\n\t\t{\r\n\t\t\tif (this.isContainerEvent(evt))\r\n\t\t\t{\r\n\t\t\t\tgraph.dblClick(evt);\r\n\t\t\t}\r\n\t\t}));\r\n\r\n\t\t// Workaround for touch events which started on some DOM node\r\n\t\t// on top of the container, in which case the cells under the\r\n\t\t// mouse for the move and up events are not detected.\r\n\t\tvar getState = function(evt)\r\n\t\t{\r\n\t\t\tvar state = null;\r\n\t\t\t\r\n\t\t\t// Workaround for touch events which started on some DOM node\r\n\t\t\t// on top of the container, in which case the cells under the\r\n\t\t\t// mouse for the move and up events are not detected.\r\n\t\t\tif (mxClient.IS_TOUCH)\r\n\t\t\t{\r\n\t\t\t\tvar x = mxEvent.getClientX(evt);\r\n\t\t\t\tvar y = mxEvent.getClientY(evt);\r\n\t\t\t\t\r\n\t\t\t\t// Dispatches the drop event to the graph which\r\n\t\t\t\t// consumes and executes the source function\r\n\t\t\t\tvar pt = mxUtils.convertPoint(container, x, y);\r\n\t\t\t\tstate = graph.view.getState(graph.getCellAt(pt.x, pt.y));\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\treturn state;\r\n\t\t};\r\n\t\t\r\n\t\t// Adds basic listeners for graph event dispatching outside of the\r\n\t\t// container and finishing the handling of a single gesture\r\n\t\t// Implemented via graph event dispatch loop to avoid duplicate events\r\n\t\t// in Firefox and Chrome\r\n\t\tgraph.addMouseListener(\r\n\t\t{\r\n\t\t\tmouseDown: function(sender, me)\r\n\t\t\t{\r\n\t\t\t\tgraph.popupMenuHandler.hideMenu();\r\n\t\t\t},\r\n\t\t\tmouseMove: function() { },\r\n\t\t\tmouseUp: function() { }\r\n\t\t});\r\n\t\t\r\n\t\tthis.moveHandler = mxUtils.bind(this, function(evt)\r\n\t\t{\r\n\t\t\t// Hides the tooltip if mouse is outside container\r\n\t\t\tif (graph.tooltipHandler != null && graph.tooltipHandler.isHideOnHover())\r\n\t\t\t{\r\n\t\t\t\tgraph.tooltipHandler.hide();\r\n\t\t\t}\r\n\r\n\t\t\tif (this.captureDocumentGesture && graph.isMouseDown && graph.container != null &&\r\n\t\t\t\t!this.isContainerEvent(evt) && graph.container.style.display != 'none' &&\r\n\t\t\t\tgraph.container.style.visibility != 'hidden' && !mxEvent.isConsumed(evt))\r\n\t\t\t{\r\n\t\t\t\tgraph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));\r\n\t\t\t}\r\n\t\t});\r\n\t\t\r\n\t\tthis.endHandler = mxUtils.bind(this, function(evt)\r\n\t\t{\r\n\t\t\tif (this.captureDocumentGesture && graph.isMouseDown && graph.container != null &&\r\n\t\t\t\t!this.isContainerEvent(evt) && graph.container.style.display != 'none' &&\r\n\t\t\t\tgraph.container.style.visibility != 'hidden')\r\n\t\t\t{\r\n\t\t\t\tgraph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));\r\n\t\t\t}\r\n\t\t});\r\n\t\t\r\n\t\tmxEvent.addGestureListeners(document, null, this.moveHandler, this.endHandler);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: create\r\n *\r\n * Creates the DOM nodes for the HTML display.\r\n */\r\nmxGraphView.prototype.createHtml = function()\r\n{\r\n\tvar container = this.graph.container;\r\n\t\r\n\tif (container != null)\r\n\t{\r\n\t\tthis.canvas = this.createHtmlPane('100%', '100%');\r\n\t\tthis.canvas.style.overflow = 'hidden';\r\n\t\r\n\t\t// Uses minimal size for inner DIVs on Canvas. This is required\r\n\t\t// for correct event processing in IE. If we have an overlapping\r\n\t\t// DIV then the events on the cells are only fired for labels.\r\n\t\tthis.backgroundPane = this.createHtmlPane('1px', '1px');\r\n\t\tthis.drawPane = this.createHtmlPane('1px', '1px');\r\n\t\tthis.overlayPane = this.createHtmlPane('1px', '1px');\r\n\t\tthis.decoratorPane = this.createHtmlPane('1px', '1px');\r\n\t\t\r\n\t\tthis.canvas.appendChild(this.backgroundPane);\r\n\t\tthis.canvas.appendChild(this.drawPane);\r\n\t\tthis.canvas.appendChild(this.overlayPane);\r\n\t\tthis.canvas.appendChild(this.decoratorPane);\r\n\r\n\t\tcontainer.appendChild(this.canvas);\r\n\t\tthis.updateContainerStyle(container);\r\n\t\t\r\n\t\t// Implements minWidth/minHeight in quirks mode\r\n\t\tif (mxClient.IS_QUIRKS)\r\n\t\t{\r\n\t\t\tvar onResize = mxUtils.bind(this, function(evt)\r\n\t\t\t{\r\n\t\t\t\tvar bounds = this.getGraphBounds();\r\n\t\t\t\tvar width = bounds.x + bounds.width + this.graph.border;\r\n\t\t\t\tvar height = bounds.y + bounds.height + this.graph.border;\r\n\t\t\t\t\r\n\t\t\t\tthis.updateHtmlCanvasSize(width, height);\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\tmxEvent.addListener(window, 'resize', onResize);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: updateHtmlCanvasSize\r\n * \r\n * Updates the size of the HTML canvas.\r\n */\r\nmxGraphView.prototype.updateHtmlCanvasSize = function(width, height)\r\n{\r\n\tif (this.graph.container != null)\r\n\t{\r\n\t\tvar ow = this.graph.container.offsetWidth;\r\n\t\tvar oh = this.graph.container.offsetHeight;\r\n\r\n\t\tif (ow < width)\r\n\t\t{\r\n\t\t\tthis.canvas.style.width = width + 'px';\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.canvas.style.width = '100%';\r\n\t\t}\r\n\r\n\t\tif (oh < height)\r\n\t\t{\r\n\t\t\tthis.canvas.style.height = height + 'px';\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.canvas.style.height = '100%';\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createHtmlPane\r\n * \r\n * Creates and returns a drawing pane in HTML (DIV).\r\n */\r\nmxGraphView.prototype.createHtmlPane = function(width, height)\r\n{\r\n\tvar pane = document.createElement('DIV');\r\n\t\r\n\tif (width != null && height != null)\r\n\t{\r\n\t\tpane.style.position = 'absolute';\r\n\t\tpane.style.left = '0px';\r\n\t\tpane.style.top = '0px';\r\n\r\n\t\tpane.style.width = width;\r\n\t\tpane.style.height = height;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tpane.style.position = 'relative';\r\n\t}\r\n\t\r\n\treturn pane;\r\n};\r\n\r\n/**\r\n * Function: create\r\n *\r\n * Creates the DOM nodes for the VML display.\r\n */\r\nmxGraphView.prototype.createVml = function()\r\n{\r\n\tvar container = this.graph.container;\r\n\r\n\tif (container != null)\r\n\t{\r\n\t\tvar width = container.offsetWidth;\r\n\t\tvar height = container.offsetHeight;\r\n\t\tthis.canvas = this.createVmlPane(width, height);\r\n\t\tthis.canvas.style.overflow = 'hidden';\r\n\t\t\r\n\t\tthis.backgroundPane = this.createVmlPane(width, height);\r\n\t\tthis.drawPane = this.createVmlPane(width, height);\r\n\t\tthis.overlayPane = this.createVmlPane(width, height);\r\n\t\tthis.decoratorPane = this.createVmlPane(width, height);\r\n\t\t\r\n\t\tthis.canvas.appendChild(this.backgroundPane);\r\n\t\tthis.canvas.appendChild(this.drawPane);\r\n\t\tthis.canvas.appendChild(this.overlayPane);\r\n\t\tthis.canvas.appendChild(this.decoratorPane);\r\n\t\t\r\n\t\tcontainer.appendChild(this.canvas);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createVmlPane\r\n * \r\n * Creates a drawing pane in VML (group).\r\n */\r\nmxGraphView.prototype.createVmlPane = function(width, height)\r\n{\r\n\tvar pane = document.createElement(mxClient.VML_PREFIX + ':group');\r\n\t\r\n\t// At this point the width and height are potentially\r\n\t// uninitialized. That's OK.\r\n\tpane.style.position = 'absolute';\r\n\tpane.style.left = '0px';\r\n\tpane.style.top = '0px';\r\n\r\n\tpane.style.width = width + 'px';\r\n\tpane.style.height = height + 'px';\r\n\r\n\tpane.setAttribute('coordsize', width + ',' + height);\r\n\tpane.setAttribute('coordorigin', '0,0');\r\n\t\r\n\treturn pane;\r\n};\r\n\r\n/**\r\n * Function: create\r\n *\r\n * Creates and returns the DOM nodes for the SVG display.\r\n */\r\nmxGraphView.prototype.createSvg = function()\r\n{\r\n\tvar container = this.graph.container;\r\n\tthis.canvas = document.createElementNS(mxConstants.NS_SVG, 'g');\r\n\t\r\n\t// For background image\r\n\tthis.backgroundPane = document.createElementNS(mxConstants.NS_SVG, 'g');\r\n\tthis.canvas.appendChild(this.backgroundPane);\r\n\r\n\t// Adds two layers (background is early feature)\r\n\tthis.drawPane = document.createElementNS(mxConstants.NS_SVG, 'g');\r\n\tthis.canvas.appendChild(this.drawPane);\r\n\r\n\tthis.overlayPane = document.createElementNS(mxConstants.NS_SVG, 'g');\r\n\tthis.canvas.appendChild(this.overlayPane);\r\n\t\r\n\tthis.decoratorPane = document.createElementNS(mxConstants.NS_SVG, 'g');\r\n\tthis.canvas.appendChild(this.decoratorPane);\r\n\t\r\n\tvar root = document.createElementNS(mxConstants.NS_SVG, 'svg');\r\n\troot.style.left = '0px';\r\n\troot.style.top = '0px';\r\n\troot.style.width = '100%';\r\n\troot.style.height = '100%';\r\n\t\r\n\t// NOTE: In standards mode, the SVG must have block layout\r\n\t// in order for the container DIV to not show scrollbars.\r\n\troot.style.display = 'block';\r\n\troot.appendChild(this.canvas);\r\n\t\r\n\t// Workaround for scrollbars in IE11 and below\r\n\tif (mxClient.IS_IE || mxClient.IS_IE11)\r\n\t{\r\n\t\troot.style.overflow = 'hidden';\r\n\t}\r\n\r\n\tif (container != null)\r\n\t{\r\n\t\tcontainer.appendChild(root);\r\n\t\tthis.updateContainerStyle(container);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: updateContainerStyle\r\n * \r\n * Updates the style of the container after installing the SVG DOM elements.\r\n */\r\nmxGraphView.prototype.updateContainerStyle = function(container)\r\n{\r\n\t// Workaround for offset of container\r\n\tvar style = mxUtils.getCurrentStyle(container);\r\n\t\r\n\tif (style != null && style.position == 'static')\r\n\t{\r\n\t\tcontainer.style.position = 'relative';\r\n\t}\r\n\t\r\n\t// Disables built-in pan and zoom in IE10 and later\r\n\tif (mxClient.IS_POINTER)\r\n\t{\r\n\t\tcontainer.style.touchAction = 'none';\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Destroys the view and all its resources.\r\n */\r\nmxGraphView.prototype.destroy = function()\r\n{\r\n\tvar root = (this.canvas != null) ? this.canvas.ownerSVGElement : null;\r\n\t\r\n\tif (root == null)\r\n\t{\r\n\t\troot = this.canvas;\r\n\t}\r\n\t\r\n\tif (root != null && root.parentNode != null)\r\n\t{\r\n\t\tthis.clear(this.currentRoot, true);\r\n\t\tmxEvent.removeGestureListeners(document, null, this.moveHandler, this.endHandler);\r\n\t\tmxEvent.release(this.graph.container);\r\n\t\troot.parentNode.removeChild(root);\r\n\t\t\r\n\t\tthis.moveHandler = null;\r\n\t\tthis.endHandler = null;\r\n\t\tthis.canvas = null;\r\n\t\tthis.backgroundPane = null;\r\n\t\tthis.drawPane = null;\r\n\t\tthis.overlayPane = null;\r\n\t\tthis.decoratorPane = null;\r\n\t}\r\n};\r\n\r\n/**\r\n * Class: mxCurrentRootChange\r\n *\r\n * Action to change the current root in a view.\r\n *\r\n * Constructor: mxCurrentRootChange\r\n *\r\n * Constructs a change of the current root in the given view.\r\n */\r\nfunction mxCurrentRootChange(view, root)\r\n{\r\n\tthis.view = view;\r\n\tthis.root = root;\r\n\tthis.previous = root;\r\n\tthis.isUp = root == null;\r\n\t\r\n\tif (!this.isUp)\r\n\t{\r\n\t\tvar tmp = this.view.currentRoot;\r\n\t\tvar model = this.view.graph.getModel();\r\n\t\t\r\n\t\twhile (tmp != null)\r\n\t\t{\r\n\t\t\tif (tmp == root)\r\n\t\t\t{\r\n\t\t\t\tthis.isUp = true;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\ttmp = model.getParent(tmp);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: execute\r\n *\r\n * Changes the current root of the view.\r\n */\r\nmxCurrentRootChange.prototype.execute = function()\r\n{\r\n\tvar tmp = this.view.currentRoot;\r\n\tthis.view.currentRoot = this.previous;\r\n\tthis.previous = tmp;\r\n\r\n\tvar translate = this.view.graph.getTranslateForRoot(this.view.currentRoot);\r\n\t\r\n\tif (translate != null)\r\n\t{\r\n\t\tthis.view.translate = new mxPoint(-translate.x, -translate.y);\r\n\t}\r\n\r\n\tif (this.isUp)\r\n\t{\r\n\t\tthis.view.clear(this.view.currentRoot, true);\r\n\t\tthis.view.validate();\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.view.refresh();\r\n\t}\r\n\t\r\n\tvar name = (this.isUp) ? mxEvent.UP : mxEvent.DOWN;\r\n\tthis.view.fireEvent(new mxEventObject(name,\r\n\t\t'root', this.view.currentRoot, 'previous', this.previous));\r\n\tthis.isUp = !this.isUp;\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxGraph\r\n *\r\n * Extends <mxEventSource> to implement a graph component for\r\n * the browser. This is the main class of the package. To activate\r\n * panning and connections use <setPanning> and <setConnectable>.\r\n * For rubberband selection you must create a new instance of\r\n * <mxRubberband>. The following listeners are added to\r\n * <mouseListeners> by default:\r\n * \r\n * - <tooltipHandler>: <mxTooltipHandler> that displays tooltips\r\n * - <panningHandler>: <mxPanningHandler> for panning and popup menus\r\n * - <connectionHandler>: <mxConnectionHandler> for creating connections\r\n * - <graphHandler>: <mxGraphHandler> for moving and cloning cells\r\n * \r\n * These listeners will be called in the above order if they are enabled.\r\n *\r\n * Background Images:\r\n * \r\n * To display a background image, set the image, image width and\r\n * image height using <setBackgroundImage>. If one of the\r\n * above values has changed then the <view>'s <mxGraphView.validate>\r\n * should be invoked.\r\n * \r\n * Cell Images:\r\n * \r\n * To use images in cells, a shape must be specified in the default\r\n * vertex style (or any named style). Possible shapes are\r\n * <mxConstants.SHAPE_IMAGE> and <mxConstants.SHAPE_LABEL>.\r\n * The code to change the shape used in the default vertex style,\r\n * the following code is used:\r\n * \r\n * (code)\r\n * var style = graph.getStylesheet().getDefaultVertexStyle();\r\n * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_IMAGE;\r\n * (end)\r\n * \r\n * For the default vertex style, the image to be displayed can be\r\n * specified in a cell's style using the <mxConstants.STYLE_IMAGE>\r\n * key and the image URL as a value, for example:\r\n * \r\n * (code)\r\n * image=http://www.example.com/image.gif\r\n * (end)\r\n * \r\n * For a named style, the the stylename must be the first element\r\n * of the cell style:\r\n * \r\n * (code)\r\n * stylename;image=http://www.example.com/image.gif\r\n * (end)\r\n * \r\n * A cell style can have any number of key=value pairs added, divided\r\n * by a semicolon as follows:\r\n * \r\n * (code)\r\n * [stylename;|key=value;]\r\n * (end)\r\n *\r\n * Labels:\r\n * \r\n * The cell labels are defined by <getLabel> which uses <convertValueToString>\r\n * if <labelsVisible> is true. If a label must be rendered as HTML markup, then\r\n * <isHtmlLabel> should return true for the respective cell. If all labels\r\n * contain HTML markup, <htmlLabels> can be set to true. NOTE: Enabling HTML\r\n * labels carries a possible security risk (see the section on security in\r\n * the manual).\r\n * \r\n * If wrapping is needed for a label, then <isHtmlLabel> and <isWrapping> must\r\n * return true for the cell whose label should be wrapped. See <isWrapping> for\r\n * an example.\r\n * \r\n * If clipping is needed to keep the rendering of a HTML label inside the\r\n * bounds of its vertex, then <isClipping> should return true for the\r\n * respective cell.\r\n * \r\n * By default, edge labels are movable and vertex labels are fixed. This can be\r\n * changed by setting <edgeLabelsMovable> and <vertexLabelsMovable>, or by\r\n * overriding <isLabelMovable>.\r\n *\r\n * In-place Editing:\r\n * \r\n * In-place editing is started with a doubleclick or by typing F2.\r\n * Programmatically, <edit> is used to check if the cell is editable\r\n * (<isCellEditable>) and call <startEditingAtCell>, which invokes\r\n * <mxCellEditor.startEditing>. The editor uses the value returned\r\n * by <getEditingValue> as the editing value.\r\n * \r\n * After in-place editing, <labelChanged> is called, which invokes\r\n * <mxGraphModel.setValue>, which in turn calls\r\n * <mxGraphModel.valueForCellChanged> via <mxValueChange>.\r\n * \r\n * The event that triggers in-place editing is passed through to the\r\n * <cellEditor>, which may take special actions depending on the type of the\r\n * event or mouse location, and is also passed to <getEditingValue>. The event\r\n * is then passed back to the event processing functions which can perform\r\n * specific actions based on the trigger event.\r\n * \r\n * Tooltips:\r\n * \r\n * Tooltips are implemented by <getTooltip>, which calls <getTooltipForCell>\r\n * if a cell is under the mousepointer. The default implementation checks if\r\n * the cell has a getTooltip function and calls it if it exists. Hence, in order\r\n * to provide custom tooltips, the cell must provide a getTooltip function, or \r\n * one of the two above functions must be overridden.\r\n * \r\n * Typically, for custom cell tooltips, the latter function is overridden as\r\n * follows:\r\n * \r\n * (code)\r\n * graph.getTooltipForCell = function(cell)\r\n * {\r\n *   var label = this.convertValueToString(cell);\r\n *   return 'Tooltip for '+label;\r\n * }\r\n * (end)\r\n * \r\n * When using a config file, the function is overridden in the mxGraph section\r\n * using the following entry:\r\n * \r\n * (code)\r\n * <add as=\"getTooltipForCell\"><![CDATA[\r\n *   function(cell)\r\n *   {\r\n *     var label = this.convertValueToString(cell);\r\n *     return 'Tooltip for '+label;\r\n *   }\r\n * ]]></add>\r\n * (end)\r\n * \r\n * \"this\" refers to the graph in the implementation, so for example to check if \r\n * a cell is an edge, you use this.getModel().isEdge(cell)\r\n *\r\n * For replacing the default implementation of <getTooltipForCell> (rather than \r\n * replacing the function on a specific instance), the following code should be \r\n * used after loading the JavaScript files, but before creating a new mxGraph \r\n * instance using <mxGraph>:\r\n * \r\n * (code)\r\n * mxGraph.prototype.getTooltipForCell = function(cell)\r\n * {\r\n *   var label = this.convertValueToString(cell);\r\n *   return 'Tooltip for '+label;\r\n * }\r\n * (end)\r\n * \r\n * Shapes & Styles:\r\n * \r\n * The implementation of new shapes is demonstrated in the examples. We'll assume\r\n * that we have implemented a custom shape with the name BoxShape which we want\r\n * to use for drawing vertices. To use this shape, it must first be registered in\r\n * the cell renderer as follows:\r\n * \r\n * (code)\r\n * mxCellRenderer.registerShape('box', BoxShape);\r\n * (end)\r\n * \r\n * The code registers the BoxShape constructor under the name box in the cell\r\n * renderer of the graph. The shape can now be referenced using the shape-key in\r\n * a style definition. (The cell renderer contains a set of additional shapes,\r\n * namely one for each constant with a SHAPE-prefix in <mxConstants>.)\r\n *\r\n * Styles are a collection of key, value pairs and a stylesheet is a collection\r\n * of named styles. The names are referenced by the cellstyle, which is stored\r\n * in <mxCell.style> with the following format: [stylename;|key=value;]. The\r\n * string is resolved to a collection of key, value pairs, where the keys are\r\n * overridden with the values in the string.\r\n *\r\n * When introducing a new shape, the name under which the shape is registered\r\n * must be used in the stylesheet. There are three ways of doing this:\r\n * \r\n *   - By changing the default style, so that all vertices will use the new\r\n * \t\tshape\r\n *   - By defining a new style, so that only vertices with the respective\r\n * \t\tcellstyle will use the new shape\r\n *   - By using shape=box in the cellstyle's optional list of key, value pairs\r\n * \t\tto be overridden\r\n *\r\n * In the first case, the code to fetch and modify the default style for\r\n * vertices is as follows:\r\n * \r\n * (code)\r\n * var style = graph.getStylesheet().getDefaultVertexStyle();\r\n * style[mxConstants.STYLE_SHAPE] = 'box';\r\n * (end)\r\n * \r\n * The code takes the default vertex style, which is used for all vertices that\r\n * do not have a specific cellstyle, and modifies the value for the shape-key\r\n * in-place to use the new BoxShape for drawing vertices. This is done by\r\n * assigning the box value in the second line, which refers to the name of the\r\n * BoxShape in the cell renderer.\r\n * \r\n * In the second case, a collection of key, value pairs is created and then\r\n * added to the stylesheet under a new name. In order to distinguish the\r\n * shapename and the stylename we'll use boxstyle for the stylename:\r\n * \r\n * (code)\r\n * var style = new Object();\r\n * style[mxConstants.STYLE_SHAPE] = 'box';\r\n * style[mxConstants.STYLE_STROKECOLOR] = '#000000';\r\n * style[mxConstants.STYLE_FONTCOLOR] = '#000000';\r\n * graph.getStylesheet().putCellStyle('boxstyle', style);\r\n * (end)\r\n * \r\n * The code adds a new style with the name boxstyle to the stylesheet. To use\r\n * this style with a cell, it must be referenced from the cellstyle as follows:\r\n * \r\n * (code)\r\n * var vertex = graph.insertVertex(parent, null, 'Hello, World!', 20, 20, 80, 20,\r\n * \t\t\t\t'boxstyle');\r\n * (end)\r\n * \r\n * To summarize, each new shape must be registered in the <mxCellRenderer> with\r\n * a unique name. That name is then used as the value of the shape-key in a\r\n * default or custom style. If there are multiple custom shapes, then there\r\n * should be a separate style for each shape.\r\n * \r\n * Inheriting Styles:\r\n * \r\n * For fill-, stroke-, gradient- and indicatorColors special keywords can be\r\n * used. The inherit keyword for one of these colors will inherit the color\r\n * for the same key from the parent cell. The swimlane keyword does the same,\r\n * but inherits from the nearest swimlane in the ancestor hierarchy. Finally,\r\n * the indicated keyword will use the color of the indicator as the color for\r\n * the given key.\r\n * \r\n * Scrollbars:\r\n * \r\n * The <containers> overflow CSS property defines if scrollbars are used to\r\n * display the graph. For values of 'auto' or 'scroll', the scrollbars will\r\n * be shown. Note that the <resizeContainer> flag is normally not used\r\n * together with scrollbars, as it will resize the container to match the\r\n * size of the graph after each change.\r\n * \r\n * Multiplicities and Validation:\r\n * \r\n * To control the possible connections in mxGraph, <getEdgeValidationError> is\r\n * used. The default implementation of the function uses <multiplicities>,\r\n * which is an array of <mxMultiplicity>. Using this class allows to establish\r\n * simple multiplicities, which are enforced by the graph.\r\n * \r\n * The <mxMultiplicity> uses <mxCell.is> to determine for which terminals it\r\n * applies. The default implementation of <mxCell.is> works with DOM nodes (XML\r\n * nodes) and checks if the given type parameter matches the nodeName of the\r\n * node (case insensitive). Optionally, an attributename and value can be\r\n * specified which are also checked.\r\n * \r\n * <getEdgeValidationError> is called whenever the connectivity of an edge\r\n * changes. It returns an empty string or an error message if the edge is\r\n * invalid or null if the edge is valid. If the returned string is not empty\r\n * then it is displayed as an error message.\r\n * \r\n * <mxMultiplicity> allows to specify the multiplicity between a terminal and\r\n * its possible neighbors. For example, if any rectangle may only be connected\r\n * to, say, a maximum of two circles you can add the following rule to\r\n * <multiplicities>:\r\n * \r\n * (code)\r\n * graph.multiplicities.push(new mxMultiplicity(\r\n *   true, 'rectangle', null, null, 0, 2, ['circle'],\r\n *   'Only 2 targets allowed',\r\n *   'Only shape targets allowed'));\r\n * (end)\r\n * \r\n * This will display the first error message whenever a rectangle is connected\r\n * to more than two circles and the second error message if a rectangle is\r\n * connected to anything but a circle.\r\n * \r\n * For certain multiplicities, such as a minimum of 1 connection, which cannot\r\n * be enforced at cell creation time (unless the cell is created together with\r\n * the connection), mxGraph offers <validate> which checks all multiplicities\r\n * for all cells and displays the respective error messages in an overlay icon\r\n * on the cells.\r\n * \r\n * If a cell is collapsed and contains validation errors, a respective warning\r\n * icon is attached to the collapsed cell.\r\n * \r\n * Auto-Layout:\r\n * \r\n * For automatic layout, the <getLayout> hook is provided in <mxLayoutManager>.\r\n * It can be overridden to return a layout algorithm for the children of a\r\n * given cell.\r\n * \r\n * Unconnected edges:\r\n * \r\n * The default values for all switches are designed to meet the requirements of\r\n * general diagram drawing applications. A very typical set of settings to\r\n * avoid edges that are not connected is the following:\r\n * \r\n * (code)\r\n * graph.setAllowDanglingEdges(false);\r\n * graph.setDisconnectOnMove(false);\r\n * (end)\r\n * \r\n * Setting the <cloneInvalidEdges> switch to true is optional. This switch\r\n * controls if edges are inserted after a copy, paste or clone-drag if they are\r\n * invalid. For example, edges are invalid if copied or control-dragged without \r\n * having selected the corresponding terminals and allowDanglingEdges is\r\n * false, in which case the edges will not be cloned if the switch is false.\r\n * \r\n * Output:\r\n * \r\n * To produce an XML representation for a diagram, the following code can be\r\n * used.\r\n * \r\n * (code)\r\n * var enc = new mxCodec(mxUtils.createXmlDocument());\r\n * var node = enc.encode(graph.getModel());\r\n * (end)\r\n * \r\n * This will produce an XML node than can be handled using the DOM API or\r\n * turned into a string representation using the following code:\r\n * \r\n * (code)\r\n * var xml = mxUtils.getXml(node);\r\n * (end)\r\n * \r\n * To obtain a formatted string, mxUtils.getPrettyXml can be used instead.\r\n * \r\n * This string can now be stored in a local persistent storage (for example\r\n * using Google Gears) or it can be passed to a backend using mxUtils.post as\r\n * follows. The url variable is the URL of the Java servlet, PHP page or HTTP\r\n * handler, depending on the server.\r\n * \r\n * (code)\r\n * var xmlString = encodeURIComponent(mxUtils.getXml(node));\r\n * mxUtils.post(url, 'xml='+xmlString, function(req)\r\n * {\r\n *   // Process server response using req of type mxXmlRequest\r\n * });\r\n * (end)\r\n * \r\n * Input:\r\n * \r\n * To load an XML representation of a diagram into an existing graph object\r\n * mxUtils.load can be used as follows. The url variable is the URL of the Java\r\n * servlet, PHP page or HTTP handler that produces the XML string.\r\n * \r\n * (code)\r\n * var xmlDoc = mxUtils.load(url).getXml();\r\n * var node = xmlDoc.documentElement;\r\n * var dec = new mxCodec(node.ownerDocument);\r\n * dec.decode(node, graph.getModel());\r\n * (end)\r\n * \r\n * For creating a page that loads the client and a diagram using a single\r\n * request please refer to the deployment examples in the backends.\r\n * \r\n * Functional dependencies:\r\n * \r\n * (see images/callgraph.png)\r\n * \r\n * Resources:\r\n *\r\n * resources/graph - Language resources for mxGraph\r\n *\r\n * Group: Events\r\n * \r\n * Event: mxEvent.ROOT\r\n * \r\n * Fires if the root in the model has changed. This event has no properties.\r\n * \r\n * Event: mxEvent.ALIGN_CELLS\r\n * \r\n * Fires between begin- and endUpdate in <alignCells>. The <code>cells</code>\r\n * and <code>align</code> properties contain the respective arguments that were\r\n * passed to <alignCells>.\r\n *\r\n * Event: mxEvent.FLIP_EDGE\r\n *\r\n * Fires between begin- and endUpdate in <flipEdge>. The <code>edge</code>\r\n * property contains the edge passed to <flipEdge>.\r\n * \r\n * Event: mxEvent.ORDER_CELLS\r\n * \r\n * Fires between begin- and endUpdate in <orderCells>. The <code>cells</code>\r\n * and <code>back</code> properties contain the respective arguments that were\r\n * passed to <orderCells>.\r\n *\r\n * Event: mxEvent.CELLS_ORDERED\r\n *\r\n * Fires between begin- and endUpdate in <cellsOrdered>. The <code>cells</code>\r\n * and <code>back</code> arguments contain the respective arguments that were\r\n * passed to <cellsOrdered>.\r\n * \r\n * Event: mxEvent.GROUP_CELLS\r\n * \r\n * Fires between begin- and endUpdate in <groupCells>. The <code>group</code>,\r\n * <code>cells</code> and <code>border</code> arguments contain the respective\r\n * arguments that were passed to <groupCells>.\r\n * \r\n * Event: mxEvent.UNGROUP_CELLS\r\n * \r\n * Fires between begin- and endUpdate in <ungroupCells>. The <code>cells</code>\r\n * property contains the array of cells that was passed to <ungroupCells>.\r\n * \r\n * Event: mxEvent.REMOVE_CELLS_FROM_PARENT\r\n * \r\n * Fires between begin- and endUpdate in <removeCellsFromParent>. The\r\n * <code>cells</code> property contains the array of cells that was passed to\r\n * <removeCellsFromParent>.\r\n * \r\n * Event: mxEvent.ADD_CELLS\r\n * \r\n * Fires between begin- and endUpdate in <addCells>. The <code>cells</code>,\r\n * <code>parent</code>, <code>index</code>, <code>source</code> and\r\n * <code>target</code> properties contain the respective arguments that were\r\n * passed to <addCells>.\r\n * \r\n * Event: mxEvent.CELLS_ADDED\r\n * \r\n * Fires between begin- and endUpdate in <cellsAdded>. The <code>cells</code>,\r\n * <code>parent</code>, <code>index</code>, <code>source</code>,\r\n * <code>target</code> and <code>absolute</code> properties contain the\r\n * respective arguments that were passed to <cellsAdded>.\r\n * \r\n * Event: mxEvent.REMOVE_CELLS\r\n * \r\n * Fires between begin- and endUpdate in <removeCells>. The <code>cells</code>\r\n * and <code>includeEdges</code> arguments contain the respective arguments\r\n * that were passed to <removeCells>.\r\n * \r\n * Event: mxEvent.CELLS_REMOVED\r\n * \r\n * Fires between begin- and endUpdate in <cellsRemoved>. The <code>cells</code>\r\n * argument contains the array of cells that was removed.\r\n * \r\n * Event: mxEvent.SPLIT_EDGE\r\n * \r\n * Fires between begin- and endUpdate in <splitEdge>. The <code>edge</code>\r\n * property contains the edge to be splitted, the <code>cells</code>,\r\n * <code>newEdge</code>, <code>dx</code> and <code>dy</code> properties contain\r\n * the respective arguments that were passed to <splitEdge>.\r\n * \r\n * Event: mxEvent.TOGGLE_CELLS\r\n * \r\n * Fires between begin- and endUpdate in <toggleCells>. The <code>show</code>,\r\n * <code>cells</code> and <code>includeEdges</code> properties contain the\r\n * respective arguments that were passed to <toggleCells>.\r\n * \r\n * Event: mxEvent.FOLD_CELLS\r\n * \r\n * Fires between begin- and endUpdate in <foldCells>. The\r\n * <code>collapse</code>, <code>cells</code> and <code>recurse</code>\r\n * properties contain the respective arguments that were passed to <foldCells>.\r\n * \r\n * Event: mxEvent.CELLS_FOLDED\r\n * \r\n * Fires between begin- and endUpdate in cellsFolded. The\r\n * <code>collapse</code>, <code>cells</code> and <code>recurse</code>\r\n * properties contain the respective arguments that were passed to\r\n * <cellsFolded>.\r\n * \r\n * Event: mxEvent.UPDATE_CELL_SIZE\r\n * \r\n * Fires between begin- and endUpdate in <updateCellSize>. The\r\n * <code>cell</code> and <code>ignoreChildren</code> properties contain the\r\n * respective arguments that were passed to <updateCellSize>.\r\n * \r\n * Event: mxEvent.RESIZE_CELLS\r\n * \r\n * Fires between begin- and endUpdate in <resizeCells>. The <code>cells</code>\r\n * and <code>bounds</code> properties contain the respective arguments that\r\n * were passed to <resizeCells>.\r\n * \r\n * Event: mxEvent.CELLS_RESIZED\r\n * \r\n * Fires between begin- and endUpdate in <cellsResized>. The <code>cells</code>\r\n * and <code>bounds</code> properties contain the respective arguments that\r\n * were passed to <cellsResized>.\r\n * \r\n * Event: mxEvent.MOVE_CELLS\r\n * \r\n * Fires between begin- and endUpdate in <moveCells>. The <code>cells</code>,\r\n * <code>dx</code>, <code>dy</code>, <code>clone</code>, <code>target</code>\r\n * and <code>event</code> properties contain the respective arguments that\r\n * were passed to <moveCells>.\r\n * \r\n * Event: mxEvent.CELLS_MOVED\r\n * \r\n * Fires between begin- and endUpdate in <cellsMoved>. The <code>cells</code>,\r\n * <code>dx</code>, <code>dy</code> and <code>disconnect</code> properties\r\n * contain the respective arguments that were passed to <cellsMoved>.\r\n * \r\n * Event: mxEvent.CONNECT_CELL\r\n * \r\n * Fires between begin- and endUpdate in <connectCell>. The <code>edge</code>,\r\n * <code>terminal</code> and <code>source</code> properties contain the\r\n * respective arguments that were passed to <connectCell>.\r\n * \r\n * Event: mxEvent.CELL_CONNECTED\r\n * \r\n * Fires between begin- and endUpdate in <cellConnected>. The\r\n * <code>edge</code>, <code>terminal</code> and <code>source</code> properties\r\n * contain the respective arguments that were passed to <cellConnected>.\r\n * \r\n * Event: mxEvent.REFRESH\r\n * \r\n * Fires after <refresh> was executed. This event has no properties.\r\n *\r\n * Event: mxEvent.CLICK\r\n * \r\n * Fires in <click> after a click event. The <code>event</code> property\r\n * contains the original mouse event and <code>cell</code> property contains\r\n * the cell under the mouse or null if the background was clicked.\r\n * \r\n * Event: mxEvent.DOUBLE_CLICK\r\n *\r\n * Fires in <dblClick> after a double click. The <code>event</code> property\r\n * contains the original mouse event and the <code>cell</code> property\r\n * contains the cell under the mouse or null if the background was clicked.\r\n * \r\n * Event: mxEvent.GESTURE\r\n *\r\n * Fires in <fireGestureEvent> after a touch gesture. The <code>event</code>\r\n * property contains the original gesture end event and the <code>cell</code>\r\n * property contains the optional cell associated with the gesture.\r\n *\r\n * Event: mxEvent.TAP_AND_HOLD\r\n *\r\n * Fires in <tapAndHold> if a tap and hold event was detected. The <code>event</code>\r\n * property contains the initial touch event and the <code>cell</code> property\r\n * contains the cell under the mouse or null if the background was clicked.\r\n *\r\n * Event: mxEvent.FIRE_MOUSE_EVENT\r\n *\r\n * Fires in <fireMouseEvent> before the mouse listeners are invoked. The\r\n * <code>eventName</code> property contains the event name and the\r\n * <code>event</code> property contains the <mxMouseEvent>.\r\n *\r\n * Event: mxEvent.SIZE\r\n *\r\n * Fires after <sizeDidChange> was executed. The <code>bounds</code> property\r\n * contains the new graph bounds.\r\n *\r\n * Event: mxEvent.START_EDITING\r\n *\r\n * Fires before the in-place editor starts in <startEditingAtCell>. The\r\n * <code>cell</code> property contains the cell that is being edited and the\r\n * <code>event</code> property contains the optional event argument that was\r\n * passed to <startEditingAtCell>.\r\n * \r\n * Event: mxEvent.EDITING_STARTED\r\n *\r\n * Fires after the in-place editor starts in <startEditingAtCell>. The\r\n * <code>cell</code> property contains the cell that is being edited and the\r\n * <code>event</code> property contains the optional event argument that was\r\n * passed to <startEditingAtCell>.\r\n * \r\n * Event: mxEvent.EDITING_STOPPED\r\n *\r\n * Fires after the in-place editor stops in <stopEditing>.\r\n *\r\n * Event: mxEvent.LABEL_CHANGED\r\n *\r\n * Fires between begin- and endUpdate in <cellLabelChanged>. The\r\n * <code>cell</code> property contains the cell, the <code>value</code>\r\n * property contains the new value for the cell, the <code>old</code> property\r\n * contains the old value and the optional <code>event</code> property contains\r\n * the mouse event that started the edit.\r\n * \r\n * Event: mxEvent.ADD_OVERLAY\r\n *\r\n * Fires after an overlay is added in <addCellOverlay>. The <code>cell</code>\r\n * property contains the cell and the <code>overlay</code> property contains\r\n * the <mxCellOverlay> that was added.\r\n *\r\n * Event: mxEvent.REMOVE_OVERLAY\r\n *\r\n * Fires after an overlay is removed in <removeCellOverlay> and\r\n * <removeCellOverlays>. The <code>cell</code> property contains the cell and\r\n * the <code>overlay</code> property contains the <mxCellOverlay> that was\r\n * removed.\r\n * \r\n * Constructor: mxGraph\r\n * \r\n * Constructs a new mxGraph in the specified container. Model is an optional\r\n * mxGraphModel. If no model is provided, a new mxGraphModel instance is \r\n * used as the model. The container must have a valid owner document prior \r\n * to calling this function in Internet Explorer. RenderHint is a string to\r\n * affect the display performance and rendering in IE, but not in SVG-based \r\n * browsers. The parameter is mapped to <dialect>, which may \r\n * be one of <mxConstants.DIALECT_SVG> for SVG-based browsers, \r\n * <mxConstants.DIALECT_STRICTHTML> for fastest display mode,\r\n * <mxConstants.DIALECT_PREFERHTML> for faster display mode,\r\n * <mxConstants.DIALECT_MIXEDHTML> for fast and <mxConstants.DIALECT_VML> \r\n * for exact display mode (slowest). The dialects are defined in mxConstants.\r\n * The default values are DIALECT_SVG for SVG-based browsers and\r\n * DIALECT_MIXED for IE.\r\n *\r\n * The possible values for the renderingHint parameter are explained below:\r\n * \r\n * fast - The parameter is based on the fact that the display performance is \r\n * highly improved in IE if the VML is not contained within a VML group \r\n * element. The lack of a group element only slightly affects the display while \r\n * panning, but improves the performance by almost a factor of 2, while keeping \r\n * the display sufficiently accurate. This also allows to render certain shapes as HTML \r\n * if the display accuracy is not affected, which is implemented by \r\n * <mxShape.isMixedModeHtml>. This is the default setting and is mapped to\r\n * DIALECT_MIXEDHTML.\r\n * faster - Same as fast, but more expensive shapes are avoided. This is \r\n * controlled by <mxShape.preferModeHtml>. The default implementation will \r\n * avoid gradients and rounded rectangles, but more significant shapes, such \r\n * as rhombus, ellipse, actor and cylinder will be rendered accurately. This \r\n * setting is mapped to DIALECT_PREFERHTML.\r\n * fastest - Almost anything will be rendered in Html. This allows for \r\n * rectangles, labels and images. This setting is mapped to\r\n * DIALECT_STRICTHTML.\r\n * exact - If accurate panning is required and if the diagram is small (up\r\n * to 100 cells), then this value should be used. In this mode, a group is \r\n * created that contains the VML. This allows for accurate panning and is \r\n * mapped to DIALECT_VML.\r\n *\r\n * Example:\r\n * \r\n * To create a graph inside a DOM node with an id of graph:\r\n * (code)\r\n * var container = document.getElementById('graph');\r\n * var graph = new mxGraph(container);\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * container - Optional DOM node that acts as a container for the graph.\r\n * If this is null then the container can be initialized later using\r\n * <init>.\r\n * model - Optional <mxGraphModel> that constitutes the graph data.\r\n * renderHint - Optional string that specifies the display accuracy and\r\n * performance. Default is mxConstants.DIALECT_MIXEDHTML (for IE).\r\n * stylesheet - Optional <mxStylesheet> to be used in the graph.\r\n */\r\nfunction mxGraph(container, model, renderHint, stylesheet)\r\n{\r\n\t// Initializes the variable in case the prototype has been\r\n\t// modified to hold some listeners (which is possible because\r\n\t// the createHandlers call is executed regardless of the\r\n\t// arguments passed into the ctor).\r\n\tthis.mouseListeners = null;\r\n\t\r\n\t// Converts the renderHint into a dialect\r\n\tthis.renderHint = renderHint;\r\n\r\n\tif (mxClient.IS_SVG)\r\n\t{\r\n\t\tthis.dialect = mxConstants.DIALECT_SVG;\r\n\t}\r\n\telse if (renderHint == mxConstants.RENDERING_HINT_EXACT && mxClient.IS_VML)\r\n\t{\r\n\t\tthis.dialect = mxConstants.DIALECT_VML;\r\n\t}\r\n\telse if (renderHint == mxConstants.RENDERING_HINT_FASTEST)\r\n\t{\r\n\t\tthis.dialect = mxConstants.DIALECT_STRICTHTML;\r\n\t}\r\n\telse if (renderHint == mxConstants.RENDERING_HINT_FASTER)\r\n\t{\r\n\t\tthis.dialect = mxConstants.DIALECT_PREFERHTML;\r\n\t}\r\n\telse // default for VML\r\n\t{\r\n\t\tthis.dialect = mxConstants.DIALECT_MIXEDHTML;\r\n\t}\r\n\t\r\n\t// Initializes the main members that do not require a container\r\n\tthis.model = (model != null) ? model : new mxGraphModel();\r\n\tthis.multiplicities = [];\r\n\tthis.imageBundles = [];\r\n\tthis.cellRenderer = this.createCellRenderer();\r\n\tthis.setSelectionModel(this.createSelectionModel());\r\n\tthis.setStylesheet((stylesheet != null) ? stylesheet : this.createStylesheet());\r\n\tthis.view = this.createGraphView();\r\n\t\r\n\t// Adds a graph model listener to update the view\r\n\tthis.graphModelChangeListener = mxUtils.bind(this, function(sender, evt)\r\n\t{\r\n\t\tthis.graphModelChanged(evt.getProperty('edit').changes);\r\n\t});\r\n\t\r\n\tthis.model.addListener(mxEvent.CHANGE, this.graphModelChangeListener);\r\n\r\n\t// Installs basic event handlers with disabled default settings.\r\n\tthis.createHandlers();\r\n\t\r\n\t// Initializes the display if a container was specified\r\n\tif (container != null)\r\n\t{\r\n\t\tthis.init(container);\r\n\t}\r\n\t\r\n\tthis.view.revalidate();\r\n};\r\n\r\n/**\r\n * Installs the required language resources at class\r\n * loading time.\r\n */\r\nif (mxLoadResources)\r\n{\r\n\tmxResources.add(mxClient.basePath + '/resources/graph');\r\n}\r\nelse\r\n{\r\n\tmxClient.defaultBundles.push(mxClient.basePath + '/resources/graph');\r\n}\r\n\r\n/**\r\n * Extends mxEventSource.\r\n */\r\nmxGraph.prototype = new mxEventSource();\r\nmxGraph.prototype.constructor = mxGraph;\r\n\r\n/**\r\n * Group: Variables\r\n */\r\n\r\n/**\r\n * Variable: mouseListeners\r\n * \r\n * Holds the mouse event listeners. See <fireMouseEvent>.\r\n */\r\nmxGraph.prototype.mouseListeners = null;\r\n\r\n/**\r\n * Variable: isMouseDown\r\n * \r\n * Holds the state of the mouse button.\r\n */\r\nmxGraph.prototype.isMouseDown = false;\r\n\r\n/**\r\n * Variable: model\r\n * \r\n * Holds the <mxGraphModel> that contains the cells to be displayed.\r\n */\r\nmxGraph.prototype.model = null;\r\n\r\n/**\r\n * Variable: view\r\n * \r\n * Holds the <mxGraphView> that caches the <mxCellStates> for the cells.\r\n */\r\nmxGraph.prototype.view = null;\r\n\r\n/**\r\n * Variable: stylesheet\r\n * \r\n * Holds the <mxStylesheet> that defines the appearance of the cells.\r\n * \r\n * \r\n * Example:\r\n * \r\n * Use the following code to read a stylesheet into an existing graph.\r\n * \r\n * (code)\r\n * var req = mxUtils.load('stylesheet.xml');\r\n * var root = req.getDocumentElement();\r\n * var dec = new mxCodec(root.ownerDocument);\r\n * dec.decode(root, graph.stylesheet);\r\n * (end)\r\n */\r\nmxGraph.prototype.stylesheet = null;\r\n\t\r\n/**\r\n * Variable: selectionModel\r\n * \r\n * Holds the <mxGraphSelectionModel> that models the current selection.\r\n */\r\nmxGraph.prototype.selectionModel = null;\r\n\r\n/**\r\n * Variable: cellEditor\r\n * \r\n * Holds the <mxCellEditor> that is used as the in-place editing.\r\n */\r\nmxGraph.prototype.cellEditor = null;\r\n\r\n/**\r\n * Variable: cellRenderer\r\n * \r\n * Holds the <mxCellRenderer> for rendering the cells in the graph.\r\n */\r\nmxGraph.prototype.cellRenderer = null;\r\n\r\n/**\r\n * Variable: multiplicities\r\n * \r\n * An array of <mxMultiplicities> describing the allowed\r\n * connections in a graph.\r\n */\r\nmxGraph.prototype.multiplicities = null;\r\n\r\n/**\r\n * Variable: renderHint\r\n * \r\n * RenderHint as it was passed to the constructor.\r\n */\r\nmxGraph.prototype.renderHint = null;\r\n\r\n/**\r\n * Variable: dialect\r\n * \r\n * Dialect to be used for drawing the graph. Possible values are all\r\n * constants in <mxConstants> with a DIALECT-prefix.\r\n */\r\nmxGraph.prototype.dialect = null;\r\n\r\n/**\r\n * Variable: gridSize\r\n * \r\n * Specifies the grid size. Default is 10.\r\n */\r\nmxGraph.prototype.gridSize = 10;\r\n\t\r\n/**\r\n * Variable: gridEnabled\r\n * \r\n * Specifies if the grid is enabled. This is used in <snap>. Default is\r\n * true.\r\n */\r\nmxGraph.prototype.gridEnabled = true;\r\n\r\n/**\r\n * Variable: portsEnabled\r\n * \r\n * Specifies if ports are enabled. This is used in <cellConnected> to update\r\n * the respective style. Default is true.\r\n */\r\nmxGraph.prototype.portsEnabled = true;\r\n\r\n/**\r\n * Variable: nativeDoubleClickEnabled\r\n * \r\n * Specifies if native double click events should be detected. Default is true.\r\n */\r\nmxGraph.prototype.nativeDblClickEnabled = true;\r\n\r\n/**\r\n * Variable: doubleTapEnabled\r\n * \r\n * Specifies if double taps on touch-based devices should be handled as a\r\n * double click. Default is true.\r\n */\r\nmxGraph.prototype.doubleTapEnabled = true;\r\n\r\n/**\r\n * Variable: doubleTapTimeout\r\n * \r\n * Specifies the timeout for double taps and non-native double clicks. Default\r\n * is 500 ms.\r\n */\r\nmxGraph.prototype.doubleTapTimeout = 500;\r\n\r\n/**\r\n * Variable: doubleTapTolerance\r\n * \r\n * Specifies the tolerance for double taps and double clicks in quirks mode.\r\n * Default is 25 pixels.\r\n */\r\nmxGraph.prototype.doubleTapTolerance = 25;\r\n\r\n/**\r\n * Variable: lastTouchX\r\n * \r\n * Holds the x-coordinate of the last touch event for double tap detection.\r\n */\r\nmxGraph.prototype.lastTouchY = 0;\r\n\r\n/**\r\n * Variable: lastTouchX\r\n * \r\n * Holds the y-coordinate of the last touch event for double tap detection.\r\n */\r\nmxGraph.prototype.lastTouchY = 0;\r\n\r\n/**\r\n * Variable: lastTouchTime\r\n * \r\n * Holds the time of the last touch event for double click detection.\r\n */\r\nmxGraph.prototype.lastTouchTime = 0;\r\n\r\n/**\r\n * Variable: tapAndHoldEnabled\r\n * \r\n * Specifies if tap and hold should be used for starting connections on touch-based\r\n * devices. Default is true.\r\n */\r\nmxGraph.prototype.tapAndHoldEnabled = true;\r\n\r\n/**\r\n * Variable: tapAndHoldDelay\r\n * \r\n * Specifies the time for a tap and hold. Default is 500 ms.\r\n */\r\nmxGraph.prototype.tapAndHoldDelay = 500;\r\n\r\n/**\r\n * Variable: tapAndHoldInProgress\r\n * \r\n * True if the timer for tap and hold events is running.\r\n */\r\nmxGraph.prototype.tapAndHoldInProgress = false;\r\n\r\n/**\r\n * Variable: tapAndHoldValid\r\n * \r\n * True as long as the timer is running and the touch events\r\n * stay within the given <tapAndHoldTolerance>.\r\n */\r\nmxGraph.prototype.tapAndHoldValid = false;\r\n\r\n/**\r\n * Variable: initialTouchX\r\n * \r\n * Holds the x-coordinate of the intial touch event for tap and hold.\r\n */\r\nmxGraph.prototype.initialTouchX = 0;\r\n\r\n/**\r\n * Variable: initialTouchY\r\n * \r\n * Holds the y-coordinate of the intial touch event for tap and hold.\r\n */\r\nmxGraph.prototype.initialTouchY = 0;\r\n\r\n/**\r\n * Variable: tolerance\r\n * \r\n * Tolerance for a move to be handled as a single click.\r\n * Default is 4 pixels.\r\n */\r\nmxGraph.prototype.tolerance = 4;\r\n\r\n/**\r\n * Variable: defaultOverlap\r\n * \r\n * Value returned by <getOverlap> if <isAllowOverlapParent> returns\r\n * true for the given cell. <getOverlap> is used in <constrainChild> if\r\n * <isConstrainChild> returns true. The value specifies the\r\n * portion of the child which is allowed to overlap the parent.\r\n */\r\nmxGraph.prototype.defaultOverlap = 0.5;\r\n\r\n/**\r\n * Variable: defaultParent\r\n * \r\n * Specifies the default parent to be used to insert new cells.\r\n * This is used in <getDefaultParent>. Default is null.\r\n */\r\nmxGraph.prototype.defaultParent = null;\r\n\r\n/**\r\n * Variable: alternateEdgeStyle\r\n * \r\n * Specifies the alternate edge style to be used if the main control point\r\n * on an edge is being doubleclicked. Default is null.\r\n */\r\nmxGraph.prototype.alternateEdgeStyle = null;\r\n\r\n/**\r\n * Variable: backgroundImage\r\n *\r\n * Specifies the <mxImage> to be returned by <getBackgroundImage>. Default\r\n * is null.\r\n * \r\n * Example:\r\n *\r\n * (code)\r\n * var img = new mxImage('http://www.example.com/maps/examplemap.jpg', 1024, 768);\r\n * graph.setBackgroundImage(img);\r\n * graph.view.validate();\r\n * (end)\r\n */\r\nmxGraph.prototype.backgroundImage = null;\r\n\r\n/**\r\n * Variable: pageVisible\r\n *\r\n * Specifies if the background page should be visible. Default is false.\r\n * Not yet implemented.\r\n */\r\nmxGraph.prototype.pageVisible = false;\r\n\r\n/**\r\n * Variable: pageBreaksVisible\r\n * \r\n * Specifies if a dashed line should be drawn between multiple pages. Default\r\n * is false. If you change this value while a graph is being displayed then you\r\n * should call <sizeDidChange> to force an update of the display.\r\n */\r\nmxGraph.prototype.pageBreaksVisible = false;\r\n\r\n/**\r\n * Variable: pageBreakColor\r\n * \r\n * Specifies the color for page breaks. Default is 'gray'.\r\n */\r\nmxGraph.prototype.pageBreakColor = 'gray';\r\n\r\n/**\r\n * Variable: pageBreakDashed\r\n * \r\n * Specifies the page breaks should be dashed. Default is true.\r\n */\r\nmxGraph.prototype.pageBreakDashed = true;\r\n\r\n/**\r\n * Variable: minPageBreakDist\r\n * \r\n * Specifies the minimum distance for page breaks to be visible. Default is\r\n * 20 (in pixels).\r\n */\r\nmxGraph.prototype.minPageBreakDist = 20;\r\n\r\n/**\r\n * Variable: preferPageSize\r\n * \r\n * Specifies if the graph size should be rounded to the next page number in\r\n * <sizeDidChange>. This is only used if the graph container has scrollbars.\r\n * Default is false.\r\n */\r\nmxGraph.prototype.preferPageSize = false;\r\n\r\n/**\r\n * Variable: pageFormat\r\n *\r\n * Specifies the page format for the background page. Default is\r\n * <mxConstants.PAGE_FORMAT_A4_PORTRAIT>. This is used as the default in\r\n * <mxPrintPreview> and for painting the background page if <pageVisible> is\r\n * true and the pagebreaks if <pageBreaksVisible> is true.\r\n */\r\nmxGraph.prototype.pageFormat = mxConstants.PAGE_FORMAT_A4_PORTRAIT;\r\n\r\n/**\r\n * Variable: pageScale\r\n *\r\n * Specifies the scale of the background page. Default is 1.5.\r\n * Not yet implemented.\r\n */\r\nmxGraph.prototype.pageScale = 1.5;\r\n\r\n/**\r\n * Variable: enabled\r\n * \r\n * Specifies the return value for <isEnabled>. Default is true.\r\n */\r\nmxGraph.prototype.enabled = true;\r\n\r\n/**\r\n * Variable: escapeEnabled\r\n * \r\n * Specifies if <mxKeyHandler> should invoke <escape> when the escape key\r\n * is pressed. Default is true.\r\n */\r\nmxGraph.prototype.escapeEnabled = true;\r\n\r\n/**\r\n * Variable: invokesStopCellEditing\r\n * \r\n * If true, when editing is to be stopped by way of selection changing,\r\n * data in diagram changing or other means stopCellEditing is invoked, and\r\n * changes are saved. This is implemented in a focus handler in\r\n * <mxCellEditor>. Default is true.\r\n */\r\nmxGraph.prototype.invokesStopCellEditing = true;\r\n\r\n/**\r\n * Variable: enterStopsCellEditing\r\n * \r\n * If true, pressing the enter key without pressing control or shift will stop\r\n * editing and accept the new value. This is used in <mxCellEditor> to stop\r\n * cell editing. Note: You can always use F2 and escape to stop editing.\r\n * Default is false.\r\n */\r\nmxGraph.prototype.enterStopsCellEditing = false;\r\n\r\n/**\r\n * Variable: useScrollbarsForPanning\r\n * \r\n * Specifies if scrollbars should be used for panning in <panGraph> if\r\n * any scrollbars are available. If scrollbars are enabled in CSS, but no\r\n * scrollbars appear because the graph is smaller than the container size,\r\n * then no panning occurs if this is true. Default is true.\r\n */\r\nmxGraph.prototype.useScrollbarsForPanning = true;\r\n\r\n/**\r\n * Variable: exportEnabled\r\n * \r\n * Specifies the return value for <canExportCell>. Default is true.\r\n */\r\nmxGraph.prototype.exportEnabled = true;\r\n\r\n/**\r\n * Variable: importEnabled\r\n * \r\n * Specifies the return value for <canImportCell>. Default is true.\r\n */\r\nmxGraph.prototype.importEnabled = true;\r\n\r\n/**\r\n * Variable: cellsLocked\r\n * \r\n * Specifies the return value for <isCellLocked>. Default is false.\r\n */\r\nmxGraph.prototype.cellsLocked = false;\r\n\r\n/**\r\n * Variable: cellsCloneable\r\n * \r\n * Specifies the return value for <isCellCloneable>. Default is true.\r\n */\r\nmxGraph.prototype.cellsCloneable = true;\r\n\r\n/**\r\n * Variable: foldingEnabled\r\n * \r\n * Specifies if folding (collapse and expand via an image icon in the graph\r\n * should be enabled). Default is true.\r\n */\r\nmxGraph.prototype.foldingEnabled = true;\r\n\r\n/**\r\n * Variable: cellsEditable\r\n * \r\n * Specifies the return value for <isCellEditable>. Default is true.\r\n */\r\nmxGraph.prototype.cellsEditable = true;\r\n\t\t\r\n/**\r\n * Variable: cellsDeletable\r\n * \r\n * Specifies the return value for <isCellDeletable>. Default is true.\r\n */\r\nmxGraph.prototype.cellsDeletable = true;\r\n\r\n/**\r\n * Variable: cellsMovable\r\n * \r\n * Specifies the return value for <isCellMovable>. Default is true.\r\n */\r\nmxGraph.prototype.cellsMovable = true;\r\n\t\r\n/**\r\n * Variable: edgeLabelsMovable\r\n * \r\n * Specifies the return value for edges in <isLabelMovable>. Default is true.\r\n */\r\nmxGraph.prototype.edgeLabelsMovable = true;\r\n\t\r\n/**\r\n * Variable: vertexLabelsMovable\r\n * \r\n * Specifies the return value for vertices in <isLabelMovable>. Default is false.\r\n */\r\nmxGraph.prototype.vertexLabelsMovable = false;\r\n\r\n/**\r\n * Variable: dropEnabled\r\n * \r\n * Specifies the return value for <isDropEnabled>. Default is false.\r\n */\r\nmxGraph.prototype.dropEnabled = false;\r\n\r\n/**\r\n * Variable: splitEnabled\r\n * \r\n * Specifies if dropping onto edges should be enabled. This is ignored if\r\n * <dropEnabled> is false. If enabled, it will call <splitEdge> to carry\r\n * out the drop operation. Default is true.\r\n */\r\nmxGraph.prototype.splitEnabled = true;\r\n\r\n/**\r\n * Variable: cellsResizable\r\n * \r\n * Specifies the return value for <isCellResizable>. Default is true.\r\n */\r\nmxGraph.prototype.cellsResizable = true;\r\n\r\n/**\r\n * Variable: cellsBendable\r\n * \r\n * Specifies the return value for <isCellsBendable>. Default is true.\r\n */\r\nmxGraph.prototype.cellsBendable = true;\r\n\r\n/**\r\n * Variable: cellsSelectable\r\n * \r\n * Specifies the return value for <isCellSelectable>. Default is true.\r\n */\r\nmxGraph.prototype.cellsSelectable = true;\r\n\r\n/**\r\n * Variable: cellsDisconnectable\r\n * \r\n * Specifies the return value for <isCellDisconntable>. Default is true.\r\n */\r\nmxGraph.prototype.cellsDisconnectable = true;\r\n\r\n/**\r\n * Variable: autoSizeCells\r\n * \r\n * Specifies if the graph should automatically update the cell size after an\r\n * edit. This is used in <isAutoSizeCell>. Default is false.\r\n */\r\nmxGraph.prototype.autoSizeCells = false;\r\n\r\n/**\r\n * Variable: autoSizeCellsOnAdd\r\n * \r\n * Specifies if autoSize style should be applied when cells are added. Default is false.\r\n */\r\nmxGraph.prototype.autoSizeCellsOnAdd = false;\r\n\r\n/**\r\n * Variable: autoScroll\r\n * \r\n * Specifies if the graph should automatically scroll if the mouse goes near\r\n * the container edge while dragging. This is only taken into account if the\r\n * container has scrollbars. Default is true.\r\n * \r\n * If you need this to work without scrollbars then set <ignoreScrollbars> to\r\n * true. Please consult the <ignoreScrollbars> for details. In general, with\r\n * no scrollbars, the use of <allowAutoPanning> is recommended.\r\n */\r\nmxGraph.prototype.autoScroll = true;\r\n\r\n/**\r\n * Variable: ignoreScrollbars\r\n * \r\n * Specifies if the graph should automatically scroll regardless of the\r\n * scrollbars. This will scroll the container using positive values for\r\n * scroll positions (ie usually only rightwards and downwards). To avoid\r\n * possible conflicts with panning, set <translateToScrollPosition> to true.\r\n */\r\nmxGraph.prototype.ignoreScrollbars = false;\r\n\r\n/**\r\n * Variable: translateToScrollPosition\r\n * \r\n * Specifies if the graph should automatically convert the current scroll\r\n * position to a translate in the graph view when a mouseUp event is received.\r\n * This can be used to avoid conflicts when using <autoScroll> and\r\n * <ignoreScrollbars> with no scrollbars in the container.\r\n */\r\nmxGraph.prototype.translateToScrollPosition = false;\r\n\r\n/**\r\n * Variable: timerAutoScroll\r\n * \r\n * Specifies if autoscrolling should be carried out via mxPanningManager even\r\n * if the container has scrollbars. This disables <scrollPointToVisible> and\r\n * uses <mxPanningManager> instead. If this is true then <autoExtend> is\r\n * disabled. It should only be used with a scroll buffer or when scollbars\r\n * are visible and scrollable in all directions. Default is false.\r\n */\r\nmxGraph.prototype.timerAutoScroll = false;\r\n\r\n/**\r\n * Variable: allowAutoPanning\r\n * \r\n * Specifies if panning via <panGraph> should be allowed to implement autoscroll\r\n * if no scrollbars are available in <scrollPointToVisible>. To enable panning\r\n * inside the container, near the edge, set <mxPanningManager.border> to a\r\n * positive value. Default is false.\r\n */\r\nmxGraph.prototype.allowAutoPanning = false;\r\n\r\n/**\r\n * Variable: autoExtend\r\n * \r\n * Specifies if the size of the graph should be automatically extended if the\r\n * mouse goes near the container edge while dragging. This is only taken into\r\n * account if the container has scrollbars. Default is true. See <autoScroll>.\r\n */\r\nmxGraph.prototype.autoExtend = true;\r\n\r\n/**\r\n * Variable: maximumGraphBounds\r\n * \r\n * <mxRectangle> that specifies the area in which all cells in the diagram\r\n * should be placed. Uses in <getMaximumGraphBounds>. Use a width or height of\r\n * 0 if you only want to give a upper, left corner.\r\n */\r\nmxGraph.prototype.maximumGraphBounds = null;\r\n\r\n/**\r\n * Variable: minimumGraphSize\r\n * \r\n * <mxRectangle> that specifies the minimum size of the graph. This is ignored\r\n * if the graph container has no scrollbars. Default is null.\r\n */\r\nmxGraph.prototype.minimumGraphSize = null;\r\n\r\n/**\r\n * Variable: minimumContainerSize\r\n * \r\n * <mxRectangle> that specifies the minimum size of the <container> if\r\n * <resizeContainer> is true.\r\n */\r\nmxGraph.prototype.minimumContainerSize = null;\r\n\t\t\r\n/**\r\n * Variable: maximumContainerSize\r\n * \r\n * <mxRectangle> that specifies the maximum size of the container if\r\n * <resizeContainer> is true.\r\n */\r\nmxGraph.prototype.maximumContainerSize = null;\r\n\r\n/**\r\n * Variable: resizeContainer\r\n * \r\n * Specifies if the container should be resized to the graph size when\r\n * the graph size has changed. Default is false.\r\n */\r\nmxGraph.prototype.resizeContainer = false;\r\n\r\n/**\r\n * Variable: border\r\n * \r\n * Border to be added to the bottom and right side when the container is\r\n * being resized after the graph has been changed. Default is 0.\r\n */\r\nmxGraph.prototype.border = 0;\r\n\t\t\r\n/**\r\n * Variable: keepEdgesInForeground\r\n * \r\n * Specifies if edges should appear in the foreground regardless of their order\r\n * in the model. If <keepEdgesInForeground> and <keepEdgesInBackground> are\r\n * both true then the normal order is applied. Default is false.\r\n */\r\nmxGraph.prototype.keepEdgesInForeground = false;\r\n\r\n/**\r\n * Variable: keepEdgesInBackground\r\n * \r\n * Specifies if edges should appear in the background regardless of their order\r\n * in the model. If <keepEdgesInForeground> and <keepEdgesInBackground> are\r\n * both true then the normal order is applied. Default is false.\r\n */\r\nmxGraph.prototype.keepEdgesInBackground = false;\r\n\r\n/**\r\n * Variable: allowNegativeCoordinates\r\n * \r\n * Specifies if negative coordinates for vertices are allowed. Default is true.\r\n */\r\nmxGraph.prototype.allowNegativeCoordinates = true;\r\n\r\n/**\r\n * Variable: constrainChildren\r\n * \r\n * Specifies if a child should be constrained inside the parent bounds after a\r\n * move or resize of the child. Default is true.\r\n */\r\nmxGraph.prototype.constrainChildren = true;\r\n\r\n/**\r\n * Variable: constrainRelativeChildren\r\n * \r\n * Specifies if child cells with relative geometries should be constrained\r\n * inside the parent bounds, if <constrainChildren> is true, and/or the\r\n * <maximumGraphBounds>. Default is false.\r\n */\r\nmxGraph.prototype.constrainRelativeChildren = false;\r\n\r\n/**\r\n * Variable: extendParents\r\n * \r\n * Specifies if a parent should contain the child bounds after a resize of\r\n * the child. Default is true. This has precedence over <constrainChildren>.\r\n */\r\nmxGraph.prototype.extendParents = true;\r\n\r\n/**\r\n * Variable: extendParentsOnAdd\r\n * \r\n * Specifies if parents should be extended according to the <extendParents>\r\n * switch if cells are added. Default is true.\r\n */\r\nmxGraph.prototype.extendParentsOnAdd = true;\r\n\r\n/**\r\n * Variable: extendParentsOnAdd\r\n * \r\n * Specifies if parents should be extended according to the <extendParents>\r\n * switch if cells are added. Default is false for backwards compatiblity.\r\n */\r\nmxGraph.prototype.extendParentsOnMove = false;\r\n\r\n/**\r\n * Variable: recursiveResize\r\n * \r\n * Specifies the return value for <isRecursiveResize>. Default is\r\n * false for backwards compatiblity.\r\n */\r\nmxGraph.prototype.recursiveResize = false;\r\n\r\n/**\r\n * Variable: collapseToPreferredSize\r\n * \r\n * Specifies if the cell size should be changed to the preferred size when\r\n * a cell is first collapsed. Default is true.\r\n */\r\nmxGraph.prototype.collapseToPreferredSize = true;\r\n\r\n/**\r\n * Variable: zoomFactor\r\n * \r\n * Specifies the factor used for <zoomIn> and <zoomOut>. Default is 1.2\r\n * (120%).\r\n */\r\nmxGraph.prototype.zoomFactor = 1.2;\r\n\r\n/**\r\n * Variable: keepSelectionVisibleOnZoom\r\n * \r\n * Specifies if the viewport should automatically contain the selection cells\r\n * after a zoom operation. Default is false.\r\n */\r\nmxGraph.prototype.keepSelectionVisibleOnZoom = false;\r\n\r\n/**\r\n * Variable: centerZoom\r\n * \r\n * Specifies if the zoom operations should go into the center of the actual\r\n * diagram rather than going from top, left. Default is true.\r\n */\r\nmxGraph.prototype.centerZoom = true;\r\n\r\n/**\r\n * Variable: resetViewOnRootChange\r\n * \r\n * Specifies if the scale and translate should be reset if the root changes in\r\n * the model. Default is true.\r\n */\r\nmxGraph.prototype.resetViewOnRootChange = true;\r\n\r\n/**\r\n * Variable: resetEdgesOnResize\r\n * \r\n * Specifies if edge control points should be reset after the resize of a\r\n * connected cell. Default is false.\r\n */\r\nmxGraph.prototype.resetEdgesOnResize = false;\r\n\r\n/**\r\n * Variable: resetEdgesOnMove\r\n * \r\n * Specifies if edge control points should be reset after the move of a\r\n * connected cell. Default is false.\r\n */\r\nmxGraph.prototype.resetEdgesOnMove = false;\r\n\r\n/**\r\n * Variable: resetEdgesOnConnect\r\n * \r\n * Specifies if edge control points should be reset after the the edge has been\r\n * reconnected. Default is true.\r\n */\r\nmxGraph.prototype.resetEdgesOnConnect = true;\r\n\r\n/**\r\n * Variable: allowLoops\r\n * \r\n * Specifies if loops (aka self-references) are allowed. Default is false.\r\n */\r\nmxGraph.prototype.allowLoops = false;\r\n\t\r\n/**\r\n * Variable: defaultLoopStyle\r\n * \r\n * <mxEdgeStyle> to be used for loops. This is a fallback for loops if the\r\n * <mxConstants.STYLE_LOOP> is undefined. Default is <mxEdgeStyle.Loop>.\r\n */\r\nmxGraph.prototype.defaultLoopStyle = mxEdgeStyle.Loop;\r\n\r\n/**\r\n * Variable: multigraph\r\n * \r\n * Specifies if multiple edges in the same direction between the same pair of\r\n * vertices are allowed. Default is true.\r\n */\r\nmxGraph.prototype.multigraph = true;\r\n\r\n/**\r\n * Variable: connectableEdges\r\n * \r\n * Specifies if edges are connectable. Default is false. This overrides the\r\n * connectable field in edges.\r\n */\r\nmxGraph.prototype.connectableEdges = false;\r\n\r\n/**\r\n * Variable: allowDanglingEdges\r\n * \r\n * Specifies if edges with disconnected terminals are allowed in the graph.\r\n * Default is true.\r\n */\r\nmxGraph.prototype.allowDanglingEdges = true;\r\n\r\n/**\r\n * Variable: cloneInvalidEdges\r\n * \r\n * Specifies if edges that are cloned should be validated and only inserted\r\n * if they are valid. Default is true.\r\n */\r\nmxGraph.prototype.cloneInvalidEdges = false;\r\n\r\n/**\r\n * Variable: disconnectOnMove\r\n * \r\n * Specifies if edges should be disconnected from their terminals when they\r\n * are moved. Default is true.\r\n */\r\nmxGraph.prototype.disconnectOnMove = true;\r\n\r\n/**\r\n * Variable: labelsVisible\r\n * \r\n * Specifies if labels should be visible. This is used in <getLabel>. Default\r\n * is true.\r\n */\r\nmxGraph.prototype.labelsVisible = true;\r\n\t\r\n/**\r\n * Variable: htmlLabels\r\n * \r\n * Specifies the return value for <isHtmlLabel>. Default is false.\r\n */\r\nmxGraph.prototype.htmlLabels = false;\r\n\r\n/**\r\n * Variable: swimlaneSelectionEnabled\r\n * \r\n * Specifies if swimlanes should be selectable via the content if the\r\n * mouse is released. Default is true.\r\n */\r\nmxGraph.prototype.swimlaneSelectionEnabled = true;\r\n\r\n/**\r\n * Variable: swimlaneNesting\r\n * \r\n * Specifies if nesting of swimlanes is allowed. Default is true.\r\n */\r\nmxGraph.prototype.swimlaneNesting = true;\r\n\t\r\n/**\r\n * Variable: swimlaneIndicatorColorAttribute\r\n * \r\n * The attribute used to find the color for the indicator if the indicator\r\n * color is set to 'swimlane'. Default is <mxConstants.STYLE_FILLCOLOR>.\r\n */\r\nmxGraph.prototype.swimlaneIndicatorColorAttribute = mxConstants.STYLE_FILLCOLOR;\r\n\r\n/**\r\n * Variable: imageBundles\r\n * \r\n * Holds the list of image bundles.\r\n */\r\nmxGraph.prototype.imageBundles = null;\r\n\r\n/**\r\n * Variable: minFitScale\r\n * \r\n * Specifies the minimum scale to be applied in <fit>. Default is 0.1. Set this\r\n * to null to allow any value.\r\n */\r\nmxGraph.prototype.minFitScale = 0.1;\r\n\r\n/**\r\n * Variable: maxFitScale\r\n * \r\n * Specifies the maximum scale to be applied in <fit>. Default is 8. Set this\r\n * to null to allow any value.\r\n */\r\nmxGraph.prototype.maxFitScale = 8;\r\n\r\n/**\r\n * Variable: panDx\r\n * \r\n * Current horizontal panning value. Default is 0.\r\n */\r\nmxGraph.prototype.panDx = 0;\r\n\r\n/**\r\n * Variable: panDy\r\n * \r\n * Current vertical panning value. Default is 0.\r\n */\r\nmxGraph.prototype.panDy = 0;\r\n\r\n/**\r\n * Variable: collapsedImage\r\n * \r\n * Specifies the <mxImage> to indicate a collapsed state.\r\n * Default value is mxClient.imageBasePath + '/collapsed.gif'\r\n */\r\nmxGraph.prototype.collapsedImage = new mxImage(mxClient.imageBasePath + '/collapsed.gif', 9, 9);\r\n\r\n/**\r\n * Variable: expandedImage\r\n * \r\n * Specifies the <mxImage> to indicate a expanded state.\r\n * Default value is mxClient.imageBasePath + '/expanded.gif'\r\n */\r\nmxGraph.prototype.expandedImage = new mxImage(mxClient.imageBasePath + '/expanded.gif', 9, 9);\r\n\r\n/**\r\n * Variable: warningImage\r\n * \r\n * Specifies the <mxImage> for the image to be used to display a warning\r\n * overlay. See <setCellWarning>. Default value is mxClient.imageBasePath +\r\n * '/warning'.  The extension for the image depends on the platform. It is\r\n * '.png' on the Mac and '.gif' on all other platforms.\r\n */\r\nmxGraph.prototype.warningImage = new mxImage(mxClient.imageBasePath + '/warning'+\r\n\t((mxClient.IS_MAC) ? '.png' : '.gif'), 16, 16);\r\n\r\n/**\r\n * Variable: alreadyConnectedResource\r\n * \r\n * Specifies the resource key for the error message to be displayed in\r\n * non-multigraphs when two vertices are already connected. If the resource\r\n * for this key does not exist then the value is used as the error message.\r\n * Default is 'alreadyConnected'.\r\n */\r\nmxGraph.prototype.alreadyConnectedResource = (mxClient.language != 'none') ? 'alreadyConnected' : '';\r\n\r\n/**\r\n * Variable: containsValidationErrorsResource\r\n * \r\n * Specifies the resource key for the warning message to be displayed when\r\n * a collapsed cell contains validation errors. If the resource for this\r\n * key does not exist then the value is used as the warning message.\r\n * Default is 'containsValidationErrors'.\r\n */\r\nmxGraph.prototype.containsValidationErrorsResource = (mxClient.language != 'none') ? 'containsValidationErrors' : '';\r\n\r\n/**\r\n * Variable: collapseExpandResource\r\n * \r\n * Specifies the resource key for the tooltip on the collapse/expand icon.\r\n * If the resource for this key does not exist then the value is used as\r\n * the tooltip. Default is 'collapse-expand'.\r\n */\r\nmxGraph.prototype.collapseExpandResource = (mxClient.language != 'none') ? 'collapse-expand' : '';\r\n\r\n/**\r\n * Function: init\r\n * \r\n * Initializes the <container> and creates the respective datastructures.\r\n * \r\n * Parameters:\r\n * \r\n * container - DOM node that will contain the graph display.\r\n */\r\nmxGraph.prototype.init = function(container)\r\n{\r\n\tthis.container = container;\r\n\t\r\n\t// Initializes the in-place editor\r\n\tthis.cellEditor = this.createCellEditor();\t\r\n\r\n\t// Initializes the container using the view\r\n\tthis.view.init();\r\n\t\r\n\t// Updates the size of the container for the current graph\r\n\tthis.sizeDidChange();\r\n\t\r\n\t// Hides tooltips and resets tooltip timer if mouse leaves container\r\n\tmxEvent.addListener(container, 'mouseleave', mxUtils.bind(this, function()\r\n\t{\r\n\t\tif (this.tooltipHandler != null)\r\n\t\t{\r\n\t\t\tthis.tooltipHandler.hide();\r\n\t\t}\r\n\t}));\r\n\r\n\t// Automatic deallocation of memory\r\n\tif (mxClient.IS_IE)\r\n\t{\r\n\t\tmxEvent.addListener(window, 'unload', mxUtils.bind(this, function()\r\n\t\t{\r\n\t\t\tthis.destroy();\r\n\t\t}));\r\n\t\t\r\n\t\t// Disable shift-click for text\r\n\t\tmxEvent.addListener(container, 'selectstart',\r\n\t\t\tmxUtils.bind(this, function(evt)\r\n\t\t\t{\r\n\t\t\t\treturn this.isEditing() || (!this.isMouseDown && !mxEvent.isShiftDown(evt));\r\n\t\t\t})\r\n\t\t);\r\n\t}\r\n\t\r\n\t// Workaround for missing last shape and connect preview in IE8 standards\r\n\t// mode if no initial graph displayed or no label for shape defined\r\n\tif (document.documentMode == 8)\r\n\t{\r\n\t\tcontainer.insertAdjacentHTML('beforeend', '<' + mxClient.VML_PREFIX + ':group' +\r\n\t\t\t' style=\"DISPLAY: none;\"></' + mxClient.VML_PREFIX + ':group>');\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createHandlers\r\n * \r\n * Creates the tooltip-, panning-, connection- and graph-handler (in this\r\n * order). This is called in the constructor before <init> is called.\r\n */\r\nmxGraph.prototype.createHandlers = function()\r\n{\r\n\tthis.tooltipHandler = this.createTooltipHandler();\r\n\tthis.tooltipHandler.setEnabled(false);\r\n\tthis.selectionCellsHandler = this.createSelectionCellsHandler();\r\n\tthis.connectionHandler = this.createConnectionHandler();\r\n\tthis.connectionHandler.setEnabled(false);\r\n\tthis.graphHandler = this.createGraphHandler();\r\n\tthis.panningHandler = this.createPanningHandler();\r\n\tthis.panningHandler.panningEnabled = false;\r\n\tthis.popupMenuHandler = this.createPopupMenuHandler();\r\n};\r\n\r\n/**\r\n * Function: createTooltipHandler\r\n * \r\n * Creates and returns a new <mxTooltipHandler> to be used in this graph.\r\n */\r\nmxGraph.prototype.createTooltipHandler = function()\r\n{\r\n\treturn new mxTooltipHandler(this);\r\n};\r\n\r\n/**\r\n * Function: createSelectionCellsHandler\r\n * \r\n * Creates and returns a new <mxTooltipHandler> to be used in this graph.\r\n */\r\nmxGraph.prototype.createSelectionCellsHandler = function()\r\n{\r\n\treturn new mxSelectionCellsHandler(this);\r\n};\r\n\r\n/**\r\n * Function: createConnectionHandler\r\n * \r\n * Creates and returns a new <mxConnectionHandler> to be used in this graph.\r\n */\r\nmxGraph.prototype.createConnectionHandler = function()\r\n{\r\n\treturn new mxConnectionHandler(this);\r\n};\r\n\r\n/**\r\n * Function: createGraphHandler\r\n * \r\n * Creates and returns a new <mxGraphHandler> to be used in this graph.\r\n */\r\nmxGraph.prototype.createGraphHandler = function()\r\n{\r\n\treturn new mxGraphHandler(this);\r\n};\r\n\r\n/**\r\n * Function: createPanningHandler\r\n * \r\n * Creates and returns a new <mxPanningHandler> to be used in this graph.\r\n */\r\nmxGraph.prototype.createPanningHandler = function()\r\n{\r\n\treturn new mxPanningHandler(this);\r\n};\r\n\r\n/**\r\n * Function: createPopupMenuHandler\r\n * \r\n * Creates and returns a new <mxPopupMenuHandler> to be used in this graph.\r\n */\r\nmxGraph.prototype.createPopupMenuHandler = function()\r\n{\r\n\treturn new mxPopupMenuHandler(this);\r\n};\r\n\r\n/**\r\n * Function: createSelectionModel\r\n * \r\n * Creates a new <mxGraphSelectionModel> to be used in this graph.\r\n */\r\nmxGraph.prototype.createSelectionModel = function()\r\n{\r\n\treturn new mxGraphSelectionModel(this);\r\n};\r\n\r\n/**\r\n * Function: createStylesheet\r\n * \r\n * Creates a new <mxGraphSelectionModel> to be used in this graph.\r\n */\r\nmxGraph.prototype.createStylesheet = function()\r\n{\r\n\treturn new mxStylesheet();\r\n};\r\n\r\n/**\r\n * Function: createGraphView\r\n * \r\n * Creates a new <mxGraphView> to be used in this graph.\r\n */\r\nmxGraph.prototype.createGraphView = function()\r\n{\r\n\treturn new mxGraphView(this);\r\n};\r\n \r\n/**\r\n * Function: createCellRenderer\r\n * \r\n * Creates a new <mxCellRenderer> to be used in this graph.\r\n */\r\nmxGraph.prototype.createCellRenderer = function()\r\n{\r\n\treturn new mxCellRenderer();\r\n};\r\n\r\n/**\r\n * Function: createCellEditor\r\n * \r\n * Creates a new <mxCellEditor> to be used in this graph.\r\n */\r\nmxGraph.prototype.createCellEditor = function()\r\n{\r\n\treturn new mxCellEditor(this);\r\n};\r\n\r\n/**\r\n * Function: getModel\r\n * \r\n * Returns the <mxGraphModel> that contains the cells.\r\n */\r\nmxGraph.prototype.getModel = function()\r\n{\r\n\treturn this.model;\r\n};\r\n\r\n/**\r\n * Function: getView\r\n * \r\n * Returns the <mxGraphView> that contains the <mxCellStates>.\r\n */\r\nmxGraph.prototype.getView = function()\r\n{\r\n\treturn this.view;\r\n};\r\n\r\n/**\r\n * Function: getStylesheet\r\n * \r\n * Returns the <mxStylesheet> that defines the style.\r\n */\r\nmxGraph.prototype.getStylesheet = function()\r\n{\r\n\treturn this.stylesheet;\r\n};\r\n\r\n/**\r\n * Function: setStylesheet\r\n * \r\n * Sets the <mxStylesheet> that defines the style.\r\n */\r\nmxGraph.prototype.setStylesheet = function(stylesheet)\r\n{\r\n\tthis.stylesheet = stylesheet;\r\n};\r\n\r\n/**\r\n * Function: getSelectionModel\r\n * \r\n * Returns the <mxGraphSelectionModel> that contains the selection.\r\n */\r\nmxGraph.prototype.getSelectionModel = function()\r\n{\r\n\treturn this.selectionModel;\r\n};\r\n\r\n/**\r\n * Function: setSelectionModel\r\n * \r\n * Sets the <mxSelectionModel> that contains the selection.\r\n */\r\nmxGraph.prototype.setSelectionModel = function(selectionModel)\r\n{\r\n\tthis.selectionModel = selectionModel;\r\n};\r\n\r\n/**\r\n * Function: getSelectionCellsForChanges\r\n * \r\n * Returns the cells to be selected for the given array of changes.\r\n */\r\nmxGraph.prototype.getSelectionCellsForChanges = function(changes)\r\n{\r\n\tvar dict = new mxDictionary();\r\n\tvar cells = [];\r\n\t\r\n\tvar addCell = mxUtils.bind(this, function(cell)\r\n\t{\r\n\t\tif (!dict.get(cell) && this.model.contains(cell))\r\n\t\t{\r\n\t\t\tif (this.model.isEdge(cell) || this.model.isVertex(cell))\r\n\t\t\t{\r\n\t\t\t\tdict.put(cell, true);\r\n\t\t\t\tcells.push(cell);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tvar childCount = this.model.getChildCount(cell);\r\n\t\t\t\t\r\n\t\t\t\tfor (var i = 0; i < childCount; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\taddCell(this.model.getChildAt(cell, i));\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t});\r\n\r\n\tfor (var i = 0; i < changes.length; i++)\r\n\t{\r\n\t\tvar change = changes[i];\r\n\t\t\r\n\t\tif (change.constructor != mxRootChange)\r\n\t\t{\r\n\t\t\tvar cell = null;\r\n\r\n\t\t\tif (change instanceof mxChildChange)\r\n\t\t\t{\r\n\t\t\t\tcell = change.child;\r\n\t\t\t}\r\n\t\t\telse if (change.cell != null && change.cell instanceof mxCell)\r\n\t\t\t{\r\n\t\t\t\tcell = change.cell;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (cell != null)\r\n\t\t\t{\r\n\t\t\t\taddCell(cell);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn cells;\r\n};\r\n\r\n/**\r\n * Function: graphModelChanged\r\n * \r\n * Called when the graph model changes. Invokes <processChange> on each\r\n * item of the given array to update the view accordingly.\r\n * \r\n * Parameters:\r\n * \r\n * changes - Array that contains the individual changes.\r\n */\r\nmxGraph.prototype.graphModelChanged = function(changes)\r\n{\r\n\tfor (var i = 0; i < changes.length; i++)\r\n\t{\r\n\t\tthis.processChange(changes[i]);\r\n\t}\r\n\r\n\tthis.updateSelection();\r\n\tthis.view.validate();\r\n\tthis.sizeDidChange();\r\n};\r\n\r\n/**\r\n * Function: updateSelection\r\n * \r\n * Removes selection cells that are not in the model from the selection.\r\n */\r\nmxGraph.prototype.updateSelection = function()\r\n{\r\n\tvar cells = this.getSelectionCells();\r\n\tvar removed = [];\r\n\t\r\n\tfor (var i = 0; i < cells.length; i++)\r\n\t{\r\n\t\tif (!this.model.contains(cells[i]) || !this.isCellVisible(cells[i]))\r\n\t\t{\r\n\t\t\tremoved.push(cells[i]);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar par = this.model.getParent(cells[i]);\r\n\t\t\t\r\n\t\t\twhile (par != null && par != this.view.currentRoot)\r\n\t\t\t{\r\n\t\t\t\tif (this.isCellCollapsed(par) || !this.isCellVisible(par))\r\n\t\t\t\t{\r\n\t\t\t\t\tremoved.push(cells[i]);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tpar = this.model.getParent(par);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\tthis.removeSelectionCells(removed);\r\n};\r\n\r\n/**\r\n * Function: processChange\r\n * \r\n * Processes the given change and invalidates the respective cached data\r\n * in <view>. This fires a <root> event if the root has changed in the\r\n * model.\r\n * \r\n * Parameters:\r\n * \r\n * change - Object that represents the change on the model.\r\n */\r\nmxGraph.prototype.processChange = function(change)\r\n{\r\n\t// Resets the view settings, removes all cells and clears\r\n\t// the selection if the root changes.\r\n\tif (change instanceof mxRootChange)\r\n\t{\r\n\t\tthis.clearSelection();\r\n\t\tthis.setDefaultParent(null);\r\n\t\tthis.removeStateForCell(change.previous);\r\n\t\t\r\n\t\tif (this.resetViewOnRootChange)\r\n\t\t{\r\n\t\t\tthis.view.scale = 1;\r\n\t\t\tthis.view.translate.x = 0;\r\n\t\t\tthis.view.translate.y = 0;\r\n\t\t}\r\n\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.ROOT));\r\n\t}\r\n\t\r\n\t// Adds or removes a child to the view by online invaliding\r\n\t// the minimal required portions of the cache, namely, the\r\n\t// old and new parent and the child.\r\n\telse if (change instanceof mxChildChange)\r\n\t{\r\n\t\tvar newParent = this.model.getParent(change.child);\r\n\t\tthis.view.invalidate(change.child, true, true);\r\n\t\t\r\n\t\tif (!this.model.contains(newParent) || this.isCellCollapsed(newParent))\r\n\t\t{\r\n\t\t\tthis.view.invalidate(change.child, true, true);\r\n\t\t\tthis.removeStateForCell(change.child);\r\n\t\t\t\r\n\t\t\t// Handles special case of current root of view being removed\r\n\t\t\tif (this.view.currentRoot == change.child)\r\n\t\t\t{\r\n\t\t\t\tthis.home();\r\n\t\t\t}\r\n\t\t}\r\n \r\n\t\tif (newParent != change.previous)\r\n\t\t{\r\n\t\t\t// Refreshes the collapse/expand icons on the parents\r\n\t\t\tif (newParent != null)\r\n\t\t\t{\r\n\t\t\t\tthis.view.invalidate(newParent, false, false);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (change.previous != null)\r\n\t\t\t{\r\n\t\t\t\tthis.view.invalidate(change.previous, false, false);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// Handles two special cases where the shape does not need to be\r\n\t// recreated from scratch, it only needs to be invalidated.\r\n\telse if (change instanceof mxTerminalChange || change instanceof mxGeometryChange)\r\n\t{\r\n\t\t// Checks if the geometry has changed to avoid unnessecary revalidation\r\n\t\tif (change instanceof mxTerminalChange || ((change.previous == null && change.geometry != null) ||\r\n\t\t\t(change.previous != null && !change.previous.equals(change.geometry))))\r\n\t\t{\r\n\t\t\tthis.view.invalidate(change.cell);\r\n\t\t}\r\n\t}\r\n\r\n\t// Handles two special cases where only the shape, but no\r\n\t// descendants need to be recreated\r\n\telse if (change instanceof mxValueChange)\r\n\t{\r\n\t\tthis.view.invalidate(change.cell, false, false);\r\n\t}\r\n\t\r\n\t// Requires a new mxShape in JavaScript\r\n\telse if (change instanceof mxStyleChange)\r\n\t{\r\n\t\tthis.view.invalidate(change.cell, true, true);\r\n\t\tvar state = this.view.getState(change.cell);\r\n\t\t\r\n\t\tif (state != null)\r\n\t\t{\r\n\t\t\tstate.invalidStyle = true;\r\n\t\t}\r\n\t}\r\n\t\r\n\t// Removes the state from the cache by default\r\n\telse if (change.cell != null && change.cell instanceof mxCell)\r\n\t{\r\n\t\tthis.removeStateForCell(change.cell);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: removeStateForCell\r\n * \r\n * Removes all cached information for the given cell and its descendants.\r\n * This is called when a cell was removed from the model.\r\n * \r\n * Paramters:\r\n * \r\n * cell - <mxCell> that was removed from the model.\r\n */\r\nmxGraph.prototype.removeStateForCell = function(cell)\r\n{\r\n\tvar childCount = this.model.getChildCount(cell);\r\n\t\r\n\tfor (var i = 0; i < childCount; i++)\r\n\t{\r\n\t\tthis.removeStateForCell(this.model.getChildAt(cell, i));\r\n\t}\r\n\r\n\tthis.view.invalidate(cell, false, true);\r\n\tthis.view.removeState(cell);\r\n};\r\n\r\n/**\r\n * Group: Overlays\r\n */\r\n\r\n/**\r\n * Function: addCellOverlay\r\n * \r\n * Adds an <mxCellOverlay> for the specified cell. This method fires an\r\n * <addoverlay> event and returns the new <mxCellOverlay>.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> to add the overlay for.\r\n * overlay - <mxCellOverlay> to be added for the cell.\r\n */\r\nmxGraph.prototype.addCellOverlay = function(cell, overlay)\r\n{\r\n\tif (cell.overlays == null)\r\n\t{\r\n\t\tcell.overlays = [];\r\n\t}\r\n\t\r\n\tcell.overlays.push(overlay);\r\n\r\n\tvar state = this.view.getState(cell);\r\n\r\n\t// Immediately updates the cell display if the state exists\r\n\tif (state != null)\r\n\t{\r\n\t\tthis.cellRenderer.redraw(state);\r\n\t}\r\n\t\r\n\tthis.fireEvent(new mxEventObject(mxEvent.ADD_OVERLAY,\r\n\t\t\t'cell', cell, 'overlay', overlay));\r\n\t\r\n\treturn overlay;\r\n};\r\n\r\n/**\r\n * Function: getCellOverlays\r\n * \r\n * Returns the array of <mxCellOverlays> for the given cell or null, if\r\n * no overlays are defined.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose overlays should be returned.\r\n */\r\nmxGraph.prototype.getCellOverlays = function(cell)\r\n{\r\n\treturn cell.overlays;\r\n};\r\n\r\n/**\r\n * Function: removeCellOverlay\r\n * \r\n * Removes and returns the given <mxCellOverlay> from the given cell. This\r\n * method fires a <removeoverlay> event. If no overlay is given, then all\r\n * overlays are removed using <removeOverlays>.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose overlay should be removed.\r\n * overlay - Optional <mxCellOverlay> to be removed.\r\n */\r\nmxGraph.prototype.removeCellOverlay = function(cell, overlay)\r\n{\r\n\tif (overlay == null)\r\n\t{\r\n\t\tthis.removeCellOverlays(cell);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar index = mxUtils.indexOf(cell.overlays, overlay);\r\n\t\t\r\n\t\tif (index >= 0)\r\n\t\t{\r\n\t\t\tcell.overlays.splice(index, 1);\r\n\t\t\t\r\n\t\t\tif (cell.overlays.length == 0)\r\n\t\t\t{\r\n\t\t\t\tcell.overlays = null;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Immediately updates the cell display if the state exists\r\n\t\t\tvar state = this.view.getState(cell);\r\n\t\t\t\r\n\t\t\tif (state != null)\r\n\t\t\t{\r\n\t\t\t\tthis.cellRenderer.redraw(state);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY,\r\n\t\t\t\t\t'cell', cell, 'overlay', overlay));\t\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\toverlay = null;\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn overlay;\r\n};\r\n\r\n/**\r\n * Function: removeCellOverlays\r\n * \r\n * Removes all <mxCellOverlays> from the given cell. This method\r\n * fires a <removeoverlay> event for each <mxCellOverlay> and returns\r\n * the array of <mxCellOverlays> that was removed from the cell.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose overlays should be removed\r\n */\r\nmxGraph.prototype.removeCellOverlays = function(cell)\r\n{\r\n\tvar overlays = cell.overlays;\r\n\t\r\n\tif (overlays != null)\r\n\t{\r\n\t\tcell.overlays = null;\r\n\t\t\r\n\t\t// Immediately updates the cell display if the state exists\r\n\t\tvar state = this.view.getState(cell);\r\n\t\t\r\n\t\tif (state != null)\r\n\t\t{\r\n\t\t\tthis.cellRenderer.redraw(state);\r\n\t\t}\r\n\t\t\r\n\t\tfor (var i = 0; i < overlays.length; i++)\r\n\t\t{\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY,\r\n\t\t\t\t\t'cell', cell, 'overlay', overlays[i]));\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn overlays;\r\n};\r\n\r\n/**\r\n * Function: clearCellOverlays\r\n * \r\n * Removes all <mxCellOverlays> in the graph for the given cell and all its\r\n * descendants. If no cell is specified then all overlays are removed from\r\n * the graph. This implementation uses <removeCellOverlays> to remove the\r\n * overlays from the individual cells.\r\n * \r\n * Parameters:\r\n * \r\n * cell - Optional <mxCell> that represents the root of the subtree to\r\n * remove the overlays from. Default is the root in the model.\r\n */\r\nmxGraph.prototype.clearCellOverlays = function(cell)\r\n{\r\n\tcell = (cell != null) ? cell : this.model.getRoot();\r\n\tthis.removeCellOverlays(cell);\r\n\t\r\n\t// Recursively removes all overlays from the children\r\n\tvar childCount = this.model.getChildCount(cell);\r\n\t\r\n\tfor (var i = 0; i < childCount; i++)\r\n\t{\r\n\t\tvar child = this.model.getChildAt(cell, i);\r\n\t\tthis.clearCellOverlays(child); // recurse\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setCellWarning\r\n * \r\n * Creates an overlay for the given cell using the warning and image or\r\n * <warningImage> and returns the new <mxCellOverlay>. The warning is\r\n * displayed as a tooltip in a red font and may contain HTML markup. If\r\n * the warning is null or a zero length string, then all overlays are\r\n * removed from the cell.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * graph.setCellWarning(cell, '<b>Warning:</b>: Hello, World!');\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose warning should be set.\r\n * warning - String that represents the warning to be displayed.\r\n * img - Optional <mxImage> to be used for the overlay. Default is\r\n * <warningImage>.\r\n * isSelect - Optional boolean indicating if a click on the overlay\r\n * should select the corresponding cell. Default is false.\r\n */\r\nmxGraph.prototype.setCellWarning = function(cell, warning, img, isSelect)\r\n{\r\n\tif (warning != null && warning.length > 0)\r\n\t{\r\n\t\timg = (img != null) ? img : this.warningImage;\r\n\t\t\r\n\t\t// Creates the overlay with the image and warning\r\n\t\tvar overlay = new mxCellOverlay(img,\r\n\t\t\t'<font color=red>'+warning+'</font>');\r\n\t\t\r\n\t\t// Adds a handler for single mouseclicks to select the cell\r\n\t\tif (isSelect)\r\n\t\t{\r\n\t\t\toverlay.addListener(mxEvent.CLICK,\r\n\t\t\t\tmxUtils.bind(this, function(sender, evt)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (this.isEnabled())\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.setSelectionCell(cell);\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t);\r\n\t\t}\r\n\t\t\r\n\t\t// Sets and returns the overlay in the graph\r\n\t\treturn this.addCellOverlay(cell, overlay);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.removeCellOverlays(cell);\r\n\t}\r\n\t\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Group: In-place editing\r\n */\r\n\r\n/**\r\n * Function: startEditing\r\n * \r\n * Calls <startEditingAtCell> using the given cell or the first selection\r\n * cell.\r\n * \r\n * Parameters:\r\n * \r\n * evt - Optional mouse event that triggered the editing.\r\n */\r\nmxGraph.prototype.startEditing = function(evt)\r\n{\r\n\tthis.startEditingAtCell(null, evt);\r\n};\r\n\r\n/**\r\n * Function: startEditingAtCell\r\n * \r\n * Fires a <startEditing> event and invokes <mxCellEditor.startEditing>\r\n * on <editor>. After editing was started, a <editingStarted> event is\r\n * fired.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> to start the in-place editor for.\r\n * evt - Optional mouse event that triggered the editing.\r\n */\r\nmxGraph.prototype.startEditingAtCell = function(cell, evt)\r\n{\r\n\tif (evt == null || !mxEvent.isMultiTouchEvent(evt))\r\n\t{\r\n\t\tif (cell == null)\r\n\t\t{\r\n\t\t\tcell = this.getSelectionCell();\r\n\t\t\t\r\n\t\t\tif (cell != null && !this.isCellEditable(cell))\r\n\t\t\t{\r\n\t\t\t\tcell = null;\r\n\t\t\t}\r\n\t\t}\r\n\t\r\n\t\tif (cell != null)\r\n\t\t{\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.START_EDITING,\r\n\t\t\t\t\t'cell', cell, 'event', evt));\r\n\t\t\tthis.cellEditor.startEditing(cell, evt);\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.EDITING_STARTED,\r\n\t\t\t\t\t'cell', cell, 'event', evt));\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getEditingValue\r\n * \r\n * Returns the initial value for in-place editing. This implementation\r\n * returns <convertValueToString> for the given cell. If this function is\r\n * overridden, then <mxGraphModel.valueForCellChanged> should take care\r\n * of correctly storing the actual new value inside the user object.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> for which the initial editing value should be returned.\r\n * evt - Optional mouse event that triggered the editor.\r\n */\r\nmxGraph.prototype.getEditingValue = function(cell, evt)\r\n{\r\n\treturn this.convertValueToString(cell);\r\n};\r\n\r\n/**\r\n * Function: stopEditing\r\n * \r\n * Stops the current editing  and fires a <editingStopped> event.\r\n * \r\n * Parameters:\r\n * \r\n * cancel - Boolean that specifies if the current editing value\r\n * should be stored.\r\n */\r\nmxGraph.prototype.stopEditing = function(cancel)\r\n{\r\n\tthis.cellEditor.stopEditing(cancel);\r\n\tthis.fireEvent(new mxEventObject(mxEvent.EDITING_STOPPED, 'cancel', cancel));\r\n};\r\n\r\n/**\r\n * Function: labelChanged\r\n * \r\n * Sets the label of the specified cell to the given value using\r\n * <cellLabelChanged> and fires <mxEvent.LABEL_CHANGED> while the\r\n * transaction is in progress. Returns the cell whose label was changed.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose label should be changed.\r\n * value - New label to be assigned.\r\n * evt - Optional event that triggered the change.\r\n */\r\nmxGraph.prototype.labelChanged = function(cell, value, evt)\r\n{\r\n\tthis.model.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tvar old = cell.value;\r\n\t\tthis.cellLabelChanged(cell, value, this.isAutoSizeCell(cell));\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.LABEL_CHANGED,\r\n\t\t\t'cell', cell, 'value', value, 'old', old, 'event', evt));\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tthis.model.endUpdate();\r\n\t}\r\n\t\r\n\treturn cell;\r\n};\r\n\r\n/**\r\n * Function: cellLabelChanged\r\n * \r\n * Sets the new label for a cell. If autoSize is true then\r\n * <cellSizeUpdated> will be called.\r\n * \r\n * In the following example, the function is extended to map changes to\r\n * attributes in an XML node, as shown in <convertValueToString>.\r\n * Alternatively, the handling of this can be implemented as shown in\r\n * <mxGraphModel.valueForCellChanged> without the need to clone the\r\n * user object.\r\n * \r\n * (code)\r\n * var graphCellLabelChanged = graph.cellLabelChanged;\r\n * graph.cellLabelChanged = function(cell, newValue, autoSize)\r\n * {\r\n * \t// Cloned for correct undo/redo\r\n * \tvar elt = cell.value.cloneNode(true);\r\n *  elt.setAttribute('label', newValue);\r\n *  \r\n *  newValue = elt;\r\n *  graphCellLabelChanged.apply(this, arguments);\r\n * };\r\n * (end) \r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose label should be changed.\r\n * value - New label to be assigned.\r\n * autoSize - Boolean that specifies if <cellSizeUpdated> should be called.\r\n */\r\nmxGraph.prototype.cellLabelChanged = function(cell, value, autoSize)\r\n{\r\n\tthis.model.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tthis.model.setValue(cell, value);\r\n\t\t\r\n\t\tif (autoSize)\r\n\t\t{\r\n\t\t\tthis.cellSizeUpdated(cell, false);\r\n\t\t}\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tthis.model.endUpdate();\r\n\t}\r\n};\r\n\r\n/**\r\n * Group: Event processing\r\n */\r\n\r\n/**\r\n * Function: escape\r\n * \r\n * Processes an escape keystroke.\r\n * \r\n * Parameters:\r\n * \r\n * evt - Mouseevent that represents the keystroke.\r\n */\r\nmxGraph.prototype.escape = function(evt)\r\n{\r\n\tthis.fireEvent(new mxEventObject(mxEvent.ESCAPE, 'event', evt));\r\n};\r\n\r\n/**\r\n * Function: click\r\n * \r\n * Processes a singleclick on an optional cell and fires a <click> event.\r\n * The click event is fired initially. If the graph is enabled and the\r\n * event has not been consumed, then the cell is selected using\r\n * <selectCellForEvent> or the selection is cleared using\r\n * <clearSelection>. The events consumed state is set to true if the\r\n * corresponding <mxMouseEvent> has been consumed.\r\n *\r\n * To handle a click event, use the following code.\r\n * \r\n * (code)\r\n * graph.addListener(mxEvent.CLICK, function(sender, evt)\r\n * {\r\n *   var e = evt.getProperty('event'); // mouse event\r\n *   var cell = evt.getProperty('cell'); // cell may be null\r\n *   \r\n *   if (cell != null)\r\n *   {\r\n *     // Do something useful with cell and consume the event\r\n *     evt.consume();\r\n *   }\r\n * });\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * me - <mxMouseEvent> that represents the single click.\r\n */\r\nmxGraph.prototype.click = function(me)\r\n{\r\n\tvar evt = me.getEvent();\r\n\tvar cell = me.getCell();\r\n\tvar mxe = new mxEventObject(mxEvent.CLICK, 'event', evt, 'cell', cell);\r\n\t\r\n\tif (me.isConsumed())\r\n\t{\r\n\t\tmxe.consume();\r\n\t}\r\n\t\r\n\tthis.fireEvent(mxe);\r\n\t\r\n\t// Handles the event if it has not been consumed\r\n\tif (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed())\r\n\t{\r\n\t\tif (cell != null)\r\n\t\t{\r\n\t\t\tif (this.isTransparentClickEvent(evt))\r\n\t\t\t{\r\n\t\t\t\tvar active = false;\r\n\t\t\t\t\r\n\t\t\t\tvar tmp = this.getCellAt(me.graphX, me.graphY, null, null, null, mxUtils.bind(this, function(state)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar selected = this.isCellSelected(state.cell);\r\n\t\t\t\t\tactive = active || selected;\r\n\t\t\t\t\t\r\n\t\t\t\t\treturn !active || selected;\r\n\t\t\t\t}));\r\n\t\t\t\t\r\n\t\t\t\tif (tmp != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tcell = tmp;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.selectCellForEvent(cell, evt);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar swimlane = null;\r\n\t\t\t\r\n\t\t\tif (this.isSwimlaneSelectionEnabled())\r\n\t\t\t{\r\n\t\t\t\t// Gets the swimlane at the location (includes\r\n\t\t\t\t// content area of swimlanes)\r\n\t\t\t\tswimlane = this.getSwimlaneAt(me.getGraphX(), me.getGraphY());\r\n\t\t\t}\r\n\r\n\t\t\t// Selects the swimlane and consumes the event\r\n\t\t\tif (swimlane != null)\r\n\t\t\t{\r\n\t\t\t\tthis.selectCellForEvent(swimlane, evt);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Ignores the event if the control key is pressed\r\n\t\t\telse if (!this.isToggleEvent(evt))\r\n\t\t\t{\r\n\t\t\t\tthis.clearSelection();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: dblClick\r\n * \r\n * Processes a doubleclick on an optional cell and fires a <dblclick>\r\n * event. The event is fired initially. If the graph is enabled and the\r\n * event has not been consumed, then <edit> is called with the given\r\n * cell. The event is ignored if no cell was specified.\r\n *\r\n * Example for overriding this method.\r\n *\r\n * (code)\r\n * graph.dblClick = function(evt, cell)\r\n * {\r\n *   var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell', cell);\r\n *   this.fireEvent(mxe);\r\n *   \r\n *   if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed())\r\n *   {\r\n * \t   mxUtils.alert('Hello, World!');\r\n *     mxe.consume();\r\n *   }\r\n * }\r\n * (end)\r\n * \r\n * Example listener for this event.\r\n * \r\n * (code)\r\n * graph.addListener(mxEvent.DOUBLE_CLICK, function(sender, evt)\r\n * {\r\n *   var cell = evt.getProperty('cell');\r\n *   // do something with the cell and consume the\r\n *   // event to prevent in-place editing from start\r\n * });\r\n * (end) \r\n * \r\n * Parameters:\r\n * \r\n * evt - Mouseevent that represents the doubleclick.\r\n * cell - Optional <mxCell> under the mousepointer.\r\n */\r\nmxGraph.prototype.dblClick = function(evt, cell)\r\n{\r\n\tvar mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell', cell);\r\n\tthis.fireEvent(mxe);\r\n\t\r\n\t// Handles the event if it has not been consumed\r\n\tif (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed() &&\r\n\t\tcell != null && this.isCellEditable(cell) && !this.isEditing(cell))\r\n\t{\r\n\t\tthis.startEditingAtCell(cell, evt);\r\n\t\tmxEvent.consume(evt);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: tapAndHold\r\n * \r\n * Handles the <mxMouseEvent> by highlighting the <mxCellState>.\r\n * \r\n * Parameters:\r\n * \r\n * me - <mxMouseEvent> that represents the touch event.\r\n * state - Optional <mxCellState> that is associated with the event.\r\n */\r\nmxGraph.prototype.tapAndHold = function(me)\r\n{\r\n\tvar evt = me.getEvent();\r\n\tvar mxe = new mxEventObject(mxEvent.TAP_AND_HOLD, 'event', evt, 'cell', me.getCell());\r\n\r\n\t// LATER: Check if event should be consumed if me is consumed\r\n\tthis.fireEvent(mxe);\r\n\r\n\tif (mxe.isConsumed())\r\n\t{\r\n\t\t// Resets the state of the panning handler\r\n\t\tthis.panningHandler.panningTrigger = false;\r\n\t}\r\n\t\r\n\t// Handles the event if it has not been consumed\r\n\tif (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed() && this.connectionHandler.isEnabled())\r\n\t{\r\n\t\tvar state = this.view.getState(this.connectionHandler.marker.getCell(me));\r\n\r\n\t\tif (state != null)\r\n\t\t{\r\n\t\t\tthis.connectionHandler.marker.currentColor = this.connectionHandler.marker.validColor;\r\n\t\t\tthis.connectionHandler.marker.markedState = state;\r\n\t\t\tthis.connectionHandler.marker.mark();\r\n\t\t\t\r\n\t\t\tthis.connectionHandler.first = new mxPoint(me.getGraphX(), me.getGraphY());\r\n\t\t\tthis.connectionHandler.edgeState = this.connectionHandler.createEdgeState(me);\r\n\t\t\tthis.connectionHandler.previous = state;\r\n\t\t\tthis.connectionHandler.fireEvent(new mxEventObject(mxEvent.START, 'state', this.connectionHandler.previous));\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: scrollPointToVisible\r\n * \r\n * Scrolls the graph to the given point, extending the graph container if\r\n * specified.\r\n */\r\nmxGraph.prototype.scrollPointToVisible = function(x, y, extend, border)\r\n{\r\n\tif (!this.timerAutoScroll && (this.ignoreScrollbars || mxUtils.hasScrollbars(this.container)))\r\n\t{\r\n\t\tvar c = this.container;\r\n\t\tborder = (border != null) ? border : 20;\r\n\t\t\r\n\t\tif (x >= c.scrollLeft && y >= c.scrollTop && x <= c.scrollLeft + c.clientWidth &&\r\n\t\t\ty <= c.scrollTop + c.clientHeight)\r\n\t\t{\r\n\t\t\tvar dx = c.scrollLeft + c.clientWidth - x;\r\n\t\t\t\r\n\t\t\tif (dx < border)\r\n\t\t\t{\r\n\t\t\t\tvar old = c.scrollLeft;\r\n\t\t\t\tc.scrollLeft += border - dx;\r\n\r\n\t\t\t\t// Automatically extends the canvas size to the bottom, right\r\n\t\t\t\t// if the event is outside of the canvas and the edge of the\r\n\t\t\t\t// canvas has been reached. Notes: Needs fix for IE.\r\n\t\t\t\tif (extend && old == c.scrollLeft)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (this.dialect == mxConstants.DIALECT_SVG)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar root = this.view.getDrawPane().ownerSVGElement;\r\n\t\t\t\t\t\tvar width = this.container.scrollWidth + border - dx;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t// Updates the clipping region. This is an expensive\r\n\t\t\t\t\t\t// operation that should not be executed too often.\r\n\t\t\t\t\t\troot.style.width = width + 'px';\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar width = Math.max(c.clientWidth, c.scrollWidth) + border - dx;\r\n\t\t\t\t\t\tvar canvas = this.view.getCanvas();\r\n\t\t\t\t\t\tcanvas.style.width = width + 'px';\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tc.scrollLeft += border - dx;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tdx = x - c.scrollLeft;\r\n\t\t\t\t\r\n\t\t\t\tif (dx < border)\r\n\t\t\t\t{\r\n\t\t\t\t\tc.scrollLeft -= border - dx;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar dy = c.scrollTop + c.clientHeight - y;\r\n\t\t\t\r\n\t\t\tif (dy < border)\r\n\t\t\t{\r\n\t\t\t\tvar old = c.scrollTop;\r\n\t\t\t\tc.scrollTop += border - dy;\r\n\r\n\t\t\t\tif (old == c.scrollTop && extend)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (this.dialect == mxConstants.DIALECT_SVG)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar root = this.view.getDrawPane().ownerSVGElement;\r\n\t\t\t\t\t\tvar height = this.container.scrollHeight + border - dy;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t// Updates the clipping region. This is an expensive\r\n\t\t\t\t\t\t// operation that should not be executed too often.\r\n\t\t\t\t\t\troot.style.height = height + 'px';\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar height = Math.max(c.clientHeight, c.scrollHeight) + border - dy;\r\n\t\t\t\t\t\tvar canvas = this.view.getCanvas();\r\n\t\t\t\t\t\tcanvas.style.height = height + 'px';\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tc.scrollTop += border - dy;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tdy = y - c.scrollTop;\r\n\t\t\t\t\r\n\t\t\t\tif (dy < border)\r\n\t\t\t\t{\r\n\t\t\t\t\tc.scrollTop -= border - dy;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\telse if (this.allowAutoPanning && !this.panningHandler.isActive())\r\n\t{\r\n\t\tif (this.panningManager == null)\r\n\t\t{\r\n\t\t\tthis.panningManager = this.createPanningManager();\r\n\t\t}\r\n\r\n\t\tthis.panningManager.panTo(x + this.panDx, y + this.panDy);\r\n\t}\r\n};\r\n\r\n\r\n/**\r\n * Function: createPanningManager\r\n * \r\n * Creates and returns an <mxPanningManager>.\r\n */\r\nmxGraph.prototype.createPanningManager = function()\r\n{\r\n\treturn new mxPanningManager(this);\r\n};\r\n\r\n/**\r\n * Function: getBorderSizes\r\n * \r\n * Returns the size of the border and padding on all four sides of the\r\n * container. The left, top, right and bottom borders are stored in the x, y,\r\n * width and height of the returned <mxRectangle>, respectively.\r\n */\r\nmxGraph.prototype.getBorderSizes = function()\r\n{\r\n\tvar css = mxUtils.getCurrentStyle(this.container);\r\n\t\r\n\treturn new mxRectangle(mxUtils.parseCssNumber(css.paddingLeft) +\r\n\t\t\t((css.borderLeftStyle != 'none') ? mxUtils.parseCssNumber(css.borderLeftWidth) : 0),\r\n\t\tmxUtils.parseCssNumber(css.paddingTop) +\r\n\t\t\t((css.borderTopStyle != 'none') ? mxUtils.parseCssNumber(css.borderTopWidth) : 0),\r\n\t\tmxUtils.parseCssNumber(css.paddingRight) +\r\n\t\t\t((css.borderRightStyle != 'none') ? mxUtils.parseCssNumber(css.borderRightWidth) : 0),\r\n\t\tmxUtils.parseCssNumber(css.paddingBottom) +\r\n\t\t\t((css.borderBottomStyle != 'none') ? mxUtils.parseCssNumber(css.borderBottomWidth) : 0));\r\n};\r\n\r\n/**\r\n * Function: getPreferredPageSize\r\n * \r\n * Returns the preferred size of the background page if <preferPageSize> is true.\r\n */\r\nmxGraph.prototype.getPreferredPageSize = function(bounds, width, height)\r\n{\r\n\tvar scale = this.view.scale;\r\n\tvar tr = this.view.translate;\r\n\tvar fmt = this.pageFormat;\r\n\tvar ps = this.pageScale;\r\n\tvar page = new mxRectangle(0, 0, Math.ceil(fmt.width * ps), Math.ceil(fmt.height * ps));\r\n\t\r\n\tvar hCount = (this.pageBreaksVisible) ? Math.ceil(width / page.width) : 1;\r\n\tvar vCount = (this.pageBreaksVisible) ? Math.ceil(height / page.height) : 1;\r\n\t\r\n\treturn new mxRectangle(0, 0, hCount * page.width + 2 + tr.x, vCount * page.height + 2 + tr.y);\r\n};\r\n\r\n/**\r\n * Function: fit\r\n *\r\n * Scales the graph such that the complete diagram fits into <container> and\r\n * returns the current scale in the view. To fit an initial graph prior to\r\n * rendering, set <mxGraphView.rendering> to false prior to changing the model\r\n * and execute the following after changing the model.\r\n * \r\n * (code)\r\n * graph.fit();\r\n * graph.view.rendering = true;\r\n * graph.refresh();\r\n * (end)\r\n * \r\n * To fit and center the graph, the following code can be used.\r\n * \r\n * (code)\r\n * var margin = 2;\r\n * var max = 3;\r\n * \r\n * var bounds = graph.getGraphBounds();\r\n * var cw = graph.container.clientWidth - margin;\r\n * var ch = graph.container.clientHeight - margin;\r\n * var w = bounds.width / graph.view.scale;\r\n * var h = bounds.height / graph.view.scale;\r\n * var s = Math.min(max, Math.min(cw / w, ch / h));\r\n * \r\n * graph.view.scaleAndTranslate(s,\r\n *   (margin + cw - w * s) / (2 * s) - bounds.x / graph.view.scale,\r\n *   (margin + ch - h * s) / (2 * s) - bounds.y / graph.view.scale);\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * border - Optional number that specifies the border. Default is <border>.\r\n * keepOrigin - Optional boolean that specifies if the translate should be\r\n * changed. Default is false.\r\n * margin - Optional margin in pixels. Default is 0.\r\n * enabled - Optional boolean that specifies if the scale should be set or\r\n * just returned. Default is true.\r\n * ignoreWidth - Optional boolean that specifies if the width should be\r\n * ignored. Default is false.\r\n * ignoreHeight - Optional boolean that specifies if the height should be\r\n * ignored. Default is false.\r\n * maxHeight - Optional maximum height.\r\n */\r\nmxGraph.prototype.fit = function(border, keepOrigin, margin, enabled, ignoreWidth, ignoreHeight, maxHeight)\r\n{\r\n\tif (this.container != null)\r\n\t{\r\n\t\tborder = (border != null) ? border : this.getBorder();\r\n\t\tkeepOrigin = (keepOrigin != null) ? keepOrigin : false;\r\n\t\tmargin = (margin != null) ? margin : 0;\r\n\t\tenabled = (enabled != null) ? enabled : true;\r\n\t\tignoreWidth = (ignoreWidth != null) ? ignoreWidth : false;\r\n\t\tignoreHeight = (ignoreHeight != null) ? ignoreHeight : false;\r\n\t\t\r\n\t\t// Adds spacing and border from css\r\n\t\tvar cssBorder = this.getBorderSizes();\r\n\t\tvar w1 = this.container.offsetWidth - cssBorder.x - cssBorder.width - 1;\r\n\t\tvar h1 = (maxHeight != null) ? maxHeight : this.container.offsetHeight - cssBorder.y - cssBorder.height - 1;\r\n\t\tvar bounds = this.view.getGraphBounds();\r\n\t\t\r\n\t\tif (bounds.width > 0 && bounds.height > 0)\r\n\t\t{\r\n\t\t\tif (keepOrigin && bounds.x != null && bounds.y != null)\r\n\t\t\t{\r\n\t\t\t\tbounds = bounds.clone();\r\n\t\t\t\tbounds.width += bounds.x;\r\n\t\t\t\tbounds.height += bounds.y;\r\n\t\t\t\tbounds.x = 0;\r\n\t\t\t\tbounds.y = 0;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// LATER: Use unscaled bounding boxes to fix rounding errors\r\n\t\t\tvar s = this.view.scale;\r\n\t\t\tvar w2 = bounds.width / s;\r\n\t\t\tvar h2 = bounds.height / s;\r\n\t\t\t\r\n\t\t\t// Fits to the size of the background image if required\r\n\t\t\tif (this.backgroundImage != null)\r\n\t\t\t{\r\n\t\t\t\tw2 = Math.max(w2, this.backgroundImage.width - bounds.x / s);\r\n\t\t\t\th2 = Math.max(h2, this.backgroundImage.height - bounds.y / s);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar b = ((keepOrigin) ? border : 2 * border) + margin + 1;\r\n\r\n\t\t\tw1 -= b;\r\n\t\t\th1 -= b;\r\n\t\t\t\r\n\t\t\tvar s2 = (((ignoreWidth) ? h1 / h2 : (ignoreHeight) ? w1 / w2 :\r\n\t\t\t\tMath.min(w1 / w2, h1 / h2)));\r\n\t\t\t\r\n\t\t\tif (this.minFitScale != null)\r\n\t\t\t{\r\n\t\t\t\ts2 = Math.max(s2, this.minFitScale);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (this.maxFitScale != null)\r\n\t\t\t{\r\n\t\t\t\ts2 = Math.min(s2, this.maxFitScale);\r\n\t\t\t}\r\n\t\r\n\t\t\tif (enabled)\r\n\t\t\t{\r\n\t\t\t\tif (!keepOrigin)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (!mxUtils.hasScrollbars(this.container))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar x0 = (bounds.x != null) ? Math.floor(this.view.translate.x - bounds.x / s + border / s2 + margin / 2) : border;\r\n\t\t\t\t\t\tvar y0 = (bounds.y != null) ? Math.floor(this.view.translate.y - bounds.y / s + border / s2 + margin / 2) : border;\r\n\r\n\t\t\t\t\t\tthis.view.scaleAndTranslate(s2, x0, y0);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.view.setScale(s2);\r\n\t\t\t\t\t\tvar b2 = this.getGraphBounds();\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (b2.x != null)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tthis.container.scrollLeft = b2.x;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (b2.y != null)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tthis.container.scrollTop = b2.y;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if (this.view.scale != s2)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.view.setScale(s2);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\treturn s2;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn this.view.scale;\r\n};\r\n\r\n/**\r\n * Function: sizeDidChange\r\n * \r\n * Called when the size of the graph has changed. This implementation fires\r\n * a <size> event after updating the clipping region of the SVG element in\r\n * SVG-bases browsers.\r\n */\r\nmxGraph.prototype.sizeDidChange = function()\r\n{\r\n\tvar bounds = this.getGraphBounds();\r\n\t\r\n\tif (this.container != null)\r\n\t{\r\n\t\tvar border = this.getBorder();\r\n\t\t\r\n\t\tvar width = Math.max(0, bounds.x + bounds.width + 2 * border * this.view.scale);\r\n\t\tvar height = Math.max(0, bounds.y + bounds.height + 2 * border * this.view.scale);\r\n\t\t\r\n\t\tif (this.minimumContainerSize != null)\r\n\t\t{\r\n\t\t\twidth = Math.max(width, this.minimumContainerSize.width);\r\n\t\t\theight = Math.max(height, this.minimumContainerSize.height);\r\n\t\t}\r\n\r\n\t\tif (this.resizeContainer)\r\n\t\t{\r\n\t\t\tthis.doResizeContainer(width, height);\r\n\t\t}\r\n\r\n\t\tif (this.preferPageSize || (!mxClient.IS_IE && this.pageVisible))\r\n\t\t{\r\n\t\t\tvar size = this.getPreferredPageSize(bounds, Math.max(1, width), Math.max(1, height));\r\n\t\t\t\r\n\t\t\tif (size != null)\r\n\t\t\t{\r\n\t\t\t\twidth = size.width * this.view.scale;\r\n\t\t\t\theight = size.height * this.view.scale;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (this.minimumGraphSize != null)\r\n\t\t{\r\n\t\t\twidth = Math.max(width, this.minimumGraphSize.width * this.view.scale);\r\n\t\t\theight = Math.max(height, this.minimumGraphSize.height * this.view.scale);\r\n\t\t}\r\n\r\n\t\twidth = Math.ceil(width);\r\n\t\theight = Math.ceil(height);\r\n\r\n\t\tif (this.dialect == mxConstants.DIALECT_SVG)\r\n\t\t{\r\n\t\t\tvar root = this.view.getDrawPane().ownerSVGElement;\r\n\t\t\t\r\n\t\t\tif (root != null)\r\n\t\t\t{\r\n\t\t\t\troot.style.minWidth = Math.max(1, width) + 'px';\r\n\t\t\t\troot.style.minHeight = Math.max(1, height) + 'px';\r\n\t\t\t\troot.style.width = '100%';\r\n\t\t\t\troot.style.height = '100%';\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tif (mxClient.IS_QUIRKS)\r\n\t\t\t{\r\n\t\t\t\t// Quirks mode does not support minWidth/-Height\r\n\t\t\t\tthis.view.updateHtmlCanvasSize(Math.max(1, width), Math.max(1, height));\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.view.canvas.style.minWidth = Math.max(1, width) + 'px';\r\n\t\t\t\tthis.view.canvas.style.minHeight = Math.max(1, height) + 'px';\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tthis.updatePageBreaks(this.pageBreaksVisible, width, height);\r\n\t}\r\n\r\n\tthis.fireEvent(new mxEventObject(mxEvent.SIZE, 'bounds', bounds));\r\n};\r\n\r\n/**\r\n * Function: doResizeContainer\r\n * \r\n * Resizes the container for the given graph width and height.\r\n */\r\nmxGraph.prototype.doResizeContainer = function(width, height)\r\n{\r\n\tif (this.maximumContainerSize != null)\r\n\t{\r\n\t\twidth = Math.min(this.maximumContainerSize.width, width);\r\n\t\theight = Math.min(this.maximumContainerSize.height, height);\r\n\t}\r\n\r\n\tthis.container.style.width = Math.ceil(width) + 'px';\r\n\tthis.container.style.height = Math.ceil(height) + 'px';\r\n};\r\n\r\n/**\r\n * Function: updatePageBreaks\r\n * \r\n * Invokes from <sizeDidChange> to redraw the page breaks.\r\n * \r\n * Parameters:\r\n * \r\n * visible - Boolean that specifies if page breaks should be shown.\r\n * width - Specifies the width of the container in pixels.\r\n * height - Specifies the height of the container in pixels.\r\n */\r\nmxGraph.prototype.updatePageBreaks = function(visible, width, height)\r\n{\r\n\tvar scale = this.view.scale;\r\n\tvar tr = this.view.translate;\r\n\tvar fmt = this.pageFormat;\r\n\tvar ps = scale * this.pageScale;\r\n\tvar bounds = new mxRectangle(0, 0, fmt.width * ps, fmt.height * ps);\r\n\r\n\tvar gb = mxRectangle.fromRectangle(this.getGraphBounds());\r\n\tgb.width = Math.max(1, gb.width);\r\n\tgb.height = Math.max(1, gb.height);\r\n\t\r\n\tbounds.x = Math.floor((gb.x - tr.x * scale) / bounds.width) * bounds.width + tr.x * scale;\r\n\tbounds.y = Math.floor((gb.y - tr.y * scale) / bounds.height) * bounds.height + tr.y * scale;\r\n\t\r\n\tgb.width = Math.ceil((gb.width + (gb.x - bounds.x)) / bounds.width) * bounds.width;\r\n\tgb.height = Math.ceil((gb.height + (gb.y - bounds.y)) / bounds.height) * bounds.height;\r\n\t\r\n\t// Does not show page breaks if the scale is too small\r\n\tvisible = visible && Math.min(bounds.width, bounds.height) > this.minPageBreakDist;\r\n\r\n\tvar horizontalCount = (visible) ? Math.ceil(gb.height / bounds.height) + 1 : 0;\r\n\tvar verticalCount = (visible) ? Math.ceil(gb.width / bounds.width) + 1 : 0;\r\n\tvar right = (verticalCount - 1) * bounds.width;\r\n\tvar bottom = (horizontalCount - 1) * bounds.height;\r\n\t\r\n\tif (this.horizontalPageBreaks == null && horizontalCount > 0)\r\n\t{\r\n\t\tthis.horizontalPageBreaks = [];\r\n\t}\r\n\r\n\tif (this.verticalPageBreaks == null && verticalCount > 0)\r\n\t{\r\n\t\tthis.verticalPageBreaks = [];\r\n\t}\r\n\t\r\n\tvar drawPageBreaks = mxUtils.bind(this, function(breaks)\r\n\t{\r\n\t\tif (breaks != null)\r\n\t\t{\r\n\t\t\tvar count = (breaks == this.horizontalPageBreaks) ? horizontalCount : verticalCount; \r\n\t\t\t\r\n\t\t\tfor (var i = 0; i <= count; i++)\r\n\t\t\t{\r\n\t\t\t\tvar pts = (breaks == this.horizontalPageBreaks) ?\r\n\t\t\t\t\t[new mxPoint(Math.round(bounds.x), Math.round(bounds.y + i * bounds.height)),\r\n\t\t\t         new mxPoint(Math.round(bounds.x + right), Math.round(bounds.y + i * bounds.height))] :\r\n\t\t\t        [new mxPoint(Math.round(bounds.x + i * bounds.width), Math.round(bounds.y)),\r\n\t\t\t         new mxPoint(Math.round(bounds.x + i * bounds.width), Math.round(bounds.y + bottom))];\r\n\r\n\t\t\t\tif (breaks[i] != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tbreaks[i].points = pts;\r\n\t\t\t\t\tbreaks[i].redraw();\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tvar pageBreak = new mxPolyline(pts, this.pageBreakColor);\r\n\t\t\t\t\tpageBreak.dialect = this.dialect;\r\n\t\t\t\t\tpageBreak.pointerEvents = false;\r\n\t\t\t\t\tpageBreak.isDashed = this.pageBreakDashed;\r\n\t\t\t\t\tpageBreak.init(this.view.backgroundPane);\r\n\t\t\t\t\tpageBreak.redraw();\r\n\t\t\t\t\t\r\n\t\t\t\t\tbreaks[i] = pageBreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tfor (var i = count; i < breaks.length; i++)\r\n\t\t\t{\r\n\t\t\t\tbreaks[i].destroy();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tbreaks.splice(count, breaks.length - count);\r\n\t\t}\r\n\t});\r\n\t\r\n\tdrawPageBreaks(this.horizontalPageBreaks);\r\n\tdrawPageBreaks(this.verticalPageBreaks);\r\n};\r\n\r\n/**\r\n * Group: Cell styles\r\n */\r\n\r\n/**\r\n * Function: getCellStyle\r\n * \r\n * Returns an array of key, value pairs representing the cell style for the\r\n * given cell. If no string is defined in the model that specifies the\r\n * style, then the default style for the cell is returned or an empty object,\r\n * if no style can be found. Note: You should try and get the cell state\r\n * for the given cell and use the cached style in the state before using\r\n * this method.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose style should be returned as an array.\r\n */\r\nmxGraph.prototype.getCellStyle = function(cell)\r\n{\r\n\tvar stylename = this.model.getStyle(cell);\r\n\tvar style = null;\r\n\t\r\n\t// Gets the default style for the cell\r\n\tif (this.model.isEdge(cell))\r\n\t{\r\n\t\tstyle = this.stylesheet.getDefaultEdgeStyle();\r\n\t}\r\n\telse\r\n\t{\r\n\t\tstyle = this.stylesheet.getDefaultVertexStyle();\r\n\t}\r\n\t\r\n\t// Resolves the stylename using the above as the default\r\n\tif (stylename != null)\r\n\t{\r\n\t\tstyle = this.postProcessCellStyle(this.stylesheet.getCellStyle(stylename, style));\r\n\t}\r\n\t\r\n\t// Returns a non-null value if no style can be found\r\n\tif (style == null)\r\n\t{\r\n\t\tstyle = new Object();\r\n\t}\r\n\t\r\n\treturn style;\r\n};\r\n\r\n/**\r\n * Function: postProcessCellStyle\r\n * \r\n * Tries to resolve the value for the image style in the image bundles and\r\n * turns short data URIs as defined in mxImageBundle to data URIs as\r\n * defined in RFC 2397 of the IETF.\r\n */\r\nmxGraph.prototype.postProcessCellStyle = function(style)\r\n{\r\n\tif (style != null)\r\n\t{\r\n\t\tvar key = style[mxConstants.STYLE_IMAGE];\r\n\t\tvar image = this.getImageFromBundles(key);\r\n\r\n\t\tif (image != null)\r\n\t\t{\r\n\t\t\tstyle[mxConstants.STYLE_IMAGE] = image;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\timage = key;\r\n\t\t}\r\n\t\t\r\n\t\t// Converts short data uris to normal data uris\r\n\t\tif (image != null && image.substring(0, 11) == 'data:image/')\r\n\t\t{\r\n\t\t\tif (image.substring(0, 20) == 'data:image/svg+xml,<')\r\n\t\t\t{\r\n\t\t\t\t// Required for FF and IE11\r\n\t\t\t\timage = image.substring(0, 19) + encodeURIComponent(image.substring(19));\r\n\t\t\t}\r\n\t\t\telse if (image.substring(0, 22) != 'data:image/svg+xml,%3C')\r\n\t\t\t{\r\n\t\t\t\tvar comma = image.indexOf(',');\r\n\t\t\t\t\r\n\t\t\t\t// Adds base64 encoding prefix if needed\r\n\t\t\t\tif (comma > 0 && image.substring(comma - 7, comma + 1) != ';base64,')\r\n\t\t\t\t{\r\n\t\t\t\t\timage = image.substring(0, comma) + ';base64,'\r\n\t\t\t\t\t\t+ image.substring(comma + 1);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tstyle[mxConstants.STYLE_IMAGE] = image;\r\n\t\t}\r\n\t}\r\n\r\n\treturn style;\r\n};\r\n\r\n/**\r\n * Function: setCellStyle\r\n * \r\n * Sets the style of the specified cells. If no cells are given, then the\r\n * selection cells are changed.\r\n * \r\n * Parameters:\r\n * \r\n * style - String representing the new style of the cells.\r\n * cells - Optional array of <mxCells> to set the style for. Default is the\r\n * selection cells.\r\n */\r\nmxGraph.prototype.setCellStyle = function(style, cells)\r\n{\r\n\tcells = cells || this.getSelectionCells();\r\n\t\r\n\tif (cells != null)\r\n\t{\r\n\t\tthis.model.beginUpdate();\r\n\t\ttry\r\n\t\t{\r\n\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t{\r\n\t\t\t\tthis.model.setStyle(cells[i], style);\r\n\t\t\t}\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tthis.model.endUpdate();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: toggleCellStyle\r\n * \r\n * Toggles the boolean value for the given key in the style of the given cell\r\n * and returns the new value as 0 or 1. If no cell is specified then the\r\n * selection cell is used.\r\n * \r\n * Parameter:\r\n * \r\n * key - String representing the key for the boolean value to be toggled.\r\n * defaultValue - Optional boolean default value if no value is defined.\r\n * Default is false.\r\n * cell - Optional <mxCell> whose style should be modified. Default is\r\n * the selection cell.\r\n */\r\nmxGraph.prototype.toggleCellStyle = function(key, defaultValue, cell)\r\n{\r\n\tcell = cell || this.getSelectionCell();\r\n\t\r\n\treturn this.toggleCellStyles(key, defaultValue, [cell]);\r\n};\r\n\r\n/**\r\n * Function: toggleCellStyles\r\n * \r\n * Toggles the boolean value for the given key in the style of the given cells\r\n * and returns the new value as 0 or 1. If no cells are specified, then the\r\n * selection cells are used. For example, this can be used to toggle\r\n * <mxConstants.STYLE_ROUNDED> or any other style with a boolean value.\r\n * \r\n * Parameter:\r\n * \r\n * key - String representing the key for the boolean value to be toggled.\r\n * defaultValue - Optional boolean default value if no value is defined.\r\n * Default is false.\r\n * cells - Optional array of <mxCells> whose styles should be modified.\r\n * Default is the selection cells.\r\n */\r\nmxGraph.prototype.toggleCellStyles = function(key, defaultValue, cells)\r\n{\r\n\tdefaultValue = (defaultValue != null) ? defaultValue : false;\r\n\tcells = cells || this.getSelectionCells();\r\n\tvar value = null;\r\n\t\r\n\tif (cells != null && cells.length > 0)\r\n\t{\r\n\t\tvar state = this.view.getState(cells[0]);\r\n\t\tvar style = (state != null) ? state.style : this.getCellStyle(cells[0]);\r\n\t\t\r\n\t\tif (style != null)\r\n\t\t{\r\n\t\t\tvalue = (mxUtils.getValue(style, key, defaultValue)) ? 0 : 1;\r\n\t\t\tthis.setCellStyles(key, value, cells);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn value;\r\n};\r\n\r\n/**\r\n * Function: setCellStyles\r\n * \r\n * Sets the key to value in the styles of the given cells. This will modify\r\n * the existing cell styles in-place and override any existing assignment\r\n * for the given key. If no cells are specified, then the selection cells\r\n * are changed. If no value is specified, then the respective key is\r\n * removed from the styles.\r\n * \r\n * Parameters:\r\n * \r\n * key - String representing the key to be assigned.\r\n * value - String representing the new value for the key.\r\n * cells - Optional array of <mxCells> to change the style for. Default is\r\n * the selection cells.\r\n */\r\nmxGraph.prototype.setCellStyles = function(key, value, cells)\r\n{\r\n\tcells = cells || this.getSelectionCells();\r\n\tmxUtils.setCellStyles(this.model, cells, key, value);\r\n};\r\n\r\n/**\r\n * Function: toggleCellStyleFlags\r\n * \r\n * Toggles the given bit for the given key in the styles of the specified\r\n * cells.\r\n * \r\n * Parameters:\r\n * \r\n * key - String representing the key to toggle the flag in.\r\n * flag - Integer that represents the bit to be toggled.\r\n * cells - Optional array of <mxCells> to change the style for. Default is\r\n * the selection cells.\r\n */\r\nmxGraph.prototype.toggleCellStyleFlags = function(key, flag, cells)\r\n{\r\n\tthis.setCellStyleFlags(key, flag, null, cells);\r\n};\r\n\r\n/**\r\n * Function: setCellStyleFlags\r\n * \r\n * Sets or toggles the given bit for the given key in the styles of the\r\n * specified cells.\r\n * \r\n * Parameters:\r\n * \r\n * key - String representing the key to toggle the flag in.\r\n * flag - Integer that represents the bit to be toggled.\r\n * value - Boolean value to be used or null if the value should be toggled.\r\n * cells - Optional array of <mxCells> to change the style for. Default is\r\n * the selection cells.\r\n */\r\nmxGraph.prototype.setCellStyleFlags = function(key, flag, value, cells)\r\n{\r\n\tcells = cells || this.getSelectionCells();\r\n\t\r\n\tif (cells != null && cells.length > 0)\r\n\t{\r\n\t\tif (value == null)\r\n\t\t{\r\n\t\t\tvar state = this.view.getState(cells[0]);\r\n\t\t\tvar style = (state != null) ? state.style : this.getCellStyle(cells[0]);\r\n\t\t\t\r\n\t\t\tif (style != null)\r\n\t\t\t{\r\n\t\t\t\tvar current = parseInt(style[key] || 0);\r\n\t\t\t\tvalue = !((current & flag) == flag);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tmxUtils.setCellStyleFlags(this.model, cells, key, flag, value);\r\n\t}\r\n};\r\n\r\n/**\r\n * Group: Cell alignment and orientation\r\n */\r\n\r\n/**\r\n * Function: alignCells\r\n * \r\n * Aligns the given cells vertically or horizontally according to the given\r\n * alignment using the optional parameter as the coordinate.\r\n * \r\n * Parameters:\r\n * \r\n * align - Specifies the alignment. Possible values are all constants in\r\n * mxConstants with an ALIGN prefix.\r\n * cells - Array of <mxCells> to be aligned.\r\n * param - Optional coordinate for the alignment.\r\n */\r\nmxGraph.prototype.alignCells = function(align, cells, param)\r\n{\r\n\tif (cells == null)\r\n\t{\r\n\t\tcells = this.getSelectionCells();\r\n\t}\r\n\t\r\n\tif (cells != null && cells.length > 1)\r\n\t{\r\n\t\t// Finds the required coordinate for the alignment\r\n\t\tif (param == null)\r\n\t\t{\r\n\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar state = this.view.getState(cells[i]);\r\n\t\t\t\t\r\n\t\t\t\tif (state != null && !this.model.isEdge(cells[i]))\r\n\t\t\t\t{\r\n\t\t\t\t\tif (param == null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (align == mxConstants.ALIGN_CENTER)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tparam = state.x + state.width / 2;\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse if (align == mxConstants.ALIGN_RIGHT)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tparam = state.x + state.width;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse if (align == mxConstants.ALIGN_TOP)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tparam = state.y;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse if (align == mxConstants.ALIGN_MIDDLE)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tparam = state.y + state.height / 2;\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse if (align == mxConstants.ALIGN_BOTTOM)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tparam = state.y + state.height;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tparam = state.x;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (align == mxConstants.ALIGN_RIGHT)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tparam = Math.max(param, state.x + state.width);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse if (align == mxConstants.ALIGN_TOP)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tparam = Math.min(param, state.y);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse if (align == mxConstants.ALIGN_BOTTOM)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tparam = Math.max(param, state.y + state.height);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tparam = Math.min(param, state.x);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Aligns the cells to the coordinate\r\n\t\tif (param != null)\r\n\t\t{\r\n\t\t\tvar s = this.view.scale;\r\n\r\n\t\t\tthis.model.beginUpdate();\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar state = this.view.getState(cells[i]);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (state != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar geo = this.getCellGeometry(cells[i]);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (geo != null && !this.model.isEdge(cells[i]))\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tgeo = geo.clone();\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tif (align == mxConstants.ALIGN_CENTER)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tgeo.x += (param - state.x - state.width / 2) / s;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\telse if (align == mxConstants.ALIGN_RIGHT)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tgeo.x += (param - state.x - state.width) / s;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\telse if (align == mxConstants.ALIGN_TOP)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tgeo.y += (param - state.y) / s;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\telse if (align == mxConstants.ALIGN_MIDDLE)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tgeo.y += (param - state.y - state.height / 2) / s;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\telse if (align == mxConstants.ALIGN_BOTTOM)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tgeo.y += (param - state.y - state.height) / s;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tgeo.x += (param - state.x) / s;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tthis.resizeCell(cells[i], geo);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.fireEvent(new mxEventObject(mxEvent.ALIGN_CELLS,\r\n\t\t\t\t\t\t'align', align, 'cells', cells));\r\n\t\t\t}\r\n\t\t\tfinally\r\n\t\t\t{\r\n\t\t\t\tthis.model.endUpdate();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn cells;\r\n};\r\n\r\n/**\r\n * Function: flipEdge\r\n * \r\n * Toggles the style of the given edge between null (or empty) and\r\n * <alternateEdgeStyle>. This method fires <mxEvent.FLIP_EDGE> while the\r\n * transaction is in progress. Returns the edge that was flipped.\r\n * \r\n * Here is an example that overrides this implementation to invert the\r\n * value of <mxConstants.STYLE_ELBOW> without removing any existing styles.\r\n * \r\n * (code)\r\n * graph.flipEdge = function(edge)\r\n * {\r\n *   if (edge != null)\r\n *   {\r\n *     var state = this.view.getState(edge);\r\n *     var style = (state != null) ? state.style : this.getCellStyle(edge);\r\n *     \r\n *     if (style != null)\r\n *     {\r\n *       var elbow = mxUtils.getValue(style, mxConstants.STYLE_ELBOW,\r\n *           mxConstants.ELBOW_HORIZONTAL);\r\n *       var value = (elbow == mxConstants.ELBOW_HORIZONTAL) ?\r\n *           mxConstants.ELBOW_VERTICAL : mxConstants.ELBOW_HORIZONTAL;\r\n *       this.setCellStyles(mxConstants.STYLE_ELBOW, value, [edge]);\r\n *     }\r\n *   }\r\n * };\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCell> whose style should be changed.\r\n */\r\nmxGraph.prototype.flipEdge = function(edge)\r\n{\r\n\tif (edge != null &&\r\n\t\tthis.alternateEdgeStyle != null)\r\n\t{\r\n\t\tthis.model.beginUpdate();\r\n\t\ttry\r\n\t\t{\r\n\t\t\tvar style = this.model.getStyle(edge);\r\n\r\n\t\t\tif (style == null || style.length == 0)\r\n\t\t\t{\r\n\t\t\t\tthis.model.setStyle(edge, this.alternateEdgeStyle);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.model.setStyle(edge, null);\r\n\t\t\t}\r\n\r\n\t\t\t// Removes all existing control points\r\n\t\t\tthis.resetEdge(edge);\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.FLIP_EDGE, 'edge', edge));\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tthis.model.endUpdate();\r\n\t\t}\r\n\t}\r\n\r\n\treturn edge;\r\n};\r\n\r\n/**\r\n * Function: addImageBundle\r\n *\r\n * Adds the specified <mxImageBundle>.\r\n */\r\nmxGraph.prototype.addImageBundle = function(bundle)\r\n{\r\n\tthis.imageBundles.push(bundle);\r\n};\r\n\r\n/**\r\n * Function: removeImageBundle\r\n * \r\n * Removes the specified <mxImageBundle>.\r\n */\r\nmxGraph.prototype.removeImageBundle = function(bundle)\r\n{\r\n\tvar tmp = [];\r\n\t\r\n\tfor (var i = 0; i < this.imageBundles.length; i++)\r\n\t{\r\n\t\tif (this.imageBundles[i] != bundle)\r\n\t\t{\r\n\t\t\ttmp.push(this.imageBundles[i]);\r\n\t\t}\r\n\t}\r\n\t\r\n\tthis.imageBundles = tmp;\r\n};\r\n\r\n/**\r\n * Function: getImageFromBundles\r\n *\r\n * Searches all <imageBundles> for the specified key and returns the value\r\n * for the first match or null if the key is not found.\r\n */\r\nmxGraph.prototype.getImageFromBundles = function(key)\r\n{\r\n\tif (key != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.imageBundles.length; i++)\r\n\t\t{\r\n\t\t\tvar image = this.imageBundles[i].getImage(key);\r\n\t\t\t\r\n\t\t\tif (image != null)\r\n\t\t\t{\r\n\t\t\t\treturn image;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Group: Order\r\n */\r\n\r\n/**\r\n * Function: orderCells\r\n * \r\n * Moves the given cells to the front or back. The change is carried out\r\n * using <cellsOrdered>. This method fires <mxEvent.ORDER_CELLS> while the\r\n * transaction is in progress.\r\n * \r\n * Parameters:\r\n * \r\n * back - Boolean that specifies if the cells should be moved to back.\r\n * cells - Array of <mxCells> to move to the background. If null is\r\n * specified then the selection cells are used.\r\n */\r\nmxGraph.prototype.orderCells = function(back, cells)\r\n{\r\n\tif (cells == null)\r\n\t{\r\n\t\tcells = mxUtils.sortCells(this.getSelectionCells(), true);\r\n\t}\r\n\r\n\tthis.model.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tthis.cellsOrdered(cells, back);\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.ORDER_CELLS,\r\n\t\t\t\t'back', back, 'cells', cells));\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tthis.model.endUpdate();\r\n\t}\r\n\r\n\treturn cells;\r\n};\r\n\r\n/**\r\n * Function: cellsOrdered\r\n * \r\n * Moves the given cells to the front or back. This method fires\r\n * <mxEvent.CELLS_ORDERED> while the transaction is in progress.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> whose order should be changed.\r\n * back - Boolean that specifies if the cells should be moved to back.\r\n */\r\nmxGraph.prototype.cellsOrdered = function(cells, back)\r\n{\r\n\tif (cells != null)\r\n\t{\r\n\t\tthis.model.beginUpdate();\r\n\t\ttry\r\n\t\t{\r\n\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar parent = this.model.getParent(cells[i]);\r\n\r\n\t\t\t\tif (back)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.model.add(parent, cells[i], i);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.model.add(parent, cells[i],\r\n\t\t\t\t\t\t\tthis.model.getChildCount(parent) - 1);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.CELLS_ORDERED,\r\n\t\t\t\t\t'back', back, 'cells', cells));\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tthis.model.endUpdate();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Group: Grouping\r\n */\r\n\r\n/**\r\n * Function: groupCells\r\n * \r\n * Adds the cells into the given group. The change is carried out using\r\n * <cellsAdded>, <cellsMoved> and <cellsResized>. This method fires\r\n * <mxEvent.GROUP_CELLS> while the transaction is in progress. Returns the\r\n * new group. A group is only created if there is at least one entry in the\r\n * given array of cells.\r\n * \r\n * Parameters:\r\n * \r\n * group - <mxCell> that represents the target group. If null is specified\r\n * then a new group is created using <createGroupCell>.\r\n * border - Optional integer that specifies the border between the child\r\n * area and the group bounds. Default is 0.\r\n * cells - Optional array of <mxCells> to be grouped. If null is specified\r\n * then the selection cells are used.\r\n */\r\nmxGraph.prototype.groupCells = function(group, border, cells)\r\n{\r\n\tif (cells == null)\r\n\t{\r\n\t\tcells = mxUtils.sortCells(this.getSelectionCells(), true);\r\n\t}\r\n\r\n\tcells = this.getCellsForGroup(cells);\r\n\r\n\tif (group == null)\r\n\t{\r\n\t\tgroup = this.createGroupCell(cells);\r\n\t}\r\n\r\n\tvar bounds = this.getBoundsForGroup(group, cells, border);\r\n\r\n\tif (cells.length > 0 && bounds != null)\r\n\t{\r\n\t\t// Uses parent of group or previous parent of first child\r\n\t\tvar parent = this.model.getParent(group);\r\n\t\t\r\n\t\tif (parent == null)\r\n\t\t{\r\n\t\t\tparent = this.model.getParent(cells[0]);\r\n\t\t}\r\n\r\n\t\tthis.model.beginUpdate();\r\n\t\ttry\r\n\t\t{\r\n\t\t\t// Checks if the group has a geometry and\r\n\t\t\t// creates one if one does not exist\r\n\t\t\tif (this.getCellGeometry(group) == null)\r\n\t\t\t{\r\n\t\t\t\tthis.model.setGeometry(group, new mxGeometry());\r\n\t\t\t}\r\n\r\n\t\t\t// Adds the group into the parent\r\n\t\t\tvar index = this.model.getChildCount(parent);\r\n\t\t\tthis.cellsAdded([group], parent, index, null, null, false, false, false);\r\n\r\n\t\t\t// Adds the children into the group and moves\r\n\t\t\tindex = this.model.getChildCount(group);\r\n\t\t\tthis.cellsAdded(cells, group, index, null, null, false, false, false);\r\n\t\t\tthis.cellsMoved(cells, -bounds.x, -bounds.y, false, false, false);\r\n\r\n\t\t\t// Resizes the group\r\n\t\t\tthis.cellsResized([group], [bounds], false);\r\n\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.GROUP_CELLS,\r\n\t\t\t\t\t'group', group, 'border', border, 'cells', cells));\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tthis.model.endUpdate();\r\n\t\t}\r\n\t}\r\n\r\n\treturn group;\r\n};\r\n\r\n/**\r\n * Function: getCellsForGroup\r\n * \r\n * Returns the cells with the same parent as the first cell\r\n * in the given array.\r\n */\r\nmxGraph.prototype.getCellsForGroup = function(cells)\r\n{\r\n\tvar result = [];\r\n\r\n\tif (cells != null && cells.length > 0)\r\n\t{\r\n\t\tvar parent = this.model.getParent(cells[0]);\r\n\t\tresult.push(cells[0]);\r\n\r\n\t\t// Filters selection cells with the same parent\r\n\t\tfor (var i = 1; i < cells.length; i++)\r\n\t\t{\r\n\t\t\tif (this.model.getParent(cells[i]) == parent)\r\n\t\t\t{\r\n\t\t\t\tresult.push(cells[i]);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: getBoundsForGroup\r\n * \r\n * Returns the bounds to be used for the given group and children.\r\n */\r\nmxGraph.prototype.getBoundsForGroup = function(group, children, border)\r\n{\r\n\tvar result = this.getBoundingBoxFromGeometry(children, true);\r\n\t\r\n\tif (result != null)\r\n\t{\r\n\t\tif (this.isSwimlane(group))\r\n\t\t{\r\n\t\t\tvar size = this.getStartSize(group);\r\n\t\t\t\r\n\t\t\tresult.x -= size.width;\r\n\t\t\tresult.y -= size.height;\r\n\t\t\tresult.width += size.width;\r\n\t\t\tresult.height += size.height;\r\n\t\t}\r\n\t\t\r\n\t\t// Adds the border\r\n\t\tif (border != null)\r\n\t\t{\r\n\t\t\tresult.x -= border;\r\n\t\t\tresult.y -= border;\r\n\t\t\tresult.width += 2 * border;\r\n\t\t\tresult.height += 2 * border;\r\n\t\t}\r\n\t}\t\t\t\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: createGroupCell\r\n * \r\n * Hook for creating the group cell to hold the given array of <mxCells> if\r\n * no group cell was given to the <group> function.\r\n * \r\n * The following code can be used to set the style of new group cells.\r\n * \r\n * (code)\r\n * var graphCreateGroupCell = graph.createGroupCell;\r\n * graph.createGroupCell = function(cells)\r\n * {\r\n *   var group = graphCreateGroupCell.apply(this, arguments);\r\n *   group.setStyle('group');\r\n *   \r\n *   return group;\r\n * };\r\n */\r\nmxGraph.prototype.createGroupCell = function(cells)\r\n{\r\n\tvar group = new mxCell('');\r\n\tgroup.setVertex(true);\r\n\tgroup.setConnectable(false);\r\n\t\r\n\treturn group;\r\n};\r\n\r\n/**\r\n * Function: ungroupCells\r\n * \r\n * Ungroups the given cells by moving the children the children to their\r\n * parents parent and removing the empty groups. Returns the children that\r\n * have been removed from the groups.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of cells to be ungrouped. If null is specified then the\r\n * selection cells are used.\r\n */\r\nmxGraph.prototype.ungroupCells = function(cells)\r\n{\r\n\tvar result = [];\r\n\t\r\n\tif (cells == null)\r\n\t{\r\n\t\tcells = this.getSelectionCells();\r\n\r\n\t\t// Finds the cells with children\r\n\t\tvar tmp = [];\r\n\t\t\r\n\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t{\r\n\t\t\tif (this.model.getChildCount(cells[i]) > 0)\r\n\t\t\t{\r\n\t\t\t\ttmp.push(cells[i]);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tcells = tmp;\r\n\t}\r\n\t\r\n\tif (cells != null && cells.length > 0)\r\n\t{\r\n\t\tthis.model.beginUpdate();\r\n\t\ttry\r\n\t\t{\r\n\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar children = this.model.getChildren(cells[i]);\r\n\t\t\t\t\r\n\t\t\t\tif (children != null && children.length > 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tchildren = children.slice();\r\n\t\t\t\t\tvar parent = this.model.getParent(cells[i]);\r\n\t\t\t\t\tvar index = this.model.getChildCount(parent);\r\n\r\n\t\t\t\t\tthis.cellsAdded(children, parent, index, null, null, true);\r\n\t\t\t\t\tresult = result.concat(children);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tthis.removeCellsAfterUngroup(cells);\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.UNGROUP_CELLS, 'cells', cells));\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tthis.model.endUpdate();\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: removeCellsAfterUngroup\r\n * \r\n * Hook to remove the groups after <ungroupCells>.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> that were ungrouped.\r\n */\r\nmxGraph.prototype.removeCellsAfterUngroup = function(cells)\r\n{\r\n\tthis.cellsRemoved(this.addAllEdges(cells));\r\n};\r\n\r\n/**\r\n * Function: removeCellsFromParent\r\n * \r\n * Removes the specified cells from their parents and adds them to the\r\n * default parent. Returns the cells that were removed from their parents.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> to be removed from their parents.\r\n */\r\nmxGraph.prototype.removeCellsFromParent = function(cells)\r\n{\r\n\tif (cells == null)\r\n\t{\r\n\t\tcells = this.getSelectionCells();\r\n\t}\r\n\t\r\n\tthis.model.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tvar parent = this.getDefaultParent();\r\n\t\tvar index = this.model.getChildCount(parent);\r\n\r\n\t\tthis.cellsAdded(cells, parent, index, null, null, true);\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS_FROM_PARENT, 'cells', cells));\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tthis.model.endUpdate();\r\n\t}\r\n\r\n\treturn cells;\r\n};\r\n\r\n/**\r\n * Function: updateGroupBounds\r\n * \r\n * Updates the bounds of the given groups to include all children and returns\r\n * the passed-in cells. Call this with the groups in parent to child order,\r\n * top-most group first, the cells are processed in reverse order and cells\r\n * with no children are ignored.\r\n * \r\n * Parameters:\r\n * \r\n * cells - The groups whose bounds should be updated. If this is null, then\r\n * the selection cells are used.\r\n * border - Optional border to be added in the group. Default is 0.\r\n * moveGroup - Optional boolean that allows the group to be moved. Default\r\n * is false.\r\n * topBorder - Optional top border to be added in the group. Default is 0.\r\n * rightBorder - Optional top border to be added in the group. Default is 0.\r\n * bottomBorder - Optional top border to be added in the group. Default is 0.\r\n * leftBorder - Optional top border to be added in the group. Default is 0.\r\n */\r\nmxGraph.prototype.updateGroupBounds = function(cells, border, moveGroup, topBorder, rightBorder, bottomBorder, leftBorder)\r\n{\r\n\tif (cells == null)\r\n\t{\r\n\t\tcells = this.getSelectionCells();\r\n\t}\r\n\t\r\n\tborder = (border != null) ? border : 0;\r\n\tmoveGroup = (moveGroup != null) ? moveGroup : false;\r\n\ttopBorder = (topBorder != null) ? topBorder : 0;\r\n\trightBorder = (rightBorder != null) ? rightBorder : 0;\r\n\tbottomBorder = (bottomBorder != null) ? bottomBorder : 0;\r\n\tleftBorder = (leftBorder != null) ? leftBorder : 0;\r\n\r\n\tthis.model.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tfor (var i = cells.length - 1; i >= 0; i--)\r\n\t\t{\r\n\t\t\tvar geo = this.getCellGeometry(cells[i]);\r\n\t\t\t\r\n\t\t\tif (geo != null)\r\n\t\t\t{\r\n\t\t\t\tvar children = this.getChildCells(cells[i]);\r\n\t\t\t\t\r\n\t\t\t\tif (children != null && children.length > 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar bounds = this.getBoundingBoxFromGeometry(children, true);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (bounds != null && bounds.width > 0 && bounds.height > 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar left = 0;\r\n\t\t\t\t\t\tvar top = 0;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t// Adds the size of the title area for swimlanes\r\n\t\t\t\t\t\tif (this.isSwimlane(cells[i]))\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar size = this.getStartSize(cells[i]);\r\n\t\t\t\t\t\t\tleft = size.width;\r\n\t\t\t\t\t\t\ttop = size.height;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tgeo = geo.clone();\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (moveGroup)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tgeo.x = Math.round(geo.x + bounds.x - border - left - leftBorder);\r\n\t\t\t\t\t\t\tgeo.y = Math.round(geo.y + bounds.y - border - top - topBorder);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tgeo.width = Math.round(bounds.width + 2 * border + left + leftBorder + rightBorder);\r\n\t\t\t\t\t\tgeo.height = Math.round(bounds.height + 2 * border + top + topBorder + bottomBorder);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tthis.model.setGeometry(cells[i], geo);\r\n\t\t\t\t\t\tthis.moveCells(children, border + left - bounds.x + leftBorder,\r\n\t\t\t\t\t\t\t\tborder + top - bounds.y + topBorder);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tthis.model.endUpdate();\r\n\t}\r\n\r\n\treturn cells;\r\n};\r\n\r\n/**\r\n * Function: getBoundingBox\r\n * \r\n * Returns the bounding box for the given array of <mxCells>. The bounding box for\r\n * each cell and its descendants is computed using <mxGraphView.getBoundingBox>.\r\n *\r\n * Parameters:\r\n *\r\n * cells - Array of <mxCells> whose bounding box should be returned.\r\n */\r\nmxGraph.prototype.getBoundingBox = function(cells)\r\n{\r\n\tvar result = null;\r\n\t\r\n\tif (cells != null && cells.length > 0)\r\n\t{\r\n\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t{\r\n\t\t\tif (this.model.isVertex(cells[i]) || this.model.isEdge(cells[i]))\r\n\t\t\t{\r\n\t\t\t\tvar bbox = this.view.getBoundingBox(this.view.getState(cells[i]), true);\r\n\t\t\t\r\n\t\t\t\tif (bbox != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (result == null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tresult = mxRectangle.fromRectangle(bbox);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tresult.add(bbox);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Group: Cell cloning, insertion and removal\r\n */\r\n\r\n/**\r\n * Function: cloneCell\r\n * \r\n * Returns the clone for the given cell. Uses <cloneCells>.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> to be cloned.\r\n * allowInvalidEdges - Optional boolean that specifies if invalid edges\r\n * should be cloned. Default is true.\r\n * mapping - Optional mapping for existing clones.\r\n * keepPosition - Optional boolean indicating if the position of the cells should\r\n * be updated to reflect the lost parent cell. Default is false.\r\n */\r\nmxGraph.prototype.cloneCell = function(cell, allowInvalidEdges, mapping, keepPosition)\r\n{\r\n\treturn this.cloneCells([cell], allowInvalidEdges, mapping, keepPosition)[0];\r\n};\r\n\r\n/**\r\n * Function: cloneCells\r\n * \r\n * Returns the clones for the given cells. The clones are created recursively\r\n * using <mxGraphModel.cloneCells>. If the terminal of an edge is not in the\r\n * given array, then the respective end is assigned a terminal point and the\r\n * terminal is removed.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> to be cloned.\r\n * allowInvalidEdges - Optional boolean that specifies if invalid edges\r\n * should be cloned. Default is true.\r\n * mapping - Optional mapping for existing clones.\r\n * keepPosition - Optional boolean indicating if the position of the cells should\r\n * be updated to reflect the lost parent cell. Default is false.\r\n */\r\nmxGraph.prototype.cloneCells = function(cells, allowInvalidEdges, mapping, keepPosition)\r\n{\r\n\tallowInvalidEdges = (allowInvalidEdges != null) ? allowInvalidEdges : true;\r\n\tvar clones = null;\r\n\t\r\n\tif (cells != null)\r\n\t{\r\n\t\t// Creates a dictionary for fast lookups\r\n\t\tvar dict = new mxDictionary();\r\n\t\tvar tmp = [];\r\n\t\t\r\n\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t{\r\n\t\t\tdict.put(cells[i], true);\r\n\t\t\ttmp.push(cells[i]);\r\n\t\t}\r\n\t\t\r\n\t\tif (tmp.length > 0)\r\n\t\t{\r\n\t\t\tvar scale = this.view.scale;\r\n\t\t\tvar trans = this.view.translate;\r\n\t\t\tclones = this.model.cloneCells(cells, true, mapping);\r\n\t\t\r\n\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t{\r\n\t\t\t\tif (!allowInvalidEdges && this.model.isEdge(clones[i]) &&\r\n\t\t\t\t\tthis.getEdgeValidationError(clones[i],\r\n\t\t\t\t\t\tthis.model.getTerminal(clones[i], true),\r\n\t\t\t\t\t\tthis.model.getTerminal(clones[i], false)) != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tclones[i] = null;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tvar g = this.model.getGeometry(clones[i]);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (g != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar state = this.view.getState(cells[i]);\r\n\t\t\t\t\t\tvar pstate = this.view.getState(this.model.getParent(cells[i]));\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (state != null && pstate != null)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar dx = (keepPosition) ? 0 : pstate.origin.x;\r\n\t\t\t\t\t\t\tvar dy = (keepPosition) ? 0 : pstate.origin.y;\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tif (this.model.isEdge(clones[i]))\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tvar pts = state.absolutePoints;\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\tif (pts != null)\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t// Checks if the source is cloned or sets the terminal point\r\n\t\t\t\t\t\t\t\t\tvar src = this.model.getTerminal(cells[i], true);\r\n\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\twhile (src != null && !dict.get(src))\r\n\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\tsrc = this.model.getParent(src);\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\tif (src == null && pts[0] != null)\r\n\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\tg.setTerminalPoint(\r\n\t\t\t\t\t\t\t\t\t\t\tnew mxPoint(pts[0].x / scale - trans.x,\r\n\t\t\t\t\t\t\t\t\t\t\t\tpts[0].y / scale - trans.y), true);\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t// Checks if the target is cloned or sets the terminal point\r\n\t\t\t\t\t\t\t\t\tvar trg = this.model.getTerminal(cells[i], false);\r\n\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\twhile (trg != null && !dict.get(trg))\r\n\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\ttrg = this.model.getParent(trg);\r\n\t\t\t\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t\t\t\tvar n = pts.length - 1;\r\n\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\tif (trg == null && pts[n] != null)\r\n\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\tg.setTerminalPoint(\r\n\t\t\t\t\t\t\t\t\t\t\tnew mxPoint(pts[n].x / scale - trans.x,\r\n\t\t\t\t\t\t\t\t\t\t\t\tpts[n].y / scale - trans.y), false);\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t// Translates the control points\r\n\t\t\t\t\t\t\t\t\tvar points = g.points;\r\n\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\tif (points != null)\r\n\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\tfor (var j = 0; j < points.length; j++)\r\n\t\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\t\tpoints[j].x += dx;\r\n\t\t\t\t\t\t\t\t\t\t\tpoints[j].y += dy;\r\n\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tg.translate(dx, dy);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tclones = [];\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn clones;\r\n};\r\n\r\n/**\r\n * Function: insertVertex\r\n * \r\n * Adds a new vertex into the given parent <mxCell> using value as the user\r\n * object and the given coordinates as the <mxGeometry> of the new vertex.\r\n * The id and style are used for the respective properties of the new\r\n * <mxCell>, which is returned.\r\n *\r\n * When adding new vertices from a mouse event, one should take into\r\n * account the offset of the graph container and the scale and translation\r\n * of the view in order to find the correct unscaled, untranslated\r\n * coordinates using <mxGraph.getPointForEvent> as follows:\r\n * \r\n * (code)\r\n * var pt = graph.getPointForEvent(evt);\r\n * var parent = graph.getDefaultParent();\r\n * graph.insertVertex(parent, null,\r\n * \t\t\t'Hello, World!', x, y, 220, 30);\r\n * (end)\r\n * \r\n * For adding image cells, the style parameter can be assigned as\r\n * \r\n * (code)\r\n * stylename;image=imageUrl\r\n * (end)\r\n * \r\n * See <mxGraph> for more information on using images.\r\n *\r\n * Parameters:\r\n * \r\n * parent - <mxCell> that specifies the parent of the new vertex.\r\n * id - Optional string that defines the Id of the new vertex.\r\n * value - Object to be used as the user object.\r\n * x - Integer that defines the x coordinate of the vertex.\r\n * y - Integer that defines the y coordinate of the vertex.\r\n * width - Integer that defines the width of the vertex.\r\n * height - Integer that defines the height of the vertex.\r\n * style - Optional string that defines the cell style.\r\n * relative - Optional boolean that specifies if the geometry is relative.\r\n * Default is false.\r\n */\r\nmxGraph.prototype.insertVertex = function(parent, id, value,\r\n\tx, y, width, height, style, relative)\r\n{\r\n\tvar vertex = this.createVertex(parent, id, value, x, y, width, height, style, relative);\r\n\r\n\treturn this.addCell(vertex, parent);\r\n};\r\n\r\n/**\r\n * Function: createVertex\r\n * \r\n * Hook method that creates the new vertex for <insertVertex>.\r\n */\r\nmxGraph.prototype.createVertex = function(parent, id, value,\r\n\t\tx, y, width, height, style, relative)\r\n{\r\n\t// Creates the geometry for the vertex\r\n\tvar geometry = new mxGeometry(x, y, width, height);\r\n\tgeometry.relative = (relative != null) ? relative : false;\r\n\t\r\n\t// Creates the vertex\r\n\tvar vertex = new mxCell(value, geometry, style);\r\n\tvertex.setId(id);\r\n\tvertex.setVertex(true);\r\n\tvertex.setConnectable(true);\r\n\t\r\n\treturn vertex;\r\n};\r\n\t\r\n/**\r\n * Function: insertEdge\r\n * \r\n * Adds a new edge into the given parent <mxCell> using value as the user\r\n * object and the given source and target as the terminals of the new edge.\r\n * The id and style are used for the respective properties of the new\r\n * <mxCell>, which is returned.\r\n *\r\n * Parameters:\r\n * \r\n * parent - <mxCell> that specifies the parent of the new edge.\r\n * id - Optional string that defines the Id of the new edge.\r\n * value - JavaScript object to be used as the user object.\r\n * source - <mxCell> that defines the source of the edge.\r\n * target - <mxCell> that defines the target of the edge.\r\n * style - Optional string that defines the cell style.\r\n */\r\nmxGraph.prototype.insertEdge = function(parent, id, value, source, target, style)\r\n{\r\n\tvar edge = this.createEdge(parent, id, value, source, target, style);\r\n\t\r\n\treturn this.addEdge(edge, parent, source, target);\r\n};\r\n\r\n/**\r\n * Function: createEdge\r\n * \r\n * Hook method that creates the new edge for <insertEdge>. This\r\n * implementation does not set the source and target of the edge, these\r\n * are set when the edge is added to the model.\r\n * \r\n */\r\nmxGraph.prototype.createEdge = function(parent, id, value, source, target, style)\r\n{\r\n\t// Creates the edge\r\n\tvar edge = new mxCell(value, new mxGeometry(), style);\r\n\tedge.setId(id);\r\n\tedge.setEdge(true);\r\n\tedge.geometry.relative = true;\r\n\t\r\n\treturn edge;\r\n};\r\n\r\n/**\r\n * Function: addEdge\r\n * \r\n * Adds the edge to the parent and connects it to the given source and\r\n * target terminals. This is a shortcut method. Returns the edge that was\r\n * added.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCell> to be inserted into the given parent.\r\n * parent - <mxCell> that represents the new parent. If no parent is\r\n * given then the default parent is used.\r\n * source - Optional <mxCell> that represents the source terminal.\r\n * target - Optional <mxCell> that represents the target terminal.\r\n * index - Optional index to insert the cells at. Default is to append.\r\n */\r\nmxGraph.prototype.addEdge = function(edge, parent, source, target, index)\r\n{\r\n\treturn this.addCell(edge, parent, index, source, target);\r\n};\r\n\r\n/**\r\n * Function: addCell\r\n * \r\n * Adds the cell to the parent and connects it to the given source and\r\n * target terminals. This is a shortcut method. Returns the cell that was\r\n * added.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> to be inserted into the given parent.\r\n * parent - <mxCell> that represents the new parent. If no parent is\r\n * given then the default parent is used.\r\n * index - Optional index to insert the cells at. Default is to append.\r\n * source - Optional <mxCell> that represents the source terminal.\r\n * target - Optional <mxCell> that represents the target terminal.\r\n */\r\nmxGraph.prototype.addCell = function(cell, parent, index, source, target)\r\n{\r\n\treturn this.addCells([cell], parent, index, source, target)[0];\r\n};\r\n\r\n/**\r\n * Function: addCells\r\n * \r\n * Adds the cells to the parent at the given index, connecting each cell to\r\n * the optional source and target terminal. The change is carried out using\r\n * <cellsAdded>. This method fires <mxEvent.ADD_CELLS> while the\r\n * transaction is in progress. Returns the cells that were added.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> to be inserted.\r\n * parent - <mxCell> that represents the new parent. If no parent is\r\n * given then the default parent is used.\r\n * index - Optional index to insert the cells at. Default is to append.\r\n * source - Optional source <mxCell> for all inserted cells.\r\n * target - Optional target <mxCell> for all inserted cells.\r\n */\r\nmxGraph.prototype.addCells = function(cells, parent, index, source, target)\r\n{\r\n\tif (parent == null)\r\n\t{\r\n\t\tparent = this.getDefaultParent();\r\n\t}\r\n\t\r\n\tif (index == null)\r\n\t{\r\n\t\tindex = this.model.getChildCount(parent);\r\n\t}\r\n\t\r\n\tthis.model.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tthis.cellsAdded(cells, parent, index, source, target, false, true);\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.ADD_CELLS, 'cells', cells,\r\n\t\t\t\t'parent', parent, 'index', index, 'source', source, 'target', target));\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tthis.model.endUpdate();\r\n\t}\r\n\r\n\treturn cells;\r\n};\r\n\r\n/**\r\n * Function: cellsAdded\r\n * \r\n * Adds the specified cells to the given parent. This method fires\r\n * <mxEvent.CELLS_ADDED> while the transaction is in progress.\r\n */\r\nmxGraph.prototype.cellsAdded = function(cells, parent, index, source, target, absolute, constrain, extend)\r\n{\r\n\tif (cells != null && parent != null && index != null)\r\n\t{\r\n\t\tthis.model.beginUpdate();\r\n\t\ttry\r\n\t\t{\r\n\t\t\tvar parentState = (absolute) ? this.view.getState(parent) : null;\r\n\t\t\tvar o1 = (parentState != null) ? parentState.origin : null;\r\n\t\t\tvar zero = new mxPoint(0, 0);\r\n\r\n\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t{\r\n\t\t\t\tif (cells[i] == null)\r\n\t\t\t\t{\r\n\t\t\t\t\tindex--;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tvar previous = this.model.getParent(cells[i]);\r\n\t\r\n\t\t\t\t\t// Keeps the cell at its absolute location\r\n\t\t\t\t\tif (o1 != null && cells[i] != parent && parent != previous)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar oldState = this.view.getState(previous);\r\n\t\t\t\t\t\tvar o2 = (oldState != null) ? oldState.origin : zero;\r\n\t\t\t\t\t\tvar geo = this.model.getGeometry(cells[i]);\r\n\t\r\n\t\t\t\t\t\tif (geo != null)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar dx = o2.x - o1.x;\r\n\t\t\t\t\t\t\tvar dy = o2.y - o1.y;\r\n\t\r\n\t\t\t\t\t\t\t// FIXME: Cells should always be inserted first before any other edit\r\n\t\t\t\t\t\t\t// to avoid forward references in sessions.\r\n\t\t\t\t\t\t\tgeo = geo.clone();\r\n\t\t\t\t\t\t\tgeo.translate(dx, dy);\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tif (!geo.relative && this.model.isVertex(cells[i]) &&\r\n\t\t\t\t\t\t\t\t!this.isAllowNegativeCoordinates())\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tgeo.x = Math.max(0, geo.x);\r\n\t\t\t\t\t\t\t\tgeo.y = Math.max(0, geo.y);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tthis.model.setGeometry(cells[i], geo);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\r\n\t\t\t\t\t// Decrements all following indices\r\n\t\t\t\t\t// if cell is already in parent\r\n\t\t\t\t\tif (parent == previous && index + i > this.model.getChildCount(parent))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tindex--;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tthis.model.add(parent, cells[i], index + i);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (this.autoSizeCellsOnAdd)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.autoSizeCell(cells[i], true);\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// Extends the parent or constrains the child\r\n\t\t\t\t\tif ((extend == null || extend) &&\r\n\t\t\t\t\t\tthis.isExtendParentsOnAdd(cells[i]) && this.isExtendParent(cells[i]))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.extendParent(cells[i]);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Additionally constrains the child after extending the parent\r\n\t\t\t\t\tif (constrain == null || constrain)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.constrainChild(cells[i]);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Sets the source terminal\r\n\t\t\t\t\tif (source != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.cellConnected(cells[i], source, true);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Sets the target terminal\r\n\t\t\t\t\tif (target != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.cellConnected(cells[i], target, false);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.CELLS_ADDED, 'cells', cells,\r\n\t\t\t\t'parent', parent, 'index', index, 'source', source, 'target', target,\r\n\t\t\t\t'absolute', absolute));\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tthis.model.endUpdate();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: autoSizeCell\r\n * \r\n * Resizes the specified cell to just fit around the its label and/or children\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCells> to be resized.\r\n * recurse - Optional boolean which specifies if all descendants should be\r\n * autosized. Default is true.\r\n */\r\nmxGraph.prototype.autoSizeCell = function(cell, recurse)\r\n{\r\n\trecurse = (recurse != null) ? recurse : true;\r\n\t\r\n\tif (recurse)\r\n\t{\r\n\t\tvar childCount = this.model.getChildCount(cell);\r\n\t\t\r\n\t\tfor (var i = 0; i < childCount; i++)\r\n\t\t{\r\n\t\t\tthis.autoSizeCell(this.model.getChildAt(cell, i));\r\n\t\t}\r\n\t}\r\n\r\n\tif (this.getModel().isVertex(cell) && this.isAutoSizeCell(cell))\r\n\t{\r\n\t\tthis.updateCellSize(cell);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: removeCells\r\n * \r\n * Removes the given cells from the graph including all connected edges if\r\n * includeEdges is true. The change is carried out using <cellsRemoved>.\r\n * This method fires <mxEvent.REMOVE_CELLS> while the transaction is in\r\n * progress. The removed cells are returned as an array.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> to remove. If null is specified then the\r\n * selection cells which are deletable are used.\r\n * includeEdges - Optional boolean which specifies if all connected edges\r\n * should be removed as well. Default is true.\r\n */\r\nmxGraph.prototype.removeCells = function(cells, includeEdges)\r\n{\r\n\tincludeEdges = (includeEdges != null) ? includeEdges : true;\r\n\t\r\n\tif (cells == null)\r\n\t{\r\n\t\tcells = this.getDeletableCells(this.getSelectionCells());\r\n\t}\r\n\r\n\t// Adds all edges to the cells\r\n\tif (includeEdges)\r\n\t{\r\n\t\t// FIXME: Remove duplicate cells in result or do not add if\r\n\t\t// in cells or descendant of cells\r\n\t\tcells = this.getDeletableCells(this.addAllEdges(cells));\r\n\t}\r\n\telse\r\n\t{\r\n\t\tcells = cells.slice();\r\n\t\t\r\n\t\t// Removes edges that are currently not\r\n\t\t// visible as those cannot be updated\r\n\t\tvar edges = this.getDeletableCells(this.getAllEdges(cells));\r\n\t\tvar dict = new mxDictionary();\r\n\t\t\r\n\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t{\r\n\t\t\tdict.put(cells[i], true);\r\n\t\t}\r\n\t\t\r\n\t\tfor (var i = 0; i < edges.length; i++)\r\n\t\t{\r\n\t\t\tif (this.view.getState(edges[i]) == null &&\r\n\t\t\t\t!dict.get(edges[i]))\r\n\t\t\t{\r\n\t\t\t\tdict.put(edges[i], true);\r\n\t\t\t\tcells.push(edges[i]);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tthis.model.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tthis.cellsRemoved(cells);\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS, \r\n\t\t\t\t'cells', cells, 'includeEdges', includeEdges));\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tthis.model.endUpdate();\r\n\t}\r\n\t\r\n\treturn cells;\r\n};\r\n\r\n/**\r\n * Function: cellsRemoved\r\n * \r\n * Removes the given cells from the model. This method fires\r\n * <mxEvent.CELLS_REMOVED> while the transaction is in progress.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> to remove.\r\n */\r\nmxGraph.prototype.cellsRemoved = function(cells)\r\n{\r\n\tif (cells != null && cells.length > 0)\r\n\t{\r\n\t\tvar scale = this.view.scale;\r\n\t\tvar tr = this.view.translate;\r\n\t\t\r\n\t\tthis.model.beginUpdate();\r\n\t\ttry\r\n\t\t{\r\n\t\t\t// Creates hashtable for faster lookup\r\n\t\t\tvar dict = new mxDictionary();\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t{\r\n\t\t\t\tdict.put(cells[i], true);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t{\r\n\t\t\t\t// Disconnects edges which are not being removed\r\n\t\t\t\tvar edges = this.getAllEdges([cells[i]]);\r\n\t\t\t\t\r\n\t\t\t\tvar disconnectTerminal = mxUtils.bind(this, function(edge, source)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar geo = this.model.getGeometry(edge);\r\n\r\n\t\t\t\t\tif (geo != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t// Checks if terminal is being removed\r\n\t\t\t\t\t\tvar terminal = this.model.getTerminal(edge, source);\r\n\t\t\t\t\t\tvar connected = false;\r\n\t\t\t\t\t\tvar tmp = terminal;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\twhile (tmp != null)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tif (cells[i] == tmp)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tconnected = true;\r\n\t\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\ttmp = this.model.getParent(tmp);\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tif (connected)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tgeo = geo.clone();\r\n\t\t\t\t\t\t\tvar state = this.view.getState(edge);\r\n\r\n\t\t\t\t\t\t\tif (state != null && state.absolutePoints != null)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tvar pts = state.absolutePoints;\r\n\t\t\t\t\t\t\t\tvar n = (source) ? 0 : pts.length - 1;\r\n\r\n\t\t\t\t\t\t\t\tgeo.setTerminalPoint(new mxPoint(\r\n\t\t\t\t\t\t\t\t\tpts[n].x / scale - tr.x - state.origin.x,\r\n\t\t\t\t\t\t\t\t\tpts[n].y / scale - tr.y - state.origin.y), source);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t// Fallback to center of terminal if routing\r\n\t\t\t\t\t\t\t\t// points are not available to add new point\r\n\t\t\t\t\t\t\t\t// KNOWN: Should recurse to find parent offset\r\n\t\t\t\t\t\t\t\t// of edge for nested groups but invisible edges\r\n\t\t\t\t\t\t\t\t// should be removed in removeCells step\r\n\t\t\t\t\t\t\t\tvar tstate = this.view.getState(terminal);\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\tif (tstate != null)\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\tgeo.setTerminalPoint(new mxPoint(\r\n\t\t\t\t\t\t\t\t\t\ttstate.getCenterX() / scale - tr.x,\r\n\t\t\t\t\t\t\t\t\t\ttstate.getCenterY() / scale - tr.y), source);\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t\tthis.model.setGeometry(edge, geo);\r\n\t\t\t\t\t\t\tthis.model.setTerminal(edge, null, source);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t});\r\n\t\t\t\t\r\n\t\t\t\tfor (var j = 0; j < edges.length; j++)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (!dict.get(edges[j]))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tdict.put(edges[j], true);\r\n\t\t\t\t\t\tdisconnectTerminal(edges[j], true);\r\n\t\t\t\t\t\tdisconnectTerminal(edges[j], false);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tthis.model.remove(cells[i]);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.CELLS_REMOVED, 'cells', cells));\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tthis.model.endUpdate();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: splitEdge\r\n * \r\n * Splits the given edge by adding the newEdge between the previous source\r\n * and the given cell and reconnecting the source of the given edge to the\r\n * given cell. This method fires <mxEvent.SPLIT_EDGE> while the transaction\r\n * is in progress. Returns the new edge that was inserted.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCell> that represents the edge to be splitted.\r\n * cells - <mxCells> that represents the cells to insert into the edge.\r\n * newEdge - <mxCell> that represents the edge to be inserted.\r\n * dx - Optional integer that specifies the vector to move the cells.\r\n * dy - Optional integer that specifies the vector to move the cells.\r\n */\r\nmxGraph.prototype.splitEdge = function(edge, cells, newEdge, dx, dy)\r\n{\r\n\tdx = dx || 0;\r\n\tdy = dy || 0;\r\n\r\n\tvar parent = this.model.getParent(edge);\r\n\tvar source = this.model.getTerminal(edge, true);\r\n\r\n\tthis.model.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tif (newEdge == null)\r\n\t\t{\r\n\t\t\tnewEdge = this.cloneCell(edge);\r\n\t\t\t\r\n\t\t\t// Removes waypoints before/after new cell\r\n\t\t\tvar state = this.view.getState(edge);\r\n\t\t\tvar geo = this.getCellGeometry(newEdge);\r\n\t\t\t\r\n\t\t\tif (geo != null && geo.points != null && state != null)\r\n\t\t\t{\r\n\t\t\t\tvar t = this.view.translate;\r\n\t\t\t\tvar s = this.view.scale;\r\n\t\t\t\tvar idx = mxUtils.findNearestSegment(state, (dx + t.x) * s, (dy + t.y) * s);\r\n\t\t\t\tgeo.points = geo.points.slice(0, idx);\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\tgeo = this.getCellGeometry(edge);\r\n\t\t\t\t\r\n\t\t\t\tif (geo != null && geo.points != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tgeo = geo.clone();\r\n\t\t\t\t\tgeo.points = geo.points.slice(idx);\r\n\t\t\t\t\tthis.model.setGeometry(edge, geo);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tthis.cellsMoved(cells, dx, dy, false, false);\r\n\t\tthis.cellsAdded(cells, parent, this.model.getChildCount(parent), null, null,\r\n\t\t\t\ttrue);\r\n\t\tthis.cellsAdded([newEdge], parent, this.model.getChildCount(parent),\r\n\t\t\t\tsource, cells[0], false);\r\n\t\tthis.cellConnected(edge, cells[0], true);\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.SPLIT_EDGE, 'edge', edge,\r\n\t\t\t\t'cells', cells, 'newEdge', newEdge, 'dx', dx, 'dy', dy));\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tthis.model.endUpdate();\r\n\t}\r\n\r\n\treturn newEdge;\r\n};\r\n\r\n/**\r\n * Group: Cell visibility\r\n */\r\n\r\n/**\r\n * Function: toggleCells\r\n * \r\n * Sets the visible state of the specified cells and all connected edges\r\n * if includeEdges is true. The change is carried out using <cellsToggled>.\r\n * This method fires <mxEvent.TOGGLE_CELLS> while the transaction is in\r\n * progress. Returns the cells whose visible state was changed.\r\n * \r\n * Parameters:\r\n * \r\n * show - Boolean that specifies the visible state to be assigned.\r\n * cells - Array of <mxCells> whose visible state should be changed. If\r\n * null is specified then the selection cells are used.\r\n * includeEdges - Optional boolean indicating if the visible state of all\r\n * connected edges should be changed as well. Default is true.\r\n */\r\nmxGraph.prototype.toggleCells = function(show, cells, includeEdges)\r\n{\r\n\tif (cells == null)\r\n\t{\r\n\t\tcells = this.getSelectionCells();\r\n\t}\r\n\r\n\t// Adds all connected edges recursively\r\n\tif (includeEdges)\r\n\t{\r\n\t\tcells = this.addAllEdges(cells);\r\n\t}\r\n\r\n\tthis.model.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tthis.cellsToggled(cells, show);\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.TOGGLE_CELLS,\r\n\t\t\t'show', show, 'cells', cells, 'includeEdges', includeEdges));\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tthis.model.endUpdate();\r\n\t}\r\n\r\n\treturn cells;\r\n};\r\n\r\n/**\r\n * Function: cellsToggled\r\n * \r\n * Sets the visible state of the specified cells.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> whose visible state should be changed.\r\n * show - Boolean that specifies the visible state to be assigned.\r\n */\r\nmxGraph.prototype.cellsToggled = function(cells, show)\r\n{\r\n\tif (cells != null && cells.length > 0)\r\n\t{\r\n\t\tthis.model.beginUpdate();\r\n\t\ttry\r\n\t\t{\r\n\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t{\r\n\t\t\t\tthis.model.setVisible(cells[i], show);\r\n\t\t\t}\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tthis.model.endUpdate();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Group: Folding\r\n */\r\n\r\n/**\r\n * Function: foldCells\r\n * \r\n * Sets the collapsed state of the specified cells and all descendants\r\n * if recurse is true. The change is carried out using <cellsFolded>.\r\n * This method fires <mxEvent.FOLD_CELLS> while the transaction is in\r\n * progress. Returns the cells whose collapsed state was changed.\r\n * \r\n * Parameters:\r\n * \r\n * collapsed - Boolean indicating the collapsed state to be assigned.\r\n * recurse - Optional boolean indicating if the collapsed state of all\r\n * descendants should be set. Default is false.\r\n * cells - Array of <mxCells> whose collapsed state should be set. If\r\n * null is specified then the foldable selection cells are used.\r\n * checkFoldable - Optional boolean indicating of isCellFoldable should be\r\n * checked. Default is false.\r\n * evt - Optional native event that triggered the invocation.\r\n */\r\nmxGraph.prototype.foldCells = function(collapse, recurse, cells, checkFoldable, evt)\r\n{\r\n\trecurse = (recurse != null) ? recurse : false;\r\n\t\r\n\tif (cells == null)\r\n\t{\r\n\t\tcells = this.getFoldableCells(this.getSelectionCells(), collapse);\r\n\t}\r\n\r\n\tthis.stopEditing(false);\r\n\r\n\tthis.model.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tthis.cellsFolded(cells, collapse, recurse, checkFoldable);\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.FOLD_CELLS,\r\n\t\t\t'collapse', collapse, 'recurse', recurse, 'cells', cells));\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tthis.model.endUpdate();\r\n\t}\r\n\r\n\treturn cells;\r\n};\r\n\r\n/**\r\n * Function: cellsFolded\r\n * \r\n * Sets the collapsed state of the specified cells. This method fires\r\n * <mxEvent.CELLS_FOLDED> while the transaction is in progress. Returns the\r\n * cells whose collapsed state was changed.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> whose collapsed state should be set.\r\n * collapsed - Boolean indicating the collapsed state to be assigned.\r\n * recurse - Boolean indicating if the collapsed state of all descendants\r\n * should be set.\r\n * checkFoldable - Optional boolean indicating of isCellFoldable should be\r\n * checked. Default is false.\r\n */\r\nmxGraph.prototype.cellsFolded = function(cells, collapse, recurse, checkFoldable)\r\n{\r\n\tif (cells != null && cells.length > 0)\r\n\t{\r\n\t\tthis.model.beginUpdate();\r\n\t\ttry\r\n\t\t{\r\n\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t{\r\n\t\t\t\tif ((!checkFoldable || this.isCellFoldable(cells[i], collapse)) &&\r\n\t\t\t\t\tcollapse != this.isCellCollapsed(cells[i]))\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.model.setCollapsed(cells[i], collapse);\r\n\t\t\t\t\tthis.swapBounds(cells[i], collapse);\r\n\r\n\t\t\t\t\tif (this.isExtendParent(cells[i]))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.extendParent(cells[i]);\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif (recurse)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar children = this.model.getChildren(cells[i]);\r\n\t\t\t\t\t\tthis.cellsFolded(children, collapse, recurse);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tthis.constrainChild(cells[i]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.CELLS_FOLDED,\r\n\t\t\t\t'cells', cells, 'collapse', collapse, 'recurse', recurse));\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tthis.model.endUpdate();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: swapBounds\r\n * \r\n * Swaps the alternate and the actual bounds in the geometry of the given\r\n * cell invoking <updateAlternateBounds> before carrying out the swap.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> for which the bounds should be swapped.\r\n * willCollapse - Boolean indicating if the cell is going to be collapsed.\r\n */\r\nmxGraph.prototype.swapBounds = function(cell, willCollapse)\r\n{\r\n\tif (cell != null)\r\n\t{\r\n\t\tvar geo = this.model.getGeometry(cell);\r\n\t\t\r\n\t\tif (geo != null)\r\n\t\t{\r\n\t\t\tgeo = geo.clone();\r\n\t\t\t\r\n\t\t\tthis.updateAlternateBounds(cell, geo, willCollapse);\r\n\t\t\tgeo.swap();\r\n\t\t\t\r\n\t\t\tthis.model.setGeometry(cell, geo);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: updateAlternateBounds\r\n * \r\n * Updates or sets the alternate bounds in the given geometry for the given\r\n * cell depending on whether the cell is going to be collapsed. If no\r\n * alternate bounds are defined in the geometry and\r\n * <collapseToPreferredSize> is true, then the preferred size is used for\r\n * the alternate bounds. The top, left corner is always kept at the same\r\n * location.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> for which the geometry is being udpated.\r\n * g - <mxGeometry> for which the alternate bounds should be updated.\r\n * willCollapse - Boolean indicating if the cell is going to be collapsed.\r\n */\r\nmxGraph.prototype.updateAlternateBounds = function(cell, geo, willCollapse)\r\n{\r\n\tif (cell != null && geo != null)\r\n\t{\r\n\t\tvar state = this.view.getState(cell);\r\n\t\tvar style = (state != null) ? state.style : this.getCellStyle(cell);\r\n\r\n\t\tif (geo.alternateBounds == null)\r\n\t\t{\r\n\t\t\tvar bounds = geo;\r\n\t\t\t\r\n\t\t\tif (this.collapseToPreferredSize)\r\n\t\t\t{\r\n\t\t\t\tvar tmp = this.getPreferredSizeForCell(cell);\r\n\t\t\t\t\r\n\t\t\t\tif (tmp != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tbounds = tmp;\r\n\r\n\t\t\t\t\tvar startSize = mxUtils.getValue(style, mxConstants.STYLE_STARTSIZE);\r\n\r\n\t\t\t\t\tif (startSize > 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tbounds.height = Math.max(bounds.height, startSize);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tgeo.alternateBounds = new mxRectangle(0, 0, bounds.width, bounds.height);\r\n\t\t}\r\n\t\t\r\n\t\tif (geo.alternateBounds != null)\r\n\t\t{\r\n\t\t\tgeo.alternateBounds.x = geo.x;\r\n\t\t\tgeo.alternateBounds.y = geo.y;\r\n\t\t\t\r\n\t\t\tvar alpha = mxUtils.toRadians(style[mxConstants.STYLE_ROTATION] || 0);\r\n\t\t\t\r\n\t\t\tif (alpha != 0)\r\n\t\t\t{\r\n\t\t\t\tvar dx = geo.alternateBounds.getCenterX() - geo.getCenterX();\r\n\t\t\t\tvar dy = geo.alternateBounds.getCenterY() - geo.getCenterY();\r\n\t\r\n\t\t\t\tvar cos = Math.cos(alpha);\r\n\t\t\t\tvar sin = Math.sin(alpha);\r\n\t\r\n\t\t\t\tvar dx2 = cos * dx - sin * dy;\r\n\t\t\t\tvar dy2 = sin * dx + cos * dy;\r\n\t\t\t\t\r\n\t\t\t\tgeo.alternateBounds.x += dx2 - dx;\r\n\t\t\t\tgeo.alternateBounds.y += dy2 - dy;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: addAllEdges\r\n * \r\n * Returns an array with the given cells and all edges that are connected\r\n * to a cell or one of its descendants.\r\n */\r\nmxGraph.prototype.addAllEdges = function(cells)\r\n{\r\n\tvar allCells = cells.slice();\r\n\t\r\n\treturn mxUtils.removeDuplicates(allCells.concat(this.getAllEdges(cells)));\r\n};\r\n\r\n/**\r\n * Function: getAllEdges\r\n * \r\n * Returns all edges connected to the given cells or its descendants.\r\n */\r\nmxGraph.prototype.getAllEdges = function(cells)\r\n{\r\n\tvar edges = [];\r\n\t\r\n\tif (cells != null)\r\n\t{\r\n\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t{\r\n\t\t\tvar edgeCount = this.model.getEdgeCount(cells[i]);\r\n\t\t\t\r\n\t\t\tfor (var j = 0; j < edgeCount; j++)\r\n\t\t\t{\r\n\t\t\t\tedges.push(this.model.getEdgeAt(cells[i], j));\r\n\t\t\t}\r\n\r\n\t\t\t// Recurses\r\n\t\t\tvar children = this.model.getChildren(cells[i]);\r\n\t\t\tedges = edges.concat(this.getAllEdges(children));\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn edges;\r\n};\r\n\r\n/**\r\n * Group: Cell sizing\r\n */\r\n\r\n/**\r\n * Function: updateCellSize\r\n * \r\n * Updates the size of the given cell in the model using <cellSizeUpdated>.\r\n * This method fires <mxEvent.UPDATE_CELL_SIZE> while the transaction is in\r\n * progress. Returns the cell whose size was updated.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose size should be updated.\r\n */\r\nmxGraph.prototype.updateCellSize = function(cell, ignoreChildren)\r\n{\r\n\tignoreChildren = (ignoreChildren != null) ? ignoreChildren : false;\r\n\t\r\n\tthis.model.beginUpdate();\t\t\t\t\r\n\ttry\r\n\t{\r\n\t\tthis.cellSizeUpdated(cell, ignoreChildren);\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.UPDATE_CELL_SIZE,\r\n\t\t\t\t'cell', cell, 'ignoreChildren', ignoreChildren));\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tthis.model.endUpdate();\r\n\t}\r\n\t\r\n\treturn cell;\r\n};\r\n\r\n/**\r\n * Function: cellSizeUpdated\r\n * \r\n * Updates the size of the given cell in the model using\r\n * <getPreferredSizeForCell> to get the new size.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> for which the size should be changed.\r\n */\r\nmxGraph.prototype.cellSizeUpdated = function(cell, ignoreChildren)\r\n{\r\n\tif (cell != null)\r\n\t{\r\n\t\tthis.model.beginUpdate();\t\t\t\t\r\n\t\ttry\r\n\t\t{\r\n\t\t\tvar size = this.getPreferredSizeForCell(cell);\r\n\t\t\tvar geo = this.model.getGeometry(cell);\r\n\t\t\t\r\n\t\t\tif (size != null && geo != null)\r\n\t\t\t{\r\n\t\t\t\tvar collapsed = this.isCellCollapsed(cell);\r\n\t\t\t\tgeo = geo.clone();\r\n\r\n\t\t\t\tif (this.isSwimlane(cell))\r\n\t\t\t\t{\r\n\t\t\t\t\tvar state = this.view.getState(cell);\r\n\t\t\t\t\tvar style = (state != null) ? state.style : this.getCellStyle(cell);\r\n\t\t\t\t\tvar cellStyle = this.model.getStyle(cell);\r\n\r\n\t\t\t\t\tif (cellStyle == null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tcellStyle = '';\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif (mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tcellStyle = mxUtils.setStyle(cellStyle,\r\n\t\t\t\t\t\t\t\tmxConstants.STYLE_STARTSIZE, size.height + 8);\r\n\r\n\t\t\t\t\t\tif (collapsed)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tgeo.height = size.height + 8;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tgeo.width = size.width;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tcellStyle = mxUtils.setStyle(cellStyle,\r\n\t\t\t\t\t\t\t\tmxConstants.STYLE_STARTSIZE, size.width + 8);\r\n\r\n\t\t\t\t\t\tif (collapsed)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tgeo.width = size.width + 8;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tgeo.height = size.height;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tthis.model.setStyle(cell, cellStyle);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tgeo.width = size.width;\r\n\t\t\t\t\tgeo.height = size.height;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (!ignoreChildren && !collapsed)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar bounds = this.view.getBounds(this.model.getChildren(cell));\r\n\r\n\t\t\t\t\tif (bounds != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar tr = this.view.translate;\r\n\t\t\t\t\t\tvar scale = this.view.scale;\r\n\r\n\t\t\t\t\t\tvar width = (bounds.x + bounds.width) / scale - geo.x - tr.x;\r\n\t\t\t\t\t\tvar height = (bounds.y + bounds.height) / scale - geo.y - tr.y;\r\n\r\n\t\t\t\t\t\tgeo.width = Math.max(geo.width, width);\r\n\t\t\t\t\t\tgeo.height = Math.max(geo.height, height);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tthis.cellsResized([cell], [geo], false);\r\n\t\t\t}\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tthis.model.endUpdate();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getPreferredSizeForCell\r\n * \r\n * Returns the preferred width and height of the given <mxCell> as an\r\n * <mxRectangle>. To implement a minimum width, add a new style eg.\r\n * minWidth in the vertex and override this method as follows.\r\n * \r\n * (code)\r\n * var graphGetPreferredSizeForCell = graph.getPreferredSizeForCell;\r\n * graph.getPreferredSizeForCell = function(cell)\r\n * {\r\n *   var result = graphGetPreferredSizeForCell.apply(this, arguments);\r\n *   var style = this.getCellStyle(cell);\r\n *   \r\n *   if (style['minWidth'] > 0)\r\n *   {\r\n *     result.width = Math.max(style['minWidth'], result.width);\r\n *   }\r\n * \r\n *   return result;\r\n * };\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> for which the preferred size should be returned.\r\n */\r\nmxGraph.prototype.getPreferredSizeForCell = function(cell)\r\n{\r\n\tvar result = null;\r\n\t\r\n\tif (cell != null)\r\n\t{\r\n\t\tvar state = this.view.getState(cell) || this.view.createState(cell);\r\n\t\tvar style = state.style;\r\n\r\n\t\tif (!this.model.isEdge(cell))\r\n\t\t{\r\n\t\t\tvar fontSize = style[mxConstants.STYLE_FONTSIZE] || mxConstants.DEFAULT_FONTSIZE;\r\n\t\t\tvar dx = 0;\r\n\t\t\tvar dy = 0;\r\n\t\t\t\r\n\t\t\t// Adds dimension of image if shape is a label\r\n\t\t\tif (this.getImage(state) != null || style[mxConstants.STYLE_IMAGE] != null)\r\n\t\t\t{\r\n\t\t\t\tif (style[mxConstants.STYLE_SHAPE] == mxConstants.SHAPE_LABEL)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (style[mxConstants.STYLE_VERTICAL_ALIGN] == mxConstants.ALIGN_MIDDLE)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tdx += parseFloat(style[mxConstants.STYLE_IMAGE_WIDTH]) || mxLabel.prototype.imageSize;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (style[mxConstants.STYLE_ALIGN] != mxConstants.ALIGN_CENTER)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tdy += parseFloat(style[mxConstants.STYLE_IMAGE_HEIGHT]) || mxLabel.prototype.imageSize;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// Adds spacings\r\n\t\t\tdx += 2 * (style[mxConstants.STYLE_SPACING] || 0);\r\n\t\t\tdx += style[mxConstants.STYLE_SPACING_LEFT] || 0;\r\n\t\t\tdx += style[mxConstants.STYLE_SPACING_RIGHT] || 0;\r\n\r\n\t\t\tdy += 2 * (style[mxConstants.STYLE_SPACING] || 0);\r\n\t\t\tdy += style[mxConstants.STYLE_SPACING_TOP] || 0;\r\n\t\t\tdy += style[mxConstants.STYLE_SPACING_BOTTOM] || 0;\r\n\t\t\t\r\n\t\t\t// Add spacing for collapse/expand icon\r\n\t\t\t// LATER: Check alignment and use constants\r\n\t\t\t// for image spacing\r\n\t\t\tvar image = this.getFoldingImage(state);\r\n\t\t\t\r\n\t\t\tif (image != null)\r\n\t\t\t{\r\n\t\t\t\tdx += image.width + 8;\r\n\t\t\t}\r\n\r\n\t\t\t// Adds space for label\r\n\t\t\tvar value = this.cellRenderer.getLabelValue(state);\r\n\r\n\t\t\tif (value != null && value.length > 0)\r\n\t\t\t{\r\n\t\t\t\tif (!this.isHtmlLabel(state.cell))\r\n\t\t\t\t{\r\n\t\t\t\t\tvalue = mxUtils.htmlEntities(value);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tvalue = value.replace(/\\n/g, '<br>');\r\n\t\t\t\t\r\n\t\t\t\tvar size = mxUtils.getSizeForString(value, fontSize, style[mxConstants.STYLE_FONTFAMILY]);\r\n\t\t\t\tvar width = size.width + dx;\r\n\t\t\t\tvar height = size.height + dy;\r\n\t\t\t\t\r\n\t\t\t\tif (!mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true))\r\n\t\t\t\t{\r\n\t\t\t\t\tvar tmp = height;\r\n\t\t\t\t\t\r\n\t\t\t\t\theight = width;\r\n\t\t\t\t\twidth = tmp;\r\n\t\t\t\t}\r\n\t\t\t\r\n\t\t\t\tif (this.gridEnabled)\r\n\t\t\t\t{\r\n\t\t\t\t\twidth = this.snap(width + this.gridSize / 2);\r\n\t\t\t\t\theight = this.snap(height + this.gridSize / 2);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tresult = new mxRectangle(0, 0, width, height);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tvar gs2 = 4 * this.gridSize;\r\n\t\t\t\tresult = new mxRectangle(0, 0, gs2, gs2);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: resizeCell\r\n * \r\n * Sets the bounds of the given cell using <resizeCells>. Returns the\r\n * cell which was passed to the function.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose bounds should be changed.\r\n * bounds - <mxRectangle> that represents the new bounds.\r\n */\r\nmxGraph.prototype.resizeCell = function(cell, bounds, recurse)\r\n{\r\n\treturn this.resizeCells([cell], [bounds], recurse)[0];\r\n};\r\n\r\n/**\r\n * Function: resizeCells\r\n * \r\n * Sets the bounds of the given cells and fires a <mxEvent.RESIZE_CELLS>\r\n * event while the transaction is in progress. Returns the cells which\r\n * have been passed to the function.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> whose bounds should be changed.\r\n * bounds - Array of <mxRectangles> that represent the new bounds.\r\n */\r\nmxGraph.prototype.resizeCells = function(cells, bounds, recurse)\r\n{\r\n\trecurse = (recurse != null) ? recurse : this.isRecursiveResize();\r\n\t\r\n\tthis.model.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tthis.cellsResized(cells, bounds, recurse);\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.RESIZE_CELLS,\r\n\t\t\t\t'cells', cells, 'bounds', bounds));\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tthis.model.endUpdate();\r\n\t}\r\n\r\n\treturn cells;\r\n};\r\n\r\n/**\r\n * Function: cellsResized\r\n * \r\n * Sets the bounds of the given cells and fires a <mxEvent.CELLS_RESIZED>\r\n * event. If <extendParents> is true, then the parent is extended if a\r\n * child size is changed so that it overlaps with the parent.\r\n * \r\n * The following example shows how to control group resizes to make sure\r\n * that all child cells stay within the group.\r\n * \r\n * (code)\r\n * graph.addListener(mxEvent.CELLS_RESIZED, function(sender, evt)\r\n * {\r\n *   var cells = evt.getProperty('cells');\r\n *   \r\n *   if (cells != null)\r\n *   {\r\n *     for (var i = 0; i < cells.length; i++)\r\n *     {\r\n *       if (graph.getModel().getChildCount(cells[i]) > 0)\r\n *       {\r\n *         var geo = graph.getCellGeometry(cells[i]);\r\n *         \r\n *         if (geo != null)\r\n *         {\r\n *           var children = graph.getChildCells(cells[i], true, true);\r\n *           var bounds = graph.getBoundingBoxFromGeometry(children, true);\r\n *           \r\n *           geo = geo.clone();\r\n *           geo.width = Math.max(geo.width, bounds.width);\r\n *           geo.height = Math.max(geo.height, bounds.height);\r\n *           \r\n *           graph.getModel().setGeometry(cells[i], geo);\r\n *         }\r\n *       }\r\n *     }\r\n *   }\r\n * });\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> whose bounds should be changed.\r\n * bounds - Array of <mxRectangles> that represent the new bounds.\r\n * recurse - Optional boolean that specifies if the children should be resized.\r\n */\r\nmxGraph.prototype.cellsResized = function(cells, bounds, recurse)\r\n{\r\n\trecurse = (recurse != null) ? recurse : false;\r\n\t\r\n\tif (cells != null && bounds != null && cells.length == bounds.length)\r\n\t{\r\n\t\tthis.model.beginUpdate();\r\n\t\ttry\r\n\t\t{\r\n\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t{\r\n\t\t\t\tthis.cellResized(cells[i], bounds[i], false, recurse);\r\n\r\n\t\t\t\tif (this.isExtendParent(cells[i]))\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.extendParent(cells[i]);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.constrainChild(cells[i]);\r\n\t\t\t}\r\n\r\n\t\t\tif (this.resetEdgesOnResize)\r\n\t\t\t{\r\n\t\t\t\tthis.resetEdges(cells);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.CELLS_RESIZED,\r\n\t\t\t\t\t'cells', cells, 'bounds', bounds));\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tthis.model.endUpdate();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: cellResized\r\n * \r\n * Resizes the parents recursively so that they contain the complete area\r\n * of the resized child cell.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose bounds should be changed.\r\n * bounds - <mxRectangles> that represent the new bounds.\r\n * ignoreRelative - Boolean that indicates if relative cells should be ignored.\r\n * recurse - Optional boolean that specifies if the children should be resized.\r\n */\r\nmxGraph.prototype.cellResized = function(cell, bounds, ignoreRelative, recurse)\r\n{\r\n\tvar geo = this.model.getGeometry(cell);\r\n\r\n\tif (geo != null && (geo.x != bounds.x || geo.y != bounds.y ||\r\n\t\tgeo.width != bounds.width || geo.height != bounds.height))\r\n\t{\r\n\t\tgeo = geo.clone();\r\n\r\n\t\tif (!ignoreRelative && geo.relative)\r\n\t\t{\r\n\t\t\tvar offset = geo.offset;\r\n\r\n\t\t\tif (offset != null)\r\n\t\t\t{\r\n\t\t\t\toffset.x += bounds.x - geo.x;\r\n\t\t\t\toffset.y += bounds.y - geo.y;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tgeo.x = bounds.x;\r\n\t\t\tgeo.y = bounds.y;\r\n\t\t}\r\n\r\n\t\tgeo.width = bounds.width;\r\n\t\tgeo.height = bounds.height;\r\n\r\n\t\tif (!geo.relative && this.model.isVertex(cell) && !this.isAllowNegativeCoordinates())\r\n\t\t{\r\n\t\t\tgeo.x = Math.max(0, geo.x);\r\n\t\t\tgeo.y = Math.max(0, geo.y);\r\n\t\t}\r\n\r\n\t\tthis.model.beginUpdate();\r\n\t\ttry\r\n\t\t{\r\n\t\t\tif (recurse)\r\n\t\t\t{\r\n\t\t\t\tthis.resizeChildCells(cell, geo);\r\n\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\tthis.model.setGeometry(cell, geo);\r\n\t\t\tthis.constrainChildCells(cell);\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tthis.model.endUpdate();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: resizeChildCells\r\n * \r\n * Resizes the child cells of the given cell for the given new geometry with\r\n * respect to the current geometry of the cell.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> that has been resized.\r\n * newGeo - <mxGeometry> that represents the new bounds.\r\n */\r\nmxGraph.prototype.resizeChildCells = function(cell, newGeo)\r\n{\r\n\tvar geo = this.model.getGeometry(cell);\r\n\tvar dx = newGeo.width / geo.width;\r\n\tvar dy = newGeo.height / geo.height;\r\n\tvar childCount = this.model.getChildCount(cell);\r\n\t\r\n\tfor (var i = 0; i < childCount; i++)\r\n\t{\r\n\t\tthis.scaleCell(this.model.getChildAt(cell, i), dx, dy, true);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: constrainChildCells\r\n * \r\n * Constrains the children of the given cell using <constrainChild>.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> that has been resized.\r\n */\r\nmxGraph.prototype.constrainChildCells = function(cell)\r\n{\r\n\tvar childCount = this.model.getChildCount(cell);\r\n\t\r\n\tfor (var i = 0; i < childCount; i++)\r\n\t{\r\n\t\tthis.constrainChild(this.model.getChildAt(cell, i));\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: scaleCell\r\n * \r\n * Scales the points, position and size of the given cell according to the\r\n * given vertical and horizontal scaling factors.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose geometry should be scaled.\r\n * dx - Horizontal scaling factor.\r\n * dy - Vertical scaling factor.\r\n * recurse - Boolean indicating if the child cells should be scaled.\r\n */\r\nmxGraph.prototype.scaleCell = function(cell, dx, dy, recurse)\r\n{\r\n\tvar geo = this.model.getGeometry(cell);\r\n\t\r\n\tif (geo != null)\r\n\t{\r\n\t\tvar state = this.view.getState(cell);\r\n\t\tvar style = (state != null) ? state.style : this.getCellStyle(cell);\r\n\t\t\r\n\t\tgeo = geo.clone();\r\n\t\t\r\n\t\t// Stores values for restoring based on style\r\n\t\tvar x = geo.x;\r\n\t\tvar y = geo.y\r\n\t\tvar w = geo.width;\r\n\t\tvar h = geo.height;\r\n\t\t\r\n\t\tgeo.scale(dx, dy, style[mxConstants.STYLE_ASPECT] == 'fixed');\r\n\t\t\r\n\t\tif (style[mxConstants.STYLE_RESIZE_WIDTH] == '1')\r\n\t\t{\r\n\t\t\tgeo.width = w * dx;\r\n\t\t}\r\n\t\telse if (style[mxConstants.STYLE_RESIZE_WIDTH] == '0')\r\n\t\t{\r\n\t\t\tgeo.width = w;\r\n\t\t}\r\n\t\t\r\n\t\tif (style[mxConstants.STYLE_RESIZE_HEIGHT] == '1')\r\n\t\t{\r\n\t\t\tgeo.height = h * dy;\r\n\t\t}\r\n\t\telse if (style[mxConstants.STYLE_RESIZE_HEIGHT] == '0')\r\n\t\t{\r\n\t\t\tgeo.height = h;\r\n\t\t}\r\n\t\t\r\n\t\tif (!this.isCellMovable(cell))\r\n\t\t{\r\n\t\t\tgeo.x = x;\r\n\t\t\tgeo.y = y;\r\n\t\t}\r\n\t\t\r\n\t\tif (!this.isCellResizable(cell))\r\n\t\t{\r\n\t\t\tgeo.width = w;\r\n\t\t\tgeo.height = h;\r\n\t\t}\r\n\r\n\t\tif (this.model.isVertex(cell))\r\n\t\t{\r\n\t\t\tthis.cellResized(cell, geo, true, recurse);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.model.setGeometry(cell, geo);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: extendParent\r\n * \r\n * Resizes the parents recursively so that they contain the complete area\r\n * of the resized child cell.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> that has been resized.\r\n */\r\nmxGraph.prototype.extendParent = function(cell)\r\n{\r\n\tif (cell != null)\r\n\t{\r\n\t\tvar parent = this.model.getParent(cell);\r\n\t\tvar p = this.getCellGeometry(parent);\r\n\t\t\r\n\t\tif (parent != null && p != null && !this.isCellCollapsed(parent))\r\n\t\t{\r\n\t\t\tvar geo = this.getCellGeometry(cell);\r\n\t\t\t\r\n\t\t\tif (geo != null && !geo.relative &&\r\n\t\t\t\t(p.width < geo.x + geo.width ||\r\n\t\t\t\tp.height < geo.y + geo.height))\r\n\t\t\t{\r\n\t\t\t\tp = p.clone();\r\n\t\t\t\t\r\n\t\t\t\tp.width = Math.max(p.width, geo.x + geo.width);\r\n\t\t\t\tp.height = Math.max(p.height, geo.y + geo.height);\r\n\t\t\t\t\r\n\t\t\t\tthis.cellsResized([parent], [p], false);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Group: Cell moving\r\n */\r\n\r\n/**\r\n * Function: importCells\r\n * \r\n * Clones and inserts the given cells into the graph using the move\r\n * method and returns the inserted cells. This shortcut is used if\r\n * cells are inserted via datatransfer.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> to be imported.\r\n * dx - Integer that specifies the x-coordinate of the vector. Default is 0.\r\n * dy - Integer that specifies the y-coordinate of the vector. Default is 0.\r\n * target - <mxCell> that represents the new parent of the cells.\r\n * evt - Mouseevent that triggered the invocation.\r\n * mapping - Optional mapping for existing clones.\r\n */\r\nmxGraph.prototype.importCells = function(cells, dx, dy, target, evt, mapping)\r\n{\t\r\n\treturn this.moveCells(cells, dx, dy, true, target, evt, mapping);\r\n};\r\n\r\n/**\r\n * Function: moveCells\r\n * \r\n * Moves or clones the specified cells and moves the cells or clones by the\r\n * given amount, adding them to the optional target cell. The evt is the\r\n * mouse event as the mouse was released. The change is carried out using\r\n * <cellsMoved>. This method fires <mxEvent.MOVE_CELLS> while the\r\n * transaction is in progress. Returns the cells that were moved.\r\n * \r\n * Use the following code to move all cells in the graph.\r\n * \r\n * (code)\r\n * graph.moveCells(graph.getChildCells(null, true, true), 10, 10);\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> to be moved, cloned or added to the target.\r\n * dx - Integer that specifies the x-coordinate of the vector. Default is 0.\r\n * dy - Integer that specifies the y-coordinate of the vector. Default is 0.\r\n * clone - Boolean indicating if the cells should be cloned. Default is false.\r\n * target - <mxCell> that represents the new parent of the cells.\r\n * evt - Mouseevent that triggered the invocation.\r\n * mapping - Optional mapping for existing clones.\r\n */\r\nmxGraph.prototype.moveCells = function(cells, dx, dy, clone, target, evt, mapping)\r\n{\r\n\tdx = (dx != null) ? dx : 0;\r\n\tdy = (dy != null) ? dy : 0;\r\n\tclone = (clone != null) ? clone : false;\r\n\t\r\n\tif (cells != null && (dx != 0 || dy != 0 || clone || target != null))\r\n\t{\r\n\t\t// Removes descendants with ancestors in cells to avoid multiple moving\r\n\t\tcells = this.model.getTopmostCells(cells);\r\n\t\t\r\n\t\tthis.model.beginUpdate();\r\n\t\ttry\r\n\t\t{\r\n\t\t\t// Faster cell lookups to remove relative edge labels with selected\r\n\t\t\t// terminals to avoid explicit and implicit move at same time\r\n\t\t\tvar dict = new mxDictionary();\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t{\r\n\t\t\t\tdict.put(cells[i], true);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar isSelected = mxUtils.bind(this, function(cell)\r\n\t\t\t{\r\n\t\t\t\twhile (cell != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (dict.get(cell))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\treturn true;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tcell = this.model.getParent(cell);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\treturn false;\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\t// Removes relative edge labels with selected terminals\r\n\t\t\tvar checked = [];\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar geo = this.getCellGeometry(cells[i]);\r\n\t\t\t\tvar parent = this.model.getParent(cells[i]);\r\n\t\t\r\n\t\t\t\tif ((geo == null || !geo.relative) || !this.model.isEdge(parent) ||\r\n\t\t\t\t\t(!isSelected(this.model.getTerminal(parent, true)) &&\r\n\t\t\t\t\t!isSelected(this.model.getTerminal(parent, false))))\r\n\t\t\t\t{\r\n\t\t\t\t\tchecked.push(cells[i]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tcells = checked;\r\n\t\t\t\r\n\t\t\tif (clone)\r\n\t\t\t{\r\n\t\t\t\tcells = this.cloneCells(cells, this.isCloneInvalidEdges(), mapping);\r\n\r\n\t\t\t\tif (target == null)\r\n\t\t\t\t{\r\n\t\t\t\t\ttarget = this.getDefaultParent();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// FIXME: Cells should always be inserted first before any other edit\r\n\t\t\t// to avoid forward references in sessions.\r\n\t\t\t// Need to disable allowNegativeCoordinates if target not null to\r\n\t\t\t// allow for temporary negative numbers until cellsAdded is called.\r\n\t\t\tvar previous = this.isAllowNegativeCoordinates();\r\n\t\t\t\r\n\t\t\tif (target != null)\r\n\t\t\t{\r\n\t\t\t\tthis.setAllowNegativeCoordinates(true);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.cellsMoved(cells, dx, dy, !clone && this.isDisconnectOnMove()\r\n\t\t\t\t\t&& this.isAllowDanglingEdges(), target == null,\r\n\t\t\t\t\tthis.isExtendParentsOnMove() && target == null);\r\n\t\t\t\r\n\t\t\tthis.setAllowNegativeCoordinates(previous);\r\n\r\n\t\t\tif (target != null)\r\n\t\t\t{\r\n\t\t\t\tvar index = this.model.getChildCount(target);\r\n\t\t\t\tthis.cellsAdded(cells, target, index, null, null, true);\r\n\t\t\t}\r\n\r\n\t\t\t// Dispatches a move event\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.MOVE_CELLS, 'cells', cells,\r\n\t\t\t\t'dx', dx, 'dy', dy, 'clone', clone, 'target', target, 'event', evt));\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tthis.model.endUpdate();\r\n\t\t}\r\n\t}\r\n\r\n\treturn cells;\r\n};\r\n\r\n/**\r\n * Function: cellsMoved\r\n * \r\n * Moves the specified cells by the given vector, disconnecting the cells\r\n * using disconnectGraph is disconnect is true. This method fires\r\n * <mxEvent.CELLS_MOVED> while the transaction is in progress.\r\n */\r\nmxGraph.prototype.cellsMoved = function(cells, dx, dy, disconnect, constrain, extend)\r\n{\r\n\tif (cells != null && (dx != 0 || dy != 0))\r\n\t{\r\n\t\textend = (extend != null) ? extend : false;\r\n\r\n\t\tthis.model.beginUpdate();\r\n\t\ttry\r\n\t\t{\r\n\t\t\tif (disconnect)\r\n\t\t\t{\r\n\t\t\t\tthis.disconnectGraph(cells);\r\n\t\t\t}\r\n\r\n\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t{\r\n\t\t\t\tthis.translateCell(cells[i], dx, dy);\r\n\t\t\t\t\r\n\t\t\t\tif (extend && this.isExtendParent(cells[i]))\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.extendParent(cells[i]);\r\n\t\t\t\t}\r\n\t\t\t\telse if (constrain)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.constrainChild(cells[i]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (this.resetEdgesOnMove)\r\n\t\t\t{\r\n\t\t\t\tthis.resetEdges(cells);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.CELLS_MOVED,\r\n\t\t\t\t'cells', cells, 'dx', dx, 'dy', dy, 'disconnect', disconnect));\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tthis.model.endUpdate();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: translateCell\r\n * \r\n * Translates the geometry of the given cell and stores the new,\r\n * translated geometry in the model as an atomic change.\r\n */\r\nmxGraph.prototype.translateCell = function(cell, dx, dy)\r\n{\r\n\tvar geo = this.model.getGeometry(cell);\r\n\r\n\tif (geo != null)\r\n\t{\r\n\t\tdx = parseFloat(dx);\r\n\t\tdy = parseFloat(dy);\r\n\t\tgeo = geo.clone();\r\n\t\tgeo.translate(dx, dy);\r\n\r\n\t\tif (!geo.relative && this.model.isVertex(cell) && !this.isAllowNegativeCoordinates())\r\n\t\t{\r\n\t\t\tgeo.x = Math.max(0, parseFloat(geo.x));\r\n\t\t\tgeo.y = Math.max(0, parseFloat(geo.y));\r\n\t\t}\r\n\t\t\r\n\t\tif (geo.relative && !this.model.isEdge(cell))\r\n\t\t{\r\n\t\t\tvar parent = this.model.getParent(cell);\r\n\t\t\tvar angle = 0;\r\n\t\t\t\r\n\t\t\tif (this.model.isVertex(parent))\r\n\t\t\t{\r\n\t\t\t\tvar state = this.view.getState(parent);\r\n\t\t\t\tvar style = (state != null) ? state.style : this.getCellStyle(parent);\r\n\t\t\t\t\r\n\t\t\t\tangle = mxUtils.getValue(style, mxConstants.STYLE_ROTATION, 0);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (angle != 0)\r\n\t\t\t{\r\n\t\t\t\tvar rad = mxUtils.toRadians(-angle);\r\n\t\t\t\tvar cos = Math.cos(rad);\r\n\t\t\t\tvar sin = Math.sin(rad);\r\n\t\t\t\tvar pt = mxUtils.getRotatedPoint(new mxPoint(dx, dy), cos, sin, new mxPoint(0, 0));\r\n\t\t\t\tdx = pt.x;\r\n\t\t\t\tdy = pt.y;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (geo.offset == null)\r\n\t\t\t{\r\n\t\t\t\tgeo.offset = new mxPoint(dx, dy);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tgeo.offset.x = parseFloat(geo.offset.x) + dx;\r\n\t\t\t\tgeo.offset.y = parseFloat(geo.offset.y) + dy;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.model.setGeometry(cell, geo);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getCellContainmentArea\r\n * \r\n * Returns the <mxRectangle> inside which a cell is to be kept.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> for which the area should be returned.\r\n */\r\nmxGraph.prototype.getCellContainmentArea = function(cell)\r\n{\r\n\tif (cell != null && !this.model.isEdge(cell))\r\n\t{\r\n\t\tvar parent = this.model.getParent(cell);\r\n\t\t\r\n\t\tif (parent != null && parent != this.getDefaultParent())\r\n\t\t{\r\n\t\t\tvar g = this.model.getGeometry(parent);\r\n\t\t\t\r\n\t\t\tif (g != null)\r\n\t\t\t{\r\n\t\t\t\tvar x = 0;\r\n\t\t\t\tvar y = 0;\r\n\t\t\t\tvar w = g.width;\r\n\t\t\t\tvar h = g.height;\r\n\t\t\t\t\r\n\t\t\t\tif (this.isSwimlane(parent))\r\n\t\t\t\t{\r\n\t\t\t\t\tvar size = this.getStartSize(parent);\r\n\t\t\t\t\t\r\n\t\t\t\t\tvar state = this.view.getState(parent);\r\n\t\t\t\t\tvar style = (state != null) ? state.style : this.getCellStyle(parent);\r\n\t\t\t\t\tvar dir = mxUtils.getValue(style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST);\r\n\t\t\t\t\tvar flipH = mxUtils.getValue(style, mxConstants.STYLE_FLIPH, 0) == 1;\r\n\t\t\t\t\tvar flipV = mxUtils.getValue(style, mxConstants.STYLE_FLIPV, 0) == 1;\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (dir == mxConstants.DIRECTION_SOUTH || dir == mxConstants.DIRECTION_NORTH)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar tmp = size.width;\r\n\t\t\t\t\t\tsize.width = size.height;\r\n\t\t\t\t\t\tsize.height = tmp;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif ((dir == mxConstants.DIRECTION_EAST && !flipV) || (dir == mxConstants.DIRECTION_NORTH && !flipH) ||\r\n\t\t\t\t\t\t(dir == mxConstants.DIRECTION_WEST && flipV) || (dir == mxConstants.DIRECTION_SOUTH && flipH))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tx = size.width;\r\n\t\t\t\t\t\ty = size.height;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tw -= size.width;\r\n\t\t\t\t\th -= size.height;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\treturn new mxRectangle(x, y, w, h);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: getMaximumGraphBounds\r\n * \r\n * Returns the bounds inside which the diagram should be kept as an\r\n * <mxRectangle>.\r\n */\r\nmxGraph.prototype.getMaximumGraphBounds = function()\r\n{\r\n\treturn this.maximumGraphBounds;\r\n};\r\n\r\n/**\r\n * Function: constrainChild\r\n * \r\n * Keeps the given cell inside the bounds returned by\r\n * <getCellContainmentArea> for its parent, according to the rules defined by\r\n * <getOverlap> and <isConstrainChild>. This modifies the cell's geometry\r\n * in-place and does not clone it.\r\n * \r\n * Parameters:\r\n * \r\n * cells - <mxCell> which should be constrained.\r\n * sizeFirst - Specifies if the size should be changed first. Default is true.\r\n */\r\nmxGraph.prototype.constrainChild = function(cell, sizeFirst)\r\n{\r\n\tsizeFirst = (sizeFirst != null) ? sizeFirst : true;\r\n\t\r\n\tif (cell != null)\r\n\t{\r\n\t\tvar geo = this.getCellGeometry(cell);\r\n\t\t\r\n\t\tif (geo != null && (this.isConstrainRelativeChildren() || !geo.relative))\r\n\t\t{\r\n\t\t\tvar parent = this.model.getParent(cell);\r\n\t\t\tvar pgeo = this.getCellGeometry(parent);\r\n\t\t\tvar max = this.getMaximumGraphBounds();\r\n\t\t\t\r\n\t\t\t// Finds parent offset\r\n\t\t\tif (max != null)\r\n\t\t\t{\r\n\t\t\t\tvar off = this.getBoundingBoxFromGeometry([parent], false);\r\n\t\t\t\t\r\n\t\t\t\tif (off != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tmax = mxRectangle.fromRectangle(max);\r\n\t\t\t\t\t\r\n\t\t\t\t\tmax.x -= off.x;\r\n\t\t\t\t\tmax.y -= off.y;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (this.isConstrainChild(cell))\r\n\t\t\t{\r\n\t\t\t\tvar tmp = this.getCellContainmentArea(cell);\r\n\t\t\t\t\r\n\t\t\t\tif (tmp != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar overlap = this.getOverlap(cell);\r\n\t\r\n\t\t\t\t\tif (overlap > 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ttmp = mxRectangle.fromRectangle(tmp);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\ttmp.x -= tmp.width * overlap;\r\n\t\t\t\t\t\ttmp.y -= tmp.height * overlap;\r\n\t\t\t\t\t\ttmp.width += 2 * tmp.width * overlap;\r\n\t\t\t\t\t\ttmp.height += 2 * tmp.height * overlap;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Find the intersection between max and tmp\r\n\t\t\t\t\tif (max == null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tmax = tmp;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tmax = mxRectangle.fromRectangle(max);\r\n\t\t\t\t\t\tmax.intersect(tmp);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (max != null)\r\n\t\t\t{\r\n\t\t\t\tvar cells = [cell];\r\n\t\t\t\t\r\n\t\t\t\tif (!this.isCellCollapsed(cell))\r\n\t\t\t\t{\r\n\t\t\t\t\tvar desc = this.model.getDescendants(cell);\r\n\t\t\t\t\t\r\n\t\t\t\t\tfor (var i = 0; i < desc.length; i++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (this.isCellVisible(desc[i]))\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tcells.push(desc[i]);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tvar bbox = this.getBoundingBoxFromGeometry(cells, false);\r\n\t\t\t\t\r\n\t\t\t\tif (bbox != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tgeo = geo.clone();\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Cumulative horizontal movement\r\n\t\t\t\t\tvar dx = 0;\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (geo.width > max.width)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tdx = geo.width - max.width;\r\n\t\t\t\t\t\tgeo.width -= dx;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (bbox.x + bbox.width > max.x + max.width)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tdx -= bbox.x + bbox.width - max.x - max.width - dx;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Cumulative vertical movement\r\n\t\t\t\t\tvar dy = 0;\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (geo.height > max.height)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tdy = geo.height - max.height;\r\n\t\t\t\t\t\tgeo.height -= dy;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (bbox.y + bbox.height > max.y + max.height)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tdy -= bbox.y + bbox.height - max.y - max.height - dy;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (bbox.x < max.x)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tdx -= bbox.x - max.x;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (bbox.y < max.y)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tdy -= bbox.y - max.y;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (dx != 0 || dy != 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (geo.relative)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t// Relative geometries are moved via absolute offset\r\n\t\t\t\t\t\t\tif (geo.offset == null)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tgeo.offset = new mxPoint();\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tgeo.offset.x += dx;\r\n\t\t\t\t\t\t\tgeo.offset.y += dy;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tgeo.x += dx;\r\n\t\t\t\t\t\t\tgeo.y += dy;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tthis.model.setGeometry(cell, geo);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: resetEdges\r\n * \r\n * Resets the control points of the edges that are connected to the given\r\n * cells if not both ends of the edge are in the given cells array.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> for which the connected edges should be\r\n * reset.\r\n */\r\nmxGraph.prototype.resetEdges = function(cells)\r\n{\r\n\tif (cells != null)\r\n\t{\r\n\t\t// Prepares faster cells lookup\r\n\t\tvar dict = new mxDictionary();\r\n\t\t\r\n\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t{\r\n\t\t\tdict.put(cells[i], true);\r\n\t\t}\r\n\t\t\r\n\t\tthis.model.beginUpdate();\r\n\t\ttry\r\n\t\t{\r\n\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar edges = this.model.getEdges(cells[i]);\r\n\t\t\t\t\r\n\t\t\t\tif (edges != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tfor (var j = 0; j < edges.length; j++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar state = this.view.getState(edges[j]);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tvar source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[j], true);\r\n\t\t\t\t\t\tvar target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[j], false);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t// Checks if one of the terminals is not in the given array\r\n\t\t\t\t\t\tif (!dict.get(source) || !dict.get(target))\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tthis.resetEdge(edges[j]);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.resetEdges(this.model.getChildren(cells[i]));\r\n\t\t\t}\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tthis.model.endUpdate();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: resetEdge\r\n * \r\n * Resets the control points of the given edge.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCell> whose points should be reset.\r\n */\r\nmxGraph.prototype.resetEdge = function(edge)\r\n{\r\n\tvar geo = this.model.getGeometry(edge);\r\n\t\r\n\t// Resets the control points\r\n\tif (geo != null && geo.points != null && geo.points.length > 0)\r\n\t{\r\n\t\tgeo = geo.clone();\r\n\t\tgeo.points = [];\r\n\t\tthis.model.setGeometry(edge, geo);\r\n\t}\r\n\t\r\n\treturn edge;\r\n};\r\n\r\n/**\r\n * Group: Cell connecting and connection constraints\r\n */\r\n\r\n/**\r\n * Function: getOutlineConstraint\r\n * \r\n * Returns the constraint used to connect to the outline of the given state.\r\n */\r\nmxGraph.prototype.getOutlineConstraint = function(point, terminalState, me)\r\n{\r\n\tif (terminalState.shape != null)\r\n\t{\r\n\t\tvar bounds = this.view.getPerimeterBounds(terminalState);\r\n\t\tvar direction = terminalState.style[mxConstants.STYLE_DIRECTION];\r\n\t\t\r\n\t\tif (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH)\r\n\t\t{\r\n\t\t\tbounds.x += bounds.width / 2 - bounds.height / 2;\r\n\t\t\tbounds.y += bounds.height / 2 - bounds.width / 2;\r\n\t\t\tvar tmp = bounds.width;\r\n\t\t\tbounds.width = bounds.height;\r\n\t\t\tbounds.height = tmp;\r\n\t\t}\r\n\t\r\n\t\tvar alpha = mxUtils.toRadians(terminalState.shape.getShapeRotation());\r\n\t\t\r\n\t\tif (alpha != 0)\r\n\t\t{\r\n\t\t\tvar cos = Math.cos(-alpha);\r\n\t\t\tvar sin = Math.sin(-alpha);\r\n\t\r\n\t\t\tvar ct = new mxPoint(bounds.getCenterX(), bounds.getCenterY());\r\n\t\t\tpoint = mxUtils.getRotatedPoint(point, cos, sin, ct);\r\n\t\t}\r\n\r\n\t\tvar sx = 1;\r\n\t\tvar sy = 1;\r\n\t\tvar dx = 0;\r\n\t\tvar dy = 0;\r\n\t\t\r\n\t\t// LATER: Add flipping support for image shapes\r\n\t\tif (this.getModel().isVertex(terminalState.cell))\r\n\t\t{\r\n\t\t\tvar flipH = terminalState.style[mxConstants.STYLE_FLIPH];\r\n\t\t\tvar flipV = terminalState.style[mxConstants.STYLE_FLIPV];\r\n\t\t\t\r\n\t\t\t// Legacy support for stencilFlipH/V\r\n\t\t\tif (terminalState.shape != null && terminalState.shape.stencil != null)\r\n\t\t\t{\r\n\t\t\t\tflipH = mxUtils.getValue(terminalState.style, 'stencilFlipH', 0) == 1 || flipH;\r\n\t\t\t\tflipV = mxUtils.getValue(terminalState.style, 'stencilFlipV', 0) == 1 || flipV;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH)\r\n\t\t\t{\r\n\t\t\t\tvar tmp = flipH;\r\n\t\t\t\tflipH = flipV;\r\n\t\t\t\tflipV = tmp;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (flipH)\r\n\t\t\t{\r\n\t\t\t\tsx = -1;\r\n\t\t\t\tdx = -bounds.width;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (flipV)\r\n\t\t\t{\r\n\t\t\t\tsy = -1;\r\n\t\t\t\tdy = -bounds.height ;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tpoint = new mxPoint((point.x - bounds.x) * sx - dx + bounds.x, (point.y - bounds.y) * sy - dy + bounds.y);\r\n\t\t\r\n\t\tvar x = (bounds.width == 0) ? 0 : Math.round((point.x - bounds.x) * 1000 / bounds.width) / 1000;\r\n\t\tvar y = (bounds.height == 0) ? 0 : Math.round((point.y - bounds.y) * 1000 / bounds.height) / 1000;\r\n\t\t\r\n\t\treturn new mxConnectionConstraint(new mxPoint(x, y), false);\r\n\t}\r\n\t\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: getAllConnectionConstraints\r\n * \r\n * Returns an array of all <mxConnectionConstraints> for the given terminal. If\r\n * the shape of the given terminal is a <mxStencilShape> then the constraints\r\n * of the corresponding <mxStencil> are returned.\r\n * \r\n * Parameters:\r\n * \r\n * terminal - <mxCellState> that represents the terminal.\r\n * source - Boolean that specifies if the terminal is the source or target.\r\n */\r\nmxGraph.prototype.getAllConnectionConstraints = function(terminal, source)\r\n{\r\n\tif (terminal != null && terminal.shape != null && terminal.shape.stencil != null)\r\n\t{\r\n\t\treturn terminal.shape.stencil.constraints;\r\n\t}\r\n\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: getConnectionConstraint\r\n * \r\n * Returns an <mxConnectionConstraint> that describes the given connection\r\n * point. This result can then be passed to <getConnectionPoint>.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCellState> that represents the edge.\r\n * terminal - <mxCellState> that represents the terminal.\r\n * source - Boolean indicating if the terminal is the source or target.\r\n */\r\nmxGraph.prototype.getConnectionConstraint = function(edge, terminal, source)\r\n{\r\n\tvar point = null;\r\n\tvar x = edge.style[(source) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X];\r\n\r\n\tif (x != null)\r\n\t{\r\n\t\tvar y = edge.style[(source) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y];\r\n\t\t\r\n\t\tif (y != null)\r\n\t\t{\r\n\t\t\tpoint = new mxPoint(parseFloat(x), parseFloat(y));\r\n\t\t}\r\n\t}\r\n\t\r\n\tvar perimeter = false;\r\n\tvar dx = 0, dy = 0;\r\n\t\r\n\tif (point != null)\r\n\t{\r\n\t\tperimeter = mxUtils.getValue(edge.style, (source) ? mxConstants.STYLE_EXIT_PERIMETER :\r\n\t\t\tmxConstants.STYLE_ENTRY_PERIMETER, true);\r\n\r\n\t\t//Add entry/exit offset\r\n\t\tdx = parseFloat(edge.style[(source) ? mxConstants.STYLE_EXIT_DX : mxConstants.STYLE_ENTRY_DX]);\r\n\t\tdy = parseFloat(edge.style[(source) ? mxConstants.STYLE_EXIT_DY : mxConstants.STYLE_ENTRY_DY]);\r\n\t\t\r\n\t\tdx = isFinite(dx)? dx : 0;\r\n\t\tdy = isFinite(dy)? dy : 0;\r\n\t}\r\n\r\n\treturn new mxConnectionConstraint(point, perimeter, null, dx, dy);\r\n};\r\n\r\n/**\r\n * Function: setConnectionConstraint\r\n * \r\n * Sets the <mxConnectionConstraint> that describes the given connection point.\r\n * If no constraint is given then nothing is changed. To remove an existing\r\n * constraint from the given edge, use an empty constraint instead.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCell> that represents the edge.\r\n * terminal - <mxCell> that represents the terminal.\r\n * source - Boolean indicating if the terminal is the source or target.\r\n * constraint - Optional <mxConnectionConstraint> to be used for this\r\n * connection.\r\n */\r\nmxGraph.prototype.setConnectionConstraint = function(edge, terminal, source, constraint)\r\n{\r\n\tif (constraint != null)\r\n\t{\r\n\t\tthis.model.beginUpdate();\r\n\t\t\r\n\t\ttry\r\n\t\t{\r\n\t\t\tif (constraint == null || constraint.point == null)\r\n\t\t\t{\r\n\t\t\t\tthis.setCellStyles((source) ? mxConstants.STYLE_EXIT_X :\r\n\t\t\t\t\tmxConstants.STYLE_ENTRY_X, null, [edge]);\r\n\t\t\t\tthis.setCellStyles((source) ? mxConstants.STYLE_EXIT_Y :\r\n\t\t\t\t\tmxConstants.STYLE_ENTRY_Y, null, [edge]);\r\n\t\t\t\tthis.setCellStyles((source) ? mxConstants.STYLE_EXIT_DX :\r\n\t\t\t\t\tmxConstants.STYLE_ENTRY_DX, null, [edge]);\r\n\t\t\t\tthis.setCellStyles((source) ? mxConstants.STYLE_EXIT_DY :\r\n\t\t\t\t\tmxConstants.STYLE_ENTRY_DY, null, [edge]);\r\n\t\t\t\tthis.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER :\r\n\t\t\t\t\tmxConstants.STYLE_ENTRY_PERIMETER, null, [edge]);\r\n\t\t\t}\r\n\t\t\telse if (constraint.point != null)\r\n\t\t\t{\r\n\t\t\t\tthis.setCellStyles((source) ? mxConstants.STYLE_EXIT_X :\r\n\t\t\t\t\tmxConstants.STYLE_ENTRY_X, constraint.point.x, [edge]);\r\n\t\t\t\tthis.setCellStyles((source) ? mxConstants.STYLE_EXIT_Y :\r\n\t\t\t\t\tmxConstants.STYLE_ENTRY_Y, constraint.point.y, [edge]);\r\n\t\t\t\tthis.setCellStyles((source) ? mxConstants.STYLE_EXIT_DX :\r\n\t\t\t\t\tmxConstants.STYLE_ENTRY_DX, constraint.dx, [edge]);\r\n\t\t\t\tthis.setCellStyles((source) ? mxConstants.STYLE_EXIT_DY :\r\n\t\t\t\t\tmxConstants.STYLE_ENTRY_DY, constraint.dy, [edge]);\r\n\t\t\t\t\r\n\t\t\t\t// Only writes 0 since 1 is default\r\n\t\t\t\tif (!constraint.perimeter)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER :\r\n\t\t\t\t\t\tmxConstants.STYLE_ENTRY_PERIMETER, '0', [edge]);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER :\r\n\t\t\t\t\t\tmxConstants.STYLE_ENTRY_PERIMETER, null, [edge]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tthis.model.endUpdate();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getConnectionPoint\r\n *\r\n * Returns the nearest point in the list of absolute points or the center\r\n * of the opposite terminal.\r\n * \r\n * Parameters:\r\n * \r\n * vertex - <mxCellState> that represents the vertex.\r\n * constraint - <mxConnectionConstraint> that represents the connection point\r\n * constraint as returned by <getConnectionConstraint>.\r\n */\r\nmxGraph.prototype.getConnectionPoint = function(vertex, constraint)\r\n{\r\n\tvar point = null;\r\n\t\r\n\tif (vertex != null && constraint.point != null)\r\n\t{\r\n\t\tvar bounds = this.view.getPerimeterBounds(vertex);\r\n        var cx = new mxPoint(bounds.getCenterX(), bounds.getCenterY());\r\n\t\tvar direction = vertex.style[mxConstants.STYLE_DIRECTION];\r\n\t\tvar r1 = 0;\r\n\t\t\r\n\t\t// Bounds need to be rotated by 90 degrees for further computation\r\n\t\tif (direction != null && mxUtils.getValue(vertex.style,\r\n\t\t\tmxConstants.STYLE_ANCHOR_POINT_DIRECTION, 1) == 1)\r\n\t\t{\r\n\t\t\tif (direction == mxConstants.DIRECTION_NORTH)\r\n\t\t\t{\r\n\t\t\t\tr1 += 270;\r\n\t\t\t}\r\n\t\t\telse if (direction == mxConstants.DIRECTION_WEST)\r\n\t\t\t{\r\n\t\t\t\tr1 += 180;\r\n\t\t\t}\r\n\t\t\telse if (direction == mxConstants.DIRECTION_SOUTH)\r\n\t\t\t{\r\n\t\t\t\tr1 += 90;\r\n\t\t\t}\r\n\r\n\t\t\t// Bounds need to be rotated by 90 degrees for further computation\r\n\t\t\tif (direction == mxConstants.DIRECTION_NORTH ||\r\n\t\t\t\tdirection == mxConstants.DIRECTION_SOUTH)\r\n\t\t\t{\r\n\t\t\t\tbounds.rotate90();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tvar scale = this.view.scale;\r\n\t\tpoint = new mxPoint(bounds.x + constraint.point.x * bounds.width + constraint.dx * scale,\r\n\t\t\t\tbounds.y + constraint.point.y * bounds.height + constraint.dy * scale);\r\n\t\t\r\n\t\t// Rotation for direction before projection on perimeter\r\n\t\tvar r2 = vertex.style[mxConstants.STYLE_ROTATION] || 0;\r\n\t\t\r\n\t\tif (constraint.perimeter)\r\n\t\t{\r\n\t\t\tif (r1 != 0)\r\n\t\t\t{\r\n\t\t\t\t// Only 90 degrees steps possible here so no trig needed\r\n\t\t\t\tvar cos = 0;\r\n\t\t\t\tvar sin = 0;\r\n\t\t\t\t\r\n\t\t\t\tif (r1 == 90)\r\n\t\t\t\t{\r\n\t\t\t\t\tsin = 1;\r\n\t\t\t\t}\r\n\t\t\t\telse if (r1 == 180)\r\n\t\t\t\t{\r\n\t\t\t\t\tcos = -1;\r\n\t\t\t\t}\r\n\t\t\t\telse if (r1 == 270)\r\n\t\t\t\t{\r\n\t\t\t\t\tsin = -1;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t        point = mxUtils.getRotatedPoint(point, cos, sin, cx);\r\n\t\t\t}\r\n\t\r\n\t\t\tpoint = this.view.getPerimeterPoint(vertex, point, false);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tr2 += r1;\r\n\t\t\t\r\n\t\t\tif (this.getModel().isVertex(vertex.cell))\r\n\t\t\t{\r\n\t\t\t\tvar flipH = vertex.style[mxConstants.STYLE_FLIPH] == 1;\r\n\t\t\t\tvar flipV = vertex.style[mxConstants.STYLE_FLIPV] == 1;\r\n\t\t\t\t\r\n\t\t\t\t// Legacy support for stencilFlipH/V\r\n\t\t\t\tif (vertex.shape != null && vertex.shape.stencil != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tflipH = (mxUtils.getValue(vertex.style, 'stencilFlipH', 0) == 1) || flipH;\r\n\t\t\t\t\tflipV = (mxUtils.getValue(vertex.style, 'stencilFlipV', 0) == 1) || flipV;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (flipH)\r\n\t\t\t\t{\r\n\t\t\t\t\tpoint.x = 2 * bounds.getCenterX() - point.x;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (flipV)\r\n\t\t\t\t{\r\n\t\t\t\t\tpoint.y = 2 * bounds.getCenterY() - point.y;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Generic rotation after projection on perimeter\r\n\t\tif (r2 != 0 && point != null)\r\n\t\t{\r\n\t        var rad = mxUtils.toRadians(r2);\r\n\t        var cos = Math.cos(rad);\r\n\t        var sin = Math.sin(rad);\r\n\t        \r\n\t        point = mxUtils.getRotatedPoint(point, cos, sin, cx);\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (point != null)\r\n\t{\r\n\t\tpoint.x = Math.round(point.x);\r\n\t\tpoint.y = Math.round(point.y);\r\n\t}\r\n\r\n\treturn point;\r\n};\r\n\r\n/**\r\n * Function: connectCell\r\n * \r\n * Connects the specified end of the given edge to the given terminal\r\n * using <cellConnected> and fires <mxEvent.CONNECT_CELL> while the\r\n * transaction is in progress. Returns the updated edge.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCell> whose terminal should be updated.\r\n * terminal - <mxCell> that represents the new terminal to be used.\r\n * source - Boolean indicating if the new terminal is the source or target.\r\n * constraint - Optional <mxConnectionConstraint> to be used for this\r\n * connection.\r\n */\r\nmxGraph.prototype.connectCell = function(edge, terminal, source, constraint)\r\n{\r\n\tthis.model.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tvar previous = this.model.getTerminal(edge, source);\r\n\t\tthis.cellConnected(edge, terminal, source, constraint);\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.CONNECT_CELL,\r\n\t\t\t'edge', edge, 'terminal', terminal, 'source', source,\r\n\t\t\t'previous', previous));\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tthis.model.endUpdate();\r\n\t}\r\n\r\n\treturn edge;\r\n};\r\n\r\n/**\r\n * Function: cellConnected\r\n * \r\n * Sets the new terminal for the given edge and resets the edge points if\r\n * <resetEdgesOnConnect> is true. This method fires\r\n * <mxEvent.CELL_CONNECTED> while the transaction is in progress.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCell> whose terminal should be updated.\r\n * terminal - <mxCell> that represents the new terminal to be used.\r\n * source - Boolean indicating if the new terminal is the source or target.\r\n * constraint - <mxConnectionConstraint> to be used for this connection.\r\n */\r\nmxGraph.prototype.cellConnected = function(edge, terminal, source, constraint)\r\n{\r\n\tif (edge != null)\r\n\t{\r\n\t\tthis.model.beginUpdate();\r\n\t\ttry\r\n\t\t{\r\n\t\t\tvar previous = this.model.getTerminal(edge, source);\r\n\r\n\t\t\t// Updates the constraint\r\n\t\t\tthis.setConnectionConstraint(edge, terminal, source, constraint);\r\n\t\t\t\r\n\t\t\t// Checks if the new terminal is a port, uses the ID of the port in the\r\n\t\t\t// style and the parent of the port as the actual terminal of the edge.\r\n\t\t\tif (this.isPortsEnabled())\r\n\t\t\t{\r\n\t\t\t\tvar id = null;\r\n\t\r\n\t\t\t\tif (this.isPort(terminal))\r\n\t\t\t\t{\r\n\t\t\t\t\tid = terminal.getId();\r\n\t\t\t\t\tterminal = this.getTerminalForPort(terminal, source);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Sets or resets all previous information for connecting to a child port\r\n\t\t\t\tvar key = (source) ? mxConstants.STYLE_SOURCE_PORT :\r\n\t\t\t\t\tmxConstants.STYLE_TARGET_PORT;\r\n\t\t\t\tthis.setCellStyles(key, id, [edge]);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.model.setTerminal(edge, terminal, source);\r\n\t\t\t\r\n\t\t\tif (this.resetEdgesOnConnect)\r\n\t\t\t{\r\n\t\t\t\tthis.resetEdge(edge);\r\n\t\t\t}\r\n\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.CELL_CONNECTED,\r\n\t\t\t\t'edge', edge, 'terminal', terminal, 'source', source,\r\n\t\t\t\t'previous', previous));\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tthis.model.endUpdate();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: disconnectGraph\r\n * \r\n * Disconnects the given edges from the terminals which are not in the\r\n * given array.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> to be disconnected.\r\n */\r\nmxGraph.prototype.disconnectGraph = function(cells)\r\n{\r\n\tif (cells != null)\r\n\t{\r\n\t\tthis.model.beginUpdate();\r\n\t\ttry\r\n\t\t{\t\t\t\t\t\t\t\r\n\t\t\tvar scale = this.view.scale;\r\n\t\t\tvar tr = this.view.translate;\r\n\t\t\t\r\n\t\t\t// Fast lookup for finding cells in array\r\n\t\t\tvar dict = new mxDictionary();\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t{\r\n\t\t\t\tdict.put(cells[i], true);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t{\r\n\t\t\t\tif (this.model.isEdge(cells[i]))\r\n\t\t\t\t{\r\n\t\t\t\t\tvar geo = this.model.getGeometry(cells[i]);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (geo != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar state = this.view.getState(cells[i]);\r\n\t\t\t\t\t\tvar pstate = this.view.getState(\r\n\t\t\t\t\t\t\tthis.model.getParent(cells[i]));\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (state != null &&\r\n\t\t\t\t\t\t\tpstate != null)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tgeo = geo.clone();\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tvar dx = -pstate.origin.x;\r\n\t\t\t\t\t\t\tvar dy = -pstate.origin.y;\r\n\t\t\t\t\t\t\tvar pts = state.absolutePoints;\r\n\r\n\t\t\t\t\t\t\tvar src = this.model.getTerminal(cells[i], true);\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tif (src != null && this.isCellDisconnectable(cells[i], src, true))\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\twhile (src != null && !dict.get(src))\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\tsrc = this.model.getParent(src);\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\tif (src == null)\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\tgeo.setTerminalPoint(\r\n\t\t\t\t\t\t\t\t\t\tnew mxPoint(pts[0].x / scale - tr.x + dx,\r\n\t\t\t\t\t\t\t\t\t\t\tpts[0].y / scale - tr.y + dy), true);\r\n\t\t\t\t\t\t\t\t\tthis.model.setTerminal(cells[i], null, true);\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tvar trg = this.model.getTerminal(cells[i], false);\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tif (trg != null && this.isCellDisconnectable(cells[i], trg, false))\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\twhile (trg != null && !dict.get(trg))\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\ttrg = this.model.getParent(trg);\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\tif (trg == null)\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\tvar n = pts.length - 1;\r\n\t\t\t\t\t\t\t\t\tgeo.setTerminalPoint(\r\n\t\t\t\t\t\t\t\t\t\tnew mxPoint(pts[n].x / scale - tr.x + dx,\r\n\t\t\t\t\t\t\t\t\t\t\tpts[n].y / scale - tr.y + dy), false);\r\n\t\t\t\t\t\t\t\t\tthis.model.setTerminal(cells[i], null, false);\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t\tthis.model.setGeometry(cells[i], geo);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tthis.model.endUpdate();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Group: Drilldown\r\n */\r\n\r\n/**\r\n * Function: getCurrentRoot\r\n * \r\n * Returns the current root of the displayed cell hierarchy. This is a\r\n * shortcut to <mxGraphView.currentRoot> in <view>.\r\n */\r\nmxGraph.prototype.getCurrentRoot = function()\r\n{\r\n\treturn this.view.currentRoot;\r\n};\r\n \r\n/**\r\n * Function: getTranslateForRoot\r\n * \r\n * Returns the translation to be used if the given cell is the root cell as\r\n * an <mxPoint>. This implementation returns null.\r\n * \r\n * Example:\r\n * \r\n * To keep the children at their absolute position while stepping into groups,\r\n * this function can be overridden as follows.\r\n * \r\n * (code)\r\n * var offset = new mxPoint(0, 0);\r\n * \r\n * while (cell != null)\r\n * {\r\n *   var geo = this.model.getGeometry(cell);\r\n * \r\n *   if (geo != null)\r\n *   {\r\n *     offset.x -= geo.x;\r\n *     offset.y -= geo.y;\r\n *   }\r\n * \r\n *   cell = this.model.getParent(cell);\r\n * }\r\n * \r\n * return offset;\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> that represents the root.\r\n */\r\nmxGraph.prototype.getTranslateForRoot = function(cell)\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: isPort\r\n * \r\n * Returns true if the given cell is a \"port\", that is, when connecting to\r\n * it, the cell returned by getTerminalForPort should be used as the\r\n * terminal and the port should be referenced by the ID in either the\r\n * mxConstants.STYLE_SOURCE_PORT or the or the\r\n * mxConstants.STYLE_TARGET_PORT. Note that a port should not be movable.\r\n * This implementation always returns false.\r\n * \r\n * A typical implementation is the following:\r\n * \r\n * (code)\r\n * graph.isPort = function(cell)\r\n * {\r\n *   var geo = this.getCellGeometry(cell);\r\n *   \r\n *   return (geo != null) ? geo.relative : false;\r\n * };\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> that represents the port.\r\n */\r\nmxGraph.prototype.isPort = function(cell)\r\n{\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: getTerminalForPort\r\n * \r\n * Returns the terminal to be used for a given port. This implementation\r\n * always returns the parent cell.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> that represents the port.\r\n * source - If the cell is the source or target port.\r\n */\r\nmxGraph.prototype.getTerminalForPort = function(cell, source)\r\n{\r\n\treturn this.model.getParent(cell);\r\n};\r\n\r\n/**\r\n * Function: getChildOffsetForCell\r\n * \r\n * Returns the offset to be used for the cells inside the given cell. The\r\n * root and layer cells may be identified using <mxGraphModel.isRoot> and\r\n * <mxGraphModel.isLayer>. For all other current roots, the\r\n * <mxGraphView.currentRoot> field points to the respective cell, so that\r\n * the following holds: cell == this.view.currentRoot. This implementation\r\n * returns null.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose offset should be returned.\r\n */\r\nmxGraph.prototype.getChildOffsetForCell = function(cell)\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: enterGroup\r\n * \r\n * Uses the given cell as the root of the displayed cell hierarchy. If no\r\n * cell is specified then the selection cell is used. The cell is only used\r\n * if <isValidRoot> returns true.\r\n * \r\n * Parameters:\r\n * \r\n * cell - Optional <mxCell> to be used as the new root. Default is the\r\n * selection cell.\r\n */\r\nmxGraph.prototype.enterGroup = function(cell)\r\n{\r\n\tcell = cell || this.getSelectionCell();\r\n\t\r\n\tif (cell != null && this.isValidRoot(cell))\r\n\t{\r\n\t\tthis.view.setCurrentRoot(cell);\r\n\t\tthis.clearSelection();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: exitGroup\r\n * \r\n * Changes the current root to the next valid root in the displayed cell\r\n * hierarchy.\r\n */\r\nmxGraph.prototype.exitGroup = function()\r\n{\r\n\tvar root = this.model.getRoot();\r\n\tvar current = this.getCurrentRoot();\r\n\t\r\n\tif (current != null)\r\n\t{\r\n\t\tvar next = this.model.getParent(current);\r\n\t\t\r\n\t\t// Finds the next valid root in the hierarchy\r\n\t\twhile (next != root && !this.isValidRoot(next) &&\r\n\t\t\t\tthis.model.getParent(next) != root)\r\n\t\t{\r\n\t\t\tnext = this.model.getParent(next);\r\n\t\t}\r\n\t\t\r\n\t\t// Clears the current root if the new root is\r\n\t\t// the model's root or one of the layers.\r\n\t\tif (next == root || this.model.getParent(next) == root)\r\n\t\t{\r\n\t\t\tthis.view.setCurrentRoot(null);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.view.setCurrentRoot(next);\r\n\t\t}\r\n\t\t\r\n\t\tvar state = this.view.getState(current);\r\n\t\t\r\n\t\t// Selects the previous root in the graph\r\n\t\tif (state != null)\r\n\t\t{\r\n\t\t\tthis.setSelectionCell(current);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: home\r\n * \r\n * Uses the root of the model as the root of the displayed cell hierarchy\r\n * and selects the previous root.\r\n */\r\nmxGraph.prototype.home = function()\r\n{\r\n\tvar current = this.getCurrentRoot();\r\n\t\r\n\tif (current != null)\r\n\t{\r\n\t\tthis.view.setCurrentRoot(null);\r\n\t\tvar state = this.view.getState(current);\r\n\t\t\r\n\t\tif (state != null)\r\n\t\t{\r\n\t\t\tthis.setSelectionCell(current);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isValidRoot\r\n * \r\n * Returns true if the given cell is a valid root for the cell display\r\n * hierarchy. This implementation returns true for all non-null values.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> which should be checked as a possible root.\r\n */\r\nmxGraph.prototype.isValidRoot = function(cell)\r\n{\r\n\treturn (cell != null);\r\n};\r\n\r\n/**\r\n * Group: Graph display\r\n */\r\n \r\n/**\r\n * Function: getGraphBounds\r\n * \r\n * Returns the bounds of the visible graph. Shortcut to\r\n * <mxGraphView.getGraphBounds>. See also: <getBoundingBoxFromGeometry>.\r\n */\r\n mxGraph.prototype.getGraphBounds = function()\r\n {\r\n \treturn this.view.getGraphBounds();\r\n };\r\n\r\n/**\r\n * Function: getCellBounds\r\n * \r\n * Returns the scaled, translated bounds for the given cell. See\r\n * <mxGraphView.getBounds> for arrays.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose bounds should be returned.\r\n * includeEdge - Optional boolean that specifies if the bounds of\r\n * the connected edges should be included. Default is false.\r\n * includeDescendants - Optional boolean that specifies if the bounds\r\n * of all descendants should be included. Default is false.\r\n */\r\nmxGraph.prototype.getCellBounds = function(cell, includeEdges, includeDescendants)\r\n{\r\n\tvar cells = [cell];\r\n\t\r\n\t// Includes all connected edges\r\n\tif (includeEdges)\r\n\t{\r\n\t\tcells = cells.concat(this.model.getEdges(cell));\r\n\t}\r\n\t\r\n\tvar result = this.view.getBounds(cells);\r\n\t\r\n\t// Recursively includes the bounds of the children\r\n\tif (includeDescendants)\r\n\t{\r\n\t\tvar childCount = this.model.getChildCount(cell);\r\n\t\t\r\n\t\tfor (var i = 0; i < childCount; i++)\r\n\t\t{\r\n\t\t\tvar tmp = this.getCellBounds(this.model.getChildAt(cell, i),\r\n\t\t\t\tincludeEdges, true);\r\n\r\n\t\t\tif (result != null)\r\n\t\t\t{\r\n\t\t\t\tresult.add(tmp);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tresult = tmp;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: getBoundingBoxFromGeometry\r\n * \r\n * Returns the bounding box for the geometries of the vertices in the\r\n * given array of cells. This can be used to find the graph bounds during\r\n * a layout operation (ie. before the last endUpdate) as follows:\r\n * \r\n * (code)\r\n * var cells = graph.getChildCells(graph.getDefaultParent(), true, true);\r\n * var bounds = graph.getBoundingBoxFromGeometry(cells, true);\r\n * (end)\r\n * \r\n * This can then be used to move cells to the origin:\r\n * \r\n * (code)\r\n * if (bounds.x < 0 || bounds.y < 0)\r\n * {\r\n *   graph.moveCells(cells, -Math.min(bounds.x, 0), -Math.min(bounds.y, 0))\r\n * }\r\n * (end)\r\n * \r\n * Or to translate the graph view:\r\n * \r\n * (code)\r\n * if (bounds.x < 0 || bounds.y < 0)\r\n * {\r\n *   graph.view.setTranslate(-Math.min(bounds.x, 0), -Math.min(bounds.y, 0));\r\n * }\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> whose bounds should be returned.\r\n * includeEdges - Specifies if edge bounds should be included by computing\r\n * the bounding box for all points in geometry. Default is false.\r\n */\r\nmxGraph.prototype.getBoundingBoxFromGeometry = function(cells, includeEdges)\r\n{\r\n\tincludeEdges = (includeEdges != null) ? includeEdges : false;\r\n\tvar result = null;\r\n\t\r\n\tif (cells != null)\r\n\t{\r\n\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t{\r\n\t\t\tif (includeEdges || this.model.isVertex(cells[i]))\r\n\t\t\t{\r\n\t\t\t\t// Computes the bounding box for the points in the geometry\r\n\t\t\t\tvar geo = this.getCellGeometry(cells[i]);\r\n\t\t\t\t\r\n\t\t\t\tif (geo != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar bbox = null;\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (this.model.isEdge(cells[i]))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar addPoint = function(pt)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tif (pt != null)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tif (tmp == null)\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\ttmp = new mxRectangle(pt.x, pt.y, 0, 0);\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\ttmp.add(new mxRectangle(pt.x, pt.y, 0, 0));\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t};\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (this.model.getTerminal(cells[i], true) == null)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\taddPoint(geo.getTerminalPoint(true));\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (this.model.getTerminal(cells[i], false) == null)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\taddPoint(geo.getTerminalPoint(false));\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\tvar pts = geo.points;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (pts != null && pts.length > 0)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar tmp = new mxRectangle(pts[0].x, pts[0].y, 0, 0);\r\n\r\n\t\t\t\t\t\t\tfor (var j = 1; j < pts.length; j++)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\taddPoint(pts[j]);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tbbox = tmp;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar parent = this.model.getParent(cells[i]);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (geo.relative)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tif (this.model.isVertex(parent) && parent != this.view.currentRoot)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tvar tmp = this.getBoundingBoxFromGeometry([parent], false);\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\tif (tmp != null)\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\tbbox = new mxRectangle(geo.x * tmp.width, geo.y * tmp.height, geo.width, geo.height);\r\n\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\tif (mxUtils.indexOf(cells, parent) >= 0)\r\n\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\tbbox.x += tmp.x;\r\n\t\t\t\t\t\t\t\t\t\tbbox.y += tmp.y;\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tbbox = mxRectangle.fromRectangle(geo);\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tif (this.model.isVertex(parent) && mxUtils.indexOf(cells, parent) >= 0)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tvar tmp = this.getBoundingBoxFromGeometry([parent], false);\r\n\r\n\t\t\t\t\t\t\t\tif (tmp != null)\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\tbbox.x += tmp.x;\r\n\t\t\t\t\t\t\t\t\tbbox.y += tmp.y;\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (bbox != null && geo.offset != null)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tbbox.x += geo.offset.x;\r\n\t\t\t\t\t\t\tbbox.y += geo.offset.y;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (bbox != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (result == null)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tresult = mxRectangle.fromRectangle(bbox);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tresult.add(bbox);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: refresh\r\n * \r\n * Clears all cell states or the states for the hierarchy starting at the\r\n * given cell and validates the graph. This fires a refresh event as the\r\n * last step.\r\n * \r\n * Parameters:\r\n * \r\n * cell - Optional <mxCell> for which the cell states should be cleared.\r\n */\r\nmxGraph.prototype.refresh = function(cell)\r\n{\r\n\tthis.view.clear(cell, cell == null);\r\n\tthis.view.validate();\r\n\tthis.sizeDidChange();\r\n\tthis.fireEvent(new mxEventObject(mxEvent.REFRESH));\r\n};\r\n\r\n/**\r\n * Function: snap\r\n * \r\n * Snaps the given numeric value to the grid if <gridEnabled> is true.\r\n * \r\n * Parameters:\r\n * \r\n * value - Numeric value to be snapped to the grid.\r\n */\r\nmxGraph.prototype.snap = function(value)\r\n{\r\n\tif (this.gridEnabled)\r\n\t{\r\n\t\tvalue = Math.round(value / this.gridSize ) * this.gridSize;\r\n\t}\r\n\t\r\n\treturn value;\r\n};\r\n\r\n/**\r\n * Function: panGraph\r\n * \r\n * Shifts the graph display by the given amount. This is used to preview\r\n * panning operations, use <mxGraphView.setTranslate> to set a persistent\r\n * translation of the view. Fires <mxEvent.PAN>.\r\n * \r\n * Parameters:\r\n * \r\n * dx - Amount to shift the graph along the x-axis.\r\n * dy - Amount to shift the graph along the y-axis.\r\n */\r\nmxGraph.prototype.panGraph = function(dx, dy)\r\n{\r\n\tif (this.useScrollbarsForPanning && mxUtils.hasScrollbars(this.container))\r\n\t{\r\n\t\tthis.container.scrollLeft = -dx;\r\n\t\tthis.container.scrollTop = -dy;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar canvas = this.view.getCanvas();\r\n\t\t\r\n\t\tif (this.dialect == mxConstants.DIALECT_SVG)\r\n\t\t{\r\n\t\t\t// Puts everything inside the container in a DIV so that it\r\n\t\t\t// can be moved without changing the state of the container\r\n\t\t\tif (dx == 0 && dy == 0)\r\n\t\t\t{\r\n\t\t\t\t// Workaround for ignored removeAttribute on SVG element in IE9 standards\r\n\t\t\t\tif (mxClient.IS_IE)\r\n\t\t\t\t{\r\n\t\t\t\t\tcanvas.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tcanvas.removeAttribute('transform');\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (this.shiftPreview1 != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar child = this.shiftPreview1.firstChild;\r\n\t\t\t\t\t\r\n\t\t\t\t\twhile (child != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar next = child.nextSibling;\r\n\t\t\t\t\t\tthis.container.appendChild(child);\r\n\t\t\t\t\t\tchild = next;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif (this.shiftPreview1.parentNode != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.shiftPreview1.parentNode.removeChild(this.shiftPreview1);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tthis.shiftPreview1 = null;\r\n\t\t\t\t\t\r\n\t\t\t\t\tthis.container.appendChild(canvas.parentNode);\r\n\t\t\t\t\t\r\n\t\t\t\t\tchild = this.shiftPreview2.firstChild;\r\n\t\t\t\t\t\r\n\t\t\t\t\twhile (child != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar next = child.nextSibling;\r\n\t\t\t\t\t\tthis.container.appendChild(child);\r\n\t\t\t\t\t\tchild = next;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif (this.shiftPreview2.parentNode != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.shiftPreview2.parentNode.removeChild(this.shiftPreview2);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tthis.shiftPreview2 = null;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tcanvas.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');\r\n\t\t\t\t\r\n\t\t\t\tif (this.shiftPreview1 == null)\r\n\t\t\t\t{\r\n\t\t\t\t\t// Needs two divs for stuff before and after the SVG element\r\n\t\t\t\t\tthis.shiftPreview1 = document.createElement('div');\r\n\t\t\t\t\tthis.shiftPreview1.style.position = 'absolute';\r\n\t\t\t\t\tthis.shiftPreview1.style.overflow = 'visible';\r\n\t\t\t\t\t\r\n\t\t\t\t\tthis.shiftPreview2 = document.createElement('div');\r\n\t\t\t\t\tthis.shiftPreview2.style.position = 'absolute';\r\n\t\t\t\t\tthis.shiftPreview2.style.overflow = 'visible';\r\n\r\n\t\t\t\t\tvar current = this.shiftPreview1;\r\n\t\t\t\t\tvar child = this.container.firstChild;\r\n\t\t\t\t\t\r\n\t\t\t\t\twhile (child != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar next = child.nextSibling;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t// SVG element is moved via transform attribute\r\n\t\t\t\t\t\tif (child != canvas.parentNode)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tcurrent.appendChild(child);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tcurrent = this.shiftPreview2;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tchild = next;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Inserts elements only if not empty\r\n\t\t\t\t\tif (this.shiftPreview1.firstChild != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.container.insertBefore(this.shiftPreview1, canvas.parentNode);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (this.shiftPreview2.firstChild != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.container.appendChild(this.shiftPreview2);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.shiftPreview1.style.left = dx + 'px';\r\n\t\t\t\tthis.shiftPreview1.style.top = dy + 'px';\r\n\t\t\t\tthis.shiftPreview2.style.left = dx + 'px';\r\n\t\t\t\tthis.shiftPreview2.style.top = dy + 'px';\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tcanvas.style.left = dx + 'px';\r\n\t\t\tcanvas.style.top = dy + 'px';\r\n\t\t}\r\n\t\t\r\n\t\tthis.panDx = dx;\r\n\t\tthis.panDy = dy;\r\n\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.PAN));\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: zoomIn\r\n * \r\n * Zooms into the graph by <zoomFactor>.\r\n */\r\nmxGraph.prototype.zoomIn = function()\r\n{\r\n\tthis.zoom(this.zoomFactor);\r\n};\r\n\r\n/**\r\n * Function: zoomOut\r\n * \r\n * Zooms out of the graph by <zoomFactor>.\r\n */\r\nmxGraph.prototype.zoomOut = function()\r\n{\r\n\tthis.zoom(1 / this.zoomFactor);\r\n};\r\n\r\n/**\r\n * Function: zoomActual\r\n * \r\n * Resets the zoom and panning in the view.\r\n */\r\nmxGraph.prototype.zoomActual = function()\r\n{\r\n\tif (this.view.scale == 1)\r\n\t{\r\n\t\tthis.view.setTranslate(0, 0);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.view.translate.x = 0;\r\n\t\tthis.view.translate.y = 0;\r\n\r\n\t\tthis.view.setScale(1);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: zoomTo\r\n * \r\n * Zooms the graph to the given scale with an optional boolean center\r\n * argument, which is passd to <zoom>.\r\n */\r\nmxGraph.prototype.zoomTo = function(scale, center)\r\n{\r\n\tthis.zoom(scale / this.view.scale, center);\r\n};\r\n\r\n/**\r\n * Function: center\r\n * \r\n * Centers the graph in the container.\r\n * \r\n * Parameters:\r\n * \r\n * horizontal - Optional boolean that specifies if the graph should be centered\r\n * horizontally. Default is true.\r\n * vertical - Optional boolean that specifies if the graph should be centered\r\n * vertically. Default is true.\r\n * cx - Optional float that specifies the horizontal center. Default is 0.5.\r\n * cy - Optional float that specifies the vertical center. Default is 0.5.\r\n */\r\nmxGraph.prototype.center = function(horizontal, vertical, cx, cy)\r\n{\r\n\thorizontal = (horizontal != null) ? horizontal : true;\r\n\tvertical = (vertical != null) ? vertical : true;\r\n\tcx = (cx != null) ? cx : 0.5;\r\n\tcy = (cy != null) ? cy : 0.5;\r\n\t\r\n\tvar hasScrollbars = mxUtils.hasScrollbars(this.container);\r\n\tvar cw = this.container.clientWidth;\r\n\tvar ch = this.container.clientHeight;\r\n\tvar bounds = this.getGraphBounds();\r\n\r\n\tvar t = this.view.translate;\r\n\tvar s = this.view.scale;\r\n\r\n\tvar dx = (horizontal) ? cw - bounds.width : 0;\r\n\tvar dy = (vertical) ? ch - bounds.height : 0;\r\n\t\r\n\tif (!hasScrollbars)\r\n\t{\r\n\t\tthis.view.setTranslate((horizontal) ? Math.floor(t.x - bounds.x * s + dx * cx / s) : t.x,\r\n\t\t\t(vertical) ? Math.floor(t.y - bounds.y * s + dy * cy / s) : t.y);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tbounds.x -= t.x;\r\n\t\tbounds.y -= t.y;\r\n\t\r\n\t\tvar sw = this.container.scrollWidth;\r\n\t\tvar sh = this.container.scrollHeight;\r\n\t\t\r\n\t\tif (sw > cw)\r\n\t\t{\r\n\t\t\tdx = 0;\r\n\t\t}\r\n\t\t\r\n\t\tif (sh > ch)\r\n\t\t{\r\n\t\t\tdy = 0;\r\n\t\t}\r\n\r\n\t\tthis.view.setTranslate(Math.floor(dx / 2 - bounds.x), Math.floor(dy / 2 - bounds.y));\r\n\t\tthis.container.scrollLeft = (sw - cw) / 2;\r\n\t\tthis.container.scrollTop = (sh - ch) / 2;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: zoom\r\n * \r\n * Zooms the graph using the given factor. Center is an optional boolean\r\n * argument that keeps the graph scrolled to the center. If the center argument\r\n * is omitted, then <centerZoom> will be used as its value.\r\n */\r\nmxGraph.prototype.zoom = function(factor, center)\r\n{\r\n\tcenter = (center != null) ? center : this.centerZoom;\r\n\tvar scale = Math.round(this.view.scale * factor * 100) / 100;\r\n\tvar state = this.view.getState(this.getSelectionCell());\r\n\tfactor = scale / this.view.scale;\r\n\t\r\n\tif (this.keepSelectionVisibleOnZoom && state != null)\r\n\t{\r\n\t\tvar rect = new mxRectangle(state.x * factor, state.y * factor,\r\n\t\t\tstate.width * factor, state.height * factor);\r\n\t\t\r\n\t\t// Refreshes the display only once if a scroll is carried out\r\n\t\tthis.view.scale = scale;\r\n\t\t\r\n\t\tif (!this.scrollRectToVisible(rect))\r\n\t\t{\r\n\t\t\tthis.view.revalidate();\r\n\t\t\t\r\n\t\t\t// Forces an event to be fired but does not revalidate again\r\n\t\t\tthis.view.setScale(scale);\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar hasScrollbars = mxUtils.hasScrollbars(this.container);\r\n\t\t\r\n\t\tif (center && !hasScrollbars)\r\n\t\t{\r\n\t\t\tvar dx = this.container.offsetWidth;\r\n\t\t\tvar dy = this.container.offsetHeight;\r\n\t\t\t\r\n\t\t\tif (factor > 1)\r\n\t\t\t{\r\n\t\t\t\tvar f = (factor - 1) / (scale * 2);\r\n\t\t\t\tdx *= -f;\r\n\t\t\t\tdy *= -f;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tvar f = (1 / factor - 1) / (this.view.scale * 2);\r\n\t\t\t\tdx *= f;\r\n\t\t\t\tdy *= f;\r\n\t\t\t}\r\n\r\n\t\t\tthis.view.scaleAndTranslate(scale,\r\n\t\t\t\tthis.view.translate.x + dx,\r\n\t\t\t\tthis.view.translate.y + dy);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Allows for changes of translate and scrollbars during setscale\r\n\t\t\tvar tx = this.view.translate.x;\r\n\t\t\tvar ty = this.view.translate.y;\r\n\t\t\tvar sl = this.container.scrollLeft;\r\n\t\t\tvar st = this.container.scrollTop;\r\n\t\t\t\r\n\t\t\tthis.view.setScale(scale);\r\n\t\t\t\r\n\t\t\tif (hasScrollbars)\r\n\t\t\t{\r\n\t\t\t\tvar dx = 0;\r\n\t\t\t\tvar dy = 0;\r\n\t\t\t\t\r\n\t\t\t\tif (center)\r\n\t\t\t\t{\r\n\t\t\t\t\tdx = this.container.offsetWidth * (factor - 1) / 2;\r\n\t\t\t\t\tdy = this.container.offsetHeight * (factor - 1) / 2;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.container.scrollLeft = (this.view.translate.x - tx) * this.view.scale + Math.round(sl * factor + dx);\r\n\t\t\t\tthis.container.scrollTop = (this.view.translate.y - ty) * this.view.scale + Math.round(st * factor + dy);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: zoomToRect\r\n * \r\n * Zooms the graph to the specified rectangle. If the rectangle does not have same aspect\r\n * ratio as the display container, it is increased in the smaller relative dimension only\r\n * until the aspect match. The original rectangle is centralised within this expanded one.\r\n * \r\n * Note that the input rectangular must be un-scaled and un-translated.\r\n * \r\n * Parameters:\r\n * \r\n * rect - The un-scaled and un-translated rectangluar region that should be just visible \r\n * after the operation\r\n */\r\nmxGraph.prototype.zoomToRect = function(rect)\r\n{\r\n\tvar scaleX = this.container.clientWidth / rect.width;\r\n\tvar scaleY = this.container.clientHeight / rect.height;\r\n\tvar aspectFactor = scaleX / scaleY;\r\n\r\n\t// Remove any overlap of the rect outside the client area\r\n\trect.x = Math.max(0, rect.x);\r\n\trect.y = Math.max(0, rect.y);\r\n\tvar rectRight = Math.min(this.container.scrollWidth, rect.x + rect.width);\r\n\tvar rectBottom = Math.min(this.container.scrollHeight, rect.y + rect.height);\r\n\trect.width = rectRight - rect.x;\r\n\trect.height = rectBottom - rect.y;\r\n\r\n\t// The selection area has to be increased to the same aspect\r\n\t// ratio as the container, centred around the centre point of the \r\n\t// original rect passed in.\r\n\tif (aspectFactor < 1.0)\r\n\t{\r\n\t\t// Height needs increasing\r\n\t\tvar newHeight = rect.height / aspectFactor;\r\n\t\tvar deltaHeightBuffer = (newHeight - rect.height) / 2.0;\r\n\t\trect.height = newHeight;\r\n\t\t\r\n\t\t// Assign up to half the buffer to the upper part of the rect, not crossing 0\r\n\t\t// put the rest on the bottom\r\n\t\tvar upperBuffer = Math.min(rect.y , deltaHeightBuffer);\r\n\t\trect.y = rect.y - upperBuffer;\r\n\t\t\r\n\t\t// Check if the bottom has extended too far\r\n\t\trectBottom = Math.min(this.container.scrollHeight, rect.y + rect.height);\r\n\t\trect.height = rectBottom - rect.y;\r\n\t}\r\n\telse\r\n\t{\r\n\t\t// Width needs increasing\r\n\t\tvar newWidth = rect.width * aspectFactor;\r\n\t\tvar deltaWidthBuffer = (newWidth - rect.width) / 2.0;\r\n\t\trect.width = newWidth;\r\n\t\t\r\n\t\t// Assign up to half the buffer to the upper part of the rect, not crossing 0\r\n\t\t// put the rest on the bottom\r\n\t\tvar leftBuffer = Math.min(rect.x , deltaWidthBuffer);\r\n\t\trect.x = rect.x - leftBuffer;\r\n\t\t\r\n\t\t// Check if the right hand side has extended too far\r\n\t\trectRight = Math.min(this.container.scrollWidth, rect.x + rect.width);\r\n\t\trect.width = rectRight - rect.x;\r\n\t}\r\n\r\n\tvar scale = this.container.clientWidth / rect.width;\r\n\tvar newScale = this.view.scale * scale;\r\n\r\n\tif (!mxUtils.hasScrollbars(this.container))\r\n\t{\r\n\t\tthis.view.scaleAndTranslate(newScale, (this.view.translate.x - rect.x / this.view.scale), (this.view.translate.y - rect.y / this.view.scale));\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.view.setScale(newScale);\r\n\t\tthis.container.scrollLeft = Math.round(rect.x * scale);\r\n\t\tthis.container.scrollTop = Math.round(rect.y * scale);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: scrollCellToVisible\r\n * \r\n * Pans the graph so that it shows the given cell. Optionally the cell may\r\n * be centered in the container.\r\n * \r\n * To center a given graph if the <container> has no scrollbars, use the following code.\r\n * \r\n * [code]\r\n * var bounds = graph.getGraphBounds();\r\n * graph.view.setTranslate(-bounds.x - (bounds.width - container.clientWidth) / 2,\r\n * \t\t\t\t\t\t   -bounds.y - (bounds.height - container.clientHeight) / 2);\r\n * [/code]\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> to be made visible.\r\n * center - Optional boolean flag. Default is false.\r\n */\r\nmxGraph.prototype.scrollCellToVisible = function(cell, center)\r\n{\r\n\tvar x = -this.view.translate.x;\r\n\tvar y = -this.view.translate.y;\r\n\r\n\tvar state = this.view.getState(cell);\r\n\r\n\tif (state != null)\r\n\t{\r\n\t\tvar bounds = new mxRectangle(x + state.x, y + state.y, state.width,\r\n\t\t\tstate.height);\r\n\r\n\t\tif (center && this.container != null)\r\n\t\t{\r\n\t\t\tvar w = this.container.clientWidth;\r\n\t\t\tvar h = this.container.clientHeight;\r\n\r\n\t\t\tbounds.x = bounds.getCenterX() - w / 2;\r\n\t\t\tbounds.width = w;\r\n\t\t\tbounds.y = bounds.getCenterY() - h / 2;\r\n\t\t\tbounds.height = h;\r\n\t\t}\r\n\t\t\r\n\t\tvar tr = new mxPoint(this.view.translate.x, this.view.translate.y);\r\n\r\n\t\tif (this.scrollRectToVisible(bounds))\r\n\t\t{\r\n\t\t\t// Triggers an update via the view's event source\r\n\t\t\tvar tr2 = new mxPoint(this.view.translate.x, this.view.translate.y);\r\n\t\t\tthis.view.translate.x = tr.x;\r\n\t\t\tthis.view.translate.y = tr.y;\r\n\t\t\tthis.view.setTranslate(tr2.x, tr2.y);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: scrollRectToVisible\r\n * \r\n * Pans the graph so that it shows the given rectangle.\r\n * \r\n * Parameters:\r\n * \r\n * rect - <mxRectangle> to be made visible.\r\n */\r\nmxGraph.prototype.scrollRectToVisible = function(rect)\r\n{\r\n\tvar isChanged = false;\r\n\t\r\n\tif (rect != null)\r\n\t{\r\n\t\tvar w = this.container.offsetWidth;\r\n\t\tvar h = this.container.offsetHeight;\r\n\r\n        var widthLimit = Math.min(w, rect.width);\r\n        var heightLimit = Math.min(h, rect.height);\r\n\r\n\t\tif (mxUtils.hasScrollbars(this.container))\r\n\t\t{\r\n\t\t\tvar c = this.container;\r\n\t\t\trect.x += this.view.translate.x;\r\n\t\t\trect.y += this.view.translate.y;\r\n\t\t\tvar dx = c.scrollLeft - rect.x;\r\n\t\t\tvar ddx = Math.max(dx - c.scrollLeft, 0);\r\n\r\n\t\t\tif (dx > 0)\r\n\t\t\t{\r\n\t\t\t\tc.scrollLeft -= dx + 2;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tdx = rect.x + widthLimit - c.scrollLeft - c.clientWidth;\r\n\r\n\t\t\t\tif (dx > 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tc.scrollLeft += dx + 2;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tvar dy = c.scrollTop - rect.y;\r\n\t\t\tvar ddy = Math.max(0, dy - c.scrollTop);\r\n\r\n\t\t\tif (dy > 0)\r\n\t\t\t{\r\n\t\t\t\tc.scrollTop -= dy + 2;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tdy = rect.y + heightLimit - c.scrollTop - c.clientHeight;\r\n\r\n\t\t\t\tif (dy > 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tc.scrollTop += dy + 2;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (!this.useScrollbarsForPanning && (ddx != 0 || ddy != 0))\r\n\t\t\t{\r\n\t\t\t\tthis.view.setTranslate(ddx, ddy);\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar x = -this.view.translate.x;\r\n\t\t\tvar y = -this.view.translate.y;\r\n\r\n\t\t\tvar s = this.view.scale;\r\n\r\n\t\t\tif (rect.x + widthLimit > x + w)\r\n\t\t\t{\r\n\t\t\t\tthis.view.translate.x -= (rect.x + widthLimit - w - x) / s;\r\n\t\t\t\tisChanged = true;\r\n\t\t\t}\r\n\r\n\t\t\tif (rect.y + heightLimit > y + h)\r\n\t\t\t{\r\n\t\t\t\tthis.view.translate.y -= (rect.y + heightLimit - h - y) / s;\r\n\t\t\t\tisChanged = true;\r\n\t\t\t}\r\n\r\n\t\t\tif (rect.x < x)\r\n\t\t\t{\r\n\t\t\t\tthis.view.translate.x += (x - rect.x) / s;\r\n\t\t\t\tisChanged = true;\r\n\t\t\t}\r\n\r\n\t\t\tif (rect.y  < y)\r\n\t\t\t{\r\n\t\t\t\tthis.view.translate.y += (y - rect.y) / s;\r\n\t\t\t\tisChanged = true;\r\n\t\t\t}\r\n\r\n\t\t\tif (isChanged)\r\n\t\t\t{\r\n\t\t\t\tthis.view.refresh();\r\n\t\t\t\t\r\n\t\t\t\t// Repaints selection marker (ticket 18)\r\n\t\t\t\tif (this.selectionCellsHandler != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.selectionCellsHandler.refresh();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn isChanged;\r\n};\r\n\r\n/**\r\n * Function: getCellGeometry\r\n * \r\n * Returns the <mxGeometry> for the given cell. This implementation uses\r\n * <mxGraphModel.getGeometry>. Subclasses can override this to implement\r\n * specific geometries for cells in only one graph, that is, it can return\r\n * geometries that depend on the current state of the view.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose geometry should be returned.\r\n */\r\nmxGraph.prototype.getCellGeometry = function(cell)\r\n{\r\n\treturn this.model.getGeometry(cell);\r\n};\r\n\r\n/**\r\n * Function: isCellVisible\r\n * \r\n * Returns true if the given cell is visible in this graph. This\r\n * implementation uses <mxGraphModel.isVisible>. Subclassers can override\r\n * this to implement specific visibility for cells in only one graph, that\r\n * is, without affecting the visible state of the cell.\r\n * \r\n * When using dynamic filter expressions for cell visibility, then the\r\n * graph should be revalidated after the filter expression has changed.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose visible state should be returned.\r\n */\r\nmxGraph.prototype.isCellVisible = function(cell)\r\n{\r\n\treturn this.model.isVisible(cell);\r\n};\r\n\r\n/**\r\n * Function: isCellCollapsed\r\n * \r\n * Returns true if the given cell is collapsed in this graph. This\r\n * implementation uses <mxGraphModel.isCollapsed>. Subclassers can override\r\n * this to implement specific collapsed states for cells in only one graph,\r\n * that is, without affecting the collapsed state of the cell.\r\n * \r\n * When using dynamic filter expressions for the collapsed state, then the\r\n * graph should be revalidated after the filter expression has changed.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose collapsed state should be returned.\r\n */\r\nmxGraph.prototype.isCellCollapsed = function(cell)\r\n{\r\n\treturn this.model.isCollapsed(cell);\r\n};\r\n\r\n/**\r\n * Function: isCellConnectable\r\n * \r\n * Returns true if the given cell is connectable in this graph. This\r\n * implementation uses <mxGraphModel.isConnectable>. Subclassers can override\r\n * this to implement specific connectable states for cells in only one graph,\r\n * that is, without affecting the connectable state of the cell in the model.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose connectable state should be returned.\r\n */\r\nmxGraph.prototype.isCellConnectable = function(cell)\r\n{\r\n\treturn this.model.isConnectable(cell);\r\n};\r\n\r\n/**\r\n * Function: isOrthogonal\r\n * \r\n * Returns true if perimeter points should be computed such that the\r\n * resulting edge has only horizontal or vertical segments.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCellState> that represents the edge.\r\n */\r\nmxGraph.prototype.isOrthogonal = function(edge)\r\n{\r\n\tvar orthogonal = edge.style[mxConstants.STYLE_ORTHOGONAL];\r\n\t\r\n\tif (orthogonal != null)\r\n\t{\r\n\t\treturn orthogonal;\r\n\t}\r\n\t\r\n\tvar tmp = this.view.getEdgeStyle(edge);\r\n\t\r\n\treturn tmp == mxEdgeStyle.SegmentConnector ||\r\n\t\ttmp == mxEdgeStyle.ElbowConnector ||\r\n\t\ttmp == mxEdgeStyle.SideToSide ||\r\n\t\ttmp == mxEdgeStyle.TopToBottom ||\r\n\t\ttmp == mxEdgeStyle.EntityRelation ||\r\n\t\ttmp == mxEdgeStyle.OrthConnector;\r\n};\r\n\r\n/**\r\n * Function: isLoop\r\n * \r\n * Returns true if the given cell state is a loop.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> that represents a potential loop.\r\n */\r\nmxGraph.prototype.isLoop = function(state)\r\n{\r\n\tvar src = state.getVisibleTerminalState(true);\r\n\tvar trg = state.getVisibleTerminalState(false);\r\n\t\r\n\treturn (src != null && src == trg);\r\n};\r\n\r\n/**\r\n * Function: isCloneEvent\r\n * \r\n * Returns true if the given event is a clone event. This implementation\r\n * returns true if control is pressed.\r\n */\r\nmxGraph.prototype.isCloneEvent = function(evt)\r\n{\r\n\treturn mxEvent.isControlDown(evt);\r\n};\r\n\r\n/**\r\n * Function: isTransparentClickEvent\r\n * \r\n * Hook for implementing click-through behaviour on selected cells. If this\r\n * returns true the cell behind the selected cell will be selected. This\r\n * implementation returns false;\r\n */\r\nmxGraph.prototype.isTransparentClickEvent = function(evt)\r\n{\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: isToggleEvent\r\n * \r\n * Returns true if the given event is a toggle event. This implementation\r\n * returns true if the meta key (Cmd) is pressed on Macs or if control is\r\n * pressed on any other platform.\r\n */\r\nmxGraph.prototype.isToggleEvent = function(evt)\r\n{\r\n\treturn (mxClient.IS_MAC) ? mxEvent.isMetaDown(evt) : mxEvent.isControlDown(evt);\r\n};\r\n\r\n/**\r\n * Function: isGridEnabledEvent\r\n * \r\n * Returns true if the given mouse event should be aligned to the grid.\r\n */\r\nmxGraph.prototype.isGridEnabledEvent = function(evt)\r\n{\r\n\treturn evt != null && !mxEvent.isAltDown(evt);\r\n};\r\n\r\n/**\r\n * Function: isConstrainedEvent\r\n * \r\n * Returns true if the given mouse event should be aligned to the grid.\r\n */\r\nmxGraph.prototype.isConstrainedEvent = function(evt)\r\n{\r\n\treturn mxEvent.isShiftDown(evt);\r\n};\r\n\r\n/**\r\n * Function: isIgnoreTerminalEvent\r\n * \r\n * Returns true if the given mouse event should not allow any connections to be\r\n * made. This implementation returns false.\r\n */\r\nmxGraph.prototype.isIgnoreTerminalEvent = function(evt)\r\n{\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Group: Validation\r\n */\r\n\r\n/**\r\n * Function: validationAlert\r\n * \r\n * Displays the given validation error in a dialog. This implementation uses\r\n * mxUtils.alert.\r\n */\r\nmxGraph.prototype.validationAlert = function(message)\r\n{\r\n\tmxUtils.alert(message);\r\n};\r\n\r\n/**\r\n * Function: isEdgeValid\r\n * \r\n * Checks if the return value of <getEdgeValidationError> for the given\r\n * arguments is null.\r\n *  \r\n * Parameters:\r\n * \r\n * edge - <mxCell> that represents the edge to validate.\r\n * source - <mxCell> that represents the source terminal.\r\n * target - <mxCell> that represents the target terminal.\r\n */\r\nmxGraph.prototype.isEdgeValid = function(edge, source, target)\r\n{\r\n\treturn this.getEdgeValidationError(edge, source, target) == null;\r\n};\r\n\r\n/**\r\n * Function: getEdgeValidationError\r\n * \r\n * Returns the validation error message to be displayed when inserting or\r\n * changing an edges' connectivity. A return value of null means the edge\r\n * is valid, a return value of '' means it's not valid, but do not display\r\n * an error message. Any other (non-empty) string returned from this method\r\n * is displayed as an error message when trying to connect an edge to a\r\n * source and target. This implementation uses the <multiplicities>, and\r\n * checks <multigraph>, <allowDanglingEdges> and <allowLoops> to generate\r\n * validation errors.\r\n * \r\n * For extending this method with specific checks for source/target cells,\r\n * the method can be extended as follows. Returning an empty string means\r\n * the edge is invalid with no error message, a non-null string specifies\r\n * the error message, and null means the edge is valid.\r\n * \r\n * (code)\r\n * graph.getEdgeValidationError = function(edge, source, target)\r\n * {\r\n *   if (source != null && target != null &&\r\n *     this.model.getValue(source) != null &&\r\n *     this.model.getValue(target) != null)\r\n *   {\r\n *     if (target is not valid for source)\r\n *     {\r\n *       return 'Invalid Target';\r\n *     }\r\n *   }\r\n *   \r\n *   // \"Supercall\"\r\n *   return mxGraph.prototype.getEdgeValidationError.apply(this, arguments);\r\n * }\r\n * (end)\r\n *  \r\n * Parameters:\r\n * \r\n * edge - <mxCell> that represents the edge to validate.\r\n * source - <mxCell> that represents the source terminal.\r\n * target - <mxCell> that represents the target terminal.\r\n */\r\nmxGraph.prototype.getEdgeValidationError = function(edge, source, target)\r\n{\r\n\tif (edge != null && !this.isAllowDanglingEdges() && (source == null || target == null))\r\n\t{\r\n\t\treturn '';\r\n\t}\r\n\t\r\n\tif (edge != null && this.model.getTerminal(edge, true) == null &&\r\n\t\tthis.model.getTerminal(edge, false) == null)\t\r\n\t{\r\n\t\treturn null;\r\n\t}\r\n\t\r\n\t// Checks if we're dealing with a loop\r\n\tif (!this.allowLoops && source == target && source != null)\r\n\t{\r\n\t\treturn '';\r\n\t}\r\n\t\r\n\t// Checks if the connection is generally allowed\r\n\tif (!this.isValidConnection(source, target))\r\n\t{\r\n\t\treturn '';\r\n\t}\r\n\r\n\tif (source != null && target != null)\r\n\t{\r\n\t\tvar error = '';\r\n\r\n\t\t// Checks if the cells are already connected\r\n\t\t// and adds an error message if required\t\t\t\r\n\t\tif (!this.multigraph)\r\n\t\t{\r\n\t\t\tvar tmp = this.model.getEdgesBetween(source, target, true);\r\n\t\t\t\r\n\t\t\t// Checks if the source and target are not connected by another edge\r\n\t\t\tif (tmp.length > 1 || (tmp.length == 1 && tmp[0] != edge))\r\n\t\t\t{\r\n\t\t\t\terror += (mxResources.get(this.alreadyConnectedResource) ||\r\n\t\t\t\t\tthis.alreadyConnectedResource)+'\\n';\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Gets the number of outgoing edges from the source\r\n\t\t// and the number of incoming edges from the target\r\n\t\t// without counting the edge being currently changed.\r\n\t\tvar sourceOut = this.model.getDirectedEdgeCount(source, true, edge);\r\n\t\tvar targetIn = this.model.getDirectedEdgeCount(target, false, edge);\r\n\r\n\t\t// Checks the change against each multiplicity rule\r\n\t\tif (this.multiplicities != null)\r\n\t\t{\r\n\t\t\tfor (var i = 0; i < this.multiplicities.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar err = this.multiplicities[i].check(this, edge, source,\r\n\t\t\t\t\ttarget, sourceOut, targetIn);\r\n\t\t\t\t\r\n\t\t\t\tif (err != null)\r\n\t\t\t\t{\r\n\t\t\t\t\terror += err;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Validates the source and target terminals independently\r\n\t\tvar err = this.validateEdge(edge, source, target);\r\n\t\t\r\n\t\tif (err != null)\r\n\t\t{\r\n\t\t\terror += err;\r\n\t\t}\r\n\t\t\r\n\t\treturn (error.length > 0) ? error : null;\r\n\t}\r\n\t\r\n\treturn (this.allowDanglingEdges) ? null : '';\r\n};\r\n\r\n/**\r\n * Function: validateEdge\r\n * \r\n * Hook method for subclassers to return an error message for the given\r\n * edge and terminals. This implementation returns null.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCell> that represents the edge to validate.\r\n * source - <mxCell> that represents the source terminal.\r\n * target - <mxCell> that represents the target terminal.\r\n */\r\nmxGraph.prototype.validateEdge = function(edge, source, target)\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: validateGraph\r\n * \r\n * Validates the graph by validating each descendant of the given cell or\r\n * the root of the model. Context is an object that contains the validation\r\n * state for the complete validation run. The validation errors are\r\n * attached to their cells using <setCellWarning>. Returns null in the case of\r\n * successful validation or an array of strings (warnings) in the case of\r\n * failed validations.\r\n * \r\n * Paramters:\r\n * \r\n * cell - Optional <mxCell> to start the validation recursion. Default is\r\n * the graph root.\r\n * context - Object that represents the global validation state.\r\n */\r\nmxGraph.prototype.validateGraph = function(cell, context)\r\n{\r\n\tcell = (cell != null) ? cell : this.model.getRoot();\r\n\tcontext = (context != null) ? context : new Object();\r\n\t\r\n\tvar isValid = true;\r\n\tvar childCount = this.model.getChildCount(cell);\r\n\t\r\n\tfor (var i = 0; i < childCount; i++)\r\n\t{\r\n\t\tvar tmp = this.model.getChildAt(cell, i);\r\n\t\tvar ctx = context;\r\n\t\t\r\n\t\tif (this.isValidRoot(tmp))\r\n\t\t{\r\n\t\t\tctx = new Object();\r\n\t\t}\r\n\t\t\r\n\t\tvar warn = this.validateGraph(tmp, ctx);\r\n\t\t\r\n\t\tif (warn != null)\r\n\t\t{\r\n\t\t\tthis.setCellWarning(tmp, warn.replace(/\\n/g, '<br>'));\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.setCellWarning(tmp, null);\r\n\t\t}\r\n\t\t\r\n\t\tisValid = isValid && warn == null;\r\n\t}\r\n\t\r\n\tvar warning = '';\r\n\t\r\n\t// Adds error for invalid children if collapsed (children invisible)\r\n\tif (this.isCellCollapsed(cell) && !isValid)\r\n\t{\r\n\t\twarning += (mxResources.get(this.containsValidationErrorsResource) ||\r\n\t\t\tthis.containsValidationErrorsResource) + '\\n';\r\n\t}\r\n\t\r\n\t// Checks edges and cells using the defined multiplicities\r\n\tif (this.model.isEdge(cell))\r\n\t{\r\n\t\twarning += this.getEdgeValidationError(cell,\r\n\t\tthis.model.getTerminal(cell, true),\r\n\t\tthis.model.getTerminal(cell, false)) || '';\r\n\t}\r\n\telse\r\n\t{\r\n\t\twarning += this.getCellValidationError(cell) || '';\r\n\t}\r\n\t\r\n\t// Checks custom validation rules\r\n\tvar err = this.validateCell(cell, context);\r\n\t\r\n\tif (err != null)\r\n\t{\r\n\t\twarning += err;\r\n\t}\r\n\t\r\n\t// Updates the display with the warning icons\r\n\t// before any potential alerts are displayed.\r\n\t// LATER: Move this into addCellOverlay. Redraw\r\n\t// should check if overlay was added or removed.\r\n\tif (this.model.getParent(cell) == null)\r\n\t{\r\n\t\tthis.view.validate();\r\n\t}\r\n\r\n\treturn (warning.length > 0 || !isValid) ? warning : null;\r\n};\r\n\r\n/**\r\n * Function: getCellValidationError\r\n * \r\n * Checks all <multiplicities> that cannot be enforced while the graph is\r\n * being modified, namely, all multiplicities that require a minimum of\r\n * 1 edge.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> for which the multiplicities should be checked.\r\n */\r\nmxGraph.prototype.getCellValidationError = function(cell)\r\n{\r\n\tvar outCount = this.model.getDirectedEdgeCount(cell, true);\r\n\tvar inCount = this.model.getDirectedEdgeCount(cell, false);\r\n\tvar value = this.model.getValue(cell);\r\n\tvar error = '';\r\n\r\n\tif (this.multiplicities != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.multiplicities.length; i++)\r\n\t\t{\r\n\t\t\tvar rule = this.multiplicities[i];\r\n\t\t\t\r\n\t\t\tif (rule.source && mxUtils.isNode(value, rule.type,\r\n\t\t\t\trule.attr, rule.value) && (outCount > rule.max ||\r\n\t\t\t\toutCount < rule.min))\r\n\t\t\t{\r\n\t\t\t\terror += rule.countError + '\\n';\r\n\t\t\t}\r\n\t\t\telse if (!rule.source && mxUtils.isNode(value, rule.type,\r\n\t\t\t\t\trule.attr, rule.value) && (inCount > rule.max ||\r\n\t\t\t\t\tinCount < rule.min))\r\n\t\t\t{\r\n\t\t\t\terror += rule.countError + '\\n';\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn (error.length > 0) ? error : null;\r\n};\r\n\r\n/**\r\n * Function: validateCell\r\n * \r\n * Hook method for subclassers to return an error message for the given\r\n * cell and validation context. This implementation returns null. Any HTML\r\n * breaks will be converted to linefeeds in the calling method.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> that represents the cell to validate.\r\n * context - Object that represents the global validation state.\r\n */\r\nmxGraph.prototype.validateCell = function(cell, context)\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Group: Graph appearance\r\n */\r\n\r\n/**\r\n * Function: getBackgroundImage\r\n * \r\n * Returns the <backgroundImage> as an <mxImage>.\r\n */\r\nmxGraph.prototype.getBackgroundImage = function()\r\n{\r\n\treturn this.backgroundImage;\r\n};\r\n\r\n/**\r\n * Function: setBackgroundImage\r\n * \r\n * Sets the new <backgroundImage>.\r\n * \r\n * Parameters:\r\n * \r\n * image - New <mxImage> to be used for the background.\r\n */\r\nmxGraph.prototype.setBackgroundImage = function(image)\r\n{\r\n\tthis.backgroundImage = image;\r\n};\r\n\r\n/**\r\n * Function: getFoldingImage\r\n * \r\n * Returns the <mxImage> used to display the collapsed state of\r\n * the specified cell state. This returns null for all edges.\r\n */\r\nmxGraph.prototype.getFoldingImage = function(state)\r\n{\r\n\tif (state != null && this.foldingEnabled && !this.getModel().isEdge(state.cell))\r\n\t{\r\n\t\tvar tmp = this.isCellCollapsed(state.cell);\r\n\t\t\r\n\t\tif (this.isCellFoldable(state.cell, !tmp))\r\n\t\t{\r\n\t\t\treturn (tmp) ? this.collapsedImage : this.expandedImage;\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: convertValueToString\r\n * \r\n * Returns the textual representation for the given cell. This\r\n * implementation returns the nodename or string-representation of the user\r\n * object.\r\n *\r\n * Example:\r\n * \r\n * The following returns the label attribute from the cells user\r\n * object if it is an XML node.\r\n * \r\n * (code)\r\n * graph.convertValueToString = function(cell)\r\n * {\r\n * \treturn cell.getAttribute('label');\r\n * }\r\n * (end)\r\n * \r\n * See also: <cellLabelChanged>.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose textual representation should be returned.\r\n */\r\nmxGraph.prototype.convertValueToString = function(cell)\r\n{\r\n\tvar value = this.model.getValue(cell);\r\n\t\r\n\tif (value != null)\r\n\t{\r\n\t\tif (mxUtils.isNode(value))\r\n\t\t{\r\n\t\t\treturn value.nodeName;\r\n\t\t}\r\n\t\telse if (typeof(value.toString) == 'function')\r\n\t\t{\r\n\t\t\treturn value.toString();\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn '';\r\n};\r\n\r\n/**\r\n * Function: getLabel\r\n * \r\n * Returns a string or DOM node that represents the label for the given\r\n * cell. This implementation uses <convertValueToString> if <labelsVisible>\r\n * is true. Otherwise it returns an empty string.\r\n * \r\n * To truncate a label to match the size of the cell, the following code\r\n * can be used.\r\n * \r\n * (code)\r\n * graph.getLabel = function(cell)\r\n * {\r\n *   var label = mxGraph.prototype.getLabel.apply(this, arguments);\r\n * \r\n *   if (label != null && this.model.isVertex(cell))\r\n *   {\r\n *     var geo = this.getCellGeometry(cell);\r\n * \r\n *     if (geo != null)\r\n *     {\r\n *       var max = parseInt(geo.width / 8);\r\n * \r\n *       if (label.length > max)\r\n *       {\r\n *         label = label.substring(0, max)+'...';\r\n *       }\r\n *     }\r\n *   } \r\n *   return mxUtils.htmlEntities(label);\r\n * }\r\n * (end)\r\n * \r\n * A resize listener is needed in the graph to force a repaint of the label\r\n * after a resize.\r\n * \r\n * (code)\r\n * graph.addListener(mxEvent.RESIZE_CELLS, function(sender, evt)\r\n * {\r\n *   var cells = evt.getProperty('cells');\r\n * \r\n *   for (var i = 0; i < cells.length; i++)\r\n *   {\r\n *     this.view.removeState(cells[i]);\r\n *   }\r\n * });\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose label should be returned.\r\n */\r\nmxGraph.prototype.getLabel = function(cell)\r\n{\r\n\tvar result = '';\r\n\t\r\n\tif (this.labelsVisible && cell != null)\r\n\t{\r\n\t\tvar state = this.view.getState(cell);\r\n\t\tvar style = (state != null) ? state.style : this.getCellStyle(cell);\r\n\t\t\r\n\t\tif (!mxUtils.getValue(style, mxConstants.STYLE_NOLABEL, false))\r\n\t\t{\r\n\t\t\tresult = this.convertValueToString(cell);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: isHtmlLabel\r\n * \r\n * Returns true if the label must be rendered as HTML markup. The default\r\n * implementation returns <htmlLabels>.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose label should be displayed as HTML markup.\r\n */\r\nmxGraph.prototype.isHtmlLabel = function(cell)\r\n{\r\n\treturn this.isHtmlLabels();\r\n};\r\n \r\n/**\r\n * Function: isHtmlLabels\r\n * \r\n * Returns <htmlLabels>.\r\n */\r\nmxGraph.prototype.isHtmlLabels = function()\r\n{\r\n\treturn this.htmlLabels;\r\n};\r\n \r\n/**\r\n * Function: setHtmlLabels\r\n * \r\n * Sets <htmlLabels>.\r\n */\r\nmxGraph.prototype.setHtmlLabels = function(value)\r\n{\r\n\tthis.htmlLabels = value;\r\n};\r\n\r\n/**\r\n * Function: isWrapping\r\n * \r\n * This enables wrapping for HTML labels.\r\n * \r\n * Returns true if no white-space CSS style directive should be used for\r\n * displaying the given cells label. This implementation returns true if\r\n * <mxConstants.STYLE_WHITE_SPACE> in the style of the given cell is 'wrap'.\r\n * \r\n * This is used as a workaround for IE ignoring the white-space directive\r\n * of child elements if the directive appears in a parent element. It\r\n * should be overridden to return true if a white-space directive is used\r\n * in the HTML markup that represents the given cells label. In order for\r\n * HTML markup to work in labels, <isHtmlLabel> must also return true\r\n * for the given cell.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * graph.getLabel = function(cell)\r\n * {\r\n *   var tmp = mxGraph.prototype.getLabel.apply(this, arguments); // \"supercall\"\r\n *   \r\n *   if (this.model.isEdge(cell))\r\n *   {\r\n *     tmp = '<div style=\"width: 150px; white-space:normal;\">'+tmp+'</div>';\r\n *   }\r\n *   \r\n *   return tmp;\r\n * }\r\n * \r\n * graph.isWrapping = function(state)\r\n * {\r\n * \t return this.model.isEdge(state.cell);\r\n * }\r\n * (end)\r\n * \r\n * Makes sure no edge label is wider than 150 pixels, otherwise the content\r\n * is wrapped. Note: No width must be specified for wrapped vertex labels as\r\n * the vertex defines the width in its geometry.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCell> whose label should be wrapped.\r\n */\r\nmxGraph.prototype.isWrapping = function(cell)\r\n{\r\n\tvar state = this.view.getState(cell);\r\n\tvar style = (state != null) ? state.style : this.getCellStyle(cell);\r\n\r\n\treturn (style != null) ? style[mxConstants.STYLE_WHITE_SPACE] == 'wrap' : false;\r\n};\r\n\r\n/**\r\n * Function: isLabelClipped\r\n * \r\n * Returns true if the overflow portion of labels should be hidden. If this\r\n * returns true then vertex labels will be clipped to the size of the vertices.\r\n * This implementation returns true if <mxConstants.STYLE_OVERFLOW> in the\r\n * style of the given cell is 'hidden'.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCell> whose label should be clipped.\r\n */\r\nmxGraph.prototype.isLabelClipped = function(cell)\r\n{\r\n\tvar state = this.view.getState(cell);\r\n\tvar style = (state != null) ? state.style : this.getCellStyle(cell);\r\n\r\n\treturn (style != null) ? style[mxConstants.STYLE_OVERFLOW] == 'hidden' : false;\r\n};\r\n\r\n/**\r\n * Function: getTooltip\r\n * \r\n * Returns the string or DOM node that represents the tooltip for the given\r\n * state, node and coordinate pair. This implementation checks if the given\r\n * node is a folding icon or overlay and returns the respective tooltip. If\r\n * this does not result in a tooltip, the handler for the cell is retrieved\r\n * from <selectionCellsHandler> and the optional getTooltipForNode method is\r\n * called. If no special tooltip exists here then <getTooltipForCell> is used\r\n * with the cell in the given state as the argument to return a tooltip for the\r\n * given state.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose tooltip should be returned.\r\n * node - DOM node that is currently under the mouse.\r\n * x - X-coordinate of the mouse.\r\n * y - Y-coordinate of the mouse.\r\n */\r\nmxGraph.prototype.getTooltip = function(state, node, x, y)\r\n{\r\n\tvar tip = null;\r\n\t\r\n\tif (state != null)\r\n\t{\r\n\t\t// Checks if the mouse is over the folding icon\r\n\t\tif (state.control != null && (node == state.control.node ||\r\n\t\t\tnode.parentNode == state.control.node))\r\n\t\t{\r\n\t\t\ttip = this.collapseExpandResource;\r\n\t\t\ttip = mxUtils.htmlEntities(mxResources.get(tip) || tip).replace(/\\\\n/g, '<br>');\r\n\t\t}\r\n\r\n\t\tif (tip == null && state.overlays != null)\r\n\t\t{\r\n\t\t\tstate.overlays.visit(function(id, shape)\r\n\t\t\t{\r\n\t\t\t\t// LATER: Exit loop if tip is not null\r\n\t\t\t\tif (tip == null && (node == shape.node || node.parentNode == shape.node))\r\n\t\t\t\t{\r\n\t\t\t\t\ttip = shape.overlay.toString();\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\t\t\r\n\t\tif (tip == null)\r\n\t\t{\r\n\t\t\tvar handler = this.selectionCellsHandler.getHandler(state.cell);\r\n\t\t\t\r\n\t\t\tif (handler != null && typeof(handler.getTooltipForNode) == 'function')\r\n\t\t\t{\r\n\t\t\t\ttip = handler.getTooltipForNode(node);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (tip == null)\r\n\t\t{\r\n\t\t\ttip = this.getTooltipForCell(state.cell);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn tip;\r\n};\r\n\r\n/**\r\n * Function: getTooltipForCell\r\n * \r\n * Returns the string or DOM node to be used as the tooltip for the given\r\n * cell. This implementation uses the cells getTooltip function if it\r\n * exists, or else it returns <convertValueToString> for the cell.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * graph.getTooltipForCell = function(cell)\r\n * {\r\n *   return 'Hello, World!';\r\n * }\r\n * (end)\r\n * \r\n * Replaces all tooltips with the string Hello, World!\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose tooltip should be returned.\r\n */\r\nmxGraph.prototype.getTooltipForCell = function(cell)\r\n{\r\n\tvar tip = null;\r\n\t\r\n\tif (cell != null && cell.getTooltip != null)\r\n\t{\r\n\t\ttip = cell.getTooltip();\r\n\t}\r\n\telse\r\n\t{\r\n\t\ttip = this.convertValueToString(cell);\r\n\t}\r\n\t\r\n\treturn tip;\r\n};\r\n\r\n/**\r\n * Function: getLinkForCell\r\n * \r\n * Returns the string to be used as the link for the given cell. This\r\n * implementation returns null.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose tooltip should be returned.\r\n */\r\nmxGraph.prototype.getLinkForCell = function(cell)\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: getCursorForMouseEvent\r\n * \r\n * Returns the cursor value to be used for the CSS of the shape for the\r\n * given event. This implementation calls <getCursorForCell>.\r\n * \r\n * Parameters:\r\n * \r\n * me - <mxMouseEvent> whose cursor should be returned.\r\n */\r\nmxGraph.prototype.getCursorForMouseEvent = function(me)\r\n{\r\n\treturn this.getCursorForCell(me.getCell());\r\n};\r\n\r\n/**\r\n * Function: getCursorForCell\r\n * \r\n * Returns the cursor value to be used for the CSS of the shape for the\r\n * given cell. This implementation returns null.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose cursor should be returned.\r\n */\r\nmxGraph.prototype.getCursorForCell = function(cell)\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: getStartSize\r\n * \r\n * Returns the start size of the given swimlane, that is, the width or\r\n * height of the part that contains the title, depending on the\r\n * horizontal style. The return value is an <mxRectangle> with either\r\n * width or height set as appropriate.\r\n * \r\n * Parameters:\r\n * \r\n * swimlane - <mxCell> whose start size should be returned.\r\n */\r\nmxGraph.prototype.getStartSize = function(swimlane)\r\n{\r\n\tvar result = new mxRectangle();\r\n\tvar state = this.view.getState(swimlane);\r\n\tvar style = (state != null) ? state.style : this.getCellStyle(swimlane);\r\n\t\r\n\tif (style != null)\r\n\t{\r\n\t\tvar size = parseInt(mxUtils.getValue(style,\r\n\t\t\tmxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));\r\n\t\t\r\n\t\tif (mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true))\r\n\t\t{\r\n\t\t\tresult.height = size;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tresult.width = size;\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: getImage\r\n * \r\n * Returns the image URL for the given cell state. This implementation\r\n * returns the value stored under <mxConstants.STYLE_IMAGE> in the cell\r\n * style.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose image URL should be returned.\r\n */\r\nmxGraph.prototype.getImage = function(state)\r\n{\r\n\treturn (state != null && state.style != null) ? state.style[mxConstants.STYLE_IMAGE] : null;\r\n};\r\n\r\n/**\r\n * Function: getVerticalAlign\r\n * \r\n * Returns the vertical alignment for the given cell state. This\r\n * implementation returns the value stored under\r\n * <mxConstants.STYLE_VERTICAL_ALIGN> in the cell style.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose vertical alignment should be\r\n * returned.\r\n */\r\nmxGraph.prototype.getVerticalAlign = function(state)\r\n{\r\n\treturn (state != null && state.style != null) ?\r\n\t\t(state.style[mxConstants.STYLE_VERTICAL_ALIGN] ||\r\n\t\tmxConstants.ALIGN_MIDDLE) : null;\r\n};\r\n\r\n/**\r\n * Function: getIndicatorColor\r\n * \r\n * Returns the indicator color for the given cell state. This\r\n * implementation returns the value stored under\r\n * <mxConstants.STYLE_INDICATOR_COLOR> in the cell style.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose indicator color should be\r\n * returned.\r\n */\r\nmxGraph.prototype.getIndicatorColor = function(state)\r\n{\r\n\treturn (state != null && state.style != null) ? state.style[mxConstants.STYLE_INDICATOR_COLOR] : null;\r\n};\r\n\r\n/**\r\n * Function: getIndicatorGradientColor\r\n * \r\n * Returns the indicator gradient color for the given cell state. This\r\n * implementation returns the value stored under\r\n * <mxConstants.STYLE_INDICATOR_GRADIENTCOLOR> in the cell style.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose indicator gradient color should be\r\n * returned.\r\n */\r\nmxGraph.prototype.getIndicatorGradientColor = function(state)\r\n{\r\n\treturn (state != null && state.style != null) ? state.style[mxConstants.STYLE_INDICATOR_GRADIENTCOLOR] : null;\r\n};\r\n\r\n/**\r\n * Function: getIndicatorShape\r\n * \r\n * Returns the indicator shape for the given cell state. This\r\n * implementation returns the value stored under\r\n * <mxConstants.STYLE_INDICATOR_SHAPE> in the cell style.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose indicator shape should be returned.\r\n */\r\nmxGraph.prototype.getIndicatorShape = function(state)\r\n{\r\n\treturn (state != null && state.style != null) ? state.style[mxConstants.STYLE_INDICATOR_SHAPE] : null;\r\n};\r\n\r\n/**\r\n * Function: getIndicatorImage\r\n * \r\n * Returns the indicator image for the given cell state. This\r\n * implementation returns the value stored under\r\n * <mxConstants.STYLE_INDICATOR_IMAGE> in the cell style.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose indicator image should be returned.\r\n */\r\nmxGraph.prototype.getIndicatorImage = function(state)\r\n{\r\n\treturn (state != null && state.style != null) ? state.style[mxConstants.STYLE_INDICATOR_IMAGE] : null;\r\n};\r\n\r\n/**\r\n * Function: getBorder\r\n * \r\n * Returns the value of <border>.\r\n */\r\nmxGraph.prototype.getBorder = function()\r\n{\r\n\treturn this.border;\r\n};\r\n\r\n/**\r\n * Function: setBorder\r\n * \r\n * Sets the value of <border>.\r\n * \r\n * Parameters:\r\n * \r\n * value - Positive integer that represents the border to be used.\r\n */\r\nmxGraph.prototype.setBorder = function(value)\r\n{\r\n\tthis.border = value;\r\n};\r\n\r\n/**\r\n * Function: isSwimlane\r\n * \r\n * Returns true if the given cell is a swimlane in the graph. A swimlane is\r\n * a container cell with some specific behaviour. This implementation\r\n * checks if the shape associated with the given cell is a <mxSwimlane>.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> to be checked.\r\n */\r\nmxGraph.prototype.isSwimlane = function (cell)\r\n{\r\n\tif (cell != null)\r\n\t{\r\n\t\tif (this.model.getParent(cell) != this.model.getRoot())\r\n\t\t{\r\n\t\t\tvar state = this.view.getState(cell);\r\n\t\t\tvar style = (state != null) ? state.style : this.getCellStyle(cell);\r\n\r\n\t\t\tif (style != null && !this.model.isEdge(cell))\r\n\t\t\t{\r\n\t\t\t\treturn style[mxConstants.STYLE_SHAPE] == mxConstants.SHAPE_SWIMLANE;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Group: Graph behaviour\r\n */\r\n\r\n/**\r\n * Function: isResizeContainer\r\n * \r\n * Returns <resizeContainer>.\r\n */\r\nmxGraph.prototype.isResizeContainer = function()\r\n{\r\n\treturn this.resizeContainer;\r\n};\r\n\r\n/**\r\n * Function: setResizeContainer\r\n * \r\n * Sets <resizeContainer>.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean indicating if the container should be resized.\r\n */\r\nmxGraph.prototype.setResizeContainer = function(value)\r\n{\r\n\tthis.resizeContainer = value;\r\n};\r\n\r\n/**\r\n * Function: isEnabled\r\n * \r\n * Returns true if the graph is <enabled>.\r\n */\r\nmxGraph.prototype.isEnabled = function()\r\n{\r\n\treturn this.enabled;\r\n};\r\n\r\n/**\r\n * Function: setEnabled\r\n * \r\n * Specifies if the graph should allow any interactions. This\r\n * implementation updates <enabled>.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean indicating if the graph should be enabled.\r\n */\r\nmxGraph.prototype.setEnabled = function(value)\r\n{\r\n\tthis.enabled = value;\r\n};\r\n\r\n/**\r\n * Function: isEscapeEnabled\r\n * \r\n * Returns <escapeEnabled>.\r\n */\r\nmxGraph.prototype.isEscapeEnabled = function()\r\n{\r\n\treturn this.escapeEnabled;\r\n};\r\n\r\n/**\r\n * Function: setEscapeEnabled\r\n * \r\n * Sets <escapeEnabled>.\r\n * \r\n * Parameters:\r\n * \r\n * enabled - Boolean indicating if escape should be enabled.\r\n */\r\nmxGraph.prototype.setEscapeEnabled = function(value)\r\n{\r\n\tthis.escapeEnabled = value;\r\n};\r\n\r\n/**\r\n * Function: isInvokesStopCellEditing\r\n * \r\n * Returns <invokesStopCellEditing>.\r\n */\r\nmxGraph.prototype.isInvokesStopCellEditing = function()\r\n{\r\n\treturn this.invokesStopCellEditing;\r\n};\r\n\r\n/**\r\n * Function: setInvokesStopCellEditing\r\n * \r\n * Sets <invokesStopCellEditing>.\r\n */\r\nmxGraph.prototype.setInvokesStopCellEditing = function(value)\r\n{\r\n\tthis.invokesStopCellEditing = value;\r\n};\r\n\r\n/**\r\n * Function: isEnterStopsCellEditing\r\n * \r\n * Returns <enterStopsCellEditing>.\r\n */\r\nmxGraph.prototype.isEnterStopsCellEditing = function()\r\n{\r\n\treturn this.enterStopsCellEditing;\r\n};\r\n\r\n/**\r\n * Function: setEnterStopsCellEditing\r\n * \r\n * Sets <enterStopsCellEditing>.\r\n */\r\nmxGraph.prototype.setEnterStopsCellEditing = function(value)\r\n{\r\n\tthis.enterStopsCellEditing = value;\r\n};\r\n\r\n/**\r\n * Function: isCellLocked\r\n * \r\n * Returns true if the given cell may not be moved, sized, bended,\r\n * disconnected, edited or selected. This implementation returns true for\r\n * all vertices with a relative geometry if <locked> is false.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose locked state should be returned.\r\n */\r\nmxGraph.prototype.isCellLocked = function(cell)\r\n{\r\n\tvar geometry = this.model.getGeometry(cell);\r\n\t\r\n\treturn this.isCellsLocked() || (geometry != null && this.model.isVertex(cell) && geometry.relative);\r\n};\r\n\r\n/**\r\n * Function: isCellsLocked\r\n * \r\n * Returns true if the given cell may not be moved, sized, bended,\r\n * disconnected, edited or selected. This implementation returns true for\r\n * all vertices with a relative geometry if <locked> is false.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose locked state should be returned.\r\n */\r\nmxGraph.prototype.isCellsLocked = function()\r\n{\r\n\treturn this.cellsLocked;\r\n};\r\n\r\n/**\r\n * Function: setCellsLocked\r\n * \r\n * Sets if any cell may be moved, sized, bended, disconnected, edited or\r\n * selected.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean that defines the new value for <cellsLocked>.\r\n */\r\nmxGraph.prototype.setCellsLocked = function(value)\r\n{\r\n\tthis.cellsLocked = value;\r\n};\r\n\r\n/**\r\n * Function: getCloneableCells\r\n * \r\n * Returns the cells which may be exported in the given array of cells.\r\n */\r\nmxGraph.prototype.getCloneableCells = function(cells)\r\n{\r\n\treturn this.model.filterCells(cells, mxUtils.bind(this, function(cell)\r\n\t{\r\n\t\treturn this.isCellCloneable(cell);\r\n\t}));\r\n};\r\n\r\n/**\r\n * Function: isCellCloneable\r\n * \r\n * Returns true if the given cell is cloneable. This implementation returns\r\n * <isCellsCloneable> for all cells unless a cell style specifies\r\n * <mxConstants.STYLE_CLONEABLE> to be 0. \r\n * \r\n * Parameters:\r\n * \r\n * cell - Optional <mxCell> whose cloneable state should be returned.\r\n */\r\nmxGraph.prototype.isCellCloneable = function(cell)\r\n{\r\n\tvar state = this.view.getState(cell);\r\n\tvar style = (state != null) ? state.style : this.getCellStyle(cell);\r\n\r\n\treturn this.isCellsCloneable() && style[mxConstants.STYLE_CLONEABLE] != 0;\r\n};\r\n\r\n/**\r\n * Function: isCellsCloneable\r\n * \r\n * Returns <cellsCloneable>, that is, if the graph allows cloning of cells\r\n * by using control-drag.\r\n */\r\nmxGraph.prototype.isCellsCloneable = function()\r\n{\r\n\treturn this.cellsCloneable;\r\n};\r\n\r\n/**\r\n * Function: setCellsCloneable\r\n * \r\n * Specifies if the graph should allow cloning of cells by holding down the\r\n * control key while cells are being moved. This implementation updates\r\n * <cellsCloneable>.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean indicating if the graph should be cloneable.\r\n */\r\nmxGraph.prototype.setCellsCloneable = function(value)\r\n{\r\n\tthis.cellsCloneable = value;\r\n};\r\n\r\n/**\r\n * Function: getExportableCells\r\n * \r\n * Returns the cells which may be exported in the given array of cells.\r\n */\r\nmxGraph.prototype.getExportableCells = function(cells)\r\n{\r\n\treturn this.model.filterCells(cells, mxUtils.bind(this, function(cell)\r\n\t{\r\n\t\treturn this.canExportCell(cell);\r\n\t}));\r\n};\r\n\r\n/**\r\n * Function: canExportCell\r\n * \r\n * Returns true if the given cell may be exported to the clipboard. This\r\n * implementation returns <exportEnabled> for all cells.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> that represents the cell to be exported.\r\n */\r\nmxGraph.prototype.canExportCell = function(cell)\r\n{\r\n\treturn this.exportEnabled;\r\n};\r\n\r\n/**\r\n * Function: getImportableCells\r\n * \r\n * Returns the cells which may be imported in the given array of cells.\r\n */\r\nmxGraph.prototype.getImportableCells = function(cells)\r\n{\r\n\treturn this.model.filterCells(cells, mxUtils.bind(this, function(cell)\r\n\t{\r\n\t\treturn this.canImportCell(cell);\r\n\t}));\r\n};\r\n\r\n/**\r\n * Function: canImportCell\r\n * \r\n * Returns true if the given cell may be imported from the clipboard.\r\n * This implementation returns <importEnabled> for all cells.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> that represents the cell to be imported.\r\n */\r\nmxGraph.prototype.canImportCell = function(cell)\r\n{\r\n\treturn this.importEnabled;\r\n};\r\n\r\n/**\r\n * Function: isCellSelectable\r\n *\r\n * Returns true if the given cell is selectable. This implementation\r\n * returns <cellsSelectable>.\r\n * \r\n * To add a new style for making cells (un)selectable, use the following code.\r\n * \r\n * (code)\r\n * mxGraph.prototype.isCellSelectable = function(cell)\r\n * {\r\n *   var state = this.view.getState(cell);\r\n *   var style = (state != null) ? state.style : this.getCellStyle(cell);\r\n *   \r\n *   return this.isCellsSelectable() && !this.isCellLocked(cell) && style['selectable'] != 0;\r\n * };\r\n * (end)\r\n * \r\n * You can then use the new style as shown in this example.\r\n * \r\n * (code)\r\n * graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30, 'selectable=0');\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose selectable state should be returned.\r\n */\r\nmxGraph.prototype.isCellSelectable = function(cell)\r\n{\r\n\treturn this.isCellsSelectable();\r\n};\r\n\r\n/**\r\n * Function: isCellsSelectable\r\n *\r\n * Returns <cellsSelectable>.\r\n */\r\nmxGraph.prototype.isCellsSelectable = function()\r\n{\r\n\treturn this.cellsSelectable;\r\n};\r\n\r\n/**\r\n * Function: setCellsSelectable\r\n *\r\n * Sets <cellsSelectable>.\r\n */\r\nmxGraph.prototype.setCellsSelectable = function(value)\r\n{\r\n\tthis.cellsSelectable = value;\r\n};\r\n\r\n/**\r\n * Function: getDeletableCells\r\n * \r\n * Returns the cells which may be exported in the given array of cells.\r\n */\r\nmxGraph.prototype.getDeletableCells = function(cells)\r\n{\r\n\treturn this.model.filterCells(cells, mxUtils.bind(this, function(cell)\r\n\t{\r\n\t\treturn this.isCellDeletable(cell);\r\n\t}));\r\n};\r\n\r\n/**\r\n * Function: isCellDeletable\r\n *\r\n * Returns true if the given cell is moveable. This returns\r\n * <cellsDeletable> for all given cells if a cells style does not specify\r\n * <mxConstants.STYLE_DELETABLE> to be 0.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose deletable state should be returned.\r\n */\r\nmxGraph.prototype.isCellDeletable = function(cell)\r\n{\r\n\tvar state = this.view.getState(cell);\r\n\tvar style = (state != null) ? state.style : this.getCellStyle(cell);\r\n\t\r\n\treturn this.isCellsDeletable() && style[mxConstants.STYLE_DELETABLE] != 0;\r\n};\r\n\r\n/**\r\n * Function: isCellsDeletable\r\n *\r\n * Returns <cellsDeletable>.\r\n */\r\nmxGraph.prototype.isCellsDeletable = function()\r\n{\r\n\treturn this.cellsDeletable;\r\n};\r\n\r\n/**\r\n * Function: setCellsDeletable\r\n * \r\n * Sets <cellsDeletable>.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean indicating if the graph should allow deletion of cells.\r\n */\r\nmxGraph.prototype.setCellsDeletable = function(value)\r\n{\r\n\tthis.cellsDeletable = value;\r\n};\r\n\r\n/**\r\n * Function: isLabelMovable\r\n *\r\n * Returns true if the given edges's label is moveable. This returns\r\n * <movable> for all given cells if <isLocked> does not return true\r\n * for the given cell.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose label should be moved.\r\n */\r\nmxGraph.prototype.isLabelMovable = function(cell)\r\n{\r\n\treturn !this.isCellLocked(cell) &&\r\n\t\t((this.model.isEdge(cell) && this.edgeLabelsMovable) ||\r\n\t\t(this.model.isVertex(cell) && this.vertexLabelsMovable));\r\n};\r\n\r\n/**\r\n * Function: isCellRotatable\r\n *\r\n * Returns true if the given cell is rotatable. This returns true for the given\r\n * cell if its style does not specify <mxConstants.STYLE_ROTATABLE> to be 0.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose rotatable state should be returned.\r\n */\r\nmxGraph.prototype.isCellRotatable = function(cell)\r\n{\r\n\tvar state = this.view.getState(cell);\r\n\tvar style = (state != null) ? state.style : this.getCellStyle(cell);\r\n\t\r\n\treturn style[mxConstants.STYLE_ROTATABLE] != 0;\r\n};\r\n\r\n/**\r\n * Function: getMovableCells\r\n * \r\n * Returns the cells which are movable in the given array of cells.\r\n */\r\nmxGraph.prototype.getMovableCells = function(cells)\r\n{\r\n\treturn this.model.filterCells(cells, mxUtils.bind(this, function(cell)\r\n\t{\r\n\t\treturn this.isCellMovable(cell);\r\n\t}));\r\n};\r\n\r\n/**\r\n * Function: isCellMovable\r\n *\r\n * Returns true if the given cell is moveable. This returns <cellsMovable>\r\n * for all given cells if <isCellLocked> does not return true for the given\r\n * cell and its style does not specify <mxConstants.STYLE_MOVABLE> to be 0.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose movable state should be returned.\r\n */\r\nmxGraph.prototype.isCellMovable = function(cell)\r\n{\r\n\tvar state = this.view.getState(cell);\r\n\tvar style = (state != null) ? state.style : this.getCellStyle(cell);\r\n\t\r\n\treturn this.isCellsMovable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_MOVABLE] != 0;\r\n};\r\n\r\n/**\r\n * Function: isCellsMovable\r\n *\r\n * Returns <cellsMovable>.\r\n */\r\nmxGraph.prototype.isCellsMovable = function()\r\n{\r\n\treturn this.cellsMovable;\r\n};\r\n\r\n/**\r\n * Function: setCellsMovable\r\n * \r\n * Specifies if the graph should allow moving of cells. This implementation\r\n * updates <cellsMsovable>.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean indicating if the graph should allow moving of cells.\r\n */\r\nmxGraph.prototype.setCellsMovable = function(value)\r\n{\r\n\tthis.cellsMovable = value;\r\n};\r\n\r\n/**\r\n * Function: isGridEnabled\r\n *\r\n * Returns <gridEnabled> as a boolean.\r\n */\r\nmxGraph.prototype.isGridEnabled = function()\r\n{\r\n\treturn this.gridEnabled;\r\n};\r\n\r\n/**\r\n * Function: setGridEnabled\r\n * \r\n * Specifies if the grid should be enabled.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean indicating if the grid should be enabled.\r\n */\r\nmxGraph.prototype.setGridEnabled = function(value)\r\n{\r\n\tthis.gridEnabled = value;\r\n};\r\n\r\n/**\r\n * Function: isPortsEnabled\r\n *\r\n * Returns <portsEnabled> as a boolean.\r\n */\r\nmxGraph.prototype.isPortsEnabled = function()\r\n{\r\n\treturn this.portsEnabled;\r\n};\r\n\r\n/**\r\n * Function: setPortsEnabled\r\n * \r\n * Specifies if the ports should be enabled.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean indicating if the ports should be enabled.\r\n */\r\nmxGraph.prototype.setPortsEnabled = function(value)\r\n{\r\n\tthis.portsEnabled = value;\r\n};\r\n\r\n/**\r\n * Function: getGridSize\r\n *\r\n * Returns <gridSize>.\r\n */\r\nmxGraph.prototype.getGridSize = function()\r\n{\r\n\treturn this.gridSize;\r\n};\r\n\r\n/**\r\n * Function: setGridSize\r\n * \r\n * Sets <gridSize>.\r\n */\r\nmxGraph.prototype.setGridSize = function(value)\r\n{\r\n\tthis.gridSize = value;\r\n};\r\n\r\n/**\r\n * Function: getTolerance\r\n *\r\n * Returns <tolerance>.\r\n */\r\nmxGraph.prototype.getTolerance = function()\r\n{\r\n\treturn this.tolerance;\r\n};\r\n\r\n/**\r\n * Function: setTolerance\r\n * \r\n * Sets <tolerance>.\r\n */\r\nmxGraph.prototype.setTolerance = function(value)\r\n{\r\n\tthis.tolerance = value;\r\n};\r\n\r\n/**\r\n * Function: isVertexLabelsMovable\r\n *\r\n * Returns <vertexLabelsMovable>.\r\n */\r\nmxGraph.prototype.isVertexLabelsMovable = function()\r\n{\r\n\treturn this.vertexLabelsMovable;\r\n};\r\n\r\n/**\r\n * Function: setVertexLabelsMovable\r\n * \r\n * Sets <vertexLabelsMovable>.\r\n */\r\nmxGraph.prototype.setVertexLabelsMovable = function(value)\r\n{\r\n\tthis.vertexLabelsMovable = value;\r\n};\r\n\r\n/**\r\n * Function: isEdgeLabelsMovable\r\n *\r\n * Returns <edgeLabelsMovable>.\r\n */\r\nmxGraph.prototype.isEdgeLabelsMovable = function()\r\n{\r\n\treturn this.edgeLabelsMovable;\r\n};\r\n\r\n/**\r\n * Function: isEdgeLabelsMovable\r\n * \r\n * Sets <edgeLabelsMovable>.\r\n */\r\nmxGraph.prototype.setEdgeLabelsMovable = function(value)\r\n{\r\n\tthis.edgeLabelsMovable = value;\r\n};\r\n\r\n/**\r\n * Function: isSwimlaneNesting\r\n *\r\n * Returns <swimlaneNesting> as a boolean.\r\n */\r\nmxGraph.prototype.isSwimlaneNesting = function()\r\n{\r\n\treturn this.swimlaneNesting;\r\n};\r\n\r\n/**\r\n * Function: setSwimlaneNesting\r\n * \r\n * Specifies if swimlanes can be nested by drag and drop. This is only\r\n * taken into account if dropEnabled is true.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean indicating if swimlanes can be nested.\r\n */\r\nmxGraph.prototype.setSwimlaneNesting = function(value)\r\n{\r\n\tthis.swimlaneNesting = value;\r\n};\r\n\r\n/**\r\n * Function: isSwimlaneSelectionEnabled\r\n *\r\n * Returns <swimlaneSelectionEnabled> as a boolean.\r\n */\r\nmxGraph.prototype.isSwimlaneSelectionEnabled = function()\r\n{\r\n\treturn this.swimlaneSelectionEnabled;\r\n};\r\n\r\n/**\r\n * Function: setSwimlaneSelectionEnabled\r\n * \r\n * Specifies if swimlanes should be selected if the mouse is released\r\n * over their content area.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean indicating if swimlanes content areas\r\n * should be selected when the mouse is released over them.\r\n */\r\nmxGraph.prototype.setSwimlaneSelectionEnabled = function(value)\r\n{\r\n\tthis.swimlaneSelectionEnabled = value;\r\n};\r\n\r\n/**\r\n * Function: isMultigraph\r\n *\r\n * Returns <multigraph> as a boolean.\r\n */\r\nmxGraph.prototype.isMultigraph = function()\r\n{\r\n\treturn this.multigraph;\r\n};\r\n\r\n/**\r\n * Function: setMultigraph\r\n * \r\n * Specifies if the graph should allow multiple connections between the\r\n * same pair of vertices.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean indicating if the graph allows multiple connections\r\n * between the same pair of vertices.\r\n */\r\nmxGraph.prototype.setMultigraph = function(value)\r\n{\r\n\tthis.multigraph = value;\r\n};\r\n\r\n/**\r\n * Function: isAllowLoops\r\n *\r\n * Returns <allowLoops> as a boolean.\r\n */\r\nmxGraph.prototype.isAllowLoops = function()\r\n{\r\n\treturn this.allowLoops;\r\n};\r\n\r\n/**\r\n * Function: setAllowDanglingEdges\r\n * \r\n * Specifies if dangling edges are allowed, that is, if edges are allowed\r\n * that do not have a source and/or target terminal defined.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean indicating if dangling edges are allowed.\r\n */\r\nmxGraph.prototype.setAllowDanglingEdges = function(value)\r\n{\r\n\tthis.allowDanglingEdges = value;\r\n};\r\n\r\n/**\r\n * Function: isAllowDanglingEdges\r\n *\r\n * Returns <allowDanglingEdges> as a boolean.\r\n */\r\nmxGraph.prototype.isAllowDanglingEdges = function()\r\n{\r\n\treturn this.allowDanglingEdges;\r\n};\r\n\r\n/**\r\n * Function: setConnectableEdges\r\n * \r\n * Specifies if edges should be connectable.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean indicating if edges should be connectable.\r\n */\r\nmxGraph.prototype.setConnectableEdges = function(value)\r\n{\r\n\tthis.connectableEdges = value;\r\n};\r\n\r\n/**\r\n * Function: isConnectableEdges\r\n *\r\n * Returns <connectableEdges> as a boolean.\r\n */\r\nmxGraph.prototype.isConnectableEdges = function()\r\n{\r\n\treturn this.connectableEdges;\r\n};\r\n\r\n/**\r\n * Function: setCloneInvalidEdges\r\n * \r\n * Specifies if edges should be inserted when cloned but not valid wrt.\r\n * <getEdgeValidationError>. If false such edges will be silently ignored.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean indicating if cloned invalid edges should be\r\n * inserted into the graph or ignored.\r\n */\r\nmxGraph.prototype.setCloneInvalidEdges = function(value)\r\n{\r\n\tthis.cloneInvalidEdges = value;\r\n};\r\n\r\n/**\r\n * Function: isCloneInvalidEdges\r\n *\r\n * Returns <cloneInvalidEdges> as a boolean.\r\n */\r\nmxGraph.prototype.isCloneInvalidEdges = function()\r\n{\r\n\treturn this.cloneInvalidEdges;\r\n};\r\n\r\n/**\r\n * Function: setAllowLoops\r\n * \r\n * Specifies if loops are allowed.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean indicating if loops are allowed.\r\n */\r\nmxGraph.prototype.setAllowLoops = function(value)\r\n{\r\n\tthis.allowLoops = value;\r\n};\r\n\r\n/**\r\n * Function: isDisconnectOnMove\r\n *\r\n * Returns <disconnectOnMove> as a boolean.\r\n */\r\nmxGraph.prototype.isDisconnectOnMove = function()\r\n{\r\n\treturn this.disconnectOnMove;\r\n};\r\n\r\n/**\r\n * Function: setDisconnectOnMove\r\n * \r\n * Specifies if edges should be disconnected when moved. (Note: Cloned\r\n * edges are always disconnected.)\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean indicating if edges should be disconnected\r\n * when moved.\r\n */\r\nmxGraph.prototype.setDisconnectOnMove = function(value)\r\n{\r\n\tthis.disconnectOnMove = value;\r\n};\r\n\r\n/**\r\n * Function: isDropEnabled\r\n *\r\n * Returns <dropEnabled> as a boolean.\r\n */\r\nmxGraph.prototype.isDropEnabled = function()\r\n{\r\n\treturn this.dropEnabled;\r\n};\r\n\r\n/**\r\n * Function: setDropEnabled\r\n * \r\n * Specifies if the graph should allow dropping of cells onto or into other\r\n * cells.\r\n * \r\n * Parameters:\r\n * \r\n * dropEnabled - Boolean indicating if the graph should allow dropping\r\n * of cells into other cells.\r\n */\r\nmxGraph.prototype.setDropEnabled = function(value)\r\n{\r\n\tthis.dropEnabled = value;\r\n};\r\n\r\n/**\r\n * Function: isSplitEnabled\r\n *\r\n * Returns <splitEnabled> as a boolean.\r\n */\r\nmxGraph.prototype.isSplitEnabled = function()\r\n{\r\n\treturn this.splitEnabled;\r\n};\r\n\r\n/**\r\n * Function: setSplitEnabled\r\n * \r\n * Specifies if the graph should allow dropping of cells onto or into other\r\n * cells.\r\n * \r\n * Parameters:\r\n * \r\n * dropEnabled - Boolean indicating if the graph should allow dropping\r\n * of cells into other cells.\r\n */\r\nmxGraph.prototype.setSplitEnabled = function(value)\r\n{\r\n\tthis.splitEnabled = value;\r\n};\r\n\r\n/**\r\n * Function: isCellResizable\r\n *\r\n * Returns true if the given cell is resizable. This returns\r\n * <cellsResizable> for all given cells if <isCellLocked> does not return\r\n * true for the given cell and its style does not specify\r\n * <mxConstants.STYLE_RESIZABLE> to be 0.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose resizable state should be returned.\r\n */\r\nmxGraph.prototype.isCellResizable = function(cell)\r\n{\r\n\tvar state = this.view.getState(cell);\r\n\tvar style = (state != null) ? state.style : this.getCellStyle(cell);\r\n\r\n\treturn this.isCellsResizable() && !this.isCellLocked(cell) &&\r\n\t\tmxUtils.getValue(style, mxConstants.STYLE_RESIZABLE, '1') != '0';\r\n};\r\n\r\n/**\r\n * Function: isCellsResizable\r\n *\r\n * Returns <cellsResizable>.\r\n */\r\nmxGraph.prototype.isCellsResizable = function()\r\n{\r\n\treturn this.cellsResizable;\r\n};\r\n\r\n/**\r\n * Function: setCellsResizable\r\n * \r\n * Specifies if the graph should allow resizing of cells. This\r\n * implementation updates <cellsResizable>.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean indicating if the graph should allow resizing of\r\n * cells.\r\n */\r\nmxGraph.prototype.setCellsResizable = function(value)\r\n{\r\n\tthis.cellsResizable = value;\r\n};\r\n\r\n/**\r\n * Function: isTerminalPointMovable\r\n *\r\n * Returns true if the given terminal point is movable. This is independent\r\n * from <isCellConnectable> and <isCellDisconnectable> and controls if terminal\r\n * points can be moved in the graph if the edge is not connected. Note that it\r\n * is required for this to return true to connect unconnected edges. This\r\n * implementation returns true.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose terminal point should be moved.\r\n * source - Boolean indicating if the source or target terminal should be moved.\r\n */\r\nmxGraph.prototype.isTerminalPointMovable = function(cell, source)\r\n{\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: isCellBendable\r\n *\r\n * Returns true if the given cell is bendable. This returns <cellsBendable>\r\n * for all given cells if <isLocked> does not return true for the given\r\n * cell and its style does not specify <mxConstants.STYLE_BENDABLE> to be 0.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose bendable state should be returned.\r\n */\r\nmxGraph.prototype.isCellBendable = function(cell)\r\n{\r\n\tvar state = this.view.getState(cell);\r\n\tvar style = (state != null) ? state.style : this.getCellStyle(cell);\r\n\t\r\n\treturn this.isCellsBendable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_BENDABLE] != 0;\r\n};\r\n\r\n/**\r\n * Function: isCellsBendable\r\n *\r\n * Returns <cellsBenadable>.\r\n */\r\nmxGraph.prototype.isCellsBendable = function()\r\n{\r\n\treturn this.cellsBendable;\r\n};\r\n\r\n/**\r\n * Function: setCellsBendable\r\n * \r\n * Specifies if the graph should allow bending of edges. This\r\n * implementation updates <bendable>.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean indicating if the graph should allow bending of\r\n * edges.\r\n */\r\nmxGraph.prototype.setCellsBendable = function(value)\r\n{\r\n\tthis.cellsBendable = value;\r\n};\r\n\r\n/**\r\n * Function: isCellEditable\r\n *\r\n * Returns true if the given cell is editable. This returns <cellsEditable> for\r\n * all given cells if <isCellLocked> does not return true for the given cell\r\n * and its style does not specify <mxConstants.STYLE_EDITABLE> to be 0.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose editable state should be returned.\r\n */\r\nmxGraph.prototype.isCellEditable = function(cell)\r\n{\r\n\tvar state = this.view.getState(cell);\r\n\tvar style = (state != null) ? state.style : this.getCellStyle(cell);\r\n\t\r\n\treturn this.isCellsEditable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_EDITABLE] != 0;\r\n};\r\n\r\n/**\r\n * Function: isCellsEditable\r\n *\r\n * Returns <cellsEditable>.\r\n */\r\nmxGraph.prototype.isCellsEditable = function()\r\n{\r\n\treturn this.cellsEditable;\r\n};\r\n\r\n/**\r\n * Function: setCellsEditable\r\n * \r\n * Specifies if the graph should allow in-place editing for cell labels.\r\n * This implementation updates <cellsEditable>.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean indicating if the graph should allow in-place\r\n * editing.\r\n */\r\nmxGraph.prototype.setCellsEditable = function(value)\r\n{\r\n\tthis.cellsEditable = value;\r\n};\r\n\r\n/**\r\n * Function: isCellDisconnectable\r\n *\r\n * Returns true if the given cell is disconnectable from the source or\r\n * target terminal. This returns <isCellsDisconnectable> for all given\r\n * cells if <isCellLocked> does not return true for the given cell.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose disconnectable state should be returned.\r\n * terminal - <mxCell> that represents the source or target terminal.\r\n * source - Boolean indicating if the source or target terminal is to be\r\n * disconnected.\r\n */\r\nmxGraph.prototype.isCellDisconnectable = function(cell, terminal, source)\r\n{\r\n\treturn this.isCellsDisconnectable() && !this.isCellLocked(cell);\r\n};\r\n\r\n/**\r\n * Function: isCellsDisconnectable\r\n *\r\n * Returns <cellsDisconnectable>.\r\n */\r\nmxGraph.prototype.isCellsDisconnectable = function()\r\n{\r\n\treturn this.cellsDisconnectable;\r\n};\r\n\r\n/**\r\n * Function: setCellsDisconnectable\r\n *\r\n * Sets <cellsDisconnectable>.\r\n */\r\nmxGraph.prototype.setCellsDisconnectable = function(value)\r\n{\r\n\tthis.cellsDisconnectable = value;\r\n};\r\n\r\n/**\r\n * Function: isValidSource\r\n * \r\n * Returns true if the given cell is a valid source for new connections.\r\n * This implementation returns true for all non-null values and is\r\n * called by is called by <isValidConnection>.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> that represents a possible source or null.\r\n */\r\nmxGraph.prototype.isValidSource = function(cell)\r\n{\r\n\treturn (cell == null && this.allowDanglingEdges) ||\r\n\t\t(cell != null && (!this.model.isEdge(cell) ||\r\n\t\tthis.connectableEdges) && this.isCellConnectable(cell));\r\n};\r\n\t\r\n/**\r\n * Function: isValidTarget\r\n * \r\n * Returns <isValidSource> for the given cell. This is called by\r\n * <isValidConnection>.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> that represents a possible target or null.\r\n */\r\nmxGraph.prototype.isValidTarget = function(cell)\r\n{\r\n\treturn this.isValidSource(cell);\r\n};\r\n\r\n/**\r\n * Function: isValidConnection\r\n * \r\n * Returns true if the given target cell is a valid target for source.\r\n * This is a boolean implementation for not allowing connections between\r\n * certain pairs of vertices and is called by <getEdgeValidationError>.\r\n * This implementation returns true if <isValidSource> returns true for\r\n * the source and <isValidTarget> returns true for the target.\r\n * \r\n * Parameters:\r\n * \r\n * source - <mxCell> that represents the source cell.\r\n * target - <mxCell> that represents the target cell.\r\n */\r\nmxGraph.prototype.isValidConnection = function(source, target)\r\n{\r\n\treturn this.isValidSource(source) && this.isValidTarget(target);\r\n};\r\n\r\n/**\r\n * Function: setConnectable\r\n * \r\n * Specifies if the graph should allow new connections. This implementation\r\n * updates <mxConnectionHandler.enabled> in <connectionHandler>.\r\n * \r\n * Parameters:\r\n * \r\n * connectable - Boolean indicating if new connections should be allowed.\r\n */\r\nmxGraph.prototype.setConnectable = function(connectable)\r\n{\r\n\tthis.connectionHandler.setEnabled(connectable);\r\n};\r\n\t\r\n/**\r\n * Function: isConnectable\r\n * \r\n * Returns true if the <connectionHandler> is enabled.\r\n */\r\nmxGraph.prototype.isConnectable = function()\r\n{\r\n\treturn this.connectionHandler.isEnabled();\r\n};\r\n\r\n/**\r\n * Function: setTooltips\r\n * \r\n * Specifies if tooltips should be enabled. This implementation updates\r\n * <mxTooltipHandler.enabled> in <tooltipHandler>.\r\n * \r\n * Parameters:\r\n * \r\n * enabled - Boolean indicating if tooltips should be enabled.\r\n */\r\nmxGraph.prototype.setTooltips = function (enabled)\r\n{\r\n\tthis.tooltipHandler.setEnabled(enabled);\r\n};\r\n\r\n/**\r\n * Function: setPanning\r\n * \r\n * Specifies if panning should be enabled. This implementation updates\r\n * <mxPanningHandler.panningEnabled> in <panningHandler>.\r\n * \r\n * Parameters:\r\n * \r\n * enabled - Boolean indicating if panning should be enabled.\r\n */\r\nmxGraph.prototype.setPanning = function(enabled)\r\n{\r\n\tthis.panningHandler.panningEnabled = enabled;\r\n};\r\n\r\n/**\r\n * Function: isEditing\r\n * \r\n * Returns true if the given cell is currently being edited.\r\n * If no cell is specified then this returns true if any\r\n * cell is currently being edited.\r\n *\r\n * Parameters:\r\n * \r\n * cell - <mxCell> that should be checked.\r\n */\r\nmxGraph.prototype.isEditing = function(cell)\r\n{\r\n\tif (this.cellEditor != null)\r\n\t{\r\n\t\tvar editingCell = this.cellEditor.getEditingCell();\r\n\t\t\r\n\t\treturn (cell == null) ? editingCell != null : cell == editingCell;\r\n\t}\r\n\t\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: isAutoSizeCell\r\n * \r\n * Returns true if the size of the given cell should automatically be\r\n * updated after a change of the label. This implementation returns\r\n * <autoSizeCells> or checks if the cell style does specify\r\n * <mxConstants.STYLE_AUTOSIZE> to be 1.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> that should be resized.\r\n */\r\nmxGraph.prototype.isAutoSizeCell = function(cell)\r\n{\r\n\tvar state = this.view.getState(cell);\r\n\tvar style = (state != null) ? state.style : this.getCellStyle(cell);\r\n\t\r\n\treturn this.isAutoSizeCells() || style[mxConstants.STYLE_AUTOSIZE] == 1;\r\n};\r\n\r\n/**\r\n * Function: isAutoSizeCells\r\n * \r\n * Returns <autoSizeCells>.\r\n */\r\nmxGraph.prototype.isAutoSizeCells = function()\r\n{\r\n\treturn this.autoSizeCells;\r\n};\r\n\r\n/**\r\n * Function: setAutoSizeCells\r\n * \r\n * Specifies if cell sizes should be automatically updated after a label\r\n * change. This implementation sets <autoSizeCells> to the given parameter.\r\n * To update the size of cells when the cells are added, set\r\n * <autoSizeCellsOnAdd> to true.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean indicating if cells should be resized\r\n * automatically.\r\n */\r\nmxGraph.prototype.setAutoSizeCells = function(value)\r\n{\r\n\tthis.autoSizeCells = value;\r\n};\r\n\r\n/**\r\n * Function: isExtendParent\r\n * \r\n * Returns true if the parent of the given cell should be extended if the\r\n * child has been resized so that it overlaps the parent. This\r\n * implementation returns <isExtendParents> if the cell is not an edge.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> that has been resized.\r\n */\r\nmxGraph.prototype.isExtendParent = function(cell)\r\n{\r\n\treturn !this.getModel().isEdge(cell) && this.isExtendParents();\r\n};\r\n\r\n/**\r\n * Function: isExtendParents\r\n * \r\n * Returns <extendParents>.\r\n */\r\nmxGraph.prototype.isExtendParents = function()\r\n{\r\n\treturn this.extendParents;\r\n};\r\n\r\n/**\r\n * Function: setExtendParents\r\n * \r\n * Sets <extendParents>.\r\n * \r\n * Parameters:\r\n * \r\n * value - New boolean value for <extendParents>.\r\n */\r\nmxGraph.prototype.setExtendParents = function(value)\r\n{\r\n\tthis.extendParents = value;\r\n};\r\n\r\n/**\r\n * Function: isExtendParentsOnAdd\r\n * \r\n * Returns <extendParentsOnAdd>.\r\n */\r\nmxGraph.prototype.isExtendParentsOnAdd = function(cell)\r\n{\r\n\treturn this.extendParentsOnAdd;\r\n};\r\n\r\n/**\r\n * Function: setExtendParentsOnAdd\r\n * \r\n * Sets <extendParentsOnAdd>.\r\n * \r\n * Parameters:\r\n * \r\n * value - New boolean value for <extendParentsOnAdd>.\r\n */\r\nmxGraph.prototype.setExtendParentsOnAdd = function(value)\r\n{\r\n\tthis.extendParentsOnAdd = value;\r\n};\r\n\r\n/**\r\n * Function: isExtendParentsOnMove\r\n * \r\n * Returns <extendParentsOnMove>.\r\n */\r\nmxGraph.prototype.isExtendParentsOnMove = function()\r\n{\r\n\treturn this.extendParentsOnMove;\r\n};\r\n\r\n/**\r\n * Function: setExtendParentsOnMove\r\n * \r\n * Sets <extendParentsOnMove>.\r\n * \r\n * Parameters:\r\n * \r\n * value - New boolean value for <extendParentsOnAdd>.\r\n */\r\nmxGraph.prototype.setExtendParentsOnMove = function(value)\r\n{\r\n\tthis.extendParentsOnMove = value;\r\n};\r\n\r\n/**\r\n * Function: isRecursiveResize\r\n * \r\n * Returns <recursiveResize>.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> that is being resized.\r\n */\r\nmxGraph.prototype.isRecursiveResize = function(state)\r\n{\r\n\treturn this.recursiveResize;\r\n};\r\n\r\n/**\r\n * Function: setRecursiveResize\r\n * \r\n * Sets <recursiveResize>.\r\n * \r\n * Parameters:\r\n * \r\n * value - New boolean value for <recursiveResize>.\r\n */\r\nmxGraph.prototype.setRecursiveResize = function(value)\r\n{\r\n\tthis.recursiveResize = value;\r\n};\r\n\r\n/**\r\n * Function: isConstrainChild\r\n * \r\n * Returns true if the given cell should be kept inside the bounds of its\r\n * parent according to the rules defined by <getOverlap> and\r\n * <isAllowOverlapParent>. This implementation returns false for all children\r\n * of edges and <isConstrainChildren> otherwise.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> that should be constrained.\r\n */\r\nmxGraph.prototype.isConstrainChild = function(cell)\r\n{\r\n\treturn this.isConstrainChildren() && !this.getModel().isEdge(this.getModel().getParent(cell));\r\n};\r\n\r\n/**\r\n * Function: isConstrainChildren\r\n * \r\n * Returns <constrainChildren>.\r\n */\r\nmxGraph.prototype.isConstrainChildren = function()\r\n{\r\n\treturn this.constrainChildren;\r\n};\r\n\r\n/**\r\n * Function: setConstrainChildren\r\n * \r\n * Sets <constrainChildren>.\r\n */\r\nmxGraph.prototype.setConstrainChildren = function(value)\r\n{\r\n\tthis.constrainChildren = value;\r\n};\r\n\r\n/**\r\n * Function: isConstrainRelativeChildren\r\n * \r\n * Returns <constrainRelativeChildren>.\r\n */\r\nmxGraph.prototype.isConstrainRelativeChildren = function()\r\n{\r\n\treturn this.constrainRelativeChildren;\r\n};\r\n\r\n/**\r\n * Function: setConstrainRelativeChildren\r\n * \r\n * Sets <constrainRelativeChildren>.\r\n */\r\nmxGraph.prototype.setConstrainRelativeChildren = function(value)\r\n{\r\n\tthis.constrainRelativeChildren = value;\r\n};\r\n\r\n/**\r\n * Function: isConstrainChildren\r\n * \r\n * Returns <allowNegativeCoordinates>.\r\n */\r\nmxGraph.prototype.isAllowNegativeCoordinates = function()\r\n{\r\n\treturn this.allowNegativeCoordinates;\r\n};\r\n\r\n/**\r\n * Function: setConstrainChildren\r\n * \r\n * Sets <allowNegativeCoordinates>.\r\n */\r\nmxGraph.prototype.setAllowNegativeCoordinates = function(value)\r\n{\r\n\tthis.allowNegativeCoordinates = value;\r\n};\r\n\r\n/**\r\n * Function: getOverlap\r\n * \r\n * Returns a decimal number representing the amount of the width and height\r\n * of the given cell that is allowed to overlap its parent. A value of 0\r\n * means all children must stay inside the parent, 1 means the child is\r\n * allowed to be placed outside of the parent such that it touches one of\r\n * the parents sides. If <isAllowOverlapParent> returns false for the given\r\n * cell, then this method returns 0.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> for which the overlap ratio should be returned.\r\n */\r\nmxGraph.prototype.getOverlap = function(cell)\r\n{\r\n\treturn (this.isAllowOverlapParent(cell)) ? this.defaultOverlap : 0;\r\n};\r\n\t\r\n/**\r\n * Function: isAllowOverlapParent\r\n * \r\n * Returns true if the given cell is allowed to be placed outside of the\r\n * parents area.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> that represents the child to be checked.\r\n */\r\nmxGraph.prototype.isAllowOverlapParent = function(cell)\r\n{\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: getFoldableCells\r\n * \r\n * Returns the cells which are movable in the given array of cells.\r\n */\r\nmxGraph.prototype.getFoldableCells = function(cells, collapse)\r\n{\r\n\treturn this.model.filterCells(cells, mxUtils.bind(this, function(cell)\r\n\t{\r\n\t\treturn this.isCellFoldable(cell, collapse);\r\n\t}));\r\n};\r\n\r\n/**\r\n * Function: isCellFoldable\r\n * \r\n * Returns true if the given cell is foldable. This implementation\r\n * returns true if the cell has at least one child and its style\r\n * does not specify <mxConstants.STYLE_FOLDABLE> to be 0.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose foldable state should be returned.\r\n */\r\nmxGraph.prototype.isCellFoldable = function(cell, collapse)\r\n{\r\n\tvar state = this.view.getState(cell);\r\n\tvar style = (state != null) ? state.style : this.getCellStyle(cell);\r\n\t\r\n\treturn this.model.getChildCount(cell) > 0 && style[mxConstants.STYLE_FOLDABLE] != 0;\r\n};\r\n\r\n/**\r\n * Function: isValidDropTarget\r\n *\r\n * Returns true if the given cell is a valid drop target for the specified\r\n * cells. If <splitEnabled> is true then this returns <isSplitTarget> for\r\n * the given arguments else it returns true if the cell is not collapsed\r\n * and its child count is greater than 0.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> that represents the possible drop target.\r\n * cells - <mxCells> that should be dropped into the target.\r\n * evt - Mouseevent that triggered the invocation.\r\n */\r\nmxGraph.prototype.isValidDropTarget = function(cell, cells, evt)\r\n{\r\n\treturn cell != null && ((this.isSplitEnabled() &&\r\n\t\tthis.isSplitTarget(cell, cells, evt)) || (!this.model.isEdge(cell) &&\r\n\t\t(this.isSwimlane(cell) || (this.model.getChildCount(cell) > 0 &&\r\n\t\t!this.isCellCollapsed(cell)))));\r\n};\r\n\r\n/**\r\n * Function: isSplitTarget\r\n *\r\n * Returns true if the given edge may be splitted into two edges with the\r\n * given cell as a new terminal between the two.\r\n * \r\n * Parameters:\r\n * \r\n * target - <mxCell> that represents the edge to be splitted.\r\n * cells - <mxCells> that should split the edge.\r\n * evt - Mouseevent that triggered the invocation.\r\n */\r\nmxGraph.prototype.isSplitTarget = function(target, cells, evt)\r\n{\r\n\tif (this.model.isEdge(target) && cells != null && cells.length == 1 &&\r\n\t\tthis.isCellConnectable(cells[0]) && this.getEdgeValidationError(target,\r\n\t\t\tthis.model.getTerminal(target, true), cells[0]) == null)\r\n\t{\r\n\t\tvar src = this.model.getTerminal(target, true);\r\n\t\tvar trg = this.model.getTerminal(target, false);\r\n\r\n\t\treturn (!this.model.isAncestor(cells[0], src) &&\r\n\t\t\t\t!this.model.isAncestor(cells[0], trg));\r\n\t}\r\n\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: getDropTarget\r\n * \r\n * Returns the given cell if it is a drop target for the given cells or the\r\n * nearest ancestor that may be used as a drop target for the given cells.\r\n * If the given array contains a swimlane and <swimlaneNesting> is false\r\n * then this always returns null. If no cell is given, then the bottommost\r\n * swimlane at the location of the given event is returned.\r\n * \r\n * This function should only be used if <isDropEnabled> returns true.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> which are to be dropped onto the target.\r\n * evt - Mouseevent for the drag and drop.\r\n * cell - <mxCell> that is under the mousepointer.\r\n * clone - Optional boolean to indicate of cells will be cloned.\r\n */\r\nmxGraph.prototype.getDropTarget = function(cells, evt, cell, clone)\r\n{\r\n\tif (!this.isSwimlaneNesting())\r\n\t{\r\n\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t{\r\n\t\t\tif (this.isSwimlane(cells[i]))\r\n\t\t\t{\r\n\t\t\t\treturn null;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tvar pt = mxUtils.convertPoint(this.container,\r\n\t\tmxEvent.getClientX(evt), mxEvent.getClientY(evt));\r\n\tpt.x -= this.panDx;\r\n\tpt.y -= this.panDy;\r\n\tvar swimlane = this.getSwimlaneAt(pt.x, pt.y);\r\n\t\r\n\tif (cell == null)\r\n\t{\r\n\t\tcell = swimlane;\r\n\t}\r\n\telse if (swimlane != null)\r\n\t{\r\n\t\t// Checks if the cell is an ancestor of the swimlane\r\n\t\t// under the mouse and uses the swimlane in that case\r\n\t\tvar tmp = this.model.getParent(swimlane);\r\n\t\t\r\n\t\twhile (tmp != null && this.isSwimlane(tmp) && tmp != cell)\r\n\t\t{\r\n\t\t\ttmp = this.model.getParent(tmp);\r\n\t\t}\r\n\t\t\r\n\t\tif (tmp == cell)\r\n\t\t{\r\n\t\t\tcell = swimlane;\r\n\t\t}\r\n\t}\r\n\t\r\n\twhile (cell != null && !this.isValidDropTarget(cell, cells, evt) &&\r\n\t\t!this.model.isLayer(cell))\r\n\t{\r\n\t\tcell = this.model.getParent(cell);\r\n\t}\r\n\t\r\n\t// Checks if parent is dropped into child if not cloning\r\n\tif (clone == null || !clone)\r\n\t{\r\n\t\tvar parent = cell;\r\n\t\t\r\n\t\twhile (parent != null && mxUtils.indexOf(cells, parent) < 0)\r\n\t\t{\r\n\t\t\tparent = this.model.getParent(parent);\r\n\t\t}\r\n\t}\r\n\r\n\treturn (!this.model.isLayer(cell) && parent == null) ? cell : null;\r\n};\r\n\r\n/**\r\n * Group: Cell retrieval\r\n */\r\n\r\n/**\r\n * Function: getDefaultParent\r\n * \r\n * Returns <defaultParent> or <mxGraphView.currentRoot> or the first child\r\n * child of <mxGraphModel.root> if both are null. The value returned by\r\n * this function should be used as the parent for new cells (aka default\r\n * layer).\r\n */\r\nmxGraph.prototype.getDefaultParent = function()\r\n{\r\n\tvar parent = this.getCurrentRoot();\r\n\t\r\n\tif (parent == null)\r\n\t{\r\n\t\tparent = this.defaultParent;\r\n\t\t\r\n\t\tif (parent == null)\r\n\t\t{\r\n\t\t\tvar root = this.model.getRoot();\r\n\t\t\tparent = this.model.getChildAt(root, 0);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn parent;\r\n};\r\n\r\n/**\r\n * Function: setDefaultParent\r\n * \r\n * Sets the <defaultParent> to the given cell. Set this to null to return\r\n * the first child of the root in getDefaultParent.\r\n */\r\nmxGraph.prototype.setDefaultParent = function(cell)\r\n{\r\n\tthis.defaultParent = cell;\r\n};\r\n\r\n/**\r\n * Function: getSwimlane\r\n * \r\n * Returns the nearest ancestor of the given cell which is a swimlane, or\r\n * the given cell, if it is itself a swimlane.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> for which the ancestor swimlane should be returned.\r\n */\r\nmxGraph.prototype.getSwimlane = function(cell)\r\n{\r\n\twhile (cell != null && !this.isSwimlane(cell))\r\n\t{\r\n\t\tcell = this.model.getParent(cell);\r\n\t}\r\n\t\r\n\treturn cell;\r\n};\r\n\r\n/**\r\n * Function: getSwimlaneAt\r\n * \r\n * Returns the bottom-most swimlane that intersects the given point (x, y)\r\n * in the cell hierarchy that starts at the given parent.\r\n * \r\n * Parameters:\r\n * \r\n * x - X-coordinate of the location to be checked.\r\n * y - Y-coordinate of the location to be checked.\r\n * parent - <mxCell> that should be used as the root of the recursion.\r\n * Default is <defaultParent>.\r\n */\r\nmxGraph.prototype.getSwimlaneAt = function (x, y, parent)\r\n{\r\n\tparent = parent || this.getDefaultParent();\r\n\t\r\n\tif (parent != null)\r\n\t{\r\n\t\tvar childCount = this.model.getChildCount(parent);\r\n\t\t\r\n\t\tfor (var i = 0; i < childCount; i++)\r\n\t\t{\r\n\t\t\tvar child = this.model.getChildAt(parent, i);\r\n\t\t\tvar result = this.getSwimlaneAt(x, y, child);\r\n\t\t\t\r\n\t\t\tif (result != null)\r\n\t\t\t{\r\n\t\t\t\treturn result;\r\n\t\t\t}\r\n\t\t\telse if (this.isSwimlane(child))\r\n\t\t\t{\r\n\t\t\t\tvar state = this.view.getState(child);\r\n\t\t\t\t\r\n\t\t\t\tif (this.intersects(state, x, y))\r\n\t\t\t\t{\r\n\t\t\t\t\treturn child;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: getCellAt\r\n * \r\n * Returns the bottom-most cell that intersects the given point (x, y) in\r\n * the cell hierarchy starting at the given parent. This will also return\r\n * swimlanes if the given location intersects the content area of the\r\n * swimlane. If this is not desired, then the <hitsSwimlaneContent> may be\r\n * used if the returned cell is a swimlane to determine if the location\r\n * is inside the content area or on the actual title of the swimlane.\r\n * \r\n * Parameters:\r\n * \r\n * x - X-coordinate of the location to be checked.\r\n * y - Y-coordinate of the location to be checked.\r\n * parent - <mxCell> that should be used as the root of the recursion.\r\n * Default is current root of the view or the root of the model.\r\n * vertices - Optional boolean indicating if vertices should be returned.\r\n * Default is true.\r\n * edges - Optional boolean indicating if edges should be returned. Default\r\n * is true.\r\n * ignoreFn - Optional function that returns true if cell should be ignored.\r\n * The function is passed the cell state and the x and y parameter.\r\n */\r\nmxGraph.prototype.getCellAt = function(x, y, parent, vertices, edges, ignoreFn)\r\n{\r\n\tvertices = (vertices != null) ? vertices : true;\r\n\tedges = (edges != null) ? edges : true;\r\n\r\n\tif (parent == null)\r\n\t{\r\n\t\tparent = this.getCurrentRoot();\r\n\t\t\r\n\t\tif (parent == null)\r\n\t\t{\r\n\t\t\tparent = this.getModel().getRoot();\r\n\t\t}\r\n\t}\r\n\r\n\tif (parent != null)\r\n\t{\r\n\t\tvar childCount = this.model.getChildCount(parent);\r\n\t\t\r\n\t\tfor (var i = childCount - 1; i >= 0; i--)\r\n\t\t{\r\n\t\t\tvar cell = this.model.getChildAt(parent, i);\r\n\t\t\tvar result = this.getCellAt(x, y, cell, vertices, edges, ignoreFn);\r\n\t\t\t\r\n\t\t\tif (result != null)\r\n\t\t\t{\r\n\t\t\t\treturn result;\r\n\t\t\t}\r\n\t\t\telse if (this.isCellVisible(cell) && (edges && this.model.isEdge(cell) ||\r\n\t\t\t\tvertices && this.model.isVertex(cell)))\r\n\t\t\t{\r\n\t\t\t\tvar state = this.view.getState(cell);\r\n\r\n\t\t\t\tif (state != null && (ignoreFn == null || !ignoreFn(state, x, y)) &&\r\n\t\t\t\t\tthis.intersects(state, x, y))\r\n\t\t\t\t{\r\n\t\t\t\t\treturn cell;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: intersects\r\n * \r\n * Returns the bottom-most cell that intersects the given point (x, y) in\r\n * the cell hierarchy that starts at the given parent.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> that represents the cell state.\r\n * x - X-coordinate of the location to be checked.\r\n * y - Y-coordinate of the location to be checked.\r\n */\r\nmxGraph.prototype.intersects = function(state, x, y)\r\n{\r\n\tif (state != null)\r\n\t{\r\n\t\tvar pts = state.absolutePoints;\r\n\r\n\t\tif (pts != null)\r\n\t\t{\r\n\t\t\tvar t2 = this.tolerance * this.tolerance;\r\n\t\t\tvar pt = pts[0];\r\n\t\t\t\r\n\t\t\tfor (var i = 1; i < pts.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar next = pts[i];\r\n\t\t\t\tvar dist = mxUtils.ptSegDistSq(pt.x, pt.y, next.x, next.y, x, y);\r\n\t\t\t\t\r\n\t\t\t\tif (dist <= t2)\r\n\t\t\t\t{\r\n\t\t\t\t\treturn true;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tpt = next;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar alpha = mxUtils.toRadians(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0);\r\n\t\t\t\r\n\t\t\tif (alpha != 0)\r\n\t\t\t{\r\n\t\t\t\tvar cos = Math.cos(-alpha);\r\n\t\t\t\tvar sin = Math.sin(-alpha);\r\n\t\t\t\tvar cx = new mxPoint(state.getCenterX(), state.getCenterY());\r\n\t\t\t\tvar pt = mxUtils.getRotatedPoint(new mxPoint(x, y), cos, sin, cx);\r\n\t\t\t\tx = pt.x;\r\n\t\t\t\ty = pt.y;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (mxUtils.contains(state, x, y))\r\n\t\t\t{\r\n\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: hitsSwimlaneContent\r\n * \r\n * Returns true if the given coordinate pair is inside the content\r\n * are of the given swimlane.\r\n * \r\n * Parameters:\r\n * \r\n * swimlane - <mxCell> that specifies the swimlane.\r\n * x - X-coordinate of the mouse event.\r\n * y - Y-coordinate of the mouse event.\r\n */\r\nmxGraph.prototype.hitsSwimlaneContent = function(swimlane, x, y)\r\n{\r\n\tvar state = this.getView().getState(swimlane);\r\n\tvar size = this.getStartSize(swimlane);\r\n\t\r\n\tif (state != null)\r\n\t{\r\n\t\tvar scale = this.getView().getScale();\r\n\t\tx -= state.x;\r\n\t\ty -= state.y;\r\n\t\t\r\n\t\tif (size.width > 0 && x > 0 && x > size.width * scale)\r\n\t\t{\r\n\t\t\treturn true;\r\n\t\t}\r\n\t\telse if (size.height > 0 && y > 0 && y > size.height * scale)\r\n\t\t{\r\n\t\t\treturn true;\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: getChildVertices\r\n * \r\n * Returns the visible child vertices of the given parent.\r\n * \r\n * Parameters:\r\n * \r\n * parent - <mxCell> whose children should be returned.\r\n */\r\nmxGraph.prototype.getChildVertices = function(parent)\r\n{\r\n\treturn this.getChildCells(parent, true, false);\r\n};\r\n\t\r\n/**\r\n * Function: getChildEdges\r\n * \r\n * Returns the visible child edges of the given parent.\r\n * \r\n * Parameters:\r\n * \r\n * parent - <mxCell> whose child vertices should be returned.\r\n */\r\nmxGraph.prototype.getChildEdges = function(parent)\r\n{\r\n\treturn this.getChildCells(parent, false, true);\r\n};\r\n\r\n/**\r\n * Function: getChildCells\r\n * \r\n * Returns the visible child vertices or edges in the given parent. If\r\n * vertices and edges is false, then all children are returned.\r\n * \r\n * Parameters:\r\n * \r\n * parent - <mxCell> whose children should be returned.\r\n * vertices - Optional boolean that specifies if child vertices should\r\n * be returned. Default is false.\r\n * edges - Optional boolean that specifies if child edges should\r\n * be returned. Default is false.\r\n */\r\nmxGraph.prototype.getChildCells = function(parent, vertices, edges)\r\n{\r\n\tparent = (parent != null) ? parent : this.getDefaultParent();\r\n\tvertices = (vertices != null) ? vertices : false;\r\n\tedges = (edges != null) ? edges : false;\r\n\r\n\tvar cells = this.model.getChildCells(parent, vertices, edges);\r\n\tvar result = [];\r\n\r\n\t// Filters out the non-visible child cells\r\n\tfor (var i = 0; i < cells.length; i++)\r\n\t{\r\n\t\tif (this.isCellVisible(cells[i]))\r\n\t\t{\r\n\t\t\tresult.push(cells[i]);\r\n\t\t}\r\n\t}\r\n\r\n\treturn result;\r\n};\r\n\t\r\n/**\r\n * Function: getConnections\r\n * \r\n * Returns all visible edges connected to the given cell without loops.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose connections should be returned.\r\n * parent - Optional parent of the opposite end for a connection to be\r\n * returned.\r\n */\r\nmxGraph.prototype.getConnections = function(cell, parent)\r\n{\r\n\treturn this.getEdges(cell, parent, true, true, false);\r\n};\r\n\t\r\n/**\r\n * Function: getIncomingEdges\r\n * \r\n * Returns the visible incoming edges for the given cell. If the optional\r\n * parent argument is specified, then only child edges of the given parent\r\n * are returned.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose incoming edges should be returned.\r\n * parent - Optional parent of the opposite end for an edge to be\r\n * returned.\r\n */\r\nmxGraph.prototype.getIncomingEdges = function(cell, parent)\r\n{\r\n\treturn this.getEdges(cell, parent, true, false, false);\r\n};\r\n\t\r\n/**\r\n * Function: getOutgoingEdges\r\n * \r\n * Returns the visible outgoing edges for the given cell. If the optional\r\n * parent argument is specified, then only child edges of the given parent\r\n * are returned.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose outgoing edges should be returned.\r\n * parent - Optional parent of the opposite end for an edge to be\r\n * returned.\r\n */\r\nmxGraph.prototype.getOutgoingEdges = function(cell, parent)\r\n{\r\n\treturn this.getEdges(cell, parent, false, true, false);\r\n};\r\n\t\r\n/**\r\n * Function: getEdges\r\n * \r\n * Returns the incoming and/or outgoing edges for the given cell.\r\n * If the optional parent argument is specified, then only edges are returned\r\n * where the opposite is in the given parent cell. If at least one of incoming\r\n * or outgoing is true, then loops are ignored, if both are false, then all\r\n * edges connected to the given cell are returned including loops.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> whose edges should be returned.\r\n * parent - Optional parent of the opposite end for an edge to be\r\n * returned.\r\n * incoming - Optional boolean that specifies if incoming edges should\r\n * be included in the result. Default is true.\r\n * outgoing - Optional boolean that specifies if outgoing edges should\r\n * be included in the result. Default is true.\r\n * includeLoops - Optional boolean that specifies if loops should be\r\n * included in the result. Default is true.\r\n * recurse - Optional boolean the specifies if the parent specified only \r\n * need be an ancestral parent, true, or the direct parent, false.\r\n * Default is false\r\n */\r\nmxGraph.prototype.getEdges = function(cell, parent, incoming, outgoing, includeLoops, recurse)\r\n{\r\n\tincoming = (incoming != null) ? incoming : true;\r\n\toutgoing = (outgoing != null) ? outgoing : true;\r\n\tincludeLoops = (includeLoops != null) ? includeLoops : true;\r\n\trecurse = (recurse != null) ? recurse : false;\r\n\t\r\n\tvar edges = [];\r\n\tvar isCollapsed = this.isCellCollapsed(cell);\r\n\tvar childCount = this.model.getChildCount(cell);\r\n\r\n\tfor (var i = 0; i < childCount; i++)\r\n\t{\r\n\t\tvar child = this.model.getChildAt(cell, i);\r\n\r\n\t\tif (isCollapsed || !this.isCellVisible(child))\r\n\t\t{\r\n\t\t\tedges = edges.concat(this.model.getEdges(child, incoming, outgoing));\r\n\t\t}\r\n\t}\r\n\r\n\tedges = edges.concat(this.model.getEdges(cell, incoming, outgoing));\r\n\tvar result = [];\r\n\t\r\n\tfor (var i = 0; i < edges.length; i++)\r\n\t{\r\n\t\tvar state = this.view.getState(edges[i]);\r\n\t\t\r\n\t\tvar source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);\r\n\t\tvar target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);\r\n\r\n\t\tif ((includeLoops && source == target) || ((source != target) && ((incoming &&\r\n\t\t\ttarget == cell && (parent == null || this.isValidAncestor(source, parent, recurse))) ||\r\n\t\t\t(outgoing && source == cell && (parent == null ||\r\n\t\t\t\t\tthis.isValidAncestor(target, parent, recurse))))))\r\n\t\t{\r\n\t\t\tresult.push(edges[i]);\r\n\t\t}\r\n\t}\r\n\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: isValidAncestor\r\n * \r\n * Returns whether or not the specified parent is a valid\r\n * ancestor of the specified cell, either direct or indirectly\r\n * based on whether ancestor recursion is enabled.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> the possible child cell\r\n * parent - <mxCell> the possible parent cell\r\n * recurse - boolean whether or not to recurse the child ancestors\r\n */\r\nmxGraph.prototype.isValidAncestor = function(cell, parent, recurse)\r\n{\r\n\treturn (recurse ? this.model.isAncestor(parent, cell) : this.model\r\n\t\t\t.getParent(cell) == parent);\r\n};\r\n\r\n/**\r\n * Function: getOpposites\r\n * \r\n * Returns all distinct visible opposite cells for the specified terminal\r\n * on the given edges.\r\n * \r\n * Parameters:\r\n * \r\n * edges - Array of <mxCells> that contains the edges whose opposite\r\n * terminals should be returned.\r\n * terminal - Terminal that specifies the end whose opposite should be\r\n * returned.\r\n * source - Optional boolean that specifies if source terminals should be\r\n * included in the result. Default is true.\r\n * targets - Optional boolean that specifies if targer terminals should be\r\n * included in the result. Default is true.\r\n */\r\nmxGraph.prototype.getOpposites = function(edges, terminal, sources, targets)\r\n{\r\n\tsources = (sources != null) ? sources : true;\r\n\ttargets = (targets != null) ? targets : true;\r\n\t\r\n\tvar terminals = [];\r\n\t\r\n\t// Fast lookup to avoid duplicates in terminals array\r\n\tvar dict = new mxDictionary();\r\n\t\r\n\tif (edges != null)\r\n\t{\r\n\t\tfor (var i = 0; i < edges.length; i++)\r\n\t\t{\r\n\t\t\tvar state = this.view.getState(edges[i]);\r\n\t\t\t\r\n\t\t\tvar source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);\r\n\t\t\tvar target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);\r\n\t\t\t\r\n\t\t\t// Checks if the terminal is the source of the edge and if the\r\n\t\t\t// target should be stored in the result\r\n\t\t\tif (source == terminal && target != null && target != terminal && targets)\r\n\t\t\t{\r\n\t\t\t\tif (!dict.get(target))\r\n\t\t\t\t{\r\n\t\t\t\t\tdict.put(target, true);\r\n\t\t\t\t\tterminals.push(target);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Checks if the terminal is the taget of the edge and if the\r\n\t\t\t// source should be stored in the result\r\n\t\t\telse if (target == terminal && source != null && source != terminal && sources)\r\n\t\t\t{\r\n\t\t\t\tif (!dict.get(source))\r\n\t\t\t\t{\r\n\t\t\t\t\tdict.put(source, true);\r\n\t\t\t\t\tterminals.push(source);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn terminals;\r\n};\r\n\r\n/**\r\n * Function: getEdgesBetween\r\n * \r\n * Returns the edges between the given source and target. This takes into\r\n * account collapsed and invisible cells and returns the connected edges\r\n * as displayed on the screen.\r\n * \r\n * Parameters:\r\n * \r\n * source -\r\n * target -\r\n * directed -\r\n */\r\nmxGraph.prototype.getEdgesBetween = function(source, target, directed)\r\n{\r\n\tdirected = (directed != null) ? directed : false;\r\n\tvar edges = this.getEdges(source);\r\n\tvar result = [];\r\n\r\n\t// Checks if the edge is connected to the correct\r\n\t// cell and returns the first match\r\n\tfor (var i = 0; i < edges.length; i++)\r\n\t{\r\n\t\tvar state = this.view.getState(edges[i]);\r\n\t\t\r\n\t\tvar src = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);\r\n\t\tvar trg = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);\r\n\r\n\t\tif ((src == source && trg == target) || (!directed && src == target && trg == source))\r\n\t\t{\r\n\t\t\tresult.push(edges[i]);\r\n\t\t}\r\n\t}\r\n\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: getPointForEvent\r\n * \r\n * Returns an <mxPoint> representing the given event in the unscaled,\r\n * non-translated coordinate space of <container> and applies the grid.\r\n * \r\n * Parameters:\r\n * \r\n * evt - Mousevent that contains the mouse pointer location.\r\n * addOffset - Optional boolean that specifies if the position should be\r\n * offset by half of the <gridSize>. Default is true.\r\n */\r\n mxGraph.prototype.getPointForEvent = function(evt, addOffset)\r\n {\r\n\tvar p = mxUtils.convertPoint(this.container,\r\n\t\tmxEvent.getClientX(evt), mxEvent.getClientY(evt));\r\n\t\r\n\tvar s = this.view.scale;\r\n\tvar tr = this.view.translate;\r\n\tvar off = (addOffset != false) ? this.gridSize / 2 : 0;\r\n\t\r\n\tp.x = this.snap(p.x / s - tr.x - off);\r\n\tp.y = this.snap(p.y / s - tr.y - off);\r\n\t\r\n\treturn p;\r\n };\r\n\r\n/**\r\n * Function: getCells\r\n * \r\n * Returns the child vertices and edges of the given parent that are contained\r\n * in the given rectangle. The result is added to the optional result array,\r\n * which is returned. If no result array is specified then a new array is\r\n * created and returned.\r\n * \r\n * Parameters:\r\n * \r\n * x - X-coordinate of the rectangle.\r\n * y - Y-coordinate of the rectangle.\r\n * width - Width of the rectangle.\r\n * height - Height of the rectangle.\r\n * parent - <mxCell> that should be used as the root of the recursion.\r\n * Default is current root of the view or the root of the model.\r\n * result - Optional array to store the result in.\r\n */\r\nmxGraph.prototype.getCells = function(x, y, width, height, parent, result)\r\n{\r\n\tresult = (result != null) ? result : [];\r\n\t\r\n\tif (width > 0 || height > 0)\r\n\t{\r\n\t\tvar model = this.getModel();\r\n\t\tvar right = x + width;\r\n\t\tvar bottom = y + height;\r\n\r\n\t\tif (parent == null)\r\n\t\t{\r\n\t\t\tparent = this.getCurrentRoot();\r\n\t\t\t\r\n\t\t\tif (parent == null)\r\n\t\t\t{\r\n\t\t\t\tparent = model.getRoot();\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (parent != null)\r\n\t\t{\r\n\t\t\tvar childCount = model.getChildCount(parent);\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < childCount; i++)\r\n\t\t\t{\r\n\t\t\t\tvar cell = model.getChildAt(parent, i);\r\n\t\t\t\tvar state = this.view.getState(cell);\r\n\t\t\t\t\r\n\t\t\t\tif (state != null && this.isCellVisible(cell))\r\n\t\t\t\t{\r\n\t\t\t\t\tvar deg = mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0;\r\n\t\t\t\t\tvar box = state;\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (deg != 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tbox = mxUtils.getBoundingBox(box, deg);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif ((model.isEdge(cell) || model.isVertex(cell)) &&\r\n\t\t\t\t\t\tbox.x >= x && box.y + box.height <= bottom &&\r\n\t\t\t\t\t\tbox.y >= y && box.x + box.width <= right)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tresult.push(cell);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.getCells(x, y, width, height, cell, result);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: getCellsBeyond\r\n * \r\n * Returns the children of the given parent that are contained in the\r\n * halfpane from the given point (x0, y0) rightwards or downwards\r\n * depending on rightHalfpane and bottomHalfpane.\r\n * \r\n * Parameters:\r\n * \r\n * x0 - X-coordinate of the origin.\r\n * y0 - Y-coordinate of the origin.\r\n * parent - Optional <mxCell> whose children should be checked. Default is\r\n * <defaultParent>.\r\n * rightHalfpane - Boolean indicating if the cells in the right halfpane\r\n * from the origin should be returned.\r\n * bottomHalfpane - Boolean indicating if the cells in the bottom halfpane\r\n * from the origin should be returned.\r\n */\r\nmxGraph.prototype.getCellsBeyond = function(x0, y0, parent, rightHalfpane, bottomHalfpane)\r\n{\r\n\tvar result = [];\r\n\t\r\n\tif (rightHalfpane || bottomHalfpane)\r\n\t{\r\n\t\tif (parent == null)\r\n\t\t{\r\n\t\t\tparent = this.getDefaultParent();\r\n\t\t}\r\n\t\t\r\n\t\tif (parent != null)\r\n\t\t{\r\n\t\t\tvar childCount = this.model.getChildCount(parent);\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < childCount; i++)\r\n\t\t\t{\r\n\t\t\t\tvar child = this.model.getChildAt(parent, i);\r\n\t\t\t\tvar state = this.view.getState(child);\r\n\t\t\t\t\r\n\t\t\t\tif (this.isCellVisible(child) && state != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tif ((!rightHalfpane || state.x >= x0) &&\r\n\t\t\t\t\t\t(!bottomHalfpane || state.y >= y0))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tresult.push(child);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: findTreeRoots\r\n * \r\n * Returns all children in the given parent which do not have incoming\r\n * edges. If the result is empty then the with the greatest difference\r\n * between incoming and outgoing edges is returned.\r\n * \r\n * Parameters:\r\n * \r\n * parent - <mxCell> whose children should be checked.\r\n * isolate - Optional boolean that specifies if edges should be ignored if\r\n * the opposite end is not a child of the given parent cell. Default is\r\n * false.\r\n * invert - Optional boolean that specifies if outgoing or incoming edges\r\n * should be counted for a tree root. If false then outgoing edges will be\r\n * counted. Default is false.\r\n */\r\nmxGraph.prototype.findTreeRoots = function(parent, isolate, invert)\r\n{\r\n\tisolate = (isolate != null) ? isolate : false;\r\n\tinvert = (invert != null) ? invert : false;\r\n\tvar roots = [];\r\n\t\r\n\tif (parent != null)\r\n\t{\r\n\t\tvar model = this.getModel();\r\n\t\tvar childCount = model.getChildCount(parent);\r\n\t\tvar best = null;\r\n\t\tvar maxDiff = 0;\r\n\t\t\r\n\t\tfor (var i=0; i<childCount; i++)\r\n\t\t{\r\n\t\t\tvar cell = model.getChildAt(parent, i);\r\n\t\t\t\r\n\t\t\tif (this.model.isVertex(cell) && this.isCellVisible(cell))\r\n\t\t\t{\r\n\t\t\t\tvar conns = this.getConnections(cell, (isolate) ? parent : null);\r\n\t\t\t\tvar fanOut = 0;\r\n\t\t\t\tvar fanIn = 0;\r\n\t\t\t\t\r\n\t\t\t\tfor (var j = 0; j < conns.length; j++)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar src = this.view.getVisibleTerminal(conns[j], true);\r\n\r\n                    if (src == cell)\r\n                    {\r\n                        fanOut++;\r\n                    }\r\n                    else\r\n                    {\r\n                        fanIn++;\r\n                    }\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif ((invert && fanOut == 0 && fanIn > 0) ||\r\n\t\t\t\t\t(!invert && fanIn == 0 && fanOut > 0))\r\n\t\t\t\t{\r\n\t\t\t\t\troots.push(cell);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tvar diff = (invert) ? fanIn - fanOut : fanOut - fanIn;\r\n\t\t\t\t\r\n\t\t\t\tif (diff > maxDiff)\r\n\t\t\t\t{\r\n\t\t\t\t\tmaxDiff = diff;\r\n\t\t\t\t\tbest = cell;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (roots.length == 0 && best != null)\r\n\t\t{\r\n\t\t\troots.push(best);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn roots;\r\n};\r\n\r\n/**\r\n * Function: traverse\r\n * \r\n * Traverses the (directed) graph invoking the given function for each\r\n * visited vertex and edge. The function is invoked with the current vertex\r\n * and the incoming edge as a parameter. This implementation makes sure\r\n * each vertex is only visited once. The function may return false if the\r\n * traversal should stop at the given vertex.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * mxLog.show();\r\n * var cell = graph.getSelectionCell();\r\n * graph.traverse(cell, false, function(vertex, edge)\r\n * {\r\n *   mxLog.debug(graph.getLabel(vertex));\r\n * });\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * vertex - <mxCell> that represents the vertex where the traversal starts.\r\n * directed - Optional boolean indicating if edges should only be traversed\r\n * from source to target. Default is true.\r\n * func - Visitor function that takes the current vertex and the incoming\r\n * edge as arguments. The traversal stops if the function returns false.\r\n * edge - Optional <mxCell> that represents the incoming edge. This is\r\n * null for the first step of the traversal.\r\n * visited - Optional <mxDictionary> from cells to true for the visited cells.\r\n * inverse - Optional boolean to traverse in inverse direction. Default is false.\r\n * This is ignored if directed is false.\r\n */\r\nmxGraph.prototype.traverse = function(vertex, directed, func, edge, visited, inverse)\r\n{\r\n\tif (func != null && vertex != null)\r\n\t{\r\n\t\tdirected = (directed != null) ? directed : true;\r\n\t\tinverse = (inverse != null) ? inverse : false;\r\n\t\tvisited = visited || new mxDictionary();\r\n\t\t\r\n\t\tif (!visited.get(vertex))\r\n\t\t{\r\n\t\t\tvisited.put(vertex, true);\r\n\t\t\tvar result = func(vertex, edge);\r\n\t\t\t\r\n\t\t\tif (result == null || result)\r\n\t\t\t{\r\n\t\t\t\tvar edgeCount = this.model.getEdgeCount(vertex);\r\n\t\t\t\t\r\n\t\t\t\tif (edgeCount > 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tfor (var i = 0; i < edgeCount; i++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar e = this.model.getEdgeAt(vertex, i);\r\n\t\t\t\t\t\tvar isSource = this.model.getTerminal(e, true) == vertex;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (!directed || (!inverse == isSource))\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar next = this.model.getTerminal(e, !isSource);\r\n\t\t\t\t\t\t\tthis.traverse(next, directed, func, e, visited, inverse);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Group: Selection\r\n */\r\n\r\n/**\r\n * Function: isCellSelected\r\n * \r\n * Returns true if the given cell is selected.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> for which the selection state should be returned.\r\n */\r\nmxGraph.prototype.isCellSelected = function(cell)\r\n{\r\n\treturn this.getSelectionModel().isSelected(cell);\r\n};\r\n\r\n/**\r\n * Function: isSelectionEmpty\r\n * \r\n * Returns true if the selection is empty.\r\n */\r\nmxGraph.prototype.isSelectionEmpty = function()\r\n{\r\n\treturn this.getSelectionModel().isEmpty();\r\n};\r\n\r\n/**\r\n * Function: clearSelection\r\n * \r\n * Clears the selection using <mxGraphSelectionModel.clear>.\r\n */\r\nmxGraph.prototype.clearSelection = function()\r\n{\r\n\treturn this.getSelectionModel().clear();\r\n};\r\n\r\n/**\r\n * Function: getSelectionCount\r\n * \r\n * Returns the number of selected cells.\r\n */\r\nmxGraph.prototype.getSelectionCount = function()\r\n{\r\n\treturn this.getSelectionModel().cells.length;\r\n};\r\n\t\r\n/**\r\n * Function: getSelectionCell\r\n * \r\n * Returns the first cell from the array of selected <mxCells>.\r\n */\r\nmxGraph.prototype.getSelectionCell = function()\r\n{\r\n\treturn this.getSelectionModel().cells[0];\r\n};\r\n\r\n/**\r\n * Function: getSelectionCells\r\n * \r\n * Returns the array of selected <mxCells>.\r\n */\r\nmxGraph.prototype.getSelectionCells = function()\r\n{\r\n\treturn this.getSelectionModel().cells.slice();\r\n};\r\n\r\n/**\r\n * Function: setSelectionCell\r\n * \r\n * Sets the selection cell.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> to be selected.\r\n */\r\nmxGraph.prototype.setSelectionCell = function(cell)\r\n{\r\n\tthis.getSelectionModel().setCell(cell);\r\n};\r\n\r\n/**\r\n * Function: setSelectionCells\r\n * \r\n * Sets the selection cell.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> to be selected.\r\n */\r\nmxGraph.prototype.setSelectionCells = function(cells)\r\n{\r\n\tthis.getSelectionModel().setCells(cells);\r\n};\r\n\r\n/**\r\n * Function: addSelectionCell\r\n * \r\n * Adds the given cell to the selection.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> to be add to the selection.\r\n */\r\nmxGraph.prototype.addSelectionCell = function(cell)\r\n{\r\n\tthis.getSelectionModel().addCell(cell);\r\n};\r\n\r\n/**\r\n * Function: addSelectionCells\r\n * \r\n * Adds the given cells to the selection.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> to be added to the selection.\r\n */\r\nmxGraph.prototype.addSelectionCells = function(cells)\r\n{\r\n\tthis.getSelectionModel().addCells(cells);\r\n};\r\n\r\n/**\r\n * Function: removeSelectionCell\r\n * \r\n * Removes the given cell from the selection.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> to be removed from the selection.\r\n */\r\nmxGraph.prototype.removeSelectionCell = function(cell)\r\n{\r\n\tthis.getSelectionModel().removeCell(cell);\r\n};\r\n\r\n/**\r\n * Function: removeSelectionCells\r\n * \r\n * Removes the given cells from the selection.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> to be removed from the selection.\r\n */\r\nmxGraph.prototype.removeSelectionCells = function(cells)\r\n{\r\n\tthis.getSelectionModel().removeCells(cells);\r\n};\r\n\r\n/**\r\n * Function: selectRegion\r\n * \r\n * Selects and returns the cells inside the given rectangle for the\r\n * specified event.\r\n * \r\n * Parameters:\r\n * \r\n * rect - <mxRectangle> that represents the region to be selected.\r\n * evt - Mouseevent that triggered the selection.\r\n */\r\nmxGraph.prototype.selectRegion = function(rect, evt)\r\n{\r\n\tvar cells = this.getCells(rect.x, rect.y, rect.width, rect.height);\r\n\tthis.selectCellsForEvent(cells, evt);\r\n\t\r\n\treturn cells;\r\n};\r\n\r\n/**\r\n * Function: selectNextCell\r\n * \r\n * Selects the next cell.\r\n */\r\nmxGraph.prototype.selectNextCell = function()\r\n{\r\n\tthis.selectCell(true);\r\n};\r\n\r\n/**\r\n * Function: selectPreviousCell\r\n * \r\n * Selects the previous cell.\r\n */\r\nmxGraph.prototype.selectPreviousCell = function()\r\n{\r\n\tthis.selectCell();\r\n};\r\n\r\n/**\r\n * Function: selectParentCell\r\n * \r\n * Selects the parent cell.\r\n */\r\nmxGraph.prototype.selectParentCell = function()\r\n{\r\n\tthis.selectCell(false, true);\r\n};\r\n\r\n/**\r\n * Function: selectChildCell\r\n * \r\n * Selects the first child cell.\r\n */\r\nmxGraph.prototype.selectChildCell = function()\r\n{\r\n\tthis.selectCell(false, false, true);\r\n};\r\n\r\n/**\r\n * Function: selectCell\r\n * \r\n * Selects the next, parent, first child or previous cell, if all arguments\r\n * are false.\r\n * \r\n * Parameters:\r\n * \r\n * isNext - Boolean indicating if the next cell should be selected.\r\n * isParent - Boolean indicating if the parent cell should be selected.\r\n * isChild - Boolean indicating if the first child cell should be selected.\r\n */\r\nmxGraph.prototype.selectCell = function(isNext, isParent, isChild)\r\n{\r\n\tvar sel = this.selectionModel;\r\n\tvar cell = (sel.cells.length > 0) ? sel.cells[0] : null;\r\n\t\r\n\tif (sel.cells.length > 1)\r\n\t{\r\n\t\tsel.clear();\r\n\t}\r\n\t\r\n\tvar parent = (cell != null) ?\r\n\t\tthis.model.getParent(cell) :\r\n\t\tthis.getDefaultParent();\r\n\t\r\n\tvar childCount = this.model.getChildCount(parent);\r\n\t\r\n\tif (cell == null && childCount > 0)\r\n\t{\r\n\t\tvar child = this.model.getChildAt(parent, 0);\r\n\t\tthis.setSelectionCell(child);\r\n\t}\r\n\telse if ((cell == null || isParent) &&\r\n\t\tthis.view.getState(parent) != null &&\r\n\t\tthis.model.getGeometry(parent) != null)\r\n\t{\r\n\t\tif (this.getCurrentRoot() != parent)\r\n\t\t{\r\n\t\t\tthis.setSelectionCell(parent);\r\n\t\t}\r\n\t}\r\n\telse if (cell != null && isChild)\r\n\t{\r\n\t\tvar tmp = this.model.getChildCount(cell);\r\n\t\t\r\n\t\tif (tmp > 0)\r\n\t\t{\r\n\t\t\tvar child = this.model.getChildAt(cell, 0);\r\n\t\t\tthis.setSelectionCell(child);\r\n\t\t}\r\n\t}\r\n\telse if (childCount > 0)\r\n\t{\r\n\t\tvar i = parent.getIndex(cell);\r\n\t\t\r\n\t\tif (isNext)\r\n\t\t{\r\n\t\t\ti++;\r\n\t\t\tvar child = this.model.getChildAt(parent, i % childCount);\r\n\t\t\tthis.setSelectionCell(child);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\ti--;\r\n\t\t\tvar index =  (i < 0) ? childCount - 1 : i;\r\n\t\t\tvar child = this.model.getChildAt(parent, index);\r\n\t\t\tthis.setSelectionCell(child);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: selectAll\r\n * \r\n * Selects all children of the given parent cell or the children of the\r\n * default parent if no parent is specified. To select leaf vertices and/or\r\n * edges use <selectCells>.\r\n * \r\n * Parameters:\r\n * \r\n * parent - Optional <mxCell> whose children should be selected.\r\n * Default is <defaultParent>.\r\n * descendants - Optional boolean specifying whether all descendants should be\r\n * selected. Default is false.\r\n */\r\nmxGraph.prototype.selectAll = function(parent, descendants)\r\n{\r\n\tparent = parent || this.getDefaultParent();\r\n\t\r\n\tvar cells = (descendants) ? this.model.filterDescendants(function(cell)\r\n\t{\r\n\t\treturn cell != parent;\r\n\t}, parent) : this.model.getChildren(parent);\r\n\t\r\n\tif (cells != null)\r\n\t{\r\n\t\tthis.setSelectionCells(cells);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: selectVertices\r\n * \r\n * Select all vertices inside the given parent or the default parent.\r\n */\r\nmxGraph.prototype.selectVertices = function(parent)\r\n{\r\n\tthis.selectCells(true, false, parent);\r\n};\r\n\r\n/**\r\n * Function: selectVertices\r\n * \r\n * Select all vertices inside the given parent or the default parent.\r\n */\r\nmxGraph.prototype.selectEdges = function(parent)\r\n{\r\n\tthis.selectCells(false, true, parent);\r\n};\r\n\r\n/**\r\n * Function: selectCells\r\n * \r\n * Selects all vertices and/or edges depending on the given boolean\r\n * arguments recursively, starting at the given parent or the default\r\n * parent if no parent is specified. Use <selectAll> to select all cells.\r\n * For vertices, only cells with no children are selected.\r\n * \r\n * Parameters:\r\n * \r\n * vertices - Boolean indicating if vertices should be selected.\r\n * edges - Boolean indicating if edges should be selected.\r\n * parent - Optional <mxCell> that acts as the root of the recursion.\r\n * Default is <defaultParent>.\r\n */\r\nmxGraph.prototype.selectCells = function(vertices, edges, parent)\r\n{\r\n\tparent = parent || this.getDefaultParent();\r\n\t\r\n\tvar filter = mxUtils.bind(this, function(cell)\r\n\t{\r\n\t\treturn this.view.getState(cell) != null &&\r\n\t\t\t((this.model.getChildCount(cell) == 0 && this.model.isVertex(cell) && vertices\r\n\t\t\t&& !this.model.isEdge(this.model.getParent(cell))) ||\r\n\t\t\t(this.model.isEdge(cell) && edges));\r\n\t});\r\n\t\r\n\tvar cells = this.model.filterDescendants(filter, parent);\r\n\tthis.setSelectionCells(cells);\r\n};\r\n\r\n/**\r\n * Function: selectCellForEvent\r\n * \r\n * Selects the given cell by either adding it to the selection or\r\n * replacing the selection depending on whether the given mouse event is a\r\n * toggle event.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> to be selected.\r\n * evt - Optional mouseevent that triggered the selection.\r\n */\r\nmxGraph.prototype.selectCellForEvent = function(cell, evt)\r\n{\r\n\tvar isSelected = this.isCellSelected(cell);\r\n\t\r\n\tif (this.isToggleEvent(evt))\r\n\t{\r\n\t\tif (isSelected)\r\n\t\t{\r\n\t\t\tthis.removeSelectionCell(cell);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.addSelectionCell(cell);\r\n\t\t}\r\n\t}\r\n\telse if (!isSelected || this.getSelectionCount() != 1)\r\n\t{\r\n\t\tthis.setSelectionCell(cell);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: selectCellsForEvent\r\n * \r\n * Selects the given cells by either adding them to the selection or\r\n * replacing the selection depending on whether the given mouse event is a\r\n * toggle event.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> to be selected.\r\n * evt - Optional mouseevent that triggered the selection.\r\n */\r\nmxGraph.prototype.selectCellsForEvent = function(cells, evt)\r\n{\r\n\tif (this.isToggleEvent(evt))\r\n\t{\r\n\t\tthis.addSelectionCells(cells);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.setSelectionCells(cells);\r\n\t}\r\n};\r\n\r\n/**\r\n * Group: Selection state\r\n */\r\n\r\n/**\r\n * Function: createHandler\r\n * \r\n * Creates a new handler for the given cell state. This implementation\r\n * returns a new <mxEdgeHandler> of the corresponding cell is an edge,\r\n * otherwise it returns an <mxVertexHandler>.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose handler should be created.\r\n */\r\nmxGraph.prototype.createHandler = function(state)\r\n{\r\n\tvar result = null;\r\n\t\r\n\tif (state != null)\r\n\t{\r\n\t\tif (this.model.isEdge(state.cell))\r\n\t\t{\r\n\t\t\tvar source = state.getVisibleTerminalState(true);\r\n\t\t\tvar target = state.getVisibleTerminalState(false);\r\n\t\t\tvar geo = this.getCellGeometry(state.cell);\r\n\t\t\t\r\n\t\t\tvar edgeStyle = this.view.getEdgeStyle(state, (geo != null) ? geo.points : null, source, target);\r\n\t\t\tresult = this.createEdgeHandler(state, edgeStyle);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tresult = this.createVertexHandler(state);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: createVertexHandler\r\n * \r\n * Hooks to create a new <mxVertexHandler> for the given <mxCellState>.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> to create the handler for.\r\n */\r\nmxGraph.prototype.createVertexHandler = function(state)\r\n{\r\n\treturn new mxVertexHandler(state);\r\n};\r\n\r\n/**\r\n * Function: createEdgeHandler\r\n * \r\n * Hooks to create a new <mxEdgeHandler> for the given <mxCellState>.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> to create the handler for.\r\n */\r\nmxGraph.prototype.createEdgeHandler = function(state, edgeStyle)\r\n{\r\n\tvar result = null;\r\n\t\r\n\tif (edgeStyle == mxEdgeStyle.Loop ||\r\n\t\tedgeStyle == mxEdgeStyle.ElbowConnector ||\r\n\t\tedgeStyle == mxEdgeStyle.SideToSide ||\r\n\t\tedgeStyle == mxEdgeStyle.TopToBottom)\r\n\t{\r\n\t\tresult = this.createElbowEdgeHandler(state);\r\n\t}\r\n\telse if (edgeStyle == mxEdgeStyle.SegmentConnector || \r\n\t\t\tedgeStyle == mxEdgeStyle.OrthConnector)\r\n\t{\r\n\t\tresult = this.createEdgeSegmentHandler(state);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tresult = new mxEdgeHandler(state);\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: createEdgeSegmentHandler\r\n * \r\n * Hooks to create a new <mxEdgeSegmentHandler> for the given <mxCellState>.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> to create the handler for.\r\n */\r\nmxGraph.prototype.createEdgeSegmentHandler = function(state)\r\n{\r\n\treturn new mxEdgeSegmentHandler(state);\r\n};\r\n\r\n/**\r\n * Function: createElbowEdgeHandler\r\n * \r\n * Hooks to create a new <mxElbowEdgeHandler> for the given <mxCellState>.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> to create the handler for.\r\n */\r\nmxGraph.prototype.createElbowEdgeHandler = function(state)\r\n{\r\n\treturn new mxElbowEdgeHandler(state);\r\n};\r\n\r\n/**\r\n * Group: Graph events\r\n */\r\n\r\n/**\r\n * Function: addMouseListener\r\n * \r\n * Adds a listener to the graph event dispatch loop. The listener\r\n * must implement the mouseDown, mouseMove and mouseUp methods\r\n * as shown in the <mxMouseEvent> class.\r\n * \r\n * Parameters:\r\n * \r\n * listener - Listener to be added to the graph event listeners.\r\n */\r\nmxGraph.prototype.addMouseListener = function(listener)\r\n{\r\n\tif (this.mouseListeners == null)\r\n\t{\r\n\t\tthis.mouseListeners = [];\r\n\t}\r\n\t\r\n\tthis.mouseListeners.push(listener);\r\n};\r\n\r\n/**\r\n * Function: removeMouseListener\r\n * \r\n * Removes the specified graph listener.\r\n * \r\n * Parameters:\r\n * \r\n * listener - Listener to be removed from the graph event listeners.\r\n */\r\nmxGraph.prototype.removeMouseListener = function(listener)\r\n{\r\n\tif (this.mouseListeners != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.mouseListeners.length; i++)\r\n\t\t{\r\n\t\t\tif (this.mouseListeners[i] == listener)\r\n\t\t\t{\r\n\t\t\t\tthis.mouseListeners.splice(i, 1);\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: updateMouseEvent\r\n * \r\n * Sets the graphX and graphY properties if the given <mxMouseEvent> if\r\n * required and returned the event.\r\n * \r\n * Parameters:\r\n * \r\n * me - <mxMouseEvent> to be updated.\r\n * evtName - Name of the mouse event.\r\n */\r\nmxGraph.prototype.updateMouseEvent = function(me, evtName)\r\n{\r\n\tif (me.graphX == null || me.graphY == null)\r\n\t{\r\n\t\tvar pt = mxUtils.convertPoint(this.container, me.getX(), me.getY());\r\n\t\t\r\n\t\tme.graphX = pt.x - this.panDx;\r\n\t\tme.graphY = pt.y - this.panDy;\r\n\t\t\r\n\t\t// Searches for rectangles using method if native hit detection is disabled on shape\r\n\t\tif (me.getCell() == null && this.isMouseDown && evtName == mxEvent.MOUSE_MOVE)\r\n\t\t{\r\n\t\t\tme.state = this.view.getState(this.getCellAt(pt.x, pt.y, null, null, null, function(state)\r\n\t\t\t{\r\n\t\t\t\treturn state.shape == null || state.shape.paintBackground != mxRectangleShape.prototype.paintBackground ||\r\n\t\t\t\t\tmxUtils.getValue(state.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1' ||\r\n\t\t\t\t\t(state.shape.fill != null && state.shape.fill != mxConstants.NONE);\r\n\t\t\t}));\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn me;\r\n};\r\n\r\n/**\r\n * Function: getStateForEvent\r\n * \r\n * Returns the state for the given touch event.\r\n */\r\nmxGraph.prototype.getStateForTouchEvent = function(evt)\r\n{\r\n\tvar x = mxEvent.getClientX(evt);\r\n\tvar y = mxEvent.getClientY(evt);\r\n\t\r\n\t// Dispatches the drop event to the graph which\r\n\t// consumes and executes the source function\r\n\tvar pt = mxUtils.convertPoint(this.container, x, y);\r\n\r\n\treturn this.view.getState(this.getCellAt(pt.x, pt.y));\r\n};\r\n\r\n/**\r\n * Function: isEventIgnored\r\n * \r\n * Returns true if the event should be ignored in <fireMouseEvent>.\r\n */\r\nmxGraph.prototype.isEventIgnored = function(evtName, me, sender)\r\n{\r\n\tvar mouseEvent = mxEvent.isMouseEvent(me.getEvent());\r\n\tvar result = false;\r\n\r\n\t// Drops events that are fired more than once\r\n\tif (me.getEvent() == this.lastEvent)\r\n\t{\r\n\t\tresult = true;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.lastEvent = me.getEvent();\r\n\t}\r\n\r\n\t// Installs event listeners to capture the complete gesture from the event source\r\n\t// for non-MS touch events as a workaround for all events for the same geture being\r\n\t// fired from the event source even if that was removed from the DOM.\r\n\tif (this.eventSource != null && evtName != mxEvent.MOUSE_MOVE)\r\n\t{\r\n\t\tmxEvent.removeGestureListeners(this.eventSource, null, this.mouseMoveRedirect, this.mouseUpRedirect);\r\n\t\tthis.mouseMoveRedirect = null;\r\n\t\tthis.mouseUpRedirect = null;\r\n\t\tthis.eventSource = null;\r\n\t}\r\n\telse if (!mxClient.IS_GC && this.eventSource != null && me.getSource() != this.eventSource)\r\n\t{\r\n\t\tresult = true;\r\n\t}\r\n\telse if (mxClient.IS_TOUCH && evtName == mxEvent.MOUSE_DOWN && !mouseEvent && !mxEvent.isPenEvent(me.getEvent()))\r\n\t{\r\n\t\tthis.eventSource = me.getSource();\r\n\r\n\t\tthis.mouseMoveRedirect = mxUtils.bind(this, function(evt)\r\n\t\t{\r\n\t\t\tthis.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, this.getStateForTouchEvent(evt)));\r\n\t\t});\r\n\t\tthis.mouseUpRedirect = mxUtils.bind(this, function(evt)\r\n\t\t{\r\n\t\t\tthis.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, this.getStateForTouchEvent(evt)));\r\n\t\t});\r\n\t\t\r\n\t\tmxEvent.addGestureListeners(this.eventSource, null, this.mouseMoveRedirect, this.mouseUpRedirect);\r\n\t}\r\n\r\n\t// Factored out the workarounds for FF to make it easier to override/remove\r\n\t// Note this method has side-effects!\r\n\tif (this.isSyntheticEventIgnored(evtName, me, sender))\r\n\t{\r\n\t\tresult = true;\r\n\t}\r\n\r\n\t// Never fires mouseUp/-Down for double clicks\r\n\tif (!mxEvent.isPopupTrigger(this.lastEvent) && evtName != mxEvent.MOUSE_MOVE && this.lastEvent.detail == 2)\r\n\t{\r\n\t\treturn true;\r\n\t}\r\n\t\r\n\t// Filters out of sequence events or mixed event types during a gesture\r\n\tif (evtName == mxEvent.MOUSE_UP && this.isMouseDown)\r\n\t{\r\n\t\tthis.isMouseDown = false;\r\n\t}\r\n\telse if (evtName == mxEvent.MOUSE_DOWN && !this.isMouseDown)\r\n\t{\r\n\t\tthis.isMouseDown = true;\r\n\t\tthis.isMouseTrigger = mouseEvent;\r\n\t}\r\n\t// Drops mouse events that are fired during touch gestures as a workaround for Webkit\r\n\t// and mouse events that are not in sync with the current internal button state\r\n\telse if (!result && (((!mxClient.IS_FF || evtName != mxEvent.MOUSE_MOVE) &&\r\n\t\tthis.isMouseDown && this.isMouseTrigger != mouseEvent) ||\r\n\t\t(evtName == mxEvent.MOUSE_DOWN && this.isMouseDown) ||\r\n\t\t(evtName == mxEvent.MOUSE_UP && !this.isMouseDown)))\r\n\t{\r\n\t\tresult = true;\r\n\t}\r\n\t\r\n\tif (!result && evtName == mxEvent.MOUSE_DOWN)\r\n\t{\r\n\t\tthis.lastMouseX = me.getX();\r\n\t\tthis.lastMouseY = me.getY();\r\n\t}\r\n\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: isSyntheticEventIgnored\r\n * \r\n * Hook for ignoring synthetic mouse events after touchend in Firefox.\r\n */\r\nmxGraph.prototype.isSyntheticEventIgnored = function(evtName, me, sender)\r\n{\r\n\tvar result = false;\r\n\tvar mouseEvent = mxEvent.isMouseEvent(me.getEvent());\r\n\t\r\n\t// LATER: This does not cover all possible cases that can go wrong in FF\r\n\tif (this.ignoreMouseEvents && mouseEvent && evtName != mxEvent.MOUSE_MOVE)\r\n\t{\r\n\t\tthis.ignoreMouseEvents = evtName != mxEvent.MOUSE_UP;\r\n\t\tresult = true;\r\n\t}\r\n\telse if (mxClient.IS_FF && !mouseEvent && evtName == mxEvent.MOUSE_UP)\r\n\t{\r\n\t\tthis.ignoreMouseEvents = true;\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: isEventSourceIgnored\r\n * \r\n * Returns true if the event should be ignored in <fireMouseEvent>. This\r\n * implementation returns true for select, option and input (if not of type\r\n * checkbox, radio, button, submit or file) event sources if the event is not\r\n * a mouse event or a left mouse button press event.\r\n * \r\n * Parameters:\r\n * \r\n * evtName - The name of the event.\r\n * me - <mxMouseEvent> that should be ignored.\r\n */\r\nmxGraph.prototype.isEventSourceIgnored = function(evtName, me)\r\n{\r\n\tvar source = me.getSource();\r\n\tvar name = (source.nodeName != null) ? source.nodeName.toLowerCase() : '';\r\n\tvar candidate = !mxEvent.isMouseEvent(me.getEvent()) || mxEvent.isLeftMouseButton(me.getEvent());\r\n\t\r\n\treturn evtName == mxEvent.MOUSE_DOWN && candidate && (name == 'select' || name == 'option' ||\r\n\t\t(name == 'input' && source.type != 'checkbox' && source.type != 'radio' &&\r\n\t\tsource.type != 'button' && source.type != 'submit' && source.type != 'file'));\r\n};\r\n\r\n/**\r\n * Function: getEventState\r\n * \r\n * Returns the <mxCellState> to be used when firing the mouse event for the\r\n * given state. This implementation returns the given state.\r\n * \r\n * Parameters:\r\n * \r\n * <mxCellState> - State whose event source should be returned.\r\n */\r\nmxGraph.prototype.getEventState = function(state)\r\n{\r\n\treturn state;\r\n};\r\n\r\n/**\r\n * Function: fireMouseEvent\r\n * \r\n * Dispatches the given event in the graph event dispatch loop. Possible\r\n * event names are <mxEvent.MOUSE_DOWN>, <mxEvent.MOUSE_MOVE> and\r\n * <mxEvent.MOUSE_UP>. All listeners are invoked for all events regardless\r\n * of the consumed state of the event.\r\n * \r\n * Parameters:\r\n * \r\n * evtName - String that specifies the type of event to be dispatched.\r\n * me - <mxMouseEvent> to be fired.\r\n * sender - Optional sender argument. Default is this.\r\n */\r\nmxGraph.prototype.fireMouseEvent = function(evtName, me, sender)\r\n{\r\n\tif (this.isEventSourceIgnored(evtName, me))\r\n\t{\r\n\t\tif (this.tooltipHandler != null)\r\n\t\t{\r\n\t\t\tthis.tooltipHandler.hide();\r\n\t\t}\r\n\t\t\r\n\t\treturn;\r\n\t}\r\n\t\r\n\tif (sender == null)\r\n\t{\r\n\t\tsender = this;\r\n\t}\r\n\r\n\t// Updates the graph coordinates in the event\r\n\tme = this.updateMouseEvent(me, evtName);\r\n\r\n\t// Detects and processes double taps for touch-based devices which do not have native double click events\r\n\t// or where detection of double click is not always possible (quirks, IE10+). Note that this can only handle\r\n\t// double clicks on cells because the sequence of events in IE prevents detection on the background, it fires\r\n\t// two mouse ups, one of which without a cell but no mousedown for the second click which means we cannot\r\n\t// detect which mouseup(s) are part of the first click, ie we do not know when the first click ends.\r\n\tif ((!this.nativeDblClickEnabled && !mxEvent.isPopupTrigger(me.getEvent())) || (this.doubleTapEnabled &&\r\n\t\tmxClient.IS_TOUCH && (mxEvent.isTouchEvent(me.getEvent()) || mxEvent.isPenEvent(me.getEvent()))))\r\n\t{\r\n\t\tvar currentTime = new Date().getTime();\r\n\t\t\r\n\t\t// NOTE: Second mouseDown for double click missing in quirks mode\r\n\t\tif ((!mxClient.IS_QUIRKS && evtName == mxEvent.MOUSE_DOWN) || (mxClient.IS_QUIRKS && evtName == mxEvent.MOUSE_UP && !this.fireDoubleClick))\r\n\t\t{\r\n\t\t\tif (this.lastTouchEvent != null && this.lastTouchEvent != me.getEvent() &&\r\n\t\t\t\tcurrentTime - this.lastTouchTime < this.doubleTapTimeout &&\r\n\t\t\t\tMath.abs(this.lastTouchX - me.getX()) < this.doubleTapTolerance &&\r\n\t\t\t\tMath.abs(this.lastTouchY - me.getY()) < this.doubleTapTolerance &&\r\n\t\t\t\tthis.doubleClickCounter < 2)\r\n\t\t\t{\r\n\t\t\t\tthis.doubleClickCounter++;\r\n\t\t\t\tvar doubleClickFired = false;\r\n\t\t\t\t\r\n\t\t\t\tif (evtName == mxEvent.MOUSE_UP)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (me.getCell() == this.lastTouchCell && this.lastTouchCell != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.lastTouchTime = 0;\r\n\t\t\t\t\t\tvar cell = this.lastTouchCell;\r\n\t\t\t\t\t\tthis.lastTouchCell = null;\r\n\r\n\t\t\t\t\t\t// Fires native dblclick event via event source\r\n\t\t\t\t\t\t// NOTE: This fires two double click events on edges in quirks mode. While\r\n\t\t\t\t\t\t// trying to fix this, we realized that nativeDoubleClick can be disabled for\r\n\t\t\t\t\t\t// quirks and IE10+ (or we didn't find the case mentioned above where it\r\n\t\t\t\t\t\t// would not work), ie. all double clicks seem to be working without this.\r\n\t\t\t\t\t\tif (mxClient.IS_QUIRKS)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tme.getSource().fireEvent('ondblclick');\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tthis.dblClick(me.getEvent(), cell);\r\n\t\t\t\t\t\tdoubleClickFired = true;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.fireDoubleClick = true;\r\n\t\t\t\t\tthis.lastTouchTime = 0;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Do not ignore mouse up in quirks in this case\r\n\t\t\t\tif (!mxClient.IS_QUIRKS || doubleClickFired)\r\n\t\t\t\t{\r\n\t\t\t\t\tmxEvent.consume(me.getEvent());\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if (this.lastTouchEvent == null || this.lastTouchEvent != me.getEvent())\r\n\t\t\t{\r\n\t\t\t\tthis.lastTouchCell = me.getCell();\r\n\t\t\t\tthis.lastTouchX = me.getX();\r\n\t\t\t\tthis.lastTouchY = me.getY();\r\n\t\t\t\tthis.lastTouchTime = currentTime;\r\n\t\t\t\tthis.lastTouchEvent = me.getEvent();\r\n\t\t\t\tthis.doubleClickCounter = 0;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if ((this.isMouseDown || evtName == mxEvent.MOUSE_UP) && this.fireDoubleClick)\r\n\t\t{\r\n\t\t\tthis.fireDoubleClick = false;\r\n\t\t\tvar cell = this.lastTouchCell;\r\n\t\t\tthis.lastTouchCell = null;\r\n\t\t\tthis.isMouseDown = false;\r\n\t\t\t\r\n\t\t\t// Workaround for Chrome/Safari not firing native double click events for double touch on background\r\n\t\t\tvar valid = (cell != null) || ((mxEvent.isTouchEvent(me.getEvent()) || mxEvent.isPenEvent(me.getEvent())) &&\r\n\t\t\t\t(mxClient.IS_GC || mxClient.IS_SF));\r\n\t\t\t\r\n\t\t\tif (valid && Math.abs(this.lastTouchX - me.getX()) < this.doubleTapTolerance &&\r\n\t\t\t\tMath.abs(this.lastTouchY - me.getY()) < this.doubleTapTolerance)\r\n\t\t\t{\r\n\t\t\t\tthis.dblClick(me.getEvent(), cell);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tmxEvent.consume(me.getEvent());\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\treturn;\r\n\t\t}\r\n\t}\r\n\r\n\tif (!this.isEventIgnored(evtName, me, sender))\r\n\t{\r\n\t\t// Updates the event state via getEventState\r\n\t\tme.state = this.getEventState(me.getState());\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.FIRE_MOUSE_EVENT, 'eventName', evtName, 'event', me));\r\n\t\t\r\n\t\tif ((mxClient.IS_OP || mxClient.IS_SF || mxClient.IS_GC || mxClient.IS_IE11 ||\r\n\t\t\t(mxClient.IS_IE && mxClient.IS_SVG) || me.getEvent().target != this.container))\r\n\t\t{\r\n\t\t\tif (evtName == mxEvent.MOUSE_MOVE && this.isMouseDown && this.autoScroll && !mxEvent.isMultiTouchEvent(me.getEvent))\r\n\t\t\t{\r\n\t\t\t\tthis.scrollPointToVisible(me.getGraphX(), me.getGraphY(), this.autoExtend);\r\n\t\t\t}\r\n\t\t\telse if (evtName == mxEvent.MOUSE_UP && this.ignoreScrollbars && this.translateToScrollPosition &&\r\n\t\t\t\t\t(this.container.scrollLeft != 0 || this.container.scrollTop != 0))\r\n\t\t\t{\r\n\t\t\t\tvar s = this.view.scale;\r\n\t\t\t\tvar tr = this.view.translate;\r\n\t\t\t\tthis.view.setTranslate(tr.x - this.container.scrollLeft / s, tr.y - this.container.scrollTop / s);\r\n\t\t\t\tthis.container.scrollLeft = 0;\r\n\t\t\t\tthis.container.scrollTop = 0;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (this.mouseListeners != null)\r\n\t\t\t{\r\n\t\t\t\tvar args = [sender, me];\r\n\t\r\n\t\t\t\t// Does not change returnValue in Opera\r\n\t\t\t\tif (!me.getEvent().preventDefault)\r\n\t\t\t\t{\r\n\t\t\t\t\tme.getEvent().returnValue = true;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tfor (var i = 0; i < this.mouseListeners.length; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar l = this.mouseListeners[i];\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (evtName == mxEvent.MOUSE_DOWN)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tl.mouseDown.apply(l, args);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (evtName == mxEvent.MOUSE_MOVE)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tl.mouseMove.apply(l, args);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (evtName == mxEvent.MOUSE_UP)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tl.mouseUp.apply(l, args);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Invokes the click handler\r\n\t\t\tif (evtName == mxEvent.MOUSE_UP)\r\n\t\t\t{\r\n\t\t\t\tthis.click(me);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Detects tapAndHold events using a timer\r\n\t\tif ((mxEvent.isTouchEvent(me.getEvent()) || mxEvent.isPenEvent(me.getEvent())) &&\r\n\t\t\tevtName == mxEvent.MOUSE_DOWN && this.tapAndHoldEnabled && !this.tapAndHoldInProgress)\r\n\t\t{\r\n\t\t\tthis.tapAndHoldInProgress = true;\r\n\t\t\tthis.initialTouchX = me.getGraphX();\r\n\t\t\tthis.initialTouchY = me.getGraphY();\r\n\t\t\t\r\n\t\t\tvar handler = function()\r\n\t\t\t{\r\n\t\t\t\tif (this.tapAndHoldValid)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.tapAndHold(me);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.tapAndHoldInProgress = false;\r\n\t\t\t\tthis.tapAndHoldValid = false;\r\n\t\t\t};\r\n\t\t\t\r\n\t\t\tif (this.tapAndHoldThread)\r\n\t\t\t{\r\n\t\t\t\twindow.clearTimeout(this.tapAndHoldThread);\r\n\t\t\t}\r\n\t\r\n\t\t\tthis.tapAndHoldThread = window.setTimeout(mxUtils.bind(this, handler), this.tapAndHoldDelay);\r\n\t\t\tthis.tapAndHoldValid = true;\r\n\t\t}\r\n\t\telse if (evtName == mxEvent.MOUSE_UP)\r\n\t\t{\r\n\t\t\tthis.tapAndHoldInProgress = false;\r\n\t\t\tthis.tapAndHoldValid = false;\r\n\t\t}\r\n\t\telse if (this.tapAndHoldValid)\r\n\t\t{\r\n\t\t\tthis.tapAndHoldValid =\r\n\t\t\t\tMath.abs(this.initialTouchX - me.getGraphX()) < this.tolerance &&\r\n\t\t\t\tMath.abs(this.initialTouchY - me.getGraphY()) < this.tolerance;\r\n\t\t}\r\n\r\n\t\t// Stops editing for all events other than from cellEditor\r\n\t\tif (evtName == mxEvent.MOUSE_DOWN && this.isEditing() && !this.cellEditor.isEventSource(me.getEvent()))\r\n\t\t{\r\n\t\t\tthis.stopEditing(!this.isInvokesStopCellEditing());\r\n\t\t}\r\n\r\n\t\tthis.consumeMouseEvent(evtName, me, sender);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: consumeMouseEvent\r\n * \r\n * Consumes the given <mxMouseEvent> if it's a touchStart event.\r\n */\r\nmxGraph.prototype.consumeMouseEvent = function(evtName, me, sender)\r\n{\r\n\t// Workaround for duplicate click in Windows 8 with Chrome/FF/Opera with touch\r\n\tif (evtName == mxEvent.MOUSE_DOWN && mxEvent.isTouchEvent(me.getEvent()))\r\n\t{\r\n\t\tme.consume(false);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: fireGestureEvent\r\n * \r\n * Dispatches a <mxEvent.GESTURE> event. The following example will resize the\r\n * cell under the mouse based on the scale property of the native touch event.\r\n * \r\n * (code)\r\n * graph.addListener(mxEvent.GESTURE, function(sender, eo)\r\n * {\r\n *   var evt = eo.getProperty('event');\r\n *   var state = graph.view.getState(eo.getProperty('cell'));\r\n *   \r\n *   if (graph.isEnabled() && graph.isCellResizable(state.cell) && Math.abs(1 - evt.scale) > 0.2)\r\n *   {\r\n *     var scale = graph.view.scale;\r\n *     var tr = graph.view.translate;\r\n *     \r\n *     var w = state.width * evt.scale;\r\n *     var h = state.height * evt.scale;\r\n *     var x = state.x - (w - state.width) / 2;\r\n *     var y = state.y - (h - state.height) / 2;\r\n *     \r\n *     var bounds = new mxRectangle(graph.snap(x / scale) - tr.x,\r\n *     \t\tgraph.snap(y / scale) - tr.y, graph.snap(w / scale), graph.snap(h / scale));\r\n *     graph.resizeCell(state.cell, bounds);\r\n *     eo.consume();\r\n *   }\r\n * });\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * evt - Gestureend event that represents the gesture.\r\n * cell - Optional <mxCell> associated with the gesture.\r\n */\r\nmxGraph.prototype.fireGestureEvent = function(evt, cell)\r\n{\r\n\t// Resets double tap event handling when gestures take place\r\n\tthis.lastTouchTime = 0;\r\n\tthis.fireEvent(new mxEventObject(mxEvent.GESTURE, 'event', evt, 'cell', cell));\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Destroys the graph and all its resources.\r\n */\r\nmxGraph.prototype.destroy = function()\r\n{\r\n\tif (!this.destroyed)\r\n\t{\r\n\t\tthis.destroyed = true;\r\n\t\t\r\n\t\tif (this.tooltipHandler != null)\r\n\t\t{\r\n\t\t\tthis.tooltipHandler.destroy();\r\n\t\t}\r\n\t\t\r\n\t\tif (this.selectionCellsHandler != null)\r\n\t\t{\r\n\t\t\tthis.selectionCellsHandler.destroy();\r\n\t\t}\r\n\r\n\t\tif (this.panningHandler != null)\r\n\t\t{\r\n\t\t\tthis.panningHandler.destroy();\r\n\t\t}\r\n\r\n\t\tif (this.popupMenuHandler != null)\r\n\t\t{\r\n\t\t\tthis.popupMenuHandler.destroy();\r\n\t\t}\r\n\t\t\r\n\t\tif (this.connectionHandler != null)\r\n\t\t{\r\n\t\t\tthis.connectionHandler.destroy();\r\n\t\t}\r\n\t\t\r\n\t\tif (this.graphHandler != null)\r\n\t\t{\r\n\t\t\tthis.graphHandler.destroy();\r\n\t\t}\r\n\t\t\r\n\t\tif (this.cellEditor != null)\r\n\t\t{\r\n\t\t\tthis.cellEditor.destroy();\r\n\t\t}\r\n\t\t\r\n\t\tif (this.view != null)\r\n\t\t{\r\n\t\t\tthis.view.destroy();\r\n\t\t}\r\n\r\n\t\tif (this.model != null && this.graphModelChangeListener != null)\r\n\t\t{\r\n\t\t\tthis.model.removeListener(this.graphModelChangeListener);\r\n\t\t\tthis.graphModelChangeListener = null;\r\n\t\t}\r\n\r\n\t\tthis.container = null;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxCellOverlay\r\n *\r\n * Extends <mxEventSource> to implement a graph overlay, represented by an icon\r\n * and a tooltip. Overlays can handle and fire <click> events and are added to\r\n * the graph using <mxGraph.addCellOverlay>, and removed using\r\n * <mxGraph.removeCellOverlay>, or <mxGraph.removeCellOverlays> to remove all overlays.\r\n * The <mxGraph.getCellOverlays> function returns the array of overlays for a given\r\n * cell in a graph. If multiple overlays exist for the same cell, then\r\n * <getBounds> should be overridden in at least one of the overlays.\r\n * \r\n * Overlays appear on top of all cells in a special layer. If this is not\r\n * desirable, then the image must be rendered as part of the shape or label of\r\n * the cell instead.\r\n *\r\n * Example:\r\n * \r\n * The following adds a new overlays for a given vertex and selects the cell\r\n * if the overlay is clicked.\r\n *\r\n * (code)\r\n * var overlay = new mxCellOverlay(img, html);\r\n * graph.addCellOverlay(vertex, overlay);\r\n * overlay.addListener(mxEvent.CLICK, function(sender, evt)\r\n * {\r\n *   var cell = evt.getProperty('cell');\r\n *   graph.setSelectionCell(cell);\r\n * });\r\n * (end)\r\n * \r\n * For cell overlays to be printed use <mxPrintPreview.printOverlays>.\r\n *\r\n * Event: mxEvent.CLICK\r\n *\r\n * Fires when the user clicks on the overlay. The <code>event</code> property\r\n * contains the corresponding mouse event and the <code>cell</code> property\r\n * contains the cell. For touch devices this is fired if the element receives\r\n * a touchend event.\r\n * \r\n * Constructor: mxCellOverlay\r\n *\r\n * Constructs a new overlay using the given image and tooltip.\r\n * \r\n * Parameters:\r\n * \r\n * image - <mxImage> that represents the icon to be displayed.\r\n * tooltip - Optional string that specifies the tooltip.\r\n * align - Optional horizontal alignment for the overlay. Possible\r\n * values are <ALIGN_LEFT>, <ALIGN_CENTER> and <ALIGN_RIGHT>\r\n * (default).\r\n * verticalAlign - Vertical alignment for the overlay. Possible\r\n * values are <ALIGN_TOP>, <ALIGN_MIDDLE> and <ALIGN_BOTTOM>\r\n * (default).\r\n */\r\nfunction mxCellOverlay(image, tooltip, align, verticalAlign, offset, cursor)\r\n{\r\n\tthis.image = image;\r\n\tthis.tooltip = tooltip;\r\n\tthis.align = (align != null) ? align : this.align;\r\n\tthis.verticalAlign = (verticalAlign != null) ? verticalAlign : this.verticalAlign;\r\n\tthis.offset = (offset != null) ? offset : new mxPoint();\r\n\tthis.cursor = (cursor != null) ? cursor : 'help';\r\n};\r\n\r\n/**\r\n * Extends mxEventSource.\r\n */\r\nmxCellOverlay.prototype = new mxEventSource();\r\nmxCellOverlay.prototype.constructor = mxCellOverlay;\r\n\r\n/**\r\n * Variable: image\r\n *\r\n * Holds the <mxImage> to be used as the icon.\r\n */\r\nmxCellOverlay.prototype.image = null;\r\n\r\n/**\r\n * Variable: tooltip\r\n * \r\n * Holds the optional string to be used as the tooltip.\r\n */\r\nmxCellOverlay.prototype.tooltip = null;\r\n\r\n/**\r\n * Variable: align\r\n * \r\n * Holds the horizontal alignment for the overlay. Default is\r\n * <mxConstants.ALIGN_RIGHT>. For edges, the overlay always appears in the\r\n * center of the edge.\r\n */\r\nmxCellOverlay.prototype.align = mxConstants.ALIGN_RIGHT;\r\n\r\n/**\r\n * Variable: verticalAlign\r\n * \r\n * Holds the vertical alignment for the overlay. Default is\r\n * <mxConstants.ALIGN_BOTTOM>. For edges, the overlay always appears in the\r\n * center of the edge.\r\n */\r\nmxCellOverlay.prototype.verticalAlign = mxConstants.ALIGN_BOTTOM;\r\n\r\n/**\r\n * Variable: offset\r\n * \r\n * Holds the offset as an <mxPoint>. The offset will be scaled according to the\r\n * current scale.\r\n */\r\nmxCellOverlay.prototype.offset = null;\r\n\r\n/**\r\n * Variable: cursor\r\n * \r\n * Holds the cursor for the overlay. Default is 'help'.\r\n */\r\nmxCellOverlay.prototype.cursor = null;\r\n\r\n/**\r\n * Variable: defaultOverlap\r\n * \r\n * Defines the overlapping for the overlay, that is, the proportional distance\r\n * from the origin to the point defined by the alignment. Default is 0.5.\r\n */\r\nmxCellOverlay.prototype.defaultOverlap = 0.5;\r\n\r\n/**\r\n * Function: getBounds\r\n * \r\n * Returns the bounds of the overlay for the given <mxCellState> as an\r\n * <mxRectangle>. This should be overridden when using multiple overlays\r\n * per cell so that the overlays do not overlap.\r\n * \r\n * The following example will place the overlay along an edge (where\r\n * x=[-1..1] from the start to the end of the edge and y is the\r\n * orthogonal offset in px).\r\n * \r\n * (code)\r\n * overlay.getBounds = function(state)\r\n * {\r\n *   var bounds = mxCellOverlay.prototype.getBounds.apply(this, arguments);\r\n *   \r\n *   if (state.view.graph.getModel().isEdge(state.cell))\r\n *   {\r\n *     var pt = state.view.getPoint(state, {x: 0, y: 0, relative: true});\r\n *     \r\n *     bounds.x = pt.x - bounds.width / 2;\r\n *     bounds.y = pt.y - bounds.height / 2;\r\n *   }\r\n *   \r\n *   return bounds;\r\n * };\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> that represents the current state of the\r\n * associated cell.\r\n */\r\nmxCellOverlay.prototype.getBounds = function(state)\r\n{\r\n\tvar isEdge = state.view.graph.getModel().isEdge(state.cell);\r\n\tvar s = state.view.scale;\r\n\tvar pt = null;\r\n\r\n\tvar w = this.image.width;\r\n\tvar h = this.image.height;\r\n\t\r\n\tif (isEdge)\r\n\t{\r\n\t\tvar pts = state.absolutePoints;\r\n\t\t\r\n\t\tif (pts.length % 2 == 1)\r\n\t\t{\r\n\t\t\tpt = pts[Math.floor(pts.length / 2)];\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar idx = pts.length / 2;\r\n\t\t\tvar p0 = pts[idx-1];\r\n\t\t\tvar p1 = pts[idx];\r\n\t\t\tpt = new mxPoint(p0.x + (p1.x - p0.x) / 2,\r\n\t\t\t\tp0.y + (p1.y - p0.y) / 2);\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tpt = new mxPoint();\r\n\t\t\r\n\t\tif (this.align == mxConstants.ALIGN_LEFT)\r\n\t\t{\r\n\t\t\tpt.x = state.x;\r\n\t\t}\r\n\t\telse if (this.align == mxConstants.ALIGN_CENTER)\r\n\t\t{\r\n\t\t\tpt.x = state.x + state.width / 2;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tpt.x = state.x + state.width;\r\n\t\t}\r\n\t\t\r\n\t\tif (this.verticalAlign == mxConstants.ALIGN_TOP)\r\n\t\t{\r\n\t\t\tpt.y = state.y;\r\n\t\t}\r\n\t\telse if (this.verticalAlign == mxConstants.ALIGN_MIDDLE)\r\n\t\t{\r\n\t\t\tpt.y = state.y + state.height / 2;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tpt.y = state.y + state.height;\r\n\t\t}\r\n\t}\r\n\r\n\treturn new mxRectangle(Math.round(pt.x - (w * this.defaultOverlap - this.offset.x) * s),\r\n\t\tMath.round(pt.y - (h * this.defaultOverlap - this.offset.y) * s), w * s, h * s);\r\n};\r\n\r\n/**\r\n * Function: toString\r\n * \r\n * Returns the textual representation of the overlay to be used as the\r\n * tooltip. This implementation returns <tooltip>.\r\n */\r\nmxCellOverlay.prototype.toString = function()\r\n{\r\n\treturn this.tooltip;\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxOutline\r\n *\r\n * Implements an outline (aka overview) for a graph. Set <updateOnPan> to true\r\n * to enable updates while the source graph is panning.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * var outline = new mxOutline(graph, div);\r\n * (end)\r\n * \r\n * If an outline is used in an <mxWindow> in IE8 standards mode, the following\r\n * code makes sure that the shadow filter is not inherited and that any\r\n * transparent elements in the graph do not show the page background, but the\r\n * background of the graph container.\r\n * \r\n * (code)\r\n * if (document.documentMode == 8)\r\n * {\r\n *   container.style.filter = 'progid:DXImageTransform.Microsoft.alpha(opacity=100)';\r\n * }\r\n * (end)\r\n * \r\n * To move the graph to the top, left corner the following code can be used.\r\n * \r\n * (code)\r\n * var scale = graph.view.scale;\r\n * var bounds = graph.getGraphBounds();\r\n * graph.view.setTranslate(-bounds.x / scale, -bounds.y / scale);\r\n * (end)\r\n * \r\n * To toggle the suspended mode, the following can be used.\r\n * \r\n * (code)\r\n * outline.suspended = !outln.suspended;\r\n * if (!outline.suspended)\r\n * {\r\n *   outline.update(true);\r\n * }\r\n * (end)\r\n * \r\n * Constructor: mxOutline\r\n *\r\n * Constructs a new outline for the specified graph inside the given\r\n * container.\r\n * \r\n * Parameters:\r\n * \r\n * source - <mxGraph> to create the outline for.\r\n * container - DOM node that will contain the outline.\r\n */\r\nfunction mxOutline(source, container)\r\n{\r\n\tthis.source = source;\r\n\r\n\tif (container != null)\r\n\t{\r\n\t\tthis.init(container);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: source\r\n * \r\n * Reference to the source <mxGraph>.\r\n */\r\nmxOutline.prototype.source = null;\r\n\r\n/**\r\n * Function: outline\r\n * \r\n * Reference to the <mxGraph> that renders the outline.\r\n */\r\nmxOutline.prototype.outline = null;\r\n\r\n/**\r\n * Function: graphRenderHint\r\n * \r\n * Renderhint to be used for the outline graph. Default is faster.\r\n */\r\nmxOutline.prototype.graphRenderHint = mxConstants.RENDERING_HINT_FASTER;\r\n\r\n/**\r\n * Variable: enabled\r\n * \r\n * Specifies if events are handled. Default is true.\r\n */\r\nmxOutline.prototype.enabled = true;\r\n\r\n/**\r\n * Variable: showViewport\r\n * \r\n * Specifies a viewport rectangle should be shown. Default is true.\r\n */\r\nmxOutline.prototype.showViewport = true;\r\n\r\n/**\r\n * Variable: border\r\n * \r\n * Border to be added at the bottom and right. Default is 10.\r\n */\r\nmxOutline.prototype.border = 10;\r\n\r\n/**\r\n * Variable: enabled\r\n * \r\n * Specifies the size of the sizer handler. Default is 8.\r\n */\r\nmxOutline.prototype.sizerSize = 8;\r\n\r\n/**\r\n * Variable: labelsVisible\r\n * \r\n * Specifies if labels should be visible in the outline. Default is false.\r\n */\r\nmxOutline.prototype.labelsVisible = false;\r\n\r\n/**\r\n * Variable: updateOnPan\r\n * \r\n * Specifies if <update> should be called for <mxEvent.PAN> in the source\r\n * graph. Default is false.\r\n */\r\nmxOutline.prototype.updateOnPan = false;\r\n\r\n/**\r\n * Variable: sizerImage\r\n * \r\n * Optional <mxImage> to be used for the sizer. Default is null.\r\n */\r\nmxOutline.prototype.sizerImage = null;\r\n\r\n/**\r\n * Variable: minScale\r\n * \r\n * Minimum scale to be used. Default is 0.001.\r\n */\r\nmxOutline.prototype.minScale = 0.0001;\r\n\r\n/**\r\n * Variable: suspended\r\n * \r\n * Optional boolean flag to suspend updates. Default is false. This flag will\r\n * also suspend repaints of the outline. To toggle this switch, use the\r\n * following code.\r\n * \r\n * (code)\r\n * nav.suspended = !nav.suspended;\r\n * \r\n * if (!nav.suspended)\r\n * {\r\n *   nav.update(true);\r\n * }\r\n * (end)\r\n */\r\nmxOutline.prototype.suspended = false;\r\n\r\n/**\r\n * Variable: forceVmlHandles\r\n * \r\n * Specifies if VML should be used to render the handles in this control. This\r\n * is true for IE8 standards mode and false for all other browsers and modes.\r\n * This is a workaround for rendering issues of HTML elements over elements\r\n * with filters in IE 8 standards mode.\r\n */\r\nmxOutline.prototype.forceVmlHandles = document.documentMode == 8;\r\n\r\n/**\r\n * Function: createGraph\r\n * \r\n * Creates the <mxGraph> used in the outline.\r\n */\r\nmxOutline.prototype.createGraph = function(container)\r\n{\r\n\tvar graph = new mxGraph(container, this.source.getModel(), this.graphRenderHint, this.source.getStylesheet());\r\n\tgraph.foldingEnabled = false;\r\n\tgraph.autoScroll = false;\r\n\t\r\n\treturn graph;\r\n};\r\n\r\n/**\r\n * Function: init\r\n * \r\n * Initializes the outline inside the given container.\r\n */\r\nmxOutline.prototype.init = function(container)\r\n{\r\n\tthis.outline = this.createGraph(container);\r\n\t\r\n\t// Do not repaint when suspended\r\n\tvar outlineGraphModelChanged = this.outline.graphModelChanged;\r\n\tthis.outline.graphModelChanged = mxUtils.bind(this, function(changes)\r\n\t{\r\n\t\tif (!this.suspended && this.outline != null)\r\n\t\t{\r\n\t\t\toutlineGraphModelChanged.apply(this.outline, arguments);\r\n\t\t}\r\n\t});\r\n\r\n\t// Enables faster painting in SVG\r\n\tif (mxClient.IS_SVG)\r\n\t{\r\n\t\tvar node = this.outline.getView().getCanvas().parentNode;\r\n\t\tnode.setAttribute('shape-rendering', 'optimizeSpeed');\r\n\t\tnode.setAttribute('image-rendering', 'optimizeSpeed');\r\n\t}\r\n\t\r\n\t// Hides cursors and labels\r\n\tthis.outline.labelsVisible = this.labelsVisible;\r\n\tthis.outline.setEnabled(false);\r\n\t\r\n\tthis.updateHandler = mxUtils.bind(this, function(sender, evt)\r\n\t{\r\n\t\tif (!this.suspended && !this.active)\r\n\t\t{\r\n\t\t\tthis.update();\r\n\t\t}\r\n\t});\r\n\t\r\n\t// Updates the scale of the outline after a change of the main graph\r\n\tthis.source.getModel().addListener(mxEvent.CHANGE, this.updateHandler);\r\n\tthis.outline.addMouseListener(this);\r\n\t\r\n\t// Adds listeners to keep the outline in sync with the source graph\r\n\tvar view = this.source.getView();\r\n\tview.addListener(mxEvent.SCALE, this.updateHandler);\r\n\tview.addListener(mxEvent.TRANSLATE, this.updateHandler);\r\n\tview.addListener(mxEvent.SCALE_AND_TRANSLATE, this.updateHandler);\r\n\tview.addListener(mxEvent.DOWN, this.updateHandler);\r\n\tview.addListener(mxEvent.UP, this.updateHandler);\r\n\r\n\t// Updates blue rectangle on scroll\r\n\tmxEvent.addListener(this.source.container, 'scroll', this.updateHandler);\r\n\t\r\n\tthis.panHandler = mxUtils.bind(this, function(sender)\r\n\t{\r\n\t\tif (this.updateOnPan)\r\n\t\t{\r\n\t\t\tthis.updateHandler.apply(this, arguments);\r\n\t\t}\r\n\t});\r\n\tthis.source.addListener(mxEvent.PAN, this.panHandler);\r\n\t\r\n\t// Refreshes the graph in the outline after a refresh of the main graph\r\n\tthis.refreshHandler = mxUtils.bind(this, function(sender)\r\n\t{\r\n\t\tthis.outline.setStylesheet(this.source.getStylesheet());\r\n\t\tthis.outline.refresh();\r\n\t});\r\n\tthis.source.addListener(mxEvent.REFRESH, this.refreshHandler);\r\n\r\n\t// Creates the blue rectangle for the viewport\r\n\tthis.bounds = new mxRectangle(0, 0, 0, 0);\r\n\tthis.selectionBorder = new mxRectangleShape(this.bounds, null,\r\n\t\tmxConstants.OUTLINE_COLOR, mxConstants.OUTLINE_STROKEWIDTH);\r\n\tthis.selectionBorder.dialect = this.outline.dialect;\r\n\r\n\tif (this.forceVmlHandles)\r\n\t{\r\n\t\tthis.selectionBorder.isHtmlAllowed = function()\r\n\t\t{\r\n\t\t\treturn false;\r\n\t\t};\r\n\t}\r\n\t\r\n\tthis.selectionBorder.init(this.outline.getView().getOverlayPane());\r\n\r\n\t// Handles event by catching the initial pointer start and then listening to the\r\n\t// complete gesture on the event target. This is needed because all the events\r\n\t// are routed via the initial element even if that element is removed from the\r\n\t// DOM, which happens when we repaint the selection border and zoom handles.\r\n\tvar handler = mxUtils.bind(this, function(evt)\r\n\t{\r\n\t\tvar t = mxEvent.getSource(evt);\r\n\t\t\r\n\t\tvar redirect = mxUtils.bind(this, function(evt)\r\n\t\t{\r\n\t\t\tthis.outline.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));\r\n\t\t});\r\n\t\t\r\n\t\tvar redirect2 = mxUtils.bind(this, function(evt)\r\n\t\t{\r\n\t\t\tmxEvent.removeGestureListeners(t, null, redirect, redirect2);\r\n\t\t\tthis.outline.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));\r\n\t\t});\r\n\t\t\r\n\t\tmxEvent.addGestureListeners(t, null, redirect, redirect2);\r\n\t\tthis.outline.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));\r\n\t});\r\n\t\r\n\tmxEvent.addGestureListeners(this.selectionBorder.node, handler);\r\n\r\n\t// Creates a small blue rectangle for sizing (sizer handle)\r\n\tthis.sizer = this.createSizer();\r\n\t\r\n\tif (this.forceVmlHandles)\r\n\t{\r\n\t\tthis.sizer.isHtmlAllowed = function()\r\n\t\t{\r\n\t\t\treturn false;\r\n\t\t};\r\n\t}\r\n\t\r\n\tthis.sizer.init(this.outline.getView().getOverlayPane());\r\n\t\r\n\tif (this.enabled)\r\n\t{\r\n\t\tthis.sizer.node.style.cursor = 'nwse-resize';\r\n\t}\r\n\t\r\n\tmxEvent.addGestureListeners(this.sizer.node, handler);\r\n\r\n\tthis.selectionBorder.node.style.display = (this.showViewport) ? '' : 'none';\r\n\tthis.sizer.node.style.display = this.selectionBorder.node.style.display;\r\n\tthis.selectionBorder.node.style.cursor = 'move';\r\n\r\n\tthis.update(false);\r\n};\r\n\r\n/**\r\n * Function: isEnabled\r\n * \r\n * Returns true if events are handled. This implementation\r\n * returns <enabled>.\r\n */\r\nmxOutline.prototype.isEnabled = function()\r\n{\r\n\treturn this.enabled;\r\n};\r\n\r\n/**\r\n * Function: setEnabled\r\n * \r\n * Enables or disables event handling. This implementation\r\n * updates <enabled>.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean that specifies the new enabled state.\r\n */\r\nmxOutline.prototype.setEnabled = function(value)\r\n{\r\n\tthis.enabled = value;\r\n};\r\n\r\n/**\r\n * Function: setZoomEnabled\r\n * \r\n * Enables or disables the zoom handling by showing or hiding the respective\r\n * handle.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean that specifies the new enabled state.\r\n */\r\nmxOutline.prototype.setZoomEnabled = function(value)\r\n{\r\n\tthis.sizer.node.style.visibility = (value) ? 'visible' : 'hidden';\r\n};\r\n\r\n/**\r\n * Function: refresh\r\n * \r\n * Invokes <update> and revalidate the outline. This method is deprecated.\r\n */\r\nmxOutline.prototype.refresh = function()\r\n{\r\n\tthis.update(true);\r\n};\r\n\r\n/**\r\n * Function: createSizer\r\n * \r\n * Creates the shape used as the sizer.\r\n */\r\nmxOutline.prototype.createSizer = function()\r\n{\r\n\tif (this.sizerImage != null)\r\n\t{\r\n\t\tvar sizer = new mxImageShape(new mxRectangle(0, 0, this.sizerImage.width, this.sizerImage.height), this.sizerImage.src);\r\n\t\tsizer.dialect = this.outline.dialect;\r\n\t\t\r\n\t\treturn sizer;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar sizer = new mxRectangleShape(new mxRectangle(0, 0, this.sizerSize, this.sizerSize),\r\n\t\t\tmxConstants.OUTLINE_HANDLE_FILLCOLOR, mxConstants.OUTLINE_HANDLE_STROKECOLOR);\r\n\t\tsizer.dialect = this.outline.dialect;\r\n\t\r\n\t\treturn sizer;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getSourceContainerSize\r\n * \r\n * Returns the size of the source container.\r\n */\r\nmxOutline.prototype.getSourceContainerSize = function()\r\n{\r\n\treturn new mxRectangle(0, 0, this.source.container.scrollWidth, this.source.container.scrollHeight);\r\n};\r\n\r\n/**\r\n * Function: getOutlineOffset\r\n * \r\n * Returns the offset for drawing the outline graph.\r\n */\r\nmxOutline.prototype.getOutlineOffset = function(scale)\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: getOutlineOffset\r\n * \r\n * Returns the offset for drawing the outline graph.\r\n */\r\nmxOutline.prototype.getSourceGraphBounds = function()\r\n{\r\n\treturn this.source.getGraphBounds();\r\n};\r\n\r\n/**\r\n * Function: update\r\n * \r\n * Updates the outline.\r\n */\r\nmxOutline.prototype.update = function(revalidate)\r\n{\r\n\tif (this.source != null && this.source.container != null &&\r\n\t\tthis.outline != null && this.outline.container != null)\r\n\t{\r\n\t\tvar sourceScale = this.source.view.scale;\r\n\t\tvar scaledGraphBounds = this.getSourceGraphBounds();\r\n\t\tvar unscaledGraphBounds = new mxRectangle(scaledGraphBounds.x / sourceScale + this.source.panDx,\r\n\t\t\t\tscaledGraphBounds.y / sourceScale + this.source.panDy, scaledGraphBounds.width / sourceScale,\r\n\t\t\t\tscaledGraphBounds.height / sourceScale);\r\n\r\n\t\tvar unscaledFinderBounds = new mxRectangle(0, 0,\r\n\t\t\tthis.source.container.clientWidth / sourceScale,\r\n\t\t\tthis.source.container.clientHeight / sourceScale);\r\n\t\t\r\n\t\tvar union = unscaledGraphBounds.clone();\r\n\t\tunion.add(unscaledFinderBounds);\r\n\t\r\n\t\t// Zooms to the scrollable area if that is bigger than the graph\r\n\t\tvar size = this.getSourceContainerSize();\r\n\t\tvar completeWidth = Math.max(size.width / sourceScale, union.width);\r\n\t\tvar completeHeight = Math.max(size.height / sourceScale, union.height);\r\n\t\r\n\t\tvar availableWidth = Math.max(0, this.outline.container.clientWidth - this.border);\r\n\t\tvar availableHeight = Math.max(0, this.outline.container.clientHeight - this.border);\r\n\t\t\r\n\t\tvar outlineScale = Math.min(availableWidth / completeWidth, availableHeight / completeHeight);\r\n\t\tvar scale = (isNaN(outlineScale)) ? this.minScale : Math.max(this.minScale, outlineScale);\r\n\r\n\t\tif (scale > 0)\r\n\t\t{\r\n\t\t\tif (this.outline.getView().scale != scale)\r\n\t\t\t{\r\n\t\t\t\tthis.outline.getView().scale = scale;\r\n\t\t\t\trevalidate = true;\r\n\t\t\t}\r\n\t\t\r\n\t\t\tvar navView = this.outline.getView();\r\n\t\t\t\r\n\t\t\tif (navView.currentRoot != this.source.getView().currentRoot)\r\n\t\t\t{\r\n\t\t\t\tnavView.setCurrentRoot(this.source.getView().currentRoot);\r\n\t\t\t}\r\n\r\n\t\t\tvar t = this.source.view.translate;\r\n\t\t\tvar tx = t.x + this.source.panDx;\r\n\t\t\tvar ty = t.y + this.source.panDy;\r\n\t\t\t\r\n\t\t\tvar off = this.getOutlineOffset(scale);\r\n\t\t\t\r\n\t\t\tif (off != null)\r\n\t\t\t{\r\n\t\t\t\ttx += off.x;\r\n\t\t\t\tty += off.y;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (unscaledGraphBounds.x < 0)\r\n\t\t\t{\r\n\t\t\t\ttx = tx - unscaledGraphBounds.x;\r\n\t\t\t}\r\n\t\t\tif (unscaledGraphBounds.y < 0)\r\n\t\t\t{\r\n\t\t\t\tty = ty - unscaledGraphBounds.y;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (navView.translate.x != tx || navView.translate.y != ty)\r\n\t\t\t{\r\n\t\t\t\tnavView.translate.x = tx;\r\n\t\t\t\tnavView.translate.y = ty;\r\n\t\t\t\trevalidate = true;\r\n\t\t\t}\r\n\t\t\r\n\t\t\t// Prepares local variables for computations\r\n\t\t\tvar t2 = navView.translate;\r\n\t\t\tscale = this.source.getView().scale;\r\n\t\t\tvar scale2 = scale / navView.scale;\r\n\t\t\tvar scale3 = 1.0 / navView.scale;\r\n\t\t\tvar container = this.source.container;\r\n\t\t\t\r\n\t\t\t// Updates the bounds of the viewrect in the navigation\r\n\t\t\tthis.bounds = new mxRectangle(\r\n\t\t\t\t(t2.x - t.x - this.source.panDx) / scale3,\r\n\t\t\t\t(t2.y - t.y - this.source.panDy) / scale3,\r\n\t\t\t\t(container.clientWidth / scale2),\r\n\t\t\t\t(container.clientHeight / scale2));\r\n\t\t\t\r\n\t\t\t// Adds the scrollbar offset to the finder\r\n\t\t\tthis.bounds.x += this.source.container.scrollLeft * navView.scale / scale;\r\n\t\t\tthis.bounds.y += this.source.container.scrollTop * navView.scale / scale;\r\n\t\t\t\r\n\t\t\tvar b = this.selectionBorder.bounds;\r\n\t\t\t\r\n\t\t\tif (b.x != this.bounds.x || b.y != this.bounds.y || b.width != this.bounds.width || b.height != this.bounds.height)\r\n\t\t\t{\r\n\t\t\t\tthis.selectionBorder.bounds = this.bounds;\r\n\t\t\t\tthis.selectionBorder.redraw();\r\n\t\t\t}\r\n\t\t\r\n\t\t\t// Updates the bounds of the zoom handle at the bottom right\r\n\t\t\tvar b = this.sizer.bounds;\r\n\t\t\tvar b2 = new mxRectangle(this.bounds.x + this.bounds.width - b.width / 2,\r\n\t\t\t\t\tthis.bounds.y + this.bounds.height - b.height / 2, b.width, b.height);\r\n\r\n\t\t\tif (b.x != b2.x || b.y != b2.y || b.width != b2.width || b.height != b2.height)\r\n\t\t\t{\r\n\t\t\t\tthis.sizer.bounds = b2;\r\n\t\t\t\t\r\n\t\t\t\t// Avoids update of visibility in redraw for VML\r\n\t\t\t\tif (this.sizer.node.style.visibility != 'hidden')\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.sizer.redraw();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (revalidate)\r\n\t\t\t{\r\n\t\t\t\tthis.outline.view.revalidate();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: mouseDown\r\n * \r\n * Handles the event by starting a translation or zoom.\r\n */\r\nmxOutline.prototype.mouseDown = function(sender, me)\r\n{\r\n\tif (this.enabled && this.showViewport)\r\n\t{\r\n\t\tvar tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.source.tolerance : 0;\r\n\t\tvar hit = (this.source.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)) ?\r\n\t\t\t\tnew mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null;\r\n\t\tthis.zoom = me.isSource(this.sizer) || (hit != null && mxUtils.intersects(shape.bounds, hit));\r\n\t\tthis.startX = me.getX();\r\n\t\tthis.startY = me.getY();\r\n\t\tthis.active = true;\r\n\r\n\t\tif (this.source.useScrollbarsForPanning && mxUtils.hasScrollbars(this.source.container))\r\n\t\t{\r\n\t\t\tthis.dx0 = this.source.container.scrollLeft;\r\n\t\t\tthis.dy0 = this.source.container.scrollTop;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.dx0 = 0;\r\n\t\t\tthis.dy0 = 0;\r\n\t\t}\r\n\t}\r\n\r\n\tme.consume();\r\n};\r\n\r\n/**\r\n * Function: mouseMove\r\n * \r\n * Handles the event by previewing the viewrect in <graph> and updating the\r\n * rectangle that represents the viewrect in the outline.\r\n */\r\nmxOutline.prototype.mouseMove = function(sender, me)\r\n{\r\n\tif (this.active)\r\n\t{\r\n\t\tthis.selectionBorder.node.style.display = (this.showViewport) ? '' : 'none';\r\n\t\tthis.sizer.node.style.display = this.selectionBorder.node.style.display; \r\n\r\n\t\tvar delta = this.getTranslateForEvent(me);\r\n\t\tvar dx = delta.x;\r\n\t\tvar dy = delta.y;\r\n\t\tvar bounds = null;\r\n\t\t\r\n\t\tif (!this.zoom)\r\n\t\t{\r\n\t\t\t// Previews the panning on the source graph\r\n\t\t\tvar scale = this.outline.getView().scale;\r\n\t\t\tbounds = new mxRectangle(this.bounds.x + dx,\r\n\t\t\t\tthis.bounds.y + dy, this.bounds.width, this.bounds.height);\r\n\t\t\tthis.selectionBorder.bounds = bounds;\r\n\t\t\tthis.selectionBorder.redraw();\r\n\t\t\tdx /= scale;\r\n\t\t\tdx *= this.source.getView().scale;\r\n\t\t\tdy /= scale;\r\n\t\t\tdy *= this.source.getView().scale;\r\n\t\t\tthis.source.panGraph(-dx - this.dx0, -dy - this.dy0);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Does *not* preview zooming on the source graph\r\n\t\t\tvar container = this.source.container;\r\n\t\t\tvar viewRatio = container.clientWidth / container.clientHeight;\r\n\t\t\tdy = dx / viewRatio;\r\n\t\t\tbounds = new mxRectangle(this.bounds.x,\r\n\t\t\t\tthis.bounds.y,\r\n\t\t\t\tMath.max(1, this.bounds.width + dx),\r\n\t\t\t\tMath.max(1, this.bounds.height + dy));\r\n\t\t\tthis.selectionBorder.bounds = bounds;\r\n\t\t\tthis.selectionBorder.redraw();\r\n\t\t}\r\n\t\t\r\n\t\t// Updates the zoom handle\r\n\t\tvar b = this.sizer.bounds;\r\n\t\tthis.sizer.bounds = new mxRectangle(\r\n\t\t\tbounds.x + bounds.width - b.width / 2,\r\n\t\t\tbounds.y + bounds.height - b.height / 2,\r\n\t\t\tb.width, b.height);\r\n\t\t\r\n\t\t// Avoids update of visibility in redraw for VML\r\n\t\tif (this.sizer.node.style.visibility != 'hidden')\r\n\t\t{\r\n\t\t\tthis.sizer.redraw();\r\n\t\t}\r\n\t\t\r\n\t\tme.consume();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getTranslateForEvent\r\n * \r\n * Gets the translate for the given mouse event. Here is an example to limit\r\n * the outline to stay within positive coordinates:\r\n * \r\n * (code)\r\n * outline.getTranslateForEvent = function(me)\r\n * {\r\n *   var pt = new mxPoint(me.getX() - this.startX, me.getY() - this.startY);\r\n *   \r\n *   if (!this.zoom)\r\n *   {\r\n *     var tr = this.source.view.translate;\r\n *     pt.x = Math.max(tr.x * this.outline.view.scale, pt.x);\r\n *     pt.y = Math.max(tr.y * this.outline.view.scale, pt.y);\r\n *   }\r\n *   \r\n *   return pt;\r\n * };\r\n * (end)\r\n */\r\nmxOutline.prototype.getTranslateForEvent = function(me)\r\n{\r\n\treturn new mxPoint(me.getX() - this.startX, me.getY() - this.startY);\r\n};\r\n\r\n/**\r\n * Function: mouseUp\r\n * \r\n * Handles the event by applying the translation or zoom to <graph>.\r\n */\r\nmxOutline.prototype.mouseUp = function(sender, me)\r\n{\r\n\tif (this.active)\r\n\t{\r\n\t\tvar delta = this.getTranslateForEvent(me);\r\n\t\tvar dx = delta.x;\r\n\t\tvar dy = delta.y;\r\n\t\t\r\n\t\tif (Math.abs(dx) > 0 || Math.abs(dy) > 0)\r\n\t\t{\r\n\t\t\tif (!this.zoom)\r\n\t\t\t{\r\n\t\t\t\t// Applies the new translation if the source\r\n\t\t\t\t// has no scrollbars\r\n\t\t\t\tif (!this.source.useScrollbarsForPanning ||\r\n\t\t\t\t\t!mxUtils.hasScrollbars(this.source.container))\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.source.panGraph(0, 0);\r\n\t\t\t\t\tdx /= this.outline.getView().scale;\r\n\t\t\t\t\tdy /= this.outline.getView().scale;\r\n\t\t\t\t\tvar t = this.source.getView().translate;\r\n\t\t\t\t\tthis.source.getView().setTranslate(t.x - dx, t.y - dy);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\t// Applies the new zoom\r\n\t\t\t\tvar w = this.selectionBorder.bounds.width;\r\n\t\t\t\tvar scale = this.source.getView().scale;\r\n\t\t\t\tthis.source.zoomTo(Math.max(this.minScale, scale - (dx * scale) / w), false);\r\n\t\t\t}\r\n\r\n\t\t\tthis.update();\r\n\t\t\tme.consume();\r\n\t\t}\r\n\t\t\t\r\n\t\t// Resets the state of the handler\r\n\t\tthis.index = null;\r\n\t\tthis.active = false;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Destroy this outline and removes all listeners from <source>.\r\n */\r\nmxOutline.prototype.destroy = function()\r\n{\r\n\tif (this.source != null)\r\n\t{\r\n\t\tthis.source.removeListener(this.panHandler);\r\n\t\tthis.source.removeListener(this.refreshHandler);\r\n\t\tthis.source.getModel().removeListener(this.updateHandler);\r\n\t\tthis.source.getView().removeListener(this.updateHandler);\r\n\t\tmxEvent.removeListener(this.source.container, 'scroll', this.updateHandler);\r\n\t\tthis.source = null;\r\n\t}\r\n\t\r\n\tif (this.outline != null)\r\n\t{\r\n\t\tthis.outline.removeMouseListener(this);\r\n\t\tthis.outline.destroy();\r\n\t\tthis.outline = null;\r\n\t}\r\n\r\n\tif (this.selectionBorder != null)\r\n\t{\r\n\t\tthis.selectionBorder.destroy();\r\n\t\tthis.selectionBorder = null;\r\n\t}\r\n\t\r\n\tif (this.sizer != null)\r\n\t{\r\n\t\tthis.sizer.destroy();\r\n\t\tthis.sizer = null;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxMultiplicity\r\n * \r\n * Defines invalid connections along with the error messages that they produce.\r\n * To add or remove rules on a graph, you must add/remove instances of this\r\n * class to <mxGraph.multiplicities>.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * graph.multiplicities.push(new mxMultiplicity(\r\n *   true, 'rectangle', null, null, 0, 2, ['circle'],\r\n *   'Only 2 targets allowed',\r\n *   'Only circle targets allowed'));\r\n * (end)\r\n * \r\n * Defines a rule where each rectangle must be connected to no more than 2\r\n * circles and no other types of targets are allowed.\r\n * \r\n * Constructor: mxMultiplicity\r\n * \r\n * Instantiate class mxMultiplicity in order to describe allowed\r\n * connections in a graph. Not all constraints can be enforced while\r\n * editing, some must be checked at validation time. The <countError> and\r\n * <typeError> are treated as resource keys in <mxResources>.\r\n * \r\n * Parameters:\r\n * \r\n * source - Boolean indicating if this rule applies to the source or target\r\n * terminal.\r\n * type - Type of the source or target terminal that this rule applies to.\r\n * See <type> for more information.\r\n * attr - Optional attribute name to match the source or target terminal.\r\n * value - Optional attribute value to match the source or target terminal.\r\n * min - Minimum number of edges for this rule. Default is 1.\r\n * max - Maximum number of edges for this rule. n means infinite. Default\r\n * is n.\r\n * validNeighbors - Array of types of the opposite terminal for which this\r\n * rule applies.\r\n * countError - Error to be displayed for invalid number of edges.\r\n * typeError - Error to be displayed for invalid opposite terminals.\r\n * validNeighborsAllowed - Optional boolean indicating if the array of\r\n * opposite types should be valid or invalid.\r\n */\r\nfunction mxMultiplicity(source, type, attr, value, min, max,\r\n\tvalidNeighbors, countError, typeError, validNeighborsAllowed)\r\n{\r\n\tthis.source = source;\r\n\tthis.type = type;\r\n\tthis.attr = attr;\r\n\tthis.value = value;\r\n\tthis.min = (min != null) ? min : 0;\r\n\tthis.max = (max != null) ? max : 'n';\r\n\tthis.validNeighbors = validNeighbors;\r\n\tthis.countError = mxResources.get(countError) || countError;\r\n\tthis.typeError = mxResources.get(typeError) || typeError;\r\n\tthis.validNeighborsAllowed = (validNeighborsAllowed != null) ?\r\n\t\tvalidNeighborsAllowed : true;\r\n};\r\n\r\n/**\r\n * Variable: type\r\n * \r\n * Defines the type of the source or target terminal. The type is a string\r\n * passed to <mxUtils.isNode> together with the source or target vertex\r\n * value as the first argument.\r\n */\r\nmxMultiplicity.prototype.type = null;\r\n\r\n/**\r\n * Variable: attr\r\n * \r\n * Optional string that specifies the attributename to be passed to\r\n * <mxUtils.isNode> to check if the rule applies to a cell.\r\n */\r\nmxMultiplicity.prototype.attr = null;\r\n\r\n/**\r\n * Variable: value\r\n * \r\n * Optional string that specifies the value of the attribute to be passed\r\n * to <mxUtils.isNode> to check if the rule applies to a cell.\r\n */\r\nmxMultiplicity.prototype.value = null;\r\n\r\n/**\r\n * Variable: source\r\n * \r\n * Boolean that specifies if the rule is applied to the source or target\r\n * terminal of an edge.\r\n */\r\nmxMultiplicity.prototype.source = null;\r\n\r\n/**\r\n * Variable: min\r\n * \r\n * Defines the minimum number of connections for which this rule applies.\r\n * Default is 0.\r\n */\r\nmxMultiplicity.prototype.min = null;\r\n\r\n/**\r\n * Variable: max\r\n * \r\n * Defines the maximum number of connections for which this rule applies.\r\n * A value of 'n' means unlimited times. Default is 'n'. \r\n */\r\nmxMultiplicity.prototype.max = null;\r\n\r\n/**\r\n * Variable: validNeighbors\r\n * \r\n * Holds an array of strings that specify the type of neighbor for which\r\n * this rule applies. The strings are used in <mxCell.is> on the opposite\r\n * terminal to check if the rule applies to the connection.\r\n */\r\nmxMultiplicity.prototype.validNeighbors = null;\r\n\r\n/**\r\n * Variable: validNeighborsAllowed\r\n * \r\n * Boolean indicating if the list of validNeighbors are those that are allowed\r\n * for this rule or those that are not allowed for this rule.\r\n */\r\nmxMultiplicity.prototype.validNeighborsAllowed = true;\r\n\r\n/**\r\n * Variable: countError\r\n * \r\n * Holds the localized error message to be displayed if the number of\r\n * connections for which the rule applies is smaller than <min> or greater\r\n * than <max>.\r\n */\r\nmxMultiplicity.prototype.countError = null;\r\n\r\n/**\r\n * Variable: typeError\r\n * \r\n * Holds the localized error message to be displayed if the type of the\r\n * neighbor for a connection does not match the rule.\r\n */\r\nmxMultiplicity.prototype.typeError = null;\r\n\r\n/**\r\n * Function: check\r\n * \r\n * Checks the multiplicity for the given arguments and returns the error\r\n * for the given connection or null if the multiplicity does not apply.\r\n *  \r\n * Parameters:\r\n * \r\n * graph - Reference to the enclosing <mxGraph> instance.\r\n * edge - <mxCell> that represents the edge to validate.\r\n * source - <mxCell> that represents the source terminal.\r\n * target - <mxCell> that represents the target terminal.\r\n * sourceOut - Number of outgoing edges from the source terminal.\r\n * targetIn - Number of incoming edges for the target terminal.\r\n */\r\nmxMultiplicity.prototype.check = function(graph, edge, source, target, sourceOut, targetIn)\r\n{\r\n\tvar error = '';\r\n\r\n\tif ((this.source && this.checkTerminal(graph, source, edge)) ||\r\n\t\t(!this.source && this.checkTerminal(graph, target, edge)))\r\n\t{\r\n\t\tif (this.countError != null && \r\n\t\t\t((this.source && (this.max == 0 || (sourceOut >= this.max))) ||\r\n\t\t\t(!this.source && (this.max == 0 || (targetIn >= this.max)))))\r\n\t\t{\r\n\t\t\terror += this.countError + '\\n';\r\n\t\t}\r\n\r\n\t\tif (this.validNeighbors != null && this.typeError != null && this.validNeighbors.length > 0)\r\n\t\t{\r\n\t\t\tvar isValid = this.checkNeighbors(graph, edge, source, target);\r\n\r\n\t\t\tif (!isValid)\r\n\t\t\t{\r\n\t\t\t\terror += this.typeError + '\\n';\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn (error.length > 0) ? error : null;\r\n};\r\n\r\n/**\r\n * Function: checkNeighbors\r\n * \r\n * Checks if there are any valid neighbours in <validNeighbors>. This is only\r\n * called if <validNeighbors> is a non-empty array.\r\n */\r\nmxMultiplicity.prototype.checkNeighbors = function(graph, edge, source, target)\r\n{\r\n\tvar sourceValue = graph.model.getValue(source);\r\n\tvar targetValue = graph.model.getValue(target);\r\n\tvar isValid = !this.validNeighborsAllowed;\r\n\tvar valid = this.validNeighbors;\r\n\t\r\n\tfor (var j = 0; j < valid.length; j++)\r\n\t{\r\n\t\tif (this.source &&\r\n\t\t\tthis.checkType(graph, targetValue, valid[j]))\r\n\t\t{\r\n\t\t\tisValid = this.validNeighborsAllowed;\r\n\t\t\tbreak;\r\n\t\t}\r\n\t\telse if (!this.source && \r\n\t\t\tthis.checkType(graph, sourceValue, valid[j]))\r\n\t\t{\r\n\t\t\tisValid = this.validNeighborsAllowed;\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn isValid;\r\n};\r\n\r\n/**\r\n * Function: checkTerminal\r\n * \r\n * Checks the given terminal cell and returns true if this rule applies. The\r\n * given cell is the source or target of the given edge, depending on\r\n * <source>. This implementation uses <checkType> on the terminal's value.\r\n */\r\nmxMultiplicity.prototype.checkTerminal = function(graph, terminal, edge)\r\n{\r\n\tvar value = graph.model.getValue(terminal);\r\n\t\r\n\treturn this.checkType(graph, value, this.type, this.attr, this.value);\r\n};\r\n\r\n/**\r\n * Function: checkType\r\n * \r\n * Checks the type of the given value.\r\n */\r\nmxMultiplicity.prototype.checkType = function(graph, value, type, attr, attrValue)\r\n{\r\n\tif (value != null)\r\n\t{\r\n\t\tif (!isNaN(value.nodeType)) // Checks if value is a DOM node\r\n\t\t{\r\n\t\t\treturn mxUtils.isNode(value, type, attr, attrValue);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn value == type;\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn false;\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxLayoutManager\r\n * \r\n * Implements a layout manager that runs a given layout after any changes to the graph:\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * var layoutMgr = new mxLayoutManager(graph);\r\n * layoutMgr.getLayout = function(cell)\r\n * {\r\n *   return layout;\r\n * };\r\n * (end)\r\n * \r\n * Event: mxEvent.LAYOUT_CELLS\r\n * \r\n * Fires between begin- and endUpdate after all cells have been layouted in\r\n * <layoutCells>. The <code>cells</code> property contains all cells that have\r\n * been passed to <layoutCells>.\r\n * \r\n * Constructor: mxLayoutManager\r\n *\r\n * Constructs a new automatic layout for the given graph.\r\n *\r\n * Arguments:\r\n * \r\n * graph - Reference to the enclosing graph. \r\n */\r\nfunction mxLayoutManager(graph)\r\n{\r\n\t// Executes the layout before the changes are dispatched\r\n\tthis.undoHandler = mxUtils.bind(this, function(sender, evt)\r\n\t{\r\n\t\tif (this.isEnabled())\r\n\t\t{\r\n\t\t\tthis.beforeUndo(evt.getProperty('edit'));\r\n\t\t}\r\n\t});\r\n\t\r\n\t// Notifies the layout of a move operation inside a parent\r\n\tthis.moveHandler = mxUtils.bind(this, function(sender, evt)\r\n\t{\r\n\t\tif (this.isEnabled())\r\n\t\t{\r\n\t\t\tthis.cellsMoved(evt.getProperty('cells'), evt.getProperty('event'));\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.setGraph(graph);\r\n};\r\n\r\n/**\r\n * Extends mxEventSource.\r\n */\r\nmxLayoutManager.prototype = new mxEventSource();\r\nmxLayoutManager.prototype.constructor = mxLayoutManager;\r\n\r\n/**\r\n * Variable: graph\r\n * \r\n * Reference to the enclosing <mxGraph>.\r\n */\r\nmxLayoutManager.prototype.graph = null;\r\n\r\n/**\r\n * Variable: bubbling\r\n * \r\n * Specifies if the layout should bubble along\r\n * the cell hierarchy. Default is true.\r\n */\r\nmxLayoutManager.prototype.bubbling = true;\r\n\r\n/**\r\n * Variable: enabled\r\n * \r\n * Specifies if event handling is enabled. Default is true.\r\n */\r\nmxLayoutManager.prototype.enabled = true;\r\n\r\n/**\r\n * Variable: updateHandler\r\n * \r\n * Holds the function that handles the endUpdate event.\r\n */\r\nmxLayoutManager.prototype.updateHandler = null;\r\n\r\n/**\r\n * Variable: moveHandler\r\n * \r\n * Holds the function that handles the move event.\r\n */\r\nmxLayoutManager.prototype.moveHandler = null;\r\n\r\n/**\r\n * Function: isEnabled\r\n * \r\n * Returns true if events are handled. This implementation\r\n * returns <enabled>.\r\n */\r\nmxLayoutManager.prototype.isEnabled = function()\r\n{\r\n\treturn this.enabled;\r\n};\r\n\r\n/**\r\n * Function: setEnabled\r\n * \r\n * Enables or disables event handling. This implementation\r\n * updates <enabled>.\r\n * \r\n * Parameters:\r\n * \r\n * enabled - Boolean that specifies the new enabled state.\r\n */\r\nmxLayoutManager.prototype.setEnabled = function(enabled)\r\n{\r\n\tthis.enabled = enabled;\r\n};\r\n\r\n/**\r\n * Function: isBubbling\r\n * \r\n * Returns true if a layout should bubble, that is, if the parent layout\r\n * should be executed whenever a cell layout (layout of the children of\r\n * a cell) has been executed. This implementation returns <bubbling>.\r\n */\r\nmxLayoutManager.prototype.isBubbling = function()\r\n{\r\n\treturn this.bubbling;\r\n};\r\n\r\n/**\r\n * Function: setBubbling\r\n * \r\n * Sets <bubbling>.\r\n */\r\nmxLayoutManager.prototype.setBubbling = function(value)\r\n{\r\n\tthis.bubbling = value;\r\n};\r\n\r\n/**\r\n * Function: getGraph\r\n * \r\n * Returns the graph that this layout operates on.\r\n */\r\nmxLayoutManager.prototype.getGraph = function()\r\n{\r\n\treturn this.graph;\r\n};\r\n\r\n/**\r\n * Function: setGraph\r\n * \r\n * Sets the graph that the layouts operate on.\r\n */\r\nmxLayoutManager.prototype.setGraph = function(graph)\r\n{\r\n\tif (this.graph != null)\r\n\t{\r\n\t\tvar model = this.graph.getModel();\t\t\r\n\t\tmodel.removeListener(this.undoHandler);\r\n\t\tthis.graph.removeListener(this.moveHandler);\r\n\t}\r\n\t\r\n\tthis.graph = graph;\r\n\t\r\n\tif (this.graph != null)\r\n\t{\r\n\t\tvar model = this.graph.getModel();\t\r\n\t\tmodel.addListener(mxEvent.BEFORE_UNDO, this.undoHandler);\r\n\t\tthis.graph.addListener(mxEvent.MOVE_CELLS, this.moveHandler);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getLayout\r\n * \r\n * Returns the layout to be executed for the given graph and parent.\r\n */\r\nmxLayoutManager.prototype.getLayout = function(parent)\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: beforeUndo\r\n * \r\n * Called from the undoHandler.\r\n *\r\n * Parameters:\r\n * \r\n * cell - Array of <mxCells> that have been moved.\r\n * evt - Mouse event that represents the mousedown.\r\n */\r\nmxLayoutManager.prototype.beforeUndo = function(undoableEdit)\r\n{\r\n\tvar cells = this.getCellsForChanges(undoableEdit.changes);\r\n\tvar model = this.getGraph().getModel();\r\n\r\n\t// Adds all descendants\r\n\tvar tmp = [];\r\n\t\r\n\tfor (var i = 0; i < cells.length; i++)\r\n\t{\r\n\t\ttmp = tmp.concat(model.getDescendants(cells[i]));\r\n\t}\r\n\t\r\n\tcells = tmp;\r\n\t\r\n\t// Adds all parent ancestors\r\n\tif (this.isBubbling())\r\n\t{\r\n\t\ttmp = model.getParents(cells);\r\n\t\t\r\n\t\twhile (tmp.length > 0)\r\n\t\t{\r\n\t\t\tcells = cells.concat(tmp);\r\n\t\t\ttmp = model.getParents(tmp);\r\n\t\t}\r\n\t}\r\n\t\r\n\tthis.executeLayoutForCells(cells);\r\n};\r\n\r\n/**\r\n * Function: executeLayout\r\n * \r\n * Executes the given layout on the given parent.\r\n */\r\nmxLayoutManager.prototype.executeLayoutForCells = function(cells)\r\n{\r\n\t// Adds reverse to this array to avoid duplicate execution of leafes\r\n\t// Works like capture/bubble for events, first executes all layout\r\n\t// from top to bottom and in reverse order and removes duplicates.\r\n\tvar sorted = mxUtils.sortCells(cells, true);\r\n\tsorted = sorted.concat(sorted.slice().reverse());\r\n\tthis.layoutCells(sorted);\r\n};\r\n\r\n/**\r\n * Function: cellsMoved\r\n * \r\n * Called from the moveHandler.\r\n *\r\n * Parameters:\r\n * \r\n * cell - Array of <mxCells> that have been moved.\r\n * evt - Mouse event that represents the mousedown.\r\n */\r\nmxLayoutManager.prototype.cellsMoved = function(cells, evt)\r\n{\r\n\tif (cells != null && evt != null)\r\n\t{\r\n\t\tvar point = mxUtils.convertPoint(this.getGraph().container,\r\n\t\t\tmxEvent.getClientX(evt), mxEvent.getClientY(evt));\r\n\t\tvar model = this.getGraph().getModel();\r\n\t\t\r\n\t\t// Checks if a layout exists to take care of the moving if the\r\n\t\t// parent itself is not being moved\r\n\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t{\r\n\t\t\tvar parent = model.getParent(cells[i]);\r\n\t\t\t\r\n\t\t\tif (mxUtils.indexOf(cells, parent) < 0)\r\n\t\t\t{\r\n\t\t\t\tvar layout = this.getLayout(parent);\r\n\t\r\n\t\t\t\tif (layout != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tlayout.moveCell(cells[i], point.x, point.y);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getCellsForEdit\r\n * \r\n * Returns the cells to be layouted for the given sequence of changes.\r\n */\r\nmxLayoutManager.prototype.getCellsForChanges = function(changes)\r\n{\r\n\tvar dict = new mxDictionary();\r\n\tvar result = [];\r\n\t\r\n\tfor (var i = 0; i < changes.length; i++)\r\n\t{\r\n\t\tvar change = changes[i];\r\n\t\t\r\n\t\tif (change instanceof mxRootChange)\r\n\t\t{\r\n\t\t\treturn [];\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar cells = this.getCellsForChange(change);\r\n\t\t\t\r\n\t\t\tfor (var j = 0; j < cells.length; j++)\r\n\t\t\t{\r\n\t\t\t\tif (cells[j] != null && !dict.get(cells[j]))\r\n\t\t\t\t{\r\n\t\t\t\t\tdict.put(cells[j], true);\r\n\t\t\t\t\tresult.push(cells[j]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: getCellsForChange\r\n * \r\n * Executes all layouts which have been scheduled during the\r\n * changes.\r\n */\r\nmxLayoutManager.prototype.getCellsForChange = function(change)\r\n{\r\n\tvar model = this.getGraph().getModel();\r\n\t\r\n\tif (change instanceof mxChildChange)\r\n\t{\r\n\t\treturn [change.child, change.previous, model.getParent(change.child)];\r\n\t}\r\n\telse if (change instanceof mxTerminalChange || change instanceof mxGeometryChange)\r\n\t{\r\n\t\treturn [change.cell, model.getParent(change.cell)];\r\n\t}\r\n\telse if (change instanceof mxVisibleChange || change instanceof mxStyleChange)\r\n\t{\r\n\t\treturn [change.cell];\r\n\t}\r\n\t\r\n\treturn [];\r\n};\r\n\r\n/**\r\n * Function: layoutCells\r\n * \r\n * Executes all layouts which have been scheduled during the\r\n * changes.\r\n */\r\nmxLayoutManager.prototype.layoutCells = function(cells)\r\n{\r\n\tif (cells.length > 0)\r\n\t{\r\n\t\t// Invokes the layouts while removing duplicates\r\n\t\tvar model = this.getGraph().getModel();\r\n\t\t\r\n\t\tmodel.beginUpdate();\r\n\t\ttry \r\n\t\t{\r\n\t\t\tvar last = null;\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t{\r\n\t\t\t\tif (cells[i] != model.getRoot() && cells[i] != last)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (this.executeLayout(this.getLayout(cells[i]), cells[i]))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tlast = cells[i];\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.LAYOUT_CELLS, 'cells', cells));\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tmodel.endUpdate();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: executeLayout\r\n * \r\n * Executes the given layout on the given parent.\r\n */\r\nmxLayoutManager.prototype.executeLayout = function(layout, parent)\r\n{\r\n\tvar result = false;\r\n\t\r\n\tif (layout != null && parent != null)\r\n\t{\r\n\t\tlayout.execute(parent);\r\n\t\tresult = true;\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Removes all handlers from the <graph> and deletes the reference to it.\r\n */\r\nmxLayoutManager.prototype.destroy = function()\r\n{\r\n\tthis.setGraph(null);\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxSwimlaneManager\r\n * \r\n * Manager for swimlanes and nested swimlanes that sets the size of newly added\r\n * swimlanes to that of their siblings, and propagates changes to the size of a\r\n * swimlane to its siblings, if <siblings> is true, and its ancestors, if\r\n * <bubbling> is true.\r\n * \r\n * Constructor: mxSwimlaneManager\r\n *\r\n * Constructs a new swimlane manager for the given graph.\r\n *\r\n * Arguments:\r\n * \r\n * graph - Reference to the enclosing graph. \r\n */\r\nfunction mxSwimlaneManager(graph, horizontal, addEnabled, resizeEnabled)\r\n{\r\n\tthis.horizontal = (horizontal != null) ? horizontal : true;\r\n\tthis.addEnabled = (addEnabled != null) ? addEnabled : true;\r\n\tthis.resizeEnabled = (resizeEnabled != null) ? resizeEnabled : true;\r\n\r\n\tthis.addHandler = mxUtils.bind(this, function(sender, evt)\r\n\t{\r\n\t\tif (this.isEnabled() && this.isAddEnabled())\r\n\t\t{\r\n\t\t\tthis.cellsAdded(evt.getProperty('cells'));\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.resizeHandler = mxUtils.bind(this, function(sender, evt)\r\n\t{\r\n\t\tif (this.isEnabled() && this.isResizeEnabled())\r\n\t\t{\r\n\t\t\tthis.cellsResized(evt.getProperty('cells'));\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.setGraph(graph);\r\n};\r\n\r\n/**\r\n * Extends mxEventSource.\r\n */\r\nmxSwimlaneManager.prototype = new mxEventSource();\r\nmxSwimlaneManager.prototype.constructor = mxSwimlaneManager;\r\n\r\n/**\r\n * Variable: graph\r\n * \r\n * Reference to the enclosing <mxGraph>.\r\n */\r\nmxSwimlaneManager.prototype.graph = null;\r\n\r\n/**\r\n * Variable: enabled\r\n * \r\n * Specifies if event handling is enabled. Default is true.\r\n */\r\nmxSwimlaneManager.prototype.enabled = true;\r\n\r\n/**\r\n * Variable: horizontal\r\n * \r\n * Specifies the orientation of the swimlanes. Default is true.\r\n */\r\nmxSwimlaneManager.prototype.horizontal = true;\r\n\r\n/**\r\n * Variable: addEnabled\r\n * \r\n * Specifies if newly added cells should be resized to match the size of their\r\n * existing siblings. Default is true.\r\n */\r\nmxSwimlaneManager.prototype.addEnabled = true;\r\n\r\n/**\r\n * Variable: resizeEnabled\r\n * \r\n * Specifies if resizing of swimlanes should be handled. Default is true.\r\n */\r\nmxSwimlaneManager.prototype.resizeEnabled = true;\r\n\r\n/**\r\n * Variable: moveHandler\r\n * \r\n * Holds the function that handles the move event.\r\n */\r\nmxSwimlaneManager.prototype.addHandler = null;\r\n\r\n/**\r\n * Variable: moveHandler\r\n * \r\n * Holds the function that handles the move event.\r\n */\r\nmxSwimlaneManager.prototype.resizeHandler = null;\r\n\r\n/**\r\n * Function: isEnabled\r\n * \r\n * Returns true if events are handled. This implementation\r\n * returns <enabled>.\r\n */\r\nmxSwimlaneManager.prototype.isEnabled = function()\r\n{\r\n\treturn this.enabled;\r\n};\r\n\r\n/**\r\n * Function: setEnabled\r\n * \r\n * Enables or disables event handling. This implementation\r\n * updates <enabled>.\r\n * \r\n * Parameters:\r\n * \r\n * enabled - Boolean that specifies the new enabled state.\r\n */\r\nmxSwimlaneManager.prototype.setEnabled = function(value)\r\n{\r\n\tthis.enabled = value;\r\n};\r\n\r\n/**\r\n * Function: isHorizontal\r\n * \r\n * Returns <horizontal>.\r\n */\r\nmxSwimlaneManager.prototype.isHorizontal = function()\r\n{\r\n\treturn this.horizontal;\r\n};\r\n\r\n/**\r\n * Function: setHorizontal\r\n * \r\n * Sets <horizontal>.\r\n */\r\nmxSwimlaneManager.prototype.setHorizontal = function(value)\r\n{\r\n\tthis.horizontal = value;\r\n};\r\n\r\n/**\r\n * Function: isAddEnabled\r\n * \r\n * Returns <addEnabled>.\r\n */\r\nmxSwimlaneManager.prototype.isAddEnabled = function()\r\n{\r\n\treturn this.addEnabled;\r\n};\r\n\r\n/**\r\n * Function: setAddEnabled\r\n * \r\n * Sets <addEnabled>.\r\n */\r\nmxSwimlaneManager.prototype.setAddEnabled = function(value)\r\n{\r\n\tthis.addEnabled = value;\r\n};\r\n\r\n/**\r\n * Function: isResizeEnabled\r\n * \r\n * Returns <resizeEnabled>.\r\n */\r\nmxSwimlaneManager.prototype.isResizeEnabled = function()\r\n{\r\n\treturn this.resizeEnabled;\r\n};\r\n\r\n/**\r\n * Function: setResizeEnabled\r\n * \r\n * Sets <resizeEnabled>.\r\n */\r\nmxSwimlaneManager.prototype.setResizeEnabled = function(value)\r\n{\r\n\tthis.resizeEnabled = value;\r\n};\r\n\r\n/**\r\n * Function: getGraph\r\n * \r\n * Returns the graph that this manager operates on.\r\n */\r\nmxSwimlaneManager.prototype.getGraph = function()\r\n{\r\n\treturn this.graph;\r\n};\r\n\r\n/**\r\n * Function: setGraph\r\n * \r\n * Sets the graph that the manager operates on.\r\n */\r\nmxSwimlaneManager.prototype.setGraph = function(graph)\r\n{\r\n\tif (this.graph != null)\r\n\t{\r\n\t\tthis.graph.removeListener(this.addHandler);\r\n\t\tthis.graph.removeListener(this.resizeHandler);\r\n\t}\r\n\t\r\n\tthis.graph = graph;\r\n\t\r\n\tif (this.graph != null)\r\n\t{\r\n\t\tthis.graph.addListener(mxEvent.ADD_CELLS, this.addHandler);\r\n\t\tthis.graph.addListener(mxEvent.CELLS_RESIZED, this.resizeHandler);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isSwimlaneIgnored\r\n * \r\n * Returns true if the given swimlane should be ignored.\r\n */\r\nmxSwimlaneManager.prototype.isSwimlaneIgnored = function(swimlane)\r\n{\r\n\treturn !this.getGraph().isSwimlane(swimlane);\r\n};\r\n\r\n/**\r\n * Function: isCellHorizontal\r\n * \r\n * Returns true if the given cell is horizontal. If the given cell is not a\r\n * swimlane, then the global orientation is returned.\r\n */\r\nmxSwimlaneManager.prototype.isCellHorizontal = function(cell)\r\n{\r\n\tif (this.graph.isSwimlane(cell))\r\n\t{\r\n\t\tvar style = this.graph.getCellStyle(cell);\r\n\t\t\r\n\t\treturn mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, 1) == 1;\r\n\t}\r\n\t\r\n\treturn !this.isHorizontal();\r\n};\r\n\r\n/**\r\n * Function: cellsAdded\r\n * \r\n * Called if any cells have been added.\r\n * \r\n * Parameters:\r\n * \r\n * cell - Array of <mxCells> that have been added.\r\n */\r\nmxSwimlaneManager.prototype.cellsAdded = function(cells)\r\n{\r\n\tif (cells != null)\r\n\t{\r\n\t\tvar model = this.getGraph().getModel();\r\n\r\n\t\tmodel.beginUpdate();\r\n\t\ttry\r\n\t\t{\r\n\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t{\r\n\t\t\t\tif (!this.isSwimlaneIgnored(cells[i]))\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.swimlaneAdded(cells[i]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tmodel.endUpdate();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: swimlaneAdded\r\n * \r\n * Updates the size of the given swimlane to match that of any existing\r\n * siblings swimlanes.\r\n * \r\n * Parameters:\r\n * \r\n * swimlane - <mxCell> that represents the new swimlane.\r\n */\r\nmxSwimlaneManager.prototype.swimlaneAdded = function(swimlane)\r\n{\r\n\tvar model = this.getGraph().getModel();\r\n\tvar parent = model.getParent(swimlane);\r\n\tvar childCount = model.getChildCount(parent);\r\n\tvar geo = null;\r\n\t\r\n\t// Finds the first valid sibling swimlane as reference\r\n\tfor (var i = 0; i < childCount; i++)\r\n\t{\r\n\t\tvar child = model.getChildAt(parent, i);\r\n\t\t\r\n\t\tif (child != swimlane && !this.isSwimlaneIgnored(child))\r\n\t\t{\r\n\t\t\tgeo = model.getGeometry(child);\r\n\t\t\t\r\n\t\t\tif (geo != null)\r\n\t\t\t{\t\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\t// Applies the size of the refernece to the newly added swimlane\r\n\tif (geo != null)\r\n\t{\r\n\t\tvar parentHorizontal = (parent != null) ? this.isCellHorizontal(parent) : this.horizontal;\r\n\t\tthis.resizeSwimlane(swimlane, geo.width, geo.height, parentHorizontal);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: cellsResized\r\n * \r\n * Called if any cells have been resizes. Calls <swimlaneResized> for all\r\n * swimlanes where <isSwimlaneIgnored> returns false.\r\n * \r\n * Parameters:\r\n * \r\n * cells - Array of <mxCells> whose size was changed.\r\n */\r\nmxSwimlaneManager.prototype.cellsResized = function(cells)\r\n{\r\n\tif (cells != null)\r\n\t{\r\n\t\tvar model = this.getGraph().getModel();\r\n\t\t\r\n\t\tmodel.beginUpdate();\r\n\t\ttry\r\n\t\t{\r\n\t\t\t// Finds the top-level swimlanes and adds offsets\r\n\t\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t\t{\r\n\t\t\t\tif (!this.isSwimlaneIgnored(cells[i]))\r\n\t\t\t\t{\r\n\t\t\t\t\tvar geo = model.getGeometry(cells[i]);\r\n\r\n\t\t\t\t\tif (geo != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar size = new mxRectangle(0, 0, geo.width, geo.height);\r\n\t\t\t\t\t\tvar top = cells[i];\r\n\t\t\t\t\t\tvar current = top;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\twhile (current != null)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\ttop = current;\r\n\t\t\t\t\t\t\tcurrent = model.getParent(current);\r\n\t\t\t\t\t\t\tvar tmp = (this.graph.isSwimlane(current)) ?\r\n\t\t\t\t\t\t\t\t\tthis.graph.getStartSize(current) :\r\n\t\t\t\t\t\t\t\t\tnew mxRectangle();\r\n\t\t\t\t\t\t\tsize.width += tmp.width;\r\n\t\t\t\t\t\t\tsize.height += tmp.height;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tvar parentHorizontal = (current != null) ? this.isCellHorizontal(current) : this.horizontal;\r\n\t\t\t\t\t\tthis.resizeSwimlane(top, size.width, size.height, parentHorizontal);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tmodel.endUpdate();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: resizeSwimlane\r\n * \r\n * Called from <cellsResized> for all swimlanes that are not ignored to update\r\n * the size of the siblings and the size of the parent swimlanes, recursively,\r\n * if <bubbling> is true.\r\n * \r\n * Parameters:\r\n * \r\n * swimlane - <mxCell> whose size has changed.\r\n */\r\nmxSwimlaneManager.prototype.resizeSwimlane = function(swimlane, w, h, parentHorizontal)\r\n{\r\n\tvar model = this.getGraph().getModel();\r\n\t\r\n\tmodel.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tvar horizontal = this.isCellHorizontal(swimlane);\r\n\t\t\r\n\t\tif (!this.isSwimlaneIgnored(swimlane))\r\n\t\t{\r\n\t\t\tvar geo = model.getGeometry(swimlane);\r\n\t\t\t\r\n\t\t\tif (geo != null)\r\n\t\t\t{\r\n\t\t\t\tif ((parentHorizontal && geo.height != h) || (!parentHorizontal && geo.width != w))\r\n\t\t\t\t{\r\n\t\t\t\t\tgeo = geo.clone();\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (parentHorizontal)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tgeo.height = h;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tgeo.width = w;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tmodel.setGeometry(swimlane, geo);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tvar tmp = (this.graph.isSwimlane(swimlane)) ?\r\n\t\t\t\tthis.graph.getStartSize(swimlane) :\r\n\t\t\t\tnew mxRectangle();\r\n\t\tw -= tmp.width;\r\n\t\th -= tmp.height;\r\n\t\t\r\n\t\tvar childCount = model.getChildCount(swimlane);\r\n\t\t\r\n\t\tfor (var i = 0; i < childCount; i++)\r\n\t\t{\r\n\t\t\tvar child = model.getChildAt(swimlane, i);\r\n\t\t\tthis.resizeSwimlane(child, w, h, horizontal);\r\n\t\t}\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tmodel.endUpdate();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Removes all handlers from the <graph> and deletes the reference to it.\r\n */\r\nmxSwimlaneManager.prototype.destroy = function()\r\n{\r\n\tthis.setGraph(null);\r\n};\r\n/**\r\n * Copyright (c) 2006-2017, JGraph Ltd\r\n * Copyright (c) 2006-2017, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxTemporaryCellStates\r\n * \r\n * Creates a temporary set of cell states.\r\n */\r\nfunction mxTemporaryCellStates(view, scale, cells, isCellVisibleFn, getLinkForCellState)\r\n{\r\n\tscale = (scale != null) ? scale : 1;\r\n\tthis.view = view;\r\n\t\r\n\t// Stores the previous state\r\n\tthis.oldValidateCellState = view.validateCellState;\r\n\tthis.oldBounds = view.getGraphBounds();\r\n\tthis.oldStates = view.getStates();\r\n\tthis.oldScale = view.getScale();\r\n\tthis.oldDoRedrawShape = view.graph.cellRenderer.doRedrawShape;\r\n\r\n\tvar self = this;\r\n\r\n\t// Overrides doRedrawShape and paint shape to add links on shapes\r\n\tif (getLinkForCellState != null)\r\n\t{\r\n\t\tview.graph.cellRenderer.doRedrawShape = function(state)\r\n\t\t{\r\n\t\t\tvar oldPaint = state.shape.paint;\r\n\t\t\t\r\n\t\t\tstate.shape.paint = function(c)\r\n\t\t\t{\r\n\t\t\t\tvar link = getLinkForCellState(state);\r\n\t\t\t\t\r\n\t\t\t\tif (link != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tc.setLink(link);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\toldPaint.apply(this, arguments);\r\n\t\t\t\t\r\n\t\t\t\tif (link != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tc.setLink(null);\r\n\t\t\t\t}\r\n\t\t\t};\r\n\t\t\t\r\n\t\t\tself.oldDoRedrawShape.apply(view.graph.cellRenderer, arguments);\r\n\t\t\tstate.shape.paint = oldPaint;\r\n\t\t};\r\n\t}\r\n\r\n\t// Overrides validateCellState to ignore invisible cells\r\n\tview.validateCellState = function(cell, resurse)\r\n\t{\r\n\t\tif (cell == null || isCellVisibleFn == null || isCellVisibleFn(cell))\r\n\t\t{\r\n\t\t\treturn self.oldValidateCellState.apply(view, arguments);\r\n\t\t}\r\n\t\t\r\n\t\treturn null;\r\n\t};\r\n\t\r\n\t// Creates space for new states\r\n\tview.setStates(new mxDictionary());\r\n\tview.setScale(scale);\r\n\t\r\n\tif (cells != null)\r\n\t{\r\n\t\tview.resetValidationState();\r\n\t\tvar bbox = null;\r\n\r\n\t\t// Validates the vertices and edges without adding them to\r\n\t\t// the model so that the original cells are not modified\r\n\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t{\r\n\t\t\tvar bounds = view.getBoundingBox(view.validateCellState(view.validateCell(cells[i])));\r\n\t\t\t\r\n\t\t\tif (bbox == null)\r\n\t\t\t{\r\n\t\t\t\tbbox = bounds;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tbbox.add(bounds);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tview.setGraphBounds(bbox || new mxRectangle());\r\n\t}\r\n};\r\n\r\n/**\r\n * Variable: view\r\n *\r\n * Holds the width of the rectangle. Default is 0.\r\n */\r\nmxTemporaryCellStates.prototype.view = null;\r\n\r\n/**\r\n * Variable: oldStates\r\n *\r\n * Holds the height of the rectangle. Default is 0.\r\n */\r\nmxTemporaryCellStates.prototype.oldStates = null;\r\n\r\n/**\r\n * Variable: oldBounds\r\n *\r\n * Holds the height of the rectangle. Default is 0.\r\n */\r\nmxTemporaryCellStates.prototype.oldBounds = null;\r\n\r\n/**\r\n * Variable: oldScale\r\n *\r\n * Holds the height of the rectangle. Default is 0.\r\n */\r\nmxTemporaryCellStates.prototype.oldScale = null;\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Returns the top, left corner as a new <mxPoint>.\r\n */\r\nmxTemporaryCellStates.prototype.destroy = function()\r\n{\r\n\tthis.view.setScale(this.oldScale);\r\n\tthis.view.setStates(this.oldStates);\r\n\tthis.view.setGraphBounds(this.oldBounds);\r\n\tthis.view.validateCellState = this.oldValidateCellState;\r\n\tthis.view.graph.cellRenderer.doRedrawShape = this.oldDoRedrawShape;\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n *\r\n * Class: mxCellStatePreview\r\n * \r\n * Implements a live preview for moving cells.\r\n * \r\n * Constructor: mxCellStatePreview\r\n * \r\n * Constructs a move preview for the given graph.\r\n * \r\n * Parameters:\r\n * \r\n * graph - Reference to the enclosing <mxGraph>.\r\n */\r\nfunction mxCellStatePreview(graph)\r\n{\r\n\tthis.deltas = new mxDictionary();\r\n\tthis.graph = graph;\r\n};\r\n\r\n/**\r\n * Variable: graph\r\n * \r\n * Reference to the enclosing <mxGraph>.\r\n */\r\nmxCellStatePreview.prototype.graph = null;\r\n\r\n/**\r\n * Variable: deltas\r\n * \r\n * Reference to the enclosing <mxGraph>.\r\n */\r\nmxCellStatePreview.prototype.deltas = null;\r\n\r\n/**\r\n * Variable: count\r\n * \r\n * Contains the number of entries in the map.\r\n */\r\nmxCellStatePreview.prototype.count = 0;\r\n\r\n/**\r\n * Function: isEmpty\r\n * \r\n * Returns true if this contains no entries.\r\n */\r\nmxCellStatePreview.prototype.isEmpty = function()\r\n{\r\n\treturn this.count == 0;\r\n};\r\n\r\n/**\r\n * Function: moveState\r\n */\r\nmxCellStatePreview.prototype.moveState = function(state, dx, dy, add, includeEdges)\r\n{\r\n\tadd = (add != null) ? add : true;\r\n\tincludeEdges = (includeEdges != null) ? includeEdges : true;\r\n\t\r\n\tvar delta = this.deltas.get(state.cell);\r\n\r\n\tif (delta == null)\r\n\t{\r\n\t\t// Note: Deltas stores the point and the state since the key is a string.\r\n\t\tdelta = {point: new mxPoint(dx, dy), state: state};\r\n\t\tthis.deltas.put(state.cell, delta);\r\n\t\tthis.count++;\r\n\t}\r\n\telse if (add)\r\n\t{\r\n\t\tdelta.point.x += dx;\r\n\t\tdelta.point.y += dy;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tdelta.point.x = dx;\r\n\t\tdelta.point.y = dy;\r\n\t}\r\n\t\r\n\tif (includeEdges)\r\n\t{\r\n\t\tthis.addEdges(state);\r\n\t}\r\n\t\r\n\treturn delta.point;\r\n};\r\n\r\n/**\r\n * Function: show\r\n */\r\nmxCellStatePreview.prototype.show = function(visitor)\r\n{\r\n\tthis.deltas.visit(mxUtils.bind(this, function(key, delta)\r\n\t{\r\n\t\tthis.translateState(delta.state, delta.point.x, delta.point.y);\r\n\t}));\r\n\t\r\n\tthis.deltas.visit(mxUtils.bind(this, function(key, delta)\r\n\t{\r\n\t\tthis.revalidateState(delta.state, delta.point.x, delta.point.y, visitor);\r\n\t}));\r\n};\r\n\r\n/**\r\n * Function: translateState\r\n */\r\nmxCellStatePreview.prototype.translateState = function(state, dx, dy)\r\n{\r\n\tif (state != null)\r\n\t{\r\n\t\tvar model = this.graph.getModel();\r\n\t\t\r\n\t\tif (model.isVertex(state.cell))\r\n\t\t{\r\n\t\t\tstate.view.updateCellState(state);\r\n\t\t\tvar geo = model.getGeometry(state.cell);\r\n\t\t\t\r\n\t\t\t// Moves selection cells and non-relative vertices in\r\n\t\t\t// the first phase so that edge terminal points will\r\n\t\t\t// be updated in the second phase\r\n\t\t\tif ((dx != 0 || dy != 0) && geo != null && (!geo.relative || this.deltas.get(state.cell) != null))\r\n\t\t\t{\r\n\t\t\t\tstate.x += dx;\r\n\t\t\t\tstate.y += dy;\r\n\t\t\t}\r\n\t\t}\r\n\t    \r\n\t    var childCount = model.getChildCount(state.cell);\r\n\t    \r\n\t    for (var i = 0; i < childCount; i++)\r\n\t    {\r\n\t    \tthis.translateState(state.view.getState(model.getChildAt(state.cell, i)), dx, dy);\r\n\t    }\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: revalidateState\r\n */\r\nmxCellStatePreview.prototype.revalidateState = function(state, dx, dy, visitor)\r\n{\r\n\tif (state != null)\r\n\t{\r\n\t\tvar model = this.graph.getModel();\r\n\t\t\r\n\t\t// Updates the edge terminal points and restores the\r\n\t\t// (relative) positions of any (relative) children\r\n\t\tif (model.isEdge(state.cell))\r\n\t\t{\r\n\t\t\tstate.view.updateCellState(state);\r\n\t\t}\r\n\r\n\t\tvar geo = this.graph.getCellGeometry(state.cell);\r\n\t\tvar pState = state.view.getState(model.getParent(state.cell));\r\n\t\t\r\n\t\t// Moves selection vertices which are relative\r\n\t\tif ((dx != 0 || dy != 0) && geo != null && geo.relative &&\r\n\t\t\tmodel.isVertex(state.cell) && (pState == null ||\r\n\t\t\tmodel.isVertex(pState.cell) || this.deltas.get(state.cell) != null))\r\n\t\t{\r\n\t\t\tstate.x += dx;\r\n\t\t\tstate.y += dy;\r\n\t\t}\r\n\t\t\r\n\t\tthis.graph.cellRenderer.redraw(state);\r\n\t\r\n\t\t// Invokes the visitor on the given state\r\n\t\tif (visitor != null)\r\n\t\t{\r\n\t\t\tvisitor(state);\r\n\t\t}\r\n\t\t\t\t\t\t\r\n\t    var childCount = model.getChildCount(state.cell);\r\n\t    \r\n\t    for (var i = 0; i < childCount; i++)\r\n\t    {\r\n\t    \tthis.revalidateState(this.graph.view.getState(model.getChildAt(state.cell, i)), dx, dy, visitor);\r\n\t    }\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: addEdges\r\n */\r\nmxCellStatePreview.prototype.addEdges = function(state)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\tvar edgeCount = model.getEdgeCount(state.cell);\r\n\r\n\tfor (var i = 0; i < edgeCount; i++)\r\n\t{\r\n\t\tvar s = state.view.getState(model.getEdgeAt(state.cell, i));\r\n\r\n\t\tif (s != null)\r\n\t\t{\r\n\t\t\tthis.moveState(s, 0, 0);\r\n\t\t}\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxConnectionConstraint\r\n * \r\n * Defines an object that contains the constraints about how to connect one\r\n * side of an edge to its terminal.\r\n * \r\n * Constructor: mxConnectionConstraint\r\n * \r\n * Constructs a new connection constraint for the given point and boolean\r\n * arguments.\r\n * \r\n * Parameters:\r\n * \r\n * point - Optional <mxPoint> that specifies the fixed location of the point\r\n * in relative coordinates. Default is null.\r\n * perimeter - Optional boolean that specifies if the fixed point should be\r\n * projected onto the perimeter of the terminal. Default is true.\r\n */\r\nfunction mxConnectionConstraint(point, perimeter, name, dx, dy)\r\n{\r\n\tthis.point = point;\r\n\tthis.perimeter = (perimeter != null) ? perimeter : true;\r\n\tthis.name = name;\r\n\tthis.dx = dx? dx : 0;\r\n\tthis.dy = dy? dy : 0;\r\n};\r\n\r\n/**\r\n * Variable: point\r\n * \r\n * <mxPoint> that specifies the fixed location of the connection point.\r\n */\r\nmxConnectionConstraint.prototype.point = null;\r\n\r\n/**\r\n * Variable: perimeter\r\n * \r\n * Boolean that specifies if the point should be projected onto the perimeter\r\n * of the terminal.\r\n */\r\nmxConnectionConstraint.prototype.perimeter = null;\r\n\r\n/**\r\n * Variable: name\r\n * \r\n * Optional string that specifies the name of the constraint.\r\n */\r\nmxConnectionConstraint.prototype.name = null;\r\n\r\n/**\r\n * Variable: dx\r\n * \r\n * Optional float that specifies the horizontal offset of the constraint.\r\n */\r\nmxConnectionConstraint.prototype.dx = null;\r\n\r\n/**\r\n * Variable: dy\r\n * \r\n * Optional float that specifies the vertical offset of the constraint.\r\n */\r\nmxConnectionConstraint.prototype.dy = null;\r\n\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxGraphHandler\r\n * \r\n * Graph event handler that handles selection. Individual cells are handled\r\n * separately using <mxVertexHandler> or one of the edge handlers. These\r\n * handlers are created using <mxGraph.createHandler> in\r\n * <mxGraphSelectionModel.cellAdded>.\r\n * \r\n * To avoid the container to scroll a moved cell into view, set\r\n * <scrollAfterMove> to false.\r\n * \r\n * Constructor: mxGraphHandler\r\n * \r\n * Constructs an event handler that creates handles for the\r\n * selection cells.\r\n * \r\n * Parameters:\r\n * \r\n * graph - Reference to the enclosing <mxGraph>.\r\n */\r\nfunction mxGraphHandler(graph)\r\n{\r\n\tthis.graph = graph;\r\n\tthis.graph.addMouseListener(this);\r\n\t\r\n\t// Repaints the handler after autoscroll\r\n\tthis.panHandler = mxUtils.bind(this, function()\r\n\t{\r\n\t\tthis.updatePreviewShape();\r\n\t\tthis.updateHint();\r\n\t});\r\n\t\r\n\tthis.graph.addListener(mxEvent.PAN, this.panHandler);\r\n\t\r\n\t// Handles escape keystrokes\r\n\tthis.escapeHandler = mxUtils.bind(this, function(sender, evt)\r\n\t{\r\n\t\tthis.reset();\r\n\t});\r\n\t\r\n\tthis.graph.addListener(mxEvent.ESCAPE, this.escapeHandler);\r\n\t\r\n\t// Updates the preview box for remote changes\r\n\tthis.refreshHandler = mxUtils.bind(this, function(sender, evt)\r\n\t{\r\n\t\tif (this.first != null)\r\n\t\t{\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tthis.bounds = this.graph.getView().getBounds(this.cells);\r\n\t\t\t\tthis.pBounds = this.getPreviewBounds(this.cells);\r\n\t\t\t\tthis.updatePreviewShape();\r\n\t\t\t}\r\n\t\t\tcatch (e)\r\n\t\t\t{\r\n\t\t\t\t// Resets the handler if cells have vanished\r\n\t\t\t\tthis.reset();\r\n\t\t\t}\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.graph.getModel().addListener(mxEvent.CHANGE, this.refreshHandler);\r\n};\r\n\r\n/**\r\n * Variable: graph\r\n * \r\n * Reference to the enclosing <mxGraph>.\r\n */\r\nmxGraphHandler.prototype.graph = null;\r\n\r\n/**\r\n * Variable: maxCells\r\n * \r\n * Defines the maximum number of cells to paint subhandles\r\n * for. Default is 50 for Firefox and 20 for IE. Set this\r\n * to 0 if you want an unlimited number of handles to be\r\n * displayed. This is only recommended if the number of\r\n * cells in the graph is limited to a small number, eg.\r\n * 500.\r\n */\r\nmxGraphHandler.prototype.maxCells = (mxClient.IS_IE) ? 20 : 50;\r\n\r\n/**\r\n * Variable: enabled\r\n * \r\n * Specifies if events are handled. Default is true.\r\n */\r\nmxGraphHandler.prototype.enabled = true;\r\n\r\n/**\r\n * Variable: highlightEnabled\r\n * \r\n * Specifies if drop targets under the mouse should be enabled. Default is\r\n * true.\r\n */\r\nmxGraphHandler.prototype.highlightEnabled = true;\r\n\r\n/**\r\n * Variable: cloneEnabled\r\n * \r\n * Specifies if cloning by control-drag is enabled. Default is true.\r\n */\r\nmxGraphHandler.prototype.cloneEnabled = true;\r\n\r\n/**\r\n * Variable: moveEnabled\r\n * \r\n * Specifies if moving is enabled. Default is true.\r\n */\r\nmxGraphHandler.prototype.moveEnabled = true;\r\n\r\n/**\r\n * Variable: guidesEnabled\r\n * \r\n * Specifies if other cells should be used for snapping the right, center or\r\n * left side of the current selection. Default is false.\r\n */\r\nmxGraphHandler.prototype.guidesEnabled = false;\r\n\r\n/**\r\n * Variable: guide\r\n * \r\n * Holds the <mxGuide> instance that is used for alignment.\r\n */\r\nmxGraphHandler.prototype.guide = null;\r\n\r\n/**\r\n * Variable: currentDx\r\n * \r\n * Stores the x-coordinate of the current mouse move.\r\n */\r\nmxGraphHandler.prototype.currentDx = null;\r\n\r\n/**\r\n * Variable: currentDy\r\n * \r\n * Stores the y-coordinate of the current mouse move.\r\n */\r\nmxGraphHandler.prototype.currentDy = null;\r\n\r\n/**\r\n * Variable: updateCursor\r\n * \r\n * Specifies if a move cursor should be shown if the mouse is over a movable\r\n * cell. Default is true.\r\n */\r\nmxGraphHandler.prototype.updateCursor = true;\r\n\r\n/**\r\n * Variable: selectEnabled\r\n * \r\n * Specifies if selecting is enabled. Default is true.\r\n */\r\nmxGraphHandler.prototype.selectEnabled = true;\r\n\r\n/**\r\n * Variable: removeCellsFromParent\r\n * \r\n * Specifies if cells may be moved out of their parents. Default is true.\r\n */\r\nmxGraphHandler.prototype.removeCellsFromParent = true;\r\n\r\n/**\r\n * Variable: connectOnDrop\r\n * \r\n * Specifies if drop events are interpreted as new connections if no other\r\n * drop action is defined. Default is false.\r\n */\r\nmxGraphHandler.prototype.connectOnDrop = false;\r\n\r\n/**\r\n * Variable: scrollOnMove\r\n * \r\n * Specifies if the view should be scrolled so that a moved cell is\r\n * visible. Default is true.\r\n */\r\nmxGraphHandler.prototype.scrollOnMove = true;\r\n\r\n/**\r\n * Variable: minimumSize\r\n * \r\n * Specifies the minimum number of pixels for the width and height of a\r\n * selection border. Default is 6.\r\n */\r\nmxGraphHandler.prototype.minimumSize = 6;\r\n\r\n/**\r\n * Variable: previewColor\r\n * \r\n * Specifies the color of the preview shape. Default is black.\r\n */\r\nmxGraphHandler.prototype.previewColor = 'black';\r\n\r\n/**\r\n * Variable: htmlPreview\r\n * \r\n * Specifies if the graph container should be used for preview. If this is used\r\n * then drop target detection relies entirely on <mxGraph.getCellAt> because\r\n * the HTML preview does not \"let events through\". Default is false.\r\n */\r\nmxGraphHandler.prototype.htmlPreview = false;\r\n\r\n/**\r\n * Variable: shape\r\n * \r\n * Reference to the <mxShape> that represents the preview.\r\n */\r\nmxGraphHandler.prototype.shape = null;\r\n\r\n/**\r\n * Variable: scaleGrid\r\n * \r\n * Specifies if the grid should be scaled. Default is false.\r\n */\r\nmxGraphHandler.prototype.scaleGrid = false;\r\n\r\n/**\r\n * Variable: rotationEnabled\r\n * \r\n * Specifies if the bounding box should allow for rotation. Default is true.\r\n */\r\nmxGraphHandler.prototype.rotationEnabled = true;\r\n\r\n/**\r\n * Function: isEnabled\r\n * \r\n * Returns <enabled>.\r\n */\r\nmxGraphHandler.prototype.isEnabled = function()\r\n{\r\n\treturn this.enabled;\r\n};\r\n\r\n/**\r\n * Function: setEnabled\r\n * \r\n * Sets <enabled>.\r\n */\r\nmxGraphHandler.prototype.setEnabled = function(value)\r\n{\r\n\tthis.enabled = value;\r\n};\r\n\r\n/**\r\n * Function: isCloneEnabled\r\n * \r\n * Returns <cloneEnabled>.\r\n */\r\nmxGraphHandler.prototype.isCloneEnabled = function()\r\n{\r\n\treturn this.cloneEnabled;\r\n};\r\n\r\n/**\r\n * Function: setCloneEnabled\r\n * \r\n * Sets <cloneEnabled>.\r\n * \r\n * Parameters:\r\n * \r\n * value - Boolean that specifies the new clone enabled state.\r\n */\r\nmxGraphHandler.prototype.setCloneEnabled = function(value)\r\n{\r\n\tthis.cloneEnabled = value;\r\n};\r\n\r\n/**\r\n * Function: isMoveEnabled\r\n * \r\n * Returns <moveEnabled>.\r\n */\r\nmxGraphHandler.prototype.isMoveEnabled = function()\r\n{\r\n\treturn this.moveEnabled;\r\n};\r\n\r\n/**\r\n * Function: setMoveEnabled\r\n * \r\n * Sets <moveEnabled>.\r\n */\r\nmxGraphHandler.prototype.setMoveEnabled = function(value)\r\n{\r\n\tthis.moveEnabled = value;\r\n};\r\n\r\n/**\r\n * Function: isSelectEnabled\r\n * \r\n * Returns <selectEnabled>.\r\n */\r\nmxGraphHandler.prototype.isSelectEnabled = function()\r\n{\r\n\treturn this.selectEnabled;\r\n};\r\n\r\n/**\r\n * Function: setSelectEnabled\r\n * \r\n * Sets <selectEnabled>.\r\n */\r\nmxGraphHandler.prototype.setSelectEnabled = function(value)\r\n{\r\n\tthis.selectEnabled = value;\r\n};\r\n\r\n/**\r\n * Function: isRemoveCellsFromParent\r\n * \r\n * Returns <removeCellsFromParent>.\r\n */\r\nmxGraphHandler.prototype.isRemoveCellsFromParent = function()\r\n{\r\n\treturn this.removeCellsFromParent;\r\n};\r\n\r\n/**\r\n * Function: setRemoveCellsFromParent\r\n * \r\n * Sets <removeCellsFromParent>.\r\n */\r\nmxGraphHandler.prototype.setRemoveCellsFromParent = function(value)\r\n{\r\n\tthis.removeCellsFromParent = value;\r\n};\r\n\r\n/**\r\n * Function: getInitialCellForEvent\r\n * \r\n * Hook to return initial cell for the given event.\r\n */\r\nmxGraphHandler.prototype.getInitialCellForEvent = function(me)\r\n{\r\n\treturn me.getCell();\r\n};\r\n\r\n/**\r\n * Function: isDelayedSelection\r\n * \r\n * Hook to return true for delayed selections.\r\n */\r\nmxGraphHandler.prototype.isDelayedSelection = function(cell, me)\r\n{\r\n\treturn this.graph.isCellSelected(cell);\r\n};\r\n\r\n/**\r\n * Function: consumeMouseEvent\r\n * \r\n * Consumes the given mouse event. NOTE: This may be used to enable click\r\n * events for links in labels on iOS as follows as consuming the initial\r\n * touchStart disables firing the subsequent click event on the link.\r\n * \r\n * <code>\r\n * mxGraphHandler.prototype.consumeMouseEvent = function(evtName, me)\r\n * {\r\n *   var source = mxEvent.getSource(me.getEvent());\r\n *   \r\n *   if (!mxEvent.isTouchEvent(me.getEvent()) || source.nodeName != 'A')\r\n *   {\r\n *     me.consume();\r\n *   }\r\n * }\r\n * </code>\r\n */\r\nmxGraphHandler.prototype.consumeMouseEvent = function(evtName, me)\r\n{\r\n\tme.consume();\r\n};\r\n\r\n/**\r\n * Function: mouseDown\r\n * \r\n * Handles the event by selecing the given cell and creating a handle for\r\n * it. By consuming the event all subsequent events of the gesture are\r\n * redirected to this handler.\r\n */\r\nmxGraphHandler.prototype.mouseDown = function(sender, me)\r\n{\r\n\tif (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled() &&\r\n\t\tme.getState() != null && !mxEvent.isMultiTouchEvent(me.getEvent()))\r\n\t{\r\n\t\tvar cell = this.getInitialCellForEvent(me);\r\n\t\tthis.delayedSelection = this.isDelayedSelection(cell, me);\r\n\t\tthis.cell = null;\r\n\t\t\r\n\t\tif (this.isSelectEnabled() && !this.delayedSelection)\r\n\t\t{\r\n\t\t\tthis.graph.selectCellForEvent(cell, me.getEvent());\r\n\t\t}\r\n\r\n\t\tif (this.isMoveEnabled())\r\n\t\t{\r\n\t\t\tvar model = this.graph.model;\r\n\t\t\tvar geo = model.getGeometry(cell);\r\n\r\n\t\t\tif (this.graph.isCellMovable(cell) && ((!model.isEdge(cell) || this.graph.getSelectionCount() > 1 ||\r\n\t\t\t\t(geo.points != null && geo.points.length > 0) || model.getTerminal(cell, true) == null ||\r\n\t\t\t\tmodel.getTerminal(cell, false) == null) || this.graph.allowDanglingEdges || \r\n\t\t\t\t(this.graph.isCloneEvent(me.getEvent()) && this.graph.isCellsCloneable())))\r\n\t\t\t{\r\n\t\t\t\tthis.start(cell, me.getX(), me.getY());\r\n\t\t\t}\r\n\t\t\telse if (this.delayedSelection)\r\n\t\t\t{\r\n\t\t\t\tthis.cell = cell;\r\n\t\t\t}\r\n\r\n\t\t\tthis.cellWasClicked = true;\r\n\t\t\tthis.consumeMouseEvent(mxEvent.MOUSE_DOWN, me);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getGuideStates\r\n * \r\n * Creates an array of cell states which should be used as guides.\r\n */\r\nmxGraphHandler.prototype.getGuideStates = function()\r\n{\r\n\tvar parent = this.graph.getDefaultParent();\r\n\tvar model = this.graph.getModel();\r\n\t\r\n\tvar filter = mxUtils.bind(this, function(cell)\r\n\t{\r\n\t\treturn this.graph.view.getState(cell) != null &&\r\n\t\t\tmodel.isVertex(cell) &&\r\n\t\t\tmodel.getGeometry(cell) != null &&\r\n\t\t\t!model.getGeometry(cell).relative;\r\n\t});\r\n\t\r\n\treturn this.graph.view.getCellStates(model.filterDescendants(filter, parent));\r\n};\r\n\r\n/**\r\n * Function: getCells\r\n * \r\n * Returns the cells to be modified by this handler. This implementation\r\n * returns all selection cells that are movable, or the given initial cell if\r\n * the given cell is not selected and movable. This handles the case of moving\r\n * unselectable or unselected cells.\r\n * \r\n * Parameters:\r\n * \r\n * initialCell - <mxCell> that triggered this handler.\r\n */\r\nmxGraphHandler.prototype.getCells = function(initialCell)\r\n{\r\n\tif (!this.delayedSelection && this.graph.isCellMovable(initialCell))\r\n\t{\r\n\t\treturn [initialCell];\r\n\t}\r\n\telse\r\n\t{\r\n\t\treturn this.graph.getMovableCells(this.graph.getSelectionCells());\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getPreviewBounds\r\n * \r\n * Returns the <mxRectangle> used as the preview bounds for\r\n * moving the given cells.\r\n */\r\nmxGraphHandler.prototype.getPreviewBounds = function(cells)\r\n{\r\n\tvar bounds = this.getBoundingBox(cells);\r\n\t\r\n\tif (bounds != null)\r\n\t{\r\n\t\t// Corrects width and height\r\n\t\tbounds.width = Math.max(0, bounds.width - 1);\r\n\t\tbounds.height = Math.max(0, bounds.height - 1);\r\n\t\t\r\n\t\tif (bounds.width < this.minimumSize)\r\n\t\t{\r\n\t\t\tvar dx = this.minimumSize - bounds.width;\r\n\t\t\tbounds.x -= dx / 2;\r\n\t\t\tbounds.width = this.minimumSize;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tbounds.x = Math.round(bounds.x);\r\n\t\t\tbounds.width = Math.ceil(bounds.width);\r\n\t\t}\r\n\t\t\r\n\t\tvar tr = this.graph.view.translate;\r\n\t\tvar s = this.graph.view.scale;\r\n\t\t\r\n\t\tif (bounds.height < this.minimumSize)\r\n\t\t{\r\n\t\t\tvar dy = this.minimumSize - bounds.height;\r\n\t\t\tbounds.y -= dy / 2;\r\n\t\t\tbounds.height = this.minimumSize;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tbounds.y = Math.round(bounds.y);\r\n\t\t\tbounds.height = Math.ceil(bounds.height);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn bounds;\r\n};\r\n\r\n/**\r\n * Function: getBoundingBox\r\n * \r\n * Returns the union of the <mxCellStates> for the given array of <mxCells>.\r\n * For vertices, this method uses the bounding box of the corresponding shape\r\n * if one exists. The bounding box of the corresponding text label and all\r\n * controls and overlays are ignored. See also: <mxGraphView.getBounds> and\r\n * <mxGraph.getBoundingBox>.\r\n *\r\n * Parameters:\r\n *\r\n * cells - Array of <mxCells> whose bounding box should be returned.\r\n */\r\nmxGraphHandler.prototype.getBoundingBox = function(cells)\r\n{\r\n\tvar result = null;\r\n\t\r\n\tif (cells != null && cells.length > 0)\r\n\t{\r\n\t\tvar model = this.graph.getModel();\r\n\t\t\r\n\t\tfor (var i = 0; i < cells.length; i++)\r\n\t\t{\r\n\t\t\tif (model.isVertex(cells[i]) || model.isEdge(cells[i]))\r\n\t\t\t{\r\n\t\t\t\tvar state = this.graph.view.getState(cells[i]);\r\n\t\t\t\r\n\t\t\t\tif (state != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar bbox = state;\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (model.isVertex(cells[i]) && state.shape != null && state.shape.boundingBox != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tbbox = state.shape.boundingBox;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (result == null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tresult = mxRectangle.fromRectangle(bbox);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tresult.add(bbox);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: createPreviewShape\r\n * \r\n * Creates the shape used to draw the preview for the given bounds.\r\n */\r\nmxGraphHandler.prototype.createPreviewShape = function(bounds)\r\n{\r\n\tvar shape = new mxRectangleShape(bounds, null, this.previewColor);\r\n\tshape.isDashed = true;\r\n\t\r\n\tif (this.htmlPreview)\r\n\t{\r\n\t\tshape.dialect = mxConstants.DIALECT_STRICTHTML;\r\n\t\tshape.init(this.graph.container);\r\n\t}\r\n\telse\r\n\t{\r\n\t\t// Makes sure to use either VML or SVG shapes in order to implement\r\n\t\t// event-transparency on the background area of the rectangle since\r\n\t\t// HTML shapes do not let mouseevents through even when transparent\r\n\t\tshape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?\r\n\t\t\tmxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;\r\n\t\tshape.init(this.graph.getView().getOverlayPane());\r\n\t\tshape.pointerEvents = false;\r\n\t\t\r\n\t\t// Workaround for artifacts on iOS\r\n\t\tif (mxClient.IS_IOS)\r\n\t\t{\r\n\t\t\tshape.getSvgScreenOffset = function()\r\n\t\t\t{\r\n\t\t\t\treturn 0;\r\n\t\t\t};\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn shape;\r\n};\r\n\r\n/**\r\n * Function: start\r\n * \r\n * Starts the handling of the mouse gesture.\r\n */\r\nmxGraphHandler.prototype.start = function(cell, x, y)\r\n{\r\n\tthis.cell = cell;\r\n\tthis.first = mxUtils.convertPoint(this.graph.container, x, y);\r\n\tthis.cells = this.getCells(this.cell);\r\n\tthis.bounds = this.graph.getView().getBounds(this.cells);\r\n\tthis.pBounds = this.getPreviewBounds(this.cells);\r\n\r\n\tif (this.guidesEnabled)\r\n\t{\r\n\t\tthis.guide = new mxGuide(this.graph, this.getGuideStates());\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: useGuidesForEvent\r\n * \r\n * Returns true if the guides should be used for the given <mxMouseEvent>.\r\n * This implementation returns <mxGuide.isEnabledForEvent>.\r\n */\r\nmxGraphHandler.prototype.useGuidesForEvent = function(me)\r\n{\r\n\treturn (this.guide != null) ? this.guide.isEnabledForEvent(me.getEvent()) : true;\r\n};\r\n\r\n\r\n/**\r\n * Function: snap\r\n * \r\n * Snaps the given vector to the grid and returns the given mxPoint instance.\r\n */\r\nmxGraphHandler.prototype.snap = function(vector)\r\n{\r\n\tvar scale = (this.scaleGrid) ? this.graph.view.scale : 1;\r\n\t\r\n\tvector.x = this.graph.snap(vector.x / scale) * scale;\r\n\tvector.y = this.graph.snap(vector.y / scale) * scale;\r\n\t\r\n\treturn vector;\r\n};\r\n\r\n/**\r\n * Function: getDelta\r\n * \r\n * Returns an <mxPoint> that represents the vector for moving the cells\r\n * for the given <mxMouseEvent>.\r\n */\r\nmxGraphHandler.prototype.getDelta = function(me)\r\n{\r\n\tvar point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY());\r\n\tvar s = this.graph.view.scale;\r\n\t\r\n\treturn new mxPoint(this.roundLength((point.x - this.first.x) / s) * s,\r\n\t\tthis.roundLength((point.y - this.first.y) / s) * s);\r\n};\r\n\r\n/**\r\n * Function: updateHint\r\n * \r\n * Hook for subclassers do show details while the handler is active.\r\n */\r\nmxGraphHandler.prototype.updateHint = function(me) { };\r\n\r\n/**\r\n * Function: removeHint\r\n * \r\n * Hooks for subclassers to hide details when the handler gets inactive.\r\n */\r\nmxGraphHandler.prototype.removeHint = function() { };\r\n\r\n/**\r\n * Function: roundLength\r\n * \r\n * Hook for rounding the unscaled vector. This uses Math.round.\r\n */\r\nmxGraphHandler.prototype.roundLength = function(length)\r\n{\r\n\treturn Math.round(length * 2) / 2;\r\n};\r\n\r\n/**\r\n * Function: mouseMove\r\n * \r\n * Handles the event by highlighting possible drop targets and updating the\r\n * preview.\r\n */\r\nmxGraphHandler.prototype.mouseMove = function(sender, me)\r\n{\r\n\tvar graph = this.graph;\r\n\r\n\tif (!me.isConsumed() && graph.isMouseDown && this.cell != null &&\r\n\t\tthis.first != null && this.bounds != null)\r\n\t{\r\n\t\t// Stops moving if a multi touch event is received\r\n\t\tif (mxEvent.isMultiTouchEvent(me.getEvent()))\r\n\t\t{\r\n\t\t\tthis.reset();\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tvar delta = this.getDelta(me);\r\n\t\tvar dx = delta.x;\r\n\t\tvar dy = delta.y;\r\n\t\tvar tol = graph.tolerance;\r\n\r\n\t\tif (this.shape != null || Math.abs(dx) > tol || Math.abs(dy) > tol)\r\n\t\t{\r\n\t\t\t// Highlight is used for highlighting drop targets\r\n\t\t\tif (this.highlight == null)\r\n\t\t\t{\r\n\t\t\t\tthis.highlight = new mxCellHighlight(this.graph,\r\n\t\t\t\t\tmxConstants.DROP_TARGET_COLOR, 3);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (this.shape == null)\r\n\t\t\t{\r\n\t\t\t\tthis.shape = this.createPreviewShape(this.bounds);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar clone = graph.isCloneEvent(me.getEvent()) && graph.isCellsCloneable() && this.isCloneEnabled();\r\n\t\t\tvar gridEnabled = graph.isGridEnabledEvent(me.getEvent());\r\n\t\t\tvar hideGuide = true;\r\n\t\t\t\r\n\t\t\tif (this.guide != null && this.useGuidesForEvent(me))\r\n\t\t\t{\r\n\t\t\t\tdelta = this.guide.move(this.bounds, new mxPoint(dx, dy), gridEnabled, clone);\r\n\t\t\t\thideGuide = false;\r\n\t\t\t\tdx = delta.x;\r\n\t\t\t\tdy = delta.y;\r\n\t\t\t}\r\n\t\t\telse if (gridEnabled)\r\n\t\t\t{\r\n\t\t\t\tvar trx = graph.getView().translate;\r\n\t\t\t\tvar scale = graph.getView().scale;\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\tvar tx = this.bounds.x - (graph.snap(this.bounds.x / scale - trx.x) + trx.x) * scale;\r\n\t\t\t\tvar ty = this.bounds.y - (graph.snap(this.bounds.y / scale - trx.y) + trx.y) * scale;\r\n\t\t\t\tvar v = this.snap(new mxPoint(dx, dy));\r\n\t\t\t\r\n\t\t\t\tdx = v.x - tx;\r\n\t\t\t\tdy = v.y - ty;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (this.guide != null && hideGuide)\r\n\t\t\t{\r\n\t\t\t\tthis.guide.hide();\r\n\t\t\t}\r\n\r\n\t\t\t// Constrained movement if shift key is pressed\r\n\t\t\tif (graph.isConstrainedEvent(me.getEvent()))\r\n\t\t\t{\r\n\t\t\t\tif (Math.abs(dx) > Math.abs(dy))\r\n\t\t\t\t{\r\n\t\t\t\t\tdy = 0;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tdx = 0;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tthis.currentDx = dx;\r\n\t\t\tthis.currentDy = dy;\r\n\t\t\tthis.updatePreviewShape();\r\n\r\n\t\t\tvar target = null;\r\n\t\t\tvar cell = me.getCell();\r\n\r\n\t\t\tif (graph.isDropEnabled() && this.highlightEnabled)\r\n\t\t\t{\r\n\t\t\t\t// Contains a call to getCellAt to find the cell under the mouse\r\n\t\t\t\ttarget = graph.getDropTarget(this.cells, me.getEvent(), cell, clone);\r\n\t\t\t}\r\n\r\n\t\t\tvar state = graph.getView().getState(target);\r\n\t\t\tvar highlight = false;\r\n\t\t\t\r\n\t\t\tif (state != null && (graph.model.getParent(this.cell) != target || clone))\r\n\t\t\t{\r\n\t\t\t    if (this.target != target)\r\n\t\t\t    {\r\n\t\t\t\t    this.target = target;\r\n\t\t\t\t    this.setHighlightColor(mxConstants.DROP_TARGET_COLOR);\r\n\t\t\t\t}\r\n\t\t\t    \r\n\t\t\t    highlight = true;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.target = null;\r\n\r\n\t\t\t\tif (this.connectOnDrop && cell != null && this.cells.length == 1 &&\r\n\t\t\t\t\tgraph.getModel().isVertex(cell) && graph.isCellConnectable(cell))\r\n\t\t\t\t{\r\n\t\t\t\t\tstate = graph.getView().getState(cell);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (state != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar error = graph.getEdgeValidationError(null, this.cell, cell);\r\n\t\t\t\t\t\tvar color = (error == null) ?\r\n\t\t\t\t\t\t\tmxConstants.VALID_COLOR :\r\n\t\t\t\t\t\t\tmxConstants.INVALID_CONNECT_TARGET_COLOR;\r\n\t\t\t\t\t\tthis.setHighlightColor(color);\r\n\t\t\t\t\t\thighlight = true;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (state != null && highlight)\r\n\t\t\t{\r\n\t\t\t\tthis.highlight.highlight(state);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.highlight.hide();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.updateHint(me);\r\n\t\tthis.consumeMouseEvent(mxEvent.MOUSE_MOVE, me);\r\n\t\t\r\n\t\t// Cancels the bubbling of events to the container so\r\n\t\t// that the droptarget is not reset due to an mouseMove\r\n\t\t// fired on the container with no associated state.\r\n\t\tmxEvent.consume(me.getEvent());\r\n\t}\r\n\telse if ((this.isMoveEnabled() || this.isCloneEnabled()) && this.updateCursor && !me.isConsumed() &&\r\n\t\t(me.getState() != null || me.sourceState != null) && !graph.isMouseDown)\r\n\t{\r\n\t\tvar cursor = graph.getCursorForMouseEvent(me);\r\n\t\t\r\n\t\tif (cursor == null && graph.isEnabled() && graph.isCellMovable(me.getCell()))\r\n\t\t{\r\n\t\t\tif (graph.getModel().isEdge(me.getCell()))\r\n\t\t\t{\r\n\t\t\t\tcursor = mxConstants.CURSOR_MOVABLE_EDGE;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tcursor = mxConstants.CURSOR_MOVABLE_VERTEX;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Sets the cursor on the original source state under the mouse\r\n\t\t// instead of the event source state which can be the parent\r\n\t\tif (cursor != null && me.sourceState != null)\r\n\t\t{\r\n\t\t\tme.sourceState.setCursor(cursor);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: updatePreviewShape\r\n * \r\n * Updates the bounds of the preview shape.\r\n */\r\nmxGraphHandler.prototype.updatePreviewShape = function()\r\n{\r\n\tif (this.shape != null)\r\n\t{\r\n\t\tthis.shape.bounds = new mxRectangle(Math.round(this.pBounds.x + this.currentDx - this.graph.panDx),\r\n\t\t\t\tMath.round(this.pBounds.y + this.currentDy - this.graph.panDy), this.pBounds.width, this.pBounds.height);\r\n\t\tthis.shape.redraw();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setHighlightColor\r\n * \r\n * Sets the color of the rectangle used to highlight drop targets.\r\n * \r\n * Parameters:\r\n * \r\n * color - String that represents the new highlight color.\r\n */\r\nmxGraphHandler.prototype.setHighlightColor = function(color)\r\n{\r\n\tif (this.highlight != null)\r\n\t{\r\n\t\tthis.highlight.setHighlightColor(color);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: mouseUp\r\n * \r\n * Handles the event by applying the changes to the selection cells.\r\n */\r\nmxGraphHandler.prototype.mouseUp = function(sender, me)\r\n{\r\n\tif (!me.isConsumed())\r\n\t{\r\n\t\tvar graph = this.graph;\r\n\t\t\r\n\t\tif (this.cell != null && this.first != null && this.shape != null &&\r\n\t\t\tthis.currentDx != null && this.currentDy != null)\r\n\t\t{\r\n\t\t\tvar cell = me.getCell();\r\n\t\t\t\r\n\t\t\tif (this.connectOnDrop && this.target == null && cell != null && graph.getModel().isVertex(cell) &&\r\n\t\t\t\tgraph.isCellConnectable(cell) && graph.isEdgeValid(null, this.cell, cell))\r\n\t\t\t{\r\n\t\t\t\tgraph.connectionHandler.connect(this.cell, cell, me.getEvent());\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tvar clone = graph.isCloneEvent(me.getEvent()) && graph.isCellsCloneable() && this.isCloneEnabled();\r\n\t\t\t\tvar scale = graph.getView().scale;\r\n\t\t\t\tvar dx = this.roundLength(this.currentDx / scale);\r\n\t\t\t\tvar dy = this.roundLength(this.currentDy / scale);\r\n\t\t\t\tvar target = this.target;\r\n\t\t\t\t\r\n\t\t\t\tif (graph.isSplitEnabled() && graph.isSplitTarget(target, this.cells, me.getEvent()))\r\n\t\t\t\t{\r\n\t\t\t\t\tgraph.splitEdge(target, this.cells, null, dx, dy);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.moveCells(this.cells, dx, dy, clone, this.target, me.getEvent());\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (this.isSelectEnabled() && this.delayedSelection && this.cell != null)\r\n\t\t{\r\n\t\t\tthis.selectDelayed(me);\r\n\t\t}\r\n\t}\r\n\r\n\t// Consumes the event if a cell was initially clicked\r\n\tif (this.cellWasClicked)\r\n\t{\r\n\t\tthis.consumeMouseEvent(mxEvent.MOUSE_UP, me);\r\n\t}\r\n\r\n\tthis.reset();\r\n};\r\n\r\n/**\r\n * Function: selectDelayed\r\n * \r\n * Implements the delayed selection for the given mouse event.\r\n */\r\nmxGraphHandler.prototype.selectDelayed = function(me)\r\n{\r\n\tif (!this.graph.isCellSelected(this.cell) || !this.graph.popupMenuHandler.isPopupTrigger(me))\r\n\t{\r\n\t\tthis.graph.selectCellForEvent(this.cell, me.getEvent());\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: reset\r\n * \r\n * Resets the state of this handler.\r\n */\r\nmxGraphHandler.prototype.reset = function()\r\n{\r\n\tthis.destroyShapes();\r\n\tthis.removeHint();\r\n\t\r\n\tthis.cellWasClicked = false;\r\n\tthis.delayedSelection = false;\r\n\tthis.currentDx = null;\r\n\tthis.currentDy = null;\r\n\tthis.guides = null;\r\n\tthis.first = null;\r\n\tthis.cell = null;\r\n\tthis.target = null;\r\n};\r\n\r\n/**\r\n * Function: shouldRemoveCellsFromParent\r\n * \r\n * Returns true if the given cells should be removed from the parent for the specified\r\n * mousereleased event.\r\n */\r\nmxGraphHandler.prototype.shouldRemoveCellsFromParent = function(parent, cells, evt)\r\n{\r\n\tif (this.graph.getModel().isVertex(parent))\r\n\t{\r\n\t\tvar pState = this.graph.getView().getState(parent);\r\n\t\t\r\n\t\tif (pState != null)\r\n\t\t{\r\n\t\t\tvar pt = mxUtils.convertPoint(this.graph.container,\r\n\t\t\t\tmxEvent.getClientX(evt), mxEvent.getClientY(evt));\r\n\t\t\tvar alpha = mxUtils.toRadians(mxUtils.getValue(pState.style, mxConstants.STYLE_ROTATION) || 0);\r\n\t\t\t\r\n\t\t\tif (alpha != 0)\r\n\t\t\t{\r\n\t\t\t\tvar cos = Math.cos(-alpha);\r\n\t\t\t\tvar sin = Math.sin(-alpha);\r\n\t\t\t\tvar cx = new mxPoint(pState.getCenterX(), pState.getCenterY());\r\n\t\t\t\tpt = mxUtils.getRotatedPoint(pt, cos, sin, cx);\r\n\t\t\t}\r\n\t\t\r\n\t\t\treturn !mxUtils.contains(pState, pt.x, pt.y);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: moveCells\r\n * \r\n * Moves the given cells by the specified amount.\r\n */\r\nmxGraphHandler.prototype.moveCells = function(cells, dx, dy, clone, target, evt)\r\n{\r\n\tif (clone)\r\n\t{\r\n\t\tcells = this.graph.getCloneableCells(cells);\r\n\t}\r\n\t\r\n\t// Removes cells from parent\r\n\tif (target == null && this.isRemoveCellsFromParent() &&\r\n\t\tthis.shouldRemoveCellsFromParent(this.graph.getModel().getParent(this.cell), cells, evt))\r\n\t{\r\n\t\ttarget = this.graph.getDefaultParent();\r\n\t}\r\n\t\r\n\t// Cloning into locked cells is not allowed\r\n\tclone = clone && !this.graph.isCellLocked(target || this.graph.getDefaultParent());\r\n\t\r\n\t// Passes all selected cells in order to correctly clone or move into\r\n\t// the target cell. The method checks for each cell if its movable.\r\n\tcells = this.graph.moveCells(cells, dx - this.graph.panDx / this.graph.view.scale,\r\n\t\t\tdy - this.graph.panDy / this.graph.view.scale, clone, target, evt);\r\n\t\r\n\tif (this.isSelectEnabled() && this.scrollOnMove)\r\n\t{\r\n\t\tthis.graph.scrollCellToVisible(cells[0]);\r\n\t}\r\n\t\t\t\r\n\t// Selects the new cells if cells have been cloned\r\n\tif (clone)\r\n\t{\r\n\t\tthis.graph.setSelectionCells(cells);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: destroyShapes\r\n * \r\n * Destroy the preview and highlight shapes.\r\n */\r\nmxGraphHandler.prototype.destroyShapes = function()\r\n{\r\n\t// Destroys the preview dashed rectangle\r\n\tif (this.shape != null)\r\n\t{\r\n\t\tthis.shape.destroy();\r\n\t\tthis.shape = null;\r\n\t}\r\n\t\r\n\tif (this.guide != null)\r\n\t{\r\n\t\tthis.guide.destroy();\r\n\t\tthis.guide = null;\r\n\t}\r\n\t\r\n\t// Destroys the drop target highlight\r\n\tif (this.highlight != null)\r\n\t{\r\n\t\tthis.highlight.destroy();\r\n\t\tthis.highlight = null;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Destroys the handler and all its resources and DOM nodes.\r\n */\r\nmxGraphHandler.prototype.destroy = function()\r\n{\r\n\tthis.graph.removeMouseListener(this);\r\n\tthis.graph.removeListener(this.panHandler);\r\n\t\r\n\tif (this.escapeHandler != null)\r\n\t{\r\n\t\tthis.graph.removeListener(this.escapeHandler);\r\n\t\tthis.escapeHandler = null;\r\n\t}\r\n\t\r\n\tif (this.refreshHandler != null)\r\n\t{\r\n\t\tthis.graph.getModel().removeListener(this.refreshHandler);\r\n\t\tthis.refreshHandler = null;\r\n\t}\r\n\t\r\n\tthis.destroyShapes();\r\n\tthis.removeHint();\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxPanningHandler\r\n * \r\n * Event handler that pans and creates popupmenus. To use the left\r\n * mousebutton for panning without interfering with cell moving and\r\n * resizing, use <isUseLeftButton> and <isIgnoreCell>. For grid size\r\n * steps while panning, use <useGrid>. This handler is built-into\r\n * <mxGraph.panningHandler> and enabled using <mxGraph.setPanning>.\r\n * \r\n * Constructor: mxPanningHandler\r\n * \r\n * Constructs an event handler that creates a <mxPopupMenu>\r\n * and pans the graph.\r\n *\r\n * Event: mxEvent.PAN_START\r\n *\r\n * Fires when the panning handler changes its <active> state to true. The\r\n * <code>event</code> property contains the corresponding <mxMouseEvent>.\r\n *\r\n * Event: mxEvent.PAN\r\n *\r\n * Fires while handle is processing events. The <code>event</code> property contains\r\n * the corresponding <mxMouseEvent>.\r\n *\r\n * Event: mxEvent.PAN_END\r\n *\r\n * Fires when the panning handler changes its <active> state to false. The\r\n * <code>event</code> property contains the corresponding <mxMouseEvent>.\r\n */\r\nfunction mxPanningHandler(graph)\r\n{\r\n\tif (graph != null)\r\n\t{\r\n\t\tthis.graph = graph;\r\n\t\tthis.graph.addMouseListener(this);\r\n\r\n\t\t// Handles force panning event\r\n\t\tthis.forcePanningHandler = mxUtils.bind(this, function(sender, evt)\r\n\t\t{\r\n\t\t\tvar evtName = evt.getProperty('eventName');\r\n\t\t\tvar me = evt.getProperty('event');\r\n\t\t\t\r\n\t\t\tif (evtName == mxEvent.MOUSE_DOWN && this.isForcePanningEvent(me))\r\n\t\t\t{\r\n\t\t\t\tthis.start(me);\r\n\t\t\t\tthis.active = true;\r\n\t\t\t\tthis.fireEvent(new mxEventObject(mxEvent.PAN_START, 'event', me));\r\n\t\t\t\tme.consume();\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tthis.graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.forcePanningHandler);\r\n\t\t\r\n\t\t// Handles pinch gestures\r\n\t\tthis.gestureHandler = mxUtils.bind(this, function(sender, eo)\r\n\t\t{\r\n\t\t\tif (this.isPinchEnabled())\r\n\t\t\t{\r\n\t\t\t\tvar evt = eo.getProperty('event');\r\n\t\t\t\t\r\n\t\t\t\tif (!mxEvent.isConsumed(evt) && evt.type == 'gesturestart')\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.initialScale = this.graph.view.scale;\r\n\t\t\t\t\r\n\t\t\t\t\t// Forces start of panning when pinch gesture starts\r\n\t\t\t\t\tif (!this.active && this.mouseDownEvent != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.start(this.mouseDownEvent);\r\n\t\t\t\t\t\tthis.mouseDownEvent = null;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if (evt.type == 'gestureend' && this.initialScale != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.initialScale = null;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (this.initialScale != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar value = Math.round(this.initialScale * evt.scale * 100) / 100;\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (this.minScale != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvalue = Math.max(this.minScale, value);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (this.maxScale != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvalue = Math.min(this.maxScale, value);\r\n\t\t\t\t\t}\r\n\t\r\n\t\t\t\t\tif (this.graph.view.scale != value)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.graph.zoomTo(value);\r\n\t\t\t\t\t\tmxEvent.consume(evt);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t});\r\n\t\t\r\n\t\tthis.graph.addListener(mxEvent.GESTURE, this.gestureHandler);\r\n\t\t\r\n\t\tthis.mouseUpListener = mxUtils.bind(this, function()\r\n\t\t{\r\n\t\t    \tif (this.active)\r\n\t\t    \t{\r\n\t\t    \t\tthis.reset();\r\n\t\t    \t}\r\n\t\t});\r\n\t\t\r\n\t\t// Stops scrolling on every mouseup anywhere in the document\r\n\t\tmxEvent.addListener(document, 'mouseup', this.mouseUpListener);\r\n\t}\r\n};\r\n\r\n/**\r\n * Extends mxEventSource.\r\n */\r\nmxPanningHandler.prototype = new mxEventSource();\r\nmxPanningHandler.prototype.constructor = mxPanningHandler;\r\n\r\n/**\r\n * Variable: graph\r\n * \r\n * Reference to the enclosing <mxGraph>.\r\n */\r\nmxPanningHandler.prototype.graph = null;\r\n\r\n/**\r\n * Variable: useLeftButtonForPanning\r\n * \r\n * Specifies if panning should be active for the left mouse button.\r\n * Setting this to true may conflict with <mxRubberband>. Default is false.\r\n */\r\nmxPanningHandler.prototype.useLeftButtonForPanning = false;\r\n\r\n/**\r\n * Variable: usePopupTrigger\r\n * \r\n * Specifies if <mxEvent.isPopupTrigger> should also be used for panning.\r\n */\r\nmxPanningHandler.prototype.usePopupTrigger = true;\r\n\r\n/**\r\n * Variable: ignoreCell\r\n * \r\n * Specifies if panning should be active even if there is a cell under the\r\n * mousepointer. Default is false.\r\n */\r\nmxPanningHandler.prototype.ignoreCell = false;\r\n\r\n/**\r\n * Variable: previewEnabled\r\n * \r\n * Specifies if the panning should be previewed. Default is true.\r\n */\r\nmxPanningHandler.prototype.previewEnabled = true;\r\n\r\n/**\r\n * Variable: useGrid\r\n * \r\n * Specifies if the panning steps should be aligned to the grid size.\r\n * Default is false.\r\n */\r\nmxPanningHandler.prototype.useGrid = false;\r\n\r\n/**\r\n * Variable: panningEnabled\r\n * \r\n * Specifies if panning should be enabled. Default is true.\r\n */\r\nmxPanningHandler.prototype.panningEnabled = true;\r\n\r\n/**\r\n * Variable: pinchEnabled\r\n * \r\n * Specifies if pinch gestures should be handled as zoom. Default is true.\r\n */\r\nmxPanningHandler.prototype.pinchEnabled = true;\r\n\r\n/**\r\n * Variable: maxScale\r\n * \r\n * Specifies the maximum scale. Default is 8.\r\n */\r\nmxPanningHandler.prototype.maxScale = 8;\r\n\r\n/**\r\n * Variable: minScale\r\n * \r\n * Specifies the minimum scale. Default is 0.01.\r\n */\r\nmxPanningHandler.prototype.minScale = 0.01;\r\n\r\n/**\r\n * Variable: dx\r\n * \r\n * Holds the current horizontal offset.\r\n */\r\nmxPanningHandler.prototype.dx = null;\r\n\r\n/**\r\n * Variable: dy\r\n * \r\n * Holds the current vertical offset.\r\n */\r\nmxPanningHandler.prototype.dy = null;\r\n\r\n/**\r\n * Variable: startX\r\n * \r\n * Holds the x-coordinate of the start point.\r\n */\r\nmxPanningHandler.prototype.startX = 0;\r\n\r\n/**\r\n * Variable: startY\r\n * \r\n * Holds the y-coordinate of the start point.\r\n */\r\nmxPanningHandler.prototype.startY = 0;\r\n\r\n/**\r\n * Function: isActive\r\n * \r\n * Returns true if the handler is currently active.\r\n */\r\nmxPanningHandler.prototype.isActive = function()\r\n{\r\n\treturn this.active || this.initialScale != null;\r\n};\r\n\r\n/**\r\n * Function: isPanningEnabled\r\n * \r\n * Returns <panningEnabled>.\r\n */\r\nmxPanningHandler.prototype.isPanningEnabled = function()\r\n{\r\n\treturn this.panningEnabled;\r\n};\r\n\r\n/**\r\n * Function: setPanningEnabled\r\n * \r\n * Sets <panningEnabled>.\r\n */\r\nmxPanningHandler.prototype.setPanningEnabled = function(value)\r\n{\r\n\tthis.panningEnabled = value;\r\n};\r\n\r\n/**\r\n * Function: isPinchEnabled\r\n * \r\n * Returns <pinchEnabled>.\r\n */\r\nmxPanningHandler.prototype.isPinchEnabled = function()\r\n{\r\n\treturn this.pinchEnabled;\r\n};\r\n\r\n/**\r\n * Function: setPinchEnabled\r\n * \r\n * Sets <pinchEnabled>.\r\n */\r\nmxPanningHandler.prototype.setPinchEnabled = function(value)\r\n{\r\n\tthis.pinchEnabled = value;\r\n};\r\n\r\n/**\r\n * Function: isPanningTrigger\r\n * \r\n * Returns true if the given event is a panning trigger for the optional\r\n * given cell. This returns true if control-shift is pressed or if\r\n * <usePopupTrigger> is true and the event is a popup trigger.\r\n */\r\nmxPanningHandler.prototype.isPanningTrigger = function(me)\r\n{\r\n\tvar evt = me.getEvent();\r\n\t\r\n\treturn (this.useLeftButtonForPanning && me.getState() == null &&\r\n\t\t\tmxEvent.isLeftMouseButton(evt)) || (mxEvent.isControlDown(evt) &&\r\n\t\t\tmxEvent.isShiftDown(evt)) || (this.usePopupTrigger && mxEvent.isPopupTrigger(evt));\r\n};\r\n\r\n/**\r\n * Function: isForcePanningEvent\r\n * \r\n * Returns true if the given <mxMouseEvent> should start panning. This\r\n * implementation always returns true if <ignoreCell> is true or for\r\n * multi touch events.\r\n */\r\nmxPanningHandler.prototype.isForcePanningEvent = function(me)\r\n{\r\n\treturn this.ignoreCell || mxEvent.isMultiTouchEvent(me.getEvent());\r\n};\r\n\r\n/**\r\n * Function: mouseDown\r\n * \r\n * Handles the event by initiating the panning. By consuming the event all\r\n * subsequent events of the gesture are redirected to this handler.\r\n */\r\nmxPanningHandler.prototype.mouseDown = function(sender, me)\r\n{\r\n\tthis.mouseDownEvent = me;\r\n\t\r\n\tif (!me.isConsumed() && this.isPanningEnabled() && !this.active && this.isPanningTrigger(me))\r\n\t{\r\n\t\tthis.start(me);\r\n\t\tthis.consumePanningTrigger(me);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: start\r\n * \r\n * Starts panning at the given event.\r\n */\r\nmxPanningHandler.prototype.start = function(me)\r\n{\r\n\tthis.dx0 = -this.graph.container.scrollLeft;\r\n\tthis.dy0 = -this.graph.container.scrollTop;\r\n\r\n\t// Stores the location of the trigger event\r\n\tthis.startX = me.getX();\r\n\tthis.startY = me.getY();\r\n\tthis.dx = null;\r\n\tthis.dy = null;\r\n\t\r\n\tthis.panningTrigger = true;\r\n};\r\n\r\n/**\r\n * Function: consumePanningTrigger\r\n * \r\n * Consumes the given <mxMouseEvent> if it was a panning trigger in\r\n * <mouseDown>. The default is to invoke <mxMouseEvent.consume>. Note that this\r\n * will block any further event processing. If you haven't disabled built-in\r\n * context menus and require immediate selection of the cell on mouseDown in\r\n * Safari and/or on the Mac, then use the following code:\r\n * \r\n * (code)\r\n * mxPanningHandler.prototype.consumePanningTrigger = function(me)\r\n * {\r\n *   if (me.evt.preventDefault)\r\n *   {\r\n *     me.evt.preventDefault();\r\n *   }\r\n *   \r\n *   // Stops event processing in IE\r\n *   me.evt.returnValue = false;\r\n *   \r\n *   // Sets local consumed state\r\n *   if (!mxClient.IS_SF && !mxClient.IS_MAC)\r\n *   {\r\n *     me.consumed = true;\r\n *   }\r\n * };\r\n * (end)\r\n */\r\nmxPanningHandler.prototype.consumePanningTrigger = function(me)\r\n{\r\n\tme.consume();\r\n};\r\n\r\n/**\r\n * Function: mouseMove\r\n * \r\n * Handles the event by updating the panning on the graph.\r\n */\r\nmxPanningHandler.prototype.mouseMove = function(sender, me)\r\n{\r\n\tthis.dx = me.getX() - this.startX;\r\n\tthis.dy = me.getY() - this.startY;\r\n\t\r\n\tif (this.active)\r\n\t{\r\n\t\tif (this.previewEnabled)\r\n\t\t{\r\n\t\t\t// Applies the grid to the panning steps\r\n\t\t\tif (this.useGrid)\r\n\t\t\t{\r\n\t\t\t\tthis.dx = this.graph.snap(this.dx);\r\n\t\t\t\tthis.dy = this.graph.snap(this.dy);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.graph.panGraph(this.dx + this.dx0, this.dy + this.dy0);\r\n\t\t}\r\n\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.PAN, 'event', me));\r\n\t}\r\n\telse if (this.panningTrigger)\r\n\t{\r\n\t\tvar tmp = this.active;\r\n\r\n\t\t// Panning is activated only if the mouse is moved\r\n\t\t// beyond the graph tolerance\r\n\t\tthis.active = Math.abs(this.dx) > this.graph.tolerance || Math.abs(this.dy) > this.graph.tolerance;\r\n\r\n\t\tif (!tmp && this.active)\r\n\t\t{\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.PAN_START, 'event', me));\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (this.active || this.panningTrigger)\r\n\t{\r\n\t\tme.consume();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: mouseUp\r\n * \r\n * Handles the event by setting the translation on the view or showing the\r\n * popupmenu.\r\n */\r\nmxPanningHandler.prototype.mouseUp = function(sender, me)\r\n{\r\n\tif (this.active)\r\n\t{\r\n\t\tif (this.dx != null && this.dy != null)\r\n\t\t{\r\n\t\t\t// Ignores if scrollbars have been used for panning\r\n\t\t\tif (!this.graph.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.graph.container))\r\n\t\t\t{\r\n\t\t\t\tvar scale = this.graph.getView().scale;\r\n\t\t\t\tvar t = this.graph.getView().translate;\r\n\t\t\t\tthis.graph.panGraph(0, 0);\r\n\t\t\t\tthis.panGraph(t.x + this.dx / scale, t.y + this.dy / scale);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tme.consume();\r\n\t\t}\r\n\t\t\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.PAN_END, 'event', me));\r\n\t}\r\n\t\r\n\tthis.reset();\r\n};\r\n\r\n/**\r\n * Function: mouseUp\r\n * \r\n * Handles the event by setting the translation on the view or showing the\r\n * popupmenu.\r\n */\r\nmxPanningHandler.prototype.reset = function()\r\n{\r\n\tthis.panningTrigger = false;\r\n\tthis.mouseDownEvent = null;\r\n\tthis.active = false;\r\n\tthis.dx = null;\r\n\tthis.dy = null;\r\n};\r\n\r\n/**\r\n * Function: panGraph\r\n * \r\n * Pans <graph> by the given amount.\r\n */\r\nmxPanningHandler.prototype.panGraph = function(dx, dy)\r\n{\r\n\tthis.graph.getView().setTranslate(dx, dy);\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Destroys the handler and all its resources and DOM nodes.\r\n */\r\nmxPanningHandler.prototype.destroy = function()\r\n{\r\n\tthis.graph.removeMouseListener(this);\r\n\tthis.graph.removeListener(this.forcePanningHandler);\r\n\tthis.graph.removeListener(this.gestureHandler);\r\n\tmxEvent.removeListener(document, 'mouseup', this.mouseUpListener);\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxPopupMenuHandler\r\n * \r\n * Event handler that creates popupmenus.\r\n * \r\n * Constructor: mxPopupMenuHandler\r\n * \r\n * Constructs an event handler that creates a <mxPopupMenu>.\r\n */\r\nfunction mxPopupMenuHandler(graph, factoryMethod)\r\n{\r\n\tif (graph != null)\r\n\t{\r\n\t\tthis.graph = graph;\r\n\t\tthis.factoryMethod = factoryMethod;\r\n\t\tthis.graph.addMouseListener(this);\r\n\t\t\r\n\t\t// Does not show menu if any touch gestures take place after the trigger\r\n\t\tthis.gestureHandler = mxUtils.bind(this, function(sender, eo)\r\n\t\t{\r\n\t\t\tthis.inTolerance = false;\r\n\t\t});\r\n\t\t\r\n\t\tthis.graph.addListener(mxEvent.GESTURE, this.gestureHandler);\r\n\t\t\r\n\t\tthis.init();\r\n\t}\r\n};\r\n\r\n/**\r\n * Extends mxPopupMenu.\r\n */\r\nmxPopupMenuHandler.prototype = new mxPopupMenu();\r\nmxPopupMenuHandler.prototype.constructor = mxPopupMenuHandler;\r\n\r\n/**\r\n * Variable: graph\r\n * \r\n * Reference to the enclosing <mxGraph>.\r\n */\r\nmxPopupMenuHandler.prototype.graph = null;\r\n\r\n/**\r\n * Variable: selectOnPopup\r\n * \r\n * Specifies if cells should be selected if a popupmenu is displayed for\r\n * them. Default is true.\r\n */\r\nmxPopupMenuHandler.prototype.selectOnPopup = true;\r\n\r\n/**\r\n * Variable: clearSelectionOnBackground\r\n * \r\n * Specifies if cells should be deselected if a popupmenu is displayed for\r\n * the diagram background. Default is true.\r\n */\r\nmxPopupMenuHandler.prototype.clearSelectionOnBackground = true;\r\n\r\n/**\r\n * Variable: triggerX\r\n * \r\n * X-coordinate of the mouse down event.\r\n */\r\nmxPopupMenuHandler.prototype.triggerX = null;\r\n\r\n/**\r\n * Variable: triggerY\r\n * \r\n * Y-coordinate of the mouse down event.\r\n */\r\nmxPopupMenuHandler.prototype.triggerY = null;\r\n\r\n/**\r\n * Variable: screenX\r\n * \r\n * Screen X-coordinate of the mouse down event.\r\n */\r\nmxPopupMenuHandler.prototype.screenX = null;\r\n\r\n/**\r\n * Variable: screenY\r\n * \r\n * Screen Y-coordinate of the mouse down event.\r\n */\r\nmxPopupMenuHandler.prototype.screenY = null;\r\n\r\n/**\r\n * Function: init\r\n * \r\n * Initializes the shapes required for this vertex handler.\r\n */\r\nmxPopupMenuHandler.prototype.init = function()\r\n{\r\n\t// Supercall\r\n\tmxPopupMenu.prototype.init.apply(this);\r\n\r\n\t// Hides the tooltip if the mouse is over\r\n\t// the context menu\r\n\tmxEvent.addGestureListeners(this.div, mxUtils.bind(this, function(evt)\r\n\t{\r\n\t\tthis.graph.tooltipHandler.hide();\r\n\t}));\r\n};\r\n\r\n/**\r\n * Function: isSelectOnPopup\r\n * \r\n * Hook for returning if a cell should be selected for a given <mxMouseEvent>.\r\n * This implementation returns <selectOnPopup>.\r\n */\r\nmxPopupMenuHandler.prototype.isSelectOnPopup = function(me)\r\n{\r\n\treturn this.selectOnPopup;\r\n};\r\n\r\n/**\r\n * Function: mouseDown\r\n * \r\n * Handles the event by initiating the panning. By consuming the event all\r\n * subsequent events of the gesture are redirected to this handler.\r\n */\r\nmxPopupMenuHandler.prototype.mouseDown = function(sender, me)\r\n{\r\n\tif (this.isEnabled() && !mxEvent.isMultiTouchEvent(me.getEvent()))\r\n\t{\r\n\t\t// Hides the popupmenu if is is being displayed\r\n\t\tthis.hideMenu();\r\n\t\tthis.triggerX = me.getGraphX();\r\n\t\tthis.triggerY = me.getGraphY();\r\n\t\tthis.screenX = mxEvent.getMainEvent(me.getEvent()).screenX;\r\n\t\tthis.screenY = mxEvent.getMainEvent(me.getEvent()).screenY;\r\n\t\tthis.popupTrigger = this.isPopupTrigger(me);\r\n\t\tthis.inTolerance = true;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: mouseMove\r\n * \r\n * Handles the event by updating the panning on the graph.\r\n */\r\nmxPopupMenuHandler.prototype.mouseMove = function(sender, me)\r\n{\r\n\t// Popup trigger may change on mouseUp so ignore it\r\n\tif (this.inTolerance && this.screenX != null && this.screenY != null)\r\n\t{\r\n\t\tif (Math.abs(mxEvent.getMainEvent(me.getEvent()).screenX - this.screenX) > this.graph.tolerance ||\r\n\t\t\tMath.abs(mxEvent.getMainEvent(me.getEvent()).screenY - this.screenY) > this.graph.tolerance)\r\n\t\t{\r\n\t\t\tthis.inTolerance = false;\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: mouseUp\r\n * \r\n * Handles the event by setting the translation on the view or showing the\r\n * popupmenu.\r\n */\r\nmxPopupMenuHandler.prototype.mouseUp = function(sender, me)\r\n{\r\n\tif (this.popupTrigger && this.inTolerance && this.triggerX != null && this.triggerY != null)\r\n\t{\r\n\t\tvar cell = this.getCellForPopupEvent(me);\r\n\r\n\t\t// Selects the cell for which the context menu is being displayed\r\n\t\tif (this.graph.isEnabled() && this.isSelectOnPopup(me) &&\r\n\t\t\tcell != null && !this.graph.isCellSelected(cell))\r\n\t\t{\r\n\t\t\tthis.graph.setSelectionCell(cell);\r\n\t\t}\r\n\t\telse if (this.clearSelectionOnBackground && cell == null)\r\n\t\t{\r\n\t\t\tthis.graph.clearSelection();\r\n\t\t}\r\n\t\t\r\n\t\t// Hides the tooltip if there is one\r\n\t\tthis.graph.tooltipHandler.hide();\r\n\r\n\t\t// Menu is shifted by 1 pixel so that the mouse up event\r\n\t\t// is routed via the underlying shape instead of the DIV\r\n\t\tvar origin = mxUtils.getScrollOrigin();\r\n\t\tthis.popup(me.getX() + origin.x + 1, me.getY() + origin.y + 1, cell, me.getEvent());\r\n\t\tme.consume();\r\n\t}\r\n\t\r\n\tthis.popupTrigger = false;\r\n\tthis.inTolerance = false;\r\n};\r\n\r\n/**\r\n * Function: getCellForPopupEvent\r\n * \r\n * Hook to return the cell for the mouse up popup trigger handling.\r\n */\r\nmxPopupMenuHandler.prototype.getCellForPopupEvent = function(me)\r\n{\r\n\treturn me.getCell();\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Destroys the handler and all its resources and DOM nodes.\r\n */\r\nmxPopupMenuHandler.prototype.destroy = function()\r\n{\r\n\tthis.graph.removeMouseListener(this);\r\n\tthis.graph.removeListener(this.gestureHandler);\r\n\t\r\n\t// Supercall\r\n\tmxPopupMenu.prototype.destroy.apply(this);\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxCellMarker\r\n * \r\n * A helper class to process mouse locations and highlight cells.\r\n * \r\n * Helper class to highlight cells. To add a cell marker to an existing graph\r\n * for highlighting all cells, the following code is used:\r\n * \r\n * (code)\r\n * var marker = new mxCellMarker(graph);\r\n * graph.addMouseListener({\r\n *   mouseDown: function() {},\r\n *   mouseMove: function(sender, me)\r\n *   {\r\n *     marker.process(me);\r\n *   },\r\n *   mouseUp: function() {}\r\n * });\r\n * (end)\r\n *\r\n * Event: mxEvent.MARK\r\n * \r\n * Fires after a cell has been marked or unmarked. The <code>state</code>\r\n * property contains the marked <mxCellState> or null if no state is marked.\r\n * \r\n * Constructor: mxCellMarker\r\n * \r\n * Constructs a new cell marker.\r\n * \r\n * Parameters:\r\n * \r\n * graph - Reference to the enclosing <mxGraph>.\r\n * validColor - Optional marker color for valid states. Default is\r\n * <mxConstants.DEFAULT_VALID_COLOR>.\r\n * invalidColor - Optional marker color for invalid states. Default is\r\n * <mxConstants.DEFAULT_INVALID_COLOR>.\r\n * hotspot - Portion of the width and hight where a state intersects a\r\n * given coordinate pair. A value of 0 means always highlight. Default is\r\n * <mxConstants.DEFAULT_HOTSPOT>.\r\n */\r\nfunction mxCellMarker(graph, validColor, invalidColor, hotspot)\r\n{\r\n\tmxEventSource.call(this);\r\n\t\r\n\tif (graph != null)\r\n\t{\r\n\t\tthis.graph = graph;\r\n\t\tthis.validColor = (validColor != null) ? validColor : mxConstants.DEFAULT_VALID_COLOR;\r\n\t\tthis.invalidColor = (invalidColor != null) ? invalidColor : mxConstants.DEFAULT_INVALID_COLOR;\r\n\t\tthis.hotspot = (hotspot != null) ? hotspot : mxConstants.DEFAULT_HOTSPOT;\r\n\t\t\r\n\t\tthis.highlight = new mxCellHighlight(graph);\r\n\t}\r\n};\r\n\r\n/**\r\n * Extends mxEventSource.\r\n */\r\nmxUtils.extend(mxCellMarker, mxEventSource);\r\n\r\n/**\r\n * Variable: graph\r\n * \r\n * Reference to the enclosing <mxGraph>.\r\n */\r\nmxCellMarker.prototype.graph = null;\r\n\r\n/**\r\n * Variable: enabled\r\n * \r\n * Specifies if the marker is enabled. Default is true.\r\n */\r\nmxCellMarker.prototype.enabled = true;\r\n\r\n/**\r\n * Variable: hotspot\r\n * \r\n * Specifies the portion of the width and height that should trigger\r\n * a highlight. The area around the center of the cell to be marked is used\r\n * as the hotspot. Possible values are between 0 and 1. Default is\r\n * mxConstants.DEFAULT_HOTSPOT.\r\n */\r\nmxCellMarker.prototype.hotspot = mxConstants.DEFAULT_HOTSPOT; \r\n\r\n/**\r\n * Variable: hotspotEnabled\r\n * \r\n * Specifies if the hotspot is enabled. Default is false.\r\n */\r\nmxCellMarker.prototype.hotspotEnabled = false;\r\n\r\n/**\r\n * Variable: validColor\r\n * \r\n * Holds the valid marker color.\r\n */\r\nmxCellMarker.prototype.validColor = null;\r\n\r\n/**\r\n * Variable: invalidColor\r\n * \r\n * Holds the invalid marker color.\r\n */\r\nmxCellMarker.prototype.invalidColor = null;\r\n\r\n/**\r\n * Variable: currentColor\r\n * \r\n * Holds the current marker color.\r\n */\r\nmxCellMarker.prototype.currentColor = null;\r\n\r\n/**\r\n * Variable: validState\r\n * \r\n * Holds the marked <mxCellState> if it is valid.\r\n */\r\nmxCellMarker.prototype.validState = null; \r\n\r\n/**\r\n * Variable: markedState\r\n * \r\n * Holds the marked <mxCellState>.\r\n */\r\nmxCellMarker.prototype.markedState = null;\r\n\r\n/**\r\n * Function: setEnabled\r\n * \r\n * Enables or disables event handling. This implementation\r\n * updates <enabled>.\r\n * \r\n * Parameters:\r\n * \r\n * enabled - Boolean that specifies the new enabled state.\r\n */\r\nmxCellMarker.prototype.setEnabled = function(enabled)\r\n{\r\n\tthis.enabled = enabled;\r\n};\r\n\r\n/**\r\n * Function: isEnabled\r\n * \r\n * Returns true if events are handled. This implementation\r\n * returns <enabled>.\r\n */\r\nmxCellMarker.prototype.isEnabled = function()\r\n{\r\n\treturn this.enabled;\r\n};\r\n\r\n/**\r\n * Function: setHotspot\r\n * \r\n * Sets the <hotspot>.\r\n */\r\nmxCellMarker.prototype.setHotspot = function(hotspot)\r\n{\r\n\tthis.hotspot = hotspot;\r\n};\r\n\r\n/**\r\n * Function: getHotspot\r\n * \r\n * Returns the <hotspot>.\r\n */\r\nmxCellMarker.prototype.getHotspot = function()\r\n{\r\n\treturn this.hotspot;\r\n};\r\n\r\n/**\r\n * Function: setHotspotEnabled\r\n * \r\n * Specifies whether the hotspot should be used in <intersects>.\r\n */\r\nmxCellMarker.prototype.setHotspotEnabled = function(enabled)\r\n{\r\n\tthis.hotspotEnabled = enabled;\r\n};\r\n\r\n/**\r\n * Function: isHotspotEnabled\r\n * \r\n * Returns true if hotspot is used in <intersects>.\r\n */\r\nmxCellMarker.prototype.isHotspotEnabled = function()\r\n{\r\n\treturn this.hotspotEnabled;\r\n};\r\n\r\n/**\r\n * Function: hasValidState\r\n * \r\n * Returns true if <validState> is not null.\r\n */\r\nmxCellMarker.prototype.hasValidState = function()\r\n{\r\n\treturn this.validState != null;\r\n};\r\n\r\n/**\r\n * Function: getValidState\r\n * \r\n * Returns the <validState>.\r\n */\r\nmxCellMarker.prototype.getValidState = function()\r\n{\r\n\treturn this.validState;\r\n};\r\n\r\n/**\r\n * Function: getMarkedState\r\n * \r\n * Returns the <markedState>.\r\n */\r\nmxCellMarker.prototype.getMarkedState = function()\r\n{\r\n\treturn this.markedState;\r\n};\r\n\r\n/**\r\n * Function: reset\r\n * \r\n * Resets the state of the cell marker.\r\n */\r\nmxCellMarker.prototype.reset = function()\r\n{\r\n\tthis.validState = null;\r\n\t\r\n\tif (this.markedState != null)\r\n\t{\r\n\t\tthis.markedState = null;\r\n\t\tthis.unmark();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: process\r\n * \r\n * Processes the given event and cell and marks the state returned by\r\n * <getState> with the color returned by <getMarkerColor>. If the\r\n * markerColor is not null, then the state is stored in <markedState>. If\r\n * <isValidState> returns true, then the state is stored in <validState>\r\n * regardless of the marker color. The state is returned regardless of the\r\n * marker color and valid state. \r\n */\r\nmxCellMarker.prototype.process = function(me)\r\n{\r\n\tvar state = null;\r\n\t\r\n\tif (this.isEnabled())\r\n\t{\r\n\t\tstate = this.getState(me);\r\n\t\tthis.setCurrentState(state, me);\r\n\t}\r\n\t\r\n\treturn state;\r\n};\r\n\r\n/**\r\n * Function: setCurrentState\r\n * \r\n * Sets and marks the current valid state.\r\n */\r\nmxCellMarker.prototype.setCurrentState = function(state, me, color)\r\n{\r\n\tvar isValid = (state != null) ? this.isValidState(state) : false;\r\n\tcolor = (color != null) ? color : this.getMarkerColor(me.getEvent(), state, isValid);\r\n\t\r\n\tif (isValid)\r\n\t{\r\n\t\tthis.validState = state;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.validState = null;\r\n\t}\r\n\t\r\n\tif (state != this.markedState || color != this.currentColor)\r\n\t{\r\n\t\tthis.currentColor = color;\r\n\t\t\r\n\t\tif (state != null && this.currentColor != null)\r\n\t\t{\r\n\t\t\tthis.markedState = state;\r\n\t\t\tthis.mark();\t\t\r\n\t\t}\r\n\t\telse if (this.markedState != null)\r\n\t\t{\r\n\t\t\tthis.markedState = null;\r\n\t\t\tthis.unmark();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: markCell\r\n * \r\n * Marks the given cell using the given color, or <validColor> if no color is specified.\r\n */\r\nmxCellMarker.prototype.markCell = function(cell, color)\r\n{\r\n\tvar state = this.graph.getView().getState(cell);\r\n\t\r\n\tif (state != null)\r\n\t{\r\n\t\tthis.currentColor = (color != null) ? color : this.validColor;\r\n\t\tthis.markedState = state;\r\n\t\tthis.mark();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: mark\r\n * \r\n * Marks the <markedState> and fires a <mark> event.\r\n */\r\nmxCellMarker.prototype.mark = function()\r\n{\r\n\tthis.highlight.setHighlightColor(this.currentColor);\r\n\tthis.highlight.highlight(this.markedState);\r\n\tthis.fireEvent(new mxEventObject(mxEvent.MARK, 'state', this.markedState));\r\n};\r\n\r\n/**\r\n * Function: unmark\r\n * \r\n * Hides the marker and fires a <mark> event.\r\n */\r\nmxCellMarker.prototype.unmark = function()\r\n{\r\n\tthis.mark();\r\n};\r\n\r\n/**\r\n * Function: isValidState\r\n * \r\n * Returns true if the given <mxCellState> is a valid state. If this\r\n * returns true, then the state is stored in <validState>. The return value\r\n * of this method is used as the argument for <getMarkerColor>.\r\n */\r\nmxCellMarker.prototype.isValidState = function(state)\r\n{\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: getMarkerColor\r\n * \r\n * Returns the valid- or invalidColor depending on the value of isValid.\r\n * The given <mxCellState> is ignored by this implementation.\r\n */\r\nmxCellMarker.prototype.getMarkerColor = function(evt, state, isValid)\r\n{\r\n\treturn (isValid) ? this.validColor : this.invalidColor;\r\n};\r\n\r\n/**\r\n * Function: getState\r\n * \r\n * Uses <getCell>, <getStateToMark> and <intersects> to return the\r\n * <mxCellState> for the given <mxMouseEvent>.\r\n */\r\nmxCellMarker.prototype.getState = function(me)\r\n{\r\n\tvar view = this.graph.getView();\r\n\tvar cell = this.getCell(me);\r\n\tvar state = this.getStateToMark(view.getState(cell));\r\n\r\n\treturn (state != null && this.intersects(state, me)) ? state : null;\r\n};\r\n\r\n/**\r\n * Function: getCell\r\n * \r\n * Returns the <mxCell> for the given event and cell. This returns the\r\n * given cell.\r\n */\r\nmxCellMarker.prototype.getCell = function(me)\r\n{\r\n\treturn me.getCell();\r\n};\r\n\r\n/**\r\n * Function: getStateToMark\r\n * \r\n * Returns the <mxCellState> to be marked for the given <mxCellState> under\r\n * the mouse. This returns the given state.\r\n */\r\nmxCellMarker.prototype.getStateToMark = function(state)\r\n{\r\n\treturn state;\r\n};\r\n\r\n/**\r\n * Function: intersects\r\n * \r\n * Returns true if the given coordinate pair intersects the given state.\r\n * This returns true if the <hotspot> is 0 or the coordinates are inside\r\n * the hotspot for the given cell state.\r\n */\r\nmxCellMarker.prototype.intersects = function(state, me)\r\n{\r\n\tif (this.hotspotEnabled)\r\n\t{\r\n\t\treturn mxUtils.intersectsHotspot(state, me.getGraphX(), me.getGraphY(),\r\n\t\t\tthis.hotspot, mxConstants.MIN_HOTSPOT_SIZE,\r\n\t\t\tmxConstants.MAX_HOTSPOT_SIZE);\r\n\t}\r\n\t\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Destroys the handler and all its resources and DOM nodes.\r\n */\r\nmxCellMarker.prototype.destroy = function()\r\n{\r\n\tthis.graph.getView().removeListener(this.resetHandler);\r\n\tthis.graph.getModel().removeListener(this.resetHandler);\r\n\tthis.highlight.destroy();\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxSelectionCellsHandler\r\n * \r\n * An event handler that manages cell handlers and invokes their mouse event\r\n * processing functions.\r\n * \r\n * Group: Events\r\n * \r\n * Event: mxEvent.ADD\r\n * \r\n * Fires if a cell has been added to the selection. The <code>state</code>\r\n * property contains the <mxCellState> that has been added.\r\n * \r\n * Event: mxEvent.REMOVE\r\n * \r\n * Fires if a cell has been remove from the selection. The <code>state</code>\r\n * property contains the <mxCellState> that has been removed.\r\n * \r\n * Parameters:\r\n * \r\n * graph - Reference to the enclosing <mxGraph>.\r\n */\r\nfunction mxSelectionCellsHandler(graph)\r\n{\r\n\tmxEventSource.call(this);\r\n\t\r\n\tthis.graph = graph;\r\n\tthis.handlers = new mxDictionary();\r\n\tthis.graph.addMouseListener(this);\r\n\t\r\n\tthis.refreshHandler = mxUtils.bind(this, function(sender, evt)\r\n\t{\r\n\t\tif (this.isEnabled())\r\n\t\t{\r\n\t\t\tthis.refresh();\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.graph.getSelectionModel().addListener(mxEvent.CHANGE, this.refreshHandler);\r\n\tthis.graph.getModel().addListener(mxEvent.CHANGE, this.refreshHandler);\r\n\tthis.graph.getView().addListener(mxEvent.SCALE, this.refreshHandler);\r\n\tthis.graph.getView().addListener(mxEvent.TRANSLATE, this.refreshHandler);\r\n\tthis.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.refreshHandler);\r\n\tthis.graph.getView().addListener(mxEvent.DOWN, this.refreshHandler);\r\n\tthis.graph.getView().addListener(mxEvent.UP, this.refreshHandler);\r\n};\r\n\r\n/**\r\n * Extends mxEventSource.\r\n */\r\nmxUtils.extend(mxSelectionCellsHandler, mxEventSource);\r\n\r\n/**\r\n * Variable: graph\r\n * \r\n * Reference to the enclosing <mxGraph>.\r\n */\r\nmxSelectionCellsHandler.prototype.graph = null;\r\n\r\n/**\r\n * Variable: enabled\r\n * \r\n * Specifies if events are handled. Default is true.\r\n */\r\nmxSelectionCellsHandler.prototype.enabled = true;\r\n\r\n/**\r\n * Variable: refreshHandler\r\n * \r\n * Keeps a reference to an event listener for later removal.\r\n */\r\nmxSelectionCellsHandler.prototype.refreshHandler = null;\r\n\r\n/**\r\n * Variable: maxHandlers\r\n * \r\n * Defines the maximum number of handlers to paint individually. Default is 100.\r\n */\r\nmxSelectionCellsHandler.prototype.maxHandlers = 100;\r\n\r\n/**\r\n * Variable: handlers\r\n * \r\n * <mxDictionary> that maps from cells to handlers.\r\n */\r\nmxSelectionCellsHandler.prototype.handlers = null;\r\n\r\n/**\r\n * Function: isEnabled\r\n * \r\n * Returns <enabled>.\r\n */\r\nmxSelectionCellsHandler.prototype.isEnabled = function()\r\n{\r\n\treturn this.enabled;\r\n};\r\n\r\n/**\r\n * Function: setEnabled\r\n * \r\n * Sets <enabled>.\r\n */\r\nmxSelectionCellsHandler.prototype.setEnabled = function(value)\r\n{\r\n\tthis.enabled = value;\r\n};\r\n\r\n/**\r\n * Function: getHandler\r\n * \r\n * Returns the handler for the given cell.\r\n */\r\nmxSelectionCellsHandler.prototype.getHandler = function(cell)\r\n{\r\n\treturn this.handlers.get(cell);\r\n};\r\n\r\n/**\r\n * Function: reset\r\n * \r\n * Resets all handlers.\r\n */\r\nmxSelectionCellsHandler.prototype.reset = function()\r\n{\r\n\tthis.handlers.visit(function(key, handler)\r\n\t{\r\n\t\thandler.reset.apply(handler);\r\n\t});\r\n};\r\n\r\n/**\r\n * Function: refresh\r\n * \r\n * Reloads or updates all handlers.\r\n */\r\nmxSelectionCellsHandler.prototype.refresh = function()\r\n{\r\n\t// Removes all existing handlers\r\n\tvar oldHandlers = this.handlers;\r\n\tthis.handlers = new mxDictionary();\r\n\t\r\n\t// Creates handles for all selection cells\r\n\tvar tmp = this.graph.getSelectionCells();\r\n\r\n\tfor (var i = 0; i < tmp.length; i++)\r\n\t{\r\n\t\tvar state = this.graph.view.getState(tmp[i]);\r\n\r\n\t\tif (state != null)\r\n\t\t{\r\n\t\t\tvar handler = oldHandlers.remove(tmp[i]);\r\n\r\n\t\t\tif (handler != null)\r\n\t\t\t{\r\n\t\t\t\tif (handler.state != state)\r\n\t\t\t\t{\r\n\t\t\t\t\thandler.destroy();\r\n\t\t\t\t\thandler = null;\r\n\t\t\t\t}\r\n\t\t\t\telse if (!this.isHandlerActive(handler))\r\n\t\t\t\t{\r\n\t\t\t\t\tif (handler.refresh != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\thandler.refresh();\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\thandler.redraw();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (handler == null)\r\n\t\t\t{\r\n\t\t\t\thandler = this.graph.createHandler(state);\r\n\t\t\t\tthis.fireEvent(new mxEventObject(mxEvent.ADD, 'state', state));\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (handler != null)\r\n\t\t\t{\r\n\t\t\t\tthis.handlers.put(tmp[i], handler);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\t// Destroys all unused handlers\r\n\toldHandlers.visit(mxUtils.bind(this, function(key, handler)\r\n\t{\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.REMOVE, 'state', handler.state));\r\n\t\thandler.destroy();\r\n\t}));\r\n};\r\n\r\n/**\r\n * Function: isHandlerActive\r\n * \r\n * Returns true if the given handler is active and should not be redrawn.\r\n */\r\nmxSelectionCellsHandler.prototype.isHandlerActive = function(handler)\r\n{\r\n\treturn handler.index != null;\r\n};\r\n\r\n/**\r\n * Function: updateHandler\r\n * \r\n * Updates the handler for the given shape if one exists.\r\n */\r\nmxSelectionCellsHandler.prototype.updateHandler = function(state)\r\n{\r\n\tvar handler = this.handlers.remove(state.cell);\r\n\t\r\n\tif (handler != null)\r\n\t{\r\n\t\thandler.destroy();\r\n\t\thandler = this.graph.createHandler(state);\r\n\t\t\r\n\t\tif (handler != null)\r\n\t\t{\r\n\t\t\tthis.handlers.put(state.cell, handler);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: mouseDown\r\n * \r\n * Redirects the given event to the handlers.\r\n */\r\nmxSelectionCellsHandler.prototype.mouseDown = function(sender, me)\r\n{\r\n\tif (this.graph.isEnabled() && this.isEnabled())\r\n\t{\r\n\t\tvar args = [sender, me];\r\n\r\n\t\tthis.handlers.visit(function(key, handler)\r\n\t\t{\r\n\t\t\thandler.mouseDown.apply(handler, args);\r\n\t\t});\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: mouseMove\r\n * \r\n * Redirects the given event to the handlers.\r\n */\r\nmxSelectionCellsHandler.prototype.mouseMove = function(sender, me)\r\n{\r\n\tif (this.graph.isEnabled() && this.isEnabled())\r\n\t{\r\n\t\tvar args = [sender, me];\r\n\r\n\t\tthis.handlers.visit(function(key, handler)\r\n\t\t{\r\n\t\t\thandler.mouseMove.apply(handler, args);\r\n\t\t});\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: mouseUp\r\n * \r\n * Redirects the given event to the handlers.\r\n */\r\nmxSelectionCellsHandler.prototype.mouseUp = function(sender, me)\r\n{\r\n\tif (this.graph.isEnabled() && this.isEnabled())\r\n\t{\r\n\t\tvar args = [sender, me];\r\n\r\n\t\tthis.handlers.visit(function(key, handler)\r\n\t\t{\r\n\t\t\thandler.mouseUp.apply(handler, args);\r\n\t\t});\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Destroys the handler and all its resources and DOM nodes.\r\n */\r\nmxSelectionCellsHandler.prototype.destroy = function()\r\n{\r\n\tthis.graph.removeMouseListener(this);\r\n\t\r\n\tif (this.refreshHandler != null)\r\n\t{\r\n\t\tthis.graph.getSelectionModel().removeListener(this.refreshHandler);\r\n\t\tthis.graph.getModel().removeListener(this.refreshHandler);\r\n\t\tthis.graph.getView().removeListener(this.refreshHandler);\r\n\t\tthis.refreshHandler = null;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2016, JGraph Ltd\r\n * Copyright (c) 2006-2016, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxConnectionHandler\r\n *\r\n * Graph event handler that creates new connections. Uses <mxTerminalMarker>\r\n * for finding and highlighting the source and target vertices and\r\n * <factoryMethod> to create the edge instance. This handler is built-into\r\n * <mxGraph.connectionHandler> and enabled using <mxGraph.setConnectable>.\r\n *\r\n * Example:\r\n * \r\n * (code)\r\n * new mxConnectionHandler(graph, function(source, target, style)\r\n * {\r\n *   edge = new mxCell('', new mxGeometry());\r\n *   edge.setEdge(true);\r\n *   edge.setStyle(style);\r\n *   edge.geometry.relative = true;\r\n *   return edge;\r\n * });\r\n * (end)\r\n * \r\n * Here is an alternative solution that just sets a specific user object for\r\n * new edges by overriding <insertEdge>.\r\n *\r\n * (code)\r\n * mxConnectionHandlerInsertEdge = mxConnectionHandler.prototype.insertEdge;\r\n * mxConnectionHandler.prototype.insertEdge = function(parent, id, value, source, target, style)\r\n * {\r\n *   value = 'Test';\r\n * \r\n *   return mxConnectionHandlerInsertEdge.apply(this, arguments);\r\n * };\r\n * (end)\r\n * \r\n * Using images to trigger connections:\r\n * \r\n * This handler uses mxTerminalMarker to find the source and target cell for\r\n * the new connection and creates a new edge using <connect>. The new edge is\r\n * created using <createEdge> which in turn uses <factoryMethod> or creates a\r\n * new default edge.\r\n * \r\n * The handler uses a \"highlight-paradigm\" for indicating if a cell is being\r\n * used as a source or target terminal, as seen in other diagramming products.\r\n * In order to allow both, moving and connecting cells at the same time,\r\n * <mxConstants.DEFAULT_HOTSPOT> is used in the handler to determine the hotspot\r\n * of a cell, that is, the region of the cell which is used to trigger a new\r\n * connection. The constant is a value between 0 and 1 that specifies the\r\n * amount of the width and height around the center to be used for the hotspot\r\n * of a cell and its default value is 0.5. In addition,\r\n * <mxConstants.MIN_HOTSPOT_SIZE> defines the minimum number of pixels for the\r\n * width and height of the hotspot.\r\n * \r\n * This solution, while standards compliant, may be somewhat confusing because\r\n * there is no visual indicator for the hotspot and the highlight is seen to\r\n * switch on and off while the mouse is being moved in and out. Furthermore,\r\n * this paradigm does not allow to create different connections depending on\r\n * the highlighted hotspot as there is only one hotspot per cell and it\r\n * normally does not allow cells to be moved and connected at the same time as\r\n * there is no clear indication of the connectable area of the cell.\r\n * \r\n * To come across these issues, the handle has an additional <createIcons> hook\r\n * with a default implementation that allows to create one icon to be used to\r\n * trigger new connections. If this icon is specified, then new connections can\r\n * only be created if the image is clicked while the cell is being highlighted.\r\n * The <createIcons> hook may be overridden to create more than one\r\n * <mxImageShape> for creating new connections, but the default implementation\r\n * supports one image and is used as follows:\r\n * \r\n * In order to display the \"connect image\" whenever the mouse is over the cell,\r\n * an DEFAULT_HOTSPOT of 1 should be used:\r\n * \r\n * (code)\r\n * mxConstants.DEFAULT_HOTSPOT = 1;\r\n * (end)\r\n * \r\n * In order to avoid confusion with the highlighting, the highlight color\r\n * should not be used with a connect image:\r\n * \r\n * (code)\r\n * mxConstants.HIGHLIGHT_COLOR = null;\r\n * (end)\r\n * \r\n * To install the image, the connectImage field of the mxConnectionHandler must\r\n * be assigned a new <mxImage> instance:\r\n * \r\n * (code)\r\n * mxConnectionHandler.prototype.connectImage = new mxImage('images/green-dot.gif', 14, 14);\r\n * (end)\r\n * \r\n * This will use the green-dot.gif with a width and height of 14 pixels as the\r\n * image to trigger new connections. In createIcons the icon field of the\r\n * handler will be set in order to remember the icon that has been clicked for\r\n * creating the new connection. This field will be available under selectedIcon\r\n * in the connect method, which may be overridden to take the icon that\r\n * triggered the new connection into account. This is useful if more than one\r\n * icon may be used to create a connection.\r\n *\r\n * Group: Events\r\n * \r\n * Event: mxEvent.START\r\n * \r\n * Fires when a new connection is being created by the user. The <code>state</code>\r\n * property contains the state of the source cell.\r\n * \r\n * Event: mxEvent.CONNECT\r\n * \r\n * Fires between begin- and endUpdate in <connect>. The <code>cell</code>\r\n * property contains the inserted edge, the <code>event</code> and <code>target</code> \r\n * properties contain the respective arguments that were passed to <connect> (where\r\n * target corresponds to the dropTarget argument). Finally, the <code>terminal</code>\r\n * property corresponds to the target argument in <connect> or the clone of the source\r\n * terminal if <createTarget> is enabled.\r\n * \r\n * Note that the target is the cell under the mouse where the mouse button was released.\r\n * Depending on the logic in the handler, this doesn't necessarily have to be the target\r\n * of the inserted edge. To print the source, target or any optional ports IDs that the\r\n * edge is connected to, the following code can be used. To get more details about the\r\n * actual connection point, <mxGraph.getConnectionConstraint> can be used. To resolve\r\n * the port IDs, use <mxGraphModel.getCell>.\r\n * \r\n * (code)\r\n * graph.connectionHandler.addListener(mxEvent.CONNECT, function(sender, evt)\r\n * {\r\n *   var edge = evt.getProperty('cell');\r\n *   var source = graph.getModel().getTerminal(edge, true);\r\n *   var target = graph.getModel().getTerminal(edge, false);\r\n *   \r\n *   var style = graph.getCellStyle(edge);\r\n *   var sourcePortId = style[mxConstants.STYLE_SOURCE_PORT];\r\n *   var targetPortId = style[mxConstants.STYLE_TARGET_PORT];\r\n *   \r\n *   mxLog.show();\r\n *   mxLog.debug('connect', edge, source.id, target.id, sourcePortId, targetPortId);\r\n * });\r\n * (end)\r\n *\r\n * Event: mxEvent.RESET\r\n * \r\n * Fires when the <reset> method is invoked.\r\n *\r\n * Constructor: mxConnectionHandler\r\n *\r\n * Constructs an event handler that connects vertices using the specified\r\n * factory method to create the new edges. Modify\r\n * <mxConstants.ACTIVE_REGION> to setup the region on a cell which triggers\r\n * the creation of a new connection or use connect icons as explained\r\n * above.\r\n * \r\n * Parameters:\r\n * \r\n * graph - Reference to the enclosing <mxGraph>.\r\n * factoryMethod - Optional function to create the edge. The function takes\r\n * the source and target <mxCell> as the first and second argument and an\r\n * optional cell style from the preview as the third argument. It returns\r\n * the <mxCell> that represents the new edge.\r\n */\r\nfunction mxConnectionHandler(graph, factoryMethod)\r\n{\r\n\tmxEventSource.call(this);\r\n\t\r\n\tif (graph != null)\r\n\t{\r\n\t\tthis.graph = graph;\r\n\t\tthis.factoryMethod = factoryMethod;\r\n\t\tthis.init();\r\n\t\t\r\n\t\t// Handles escape keystrokes\r\n\t\tthis.escapeHandler = mxUtils.bind(this, function(sender, evt)\r\n\t\t{\r\n\t\t\tthis.reset();\r\n\t\t});\r\n\t\t\r\n\t\tthis.graph.addListener(mxEvent.ESCAPE, this.escapeHandler);\r\n\t}\r\n};\r\n\r\n/**\r\n * Extends mxEventSource.\r\n */\r\nmxUtils.extend(mxConnectionHandler, mxEventSource);\r\n\r\n/**\r\n * Variable: graph\r\n * \r\n * Reference to the enclosing <mxGraph>.\r\n */\r\nmxConnectionHandler.prototype.graph = null;\r\n\r\n/**\r\n * Variable: factoryMethod\r\n * \r\n * Function that is used for creating new edges. The function takes the\r\n * source and target <mxCell> as the first and second argument and returns\r\n * a new <mxCell> that represents the edge. This is used in <createEdge>.\r\n */\r\nmxConnectionHandler.prototype.factoryMethod = true;\r\n\r\n/**\r\n * Variable: moveIconFront\r\n * \r\n * Specifies if icons should be displayed inside the graph container instead\r\n * of the overlay pane. This is used for HTML labels on vertices which hide\r\n * the connect icon. This has precendence over <moveIconBack> when set\r\n * to true. Default is false.\r\n */\r\nmxConnectionHandler.prototype.moveIconFront = false;\r\n\r\n/**\r\n * Variable: moveIconBack\r\n * \r\n * Specifies if icons should be moved to the back of the overlay pane. This can\r\n * be set to true if the icons of the connection handler conflict with other\r\n * handles, such as the vertex label move handle. Default is false.\r\n */\r\nmxConnectionHandler.prototype.moveIconBack = false;\r\n\r\n/**\r\n * Variable: connectImage\r\n * \r\n * <mxImage> that is used to trigger the creation of a new connection. This\r\n * is used in <createIcons>. Default is null.\r\n */\r\nmxConnectionHandler.prototype.connectImage = null;\r\n\r\n/**\r\n * Variable: targetConnectImage\r\n * \r\n * Specifies if the connect icon should be centered on the target state\r\n * while connections are being previewed. Default is false.\r\n */\r\nmxConnectionHandler.prototype.targetConnectImage = false;\r\n\r\n/**\r\n * Variable: enabled\r\n * \r\n * Specifies if events are handled. Default is true.\r\n */\r\nmxConnectionHandler.prototype.enabled = true;\r\n\r\n/**\r\n * Variable: select\r\n * \r\n * Specifies if new edges should be selected. Default is true.\r\n */\r\nmxConnectionHandler.prototype.select = true;\r\n\r\n/**\r\n * Variable: createTarget\r\n * \r\n * Specifies if <createTargetVertex> should be called if no target was under the\r\n * mouse for the new connection. Setting this to true means the connection\r\n * will be drawn as valid if no target is under the mouse, and\r\n * <createTargetVertex> will be called before the connection is created between\r\n * the source cell and the newly created vertex in <createTargetVertex>, which\r\n * can be overridden to create a new target. Default is false.\r\n */\r\nmxConnectionHandler.prototype.createTarget = false;\r\n\r\n/**\r\n * Variable: marker\r\n * \r\n * Holds the <mxTerminalMarker> used for finding source and target cells.\r\n */\r\nmxConnectionHandler.prototype.marker = null;\r\n\r\n/**\r\n * Variable: constraintHandler\r\n * \r\n * Holds the <mxConstraintHandler> used for drawing and highlighting\r\n * constraints.\r\n */\r\nmxConnectionHandler.prototype.constraintHandler = null;\r\n\r\n/**\r\n * Variable: error\r\n * \r\n * Holds the current validation error while connections are being created.\r\n */\r\nmxConnectionHandler.prototype.error = null;\r\n\r\n/**\r\n * Variable: waypointsEnabled\r\n * \r\n * Specifies if single clicks should add waypoints on the new edge. Default is\r\n * false.\r\n */\r\nmxConnectionHandler.prototype.waypointsEnabled = false;\r\n\r\n/**\r\n * Variable: ignoreMouseDown\r\n * \r\n * Specifies if the connection handler should ignore the state of the mouse\r\n * button when highlighting the source. Default is false, that is, the\r\n * handler only highlights the source if no button is being pressed.\r\n */\r\nmxConnectionHandler.prototype.ignoreMouseDown = false;\r\n\r\n/**\r\n * Variable: first\r\n * \r\n * Holds the <mxPoint> where the mouseDown took place while the handler is\r\n * active.\r\n */\r\nmxConnectionHandler.prototype.first = null;\r\n\r\n/**\r\n * Variable: connectIconOffset\r\n * \r\n * Holds the offset for connect icons during connection preview.\r\n * Default is mxPoint(0, <mxConstants.TOOLTIP_VERTICAL_OFFSET>).\r\n * Note that placing the icon under the mouse pointer with an\r\n * offset of (0,0) will affect hit detection.\r\n */\r\nmxConnectionHandler.prototype.connectIconOffset = new mxPoint(0, mxConstants.TOOLTIP_VERTICAL_OFFSET);\r\n\r\n/**\r\n * Variable: edgeState\r\n * \r\n * Optional <mxCellState> that represents the preview edge while the\r\n * handler is active. This is created in <createEdgeState>.\r\n */\r\nmxConnectionHandler.prototype.edgeState = null;\r\n\r\n/**\r\n * Variable: changeHandler\r\n * \r\n * Holds the change event listener for later removal.\r\n */\r\nmxConnectionHandler.prototype.changeHandler = null;\r\n\r\n/**\r\n * Variable: drillHandler\r\n * \r\n * Holds the drill event listener for later removal.\r\n */\r\nmxConnectionHandler.prototype.drillHandler = null;\r\n\r\n/**\r\n * Variable: mouseDownCounter\r\n * \r\n * Counts the number of mouseDown events since the start. The initial mouse\r\n * down event counts as 1.\r\n */\r\nmxConnectionHandler.prototype.mouseDownCounter = 0;\r\n\r\n/**\r\n * Variable: movePreviewAway\r\n * \r\n * Switch to enable moving the preview away from the mousepointer. This is required in browsers\r\n * where the preview cannot be made transparent to events and if the built-in hit detection on\r\n * the HTML elements in the page should be used. Default is the value of <mxClient.IS_VML>.\r\n */\r\nmxConnectionHandler.prototype.movePreviewAway = mxClient.IS_VML;\r\n\r\n/**\r\n * Variable: outlineConnect\r\n * \r\n * Specifies if connections to the outline of a highlighted target should be\r\n * enabled. This will allow to place the connection point along the outline of\r\n * the highlighted target. Default is false.\r\n */\r\nmxConnectionHandler.prototype.outlineConnect = false;\r\n\r\n/**\r\n * Variable: livePreview\r\n * \r\n * Specifies if the actual shape of the edge state should be used for the preview.\r\n * Default is false. (Ignored if no edge state is created in <createEdgeState>.)\r\n */\r\nmxConnectionHandler.prototype.livePreview = false;\r\n\r\n/**\r\n * Variable: cursor\r\n * \r\n * Specifies the cursor to be used while the handler is active. Default is null.\r\n */\r\nmxConnectionHandler.prototype.cursor = null;\r\n\r\n/**\r\n * Variable: insertBeforeSource\r\n * \r\n * Specifies if new edges should be inserted before the source vertex in the\r\n * cell hierarchy. Default is false for backwards compatibility.\r\n */\r\nmxConnectionHandler.prototype.insertBeforeSource = false;\r\n\r\n/**\r\n * Function: isEnabled\r\n * \r\n * Returns true if events are handled. This implementation\r\n * returns <enabled>.\r\n */\r\nmxConnectionHandler.prototype.isEnabled = function()\r\n{\r\n\treturn this.enabled;\r\n};\r\n\t\r\n/**\r\n * Function: setEnabled\r\n * \r\n * Enables or disables event handling. This implementation\r\n * updates <enabled>.\r\n * \r\n * Parameters:\r\n * \r\n * enabled - Boolean that specifies the new enabled state.\r\n */\r\nmxConnectionHandler.prototype.setEnabled = function(enabled)\r\n{\r\n\tthis.enabled = enabled;\r\n};\r\n\r\n/**\r\n * Function: isInsertBefore\r\n * \r\n * Returns <insertBeforeSource> for non-loops and false for loops.\r\n *\r\n * Parameters:\r\n * \r\n * edge - <mxCell> that represents the edge to be inserted.\r\n * source - <mxCell> that represents the source terminal.\r\n * target - <mxCell> that represents the target terminal.\r\n * evt - Mousedown event of the connect gesture.\r\n * dropTarget - <mxCell> that represents the cell under the mouse when it was\r\n * released.\r\n */\r\nmxConnectionHandler.prototype.isInsertBefore = function(edge, source, target, evt, dropTarget)\r\n{\r\n\treturn this.insertBeforeSource && source != target;\r\n};\r\n\r\n/**\r\n * Function: isCreateTarget\r\n * \r\n * Returns <createTarget>.\r\n *\r\n * Parameters:\r\n *\r\n * evt - Current active native pointer event.\r\n */\r\nmxConnectionHandler.prototype.isCreateTarget = function(evt)\r\n{\r\n\treturn this.createTarget;\r\n};\r\n\r\n/**\r\n * Function: setCreateTarget\r\n * \r\n * Sets <createTarget>.\r\n */\r\nmxConnectionHandler.prototype.setCreateTarget = function(value)\r\n{\r\n\tthis.createTarget = value;\r\n};\r\n\r\n/**\r\n * Function: createShape\r\n * \r\n * Creates the preview shape for new connections.\r\n */\r\nmxConnectionHandler.prototype.createShape = function()\r\n{\r\n\t// Creates the edge preview\r\n\tvar shape = (this.livePreview && this.edgeState != null) ?\r\n\t\tthis.graph.cellRenderer.createShape(this.edgeState) :\r\n\t\tnew mxPolyline([], mxConstants.INVALID_COLOR);\r\n\tshape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?\r\n\t\tmxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;\r\n\tshape.scale = this.graph.view.scale;\r\n\tshape.pointerEvents = false;\r\n\tshape.isDashed = true;\r\n\tshape.init(this.graph.getView().getOverlayPane());\r\n\tmxEvent.redirectMouseEvents(shape.node, this.graph, null);\r\n\r\n\treturn shape;\r\n};\r\n\r\n/**\r\n * Function: init\r\n * \r\n * Initializes the shapes required for this connection handler. This should\r\n * be invoked if <mxGraph.container> is assigned after the connection\r\n * handler has been created.\r\n */\r\nmxConnectionHandler.prototype.init = function()\r\n{\r\n\tthis.graph.addMouseListener(this);\r\n\tthis.marker = this.createMarker();\r\n\tthis.constraintHandler = new mxConstraintHandler(this.graph);\r\n\r\n\t// Redraws the icons if the graph changes\r\n\tthis.changeHandler = mxUtils.bind(this, function(sender)\r\n\t{\r\n\t\tif (this.iconState != null)\r\n\t\t{\r\n\t\t\tthis.iconState = this.graph.getView().getState(this.iconState.cell);\r\n\t\t}\r\n\t\t\r\n\t\tif (this.iconState != null)\r\n\t\t{\r\n\t\t\tthis.redrawIcons(this.icons, this.iconState);\r\n\t\t\tthis.constraintHandler.reset();\r\n\t\t}\r\n\t\telse if (this.previous != null && this.graph.view.getState(this.previous.cell) == null)\r\n\t\t{\r\n\t\t\tthis.reset();\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);\r\n\tthis.graph.getView().addListener(mxEvent.SCALE, this.changeHandler);\r\n\tthis.graph.getView().addListener(mxEvent.TRANSLATE, this.changeHandler);\r\n\tthis.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.changeHandler);\r\n\t\r\n\t// Removes the icon if we step into/up or start editing\r\n\tthis.drillHandler = mxUtils.bind(this, function(sender)\r\n\t{\r\n\t\tthis.reset();\r\n\t});\r\n\t\r\n\tthis.graph.addListener(mxEvent.START_EDITING, this.drillHandler);\r\n\tthis.graph.getView().addListener(mxEvent.DOWN, this.drillHandler);\r\n\tthis.graph.getView().addListener(mxEvent.UP, this.drillHandler);\r\n};\r\n\r\n/**\r\n * Function: isConnectableCell\r\n * \r\n * Returns true if the given cell is connectable. This is a hook to\r\n * disable floating connections. This implementation returns true.\r\n */\r\nmxConnectionHandler.prototype.isConnectableCell = function(cell)\r\n{\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: createMarker\r\n * \r\n * Creates and returns the <mxCellMarker> used in <marker>.\r\n */\r\nmxConnectionHandler.prototype.createMarker = function()\r\n{\r\n\tvar marker = new mxCellMarker(this.graph);\r\n\tmarker.hotspotEnabled = true;\r\n\r\n\t// Overrides to return cell at location only if valid (so that\r\n\t// there is no highlight for invalid cells)\r\n\tmarker.getCell = mxUtils.bind(this, function(me)\r\n\t{\r\n\t\tvar cell = mxCellMarker.prototype.getCell.apply(marker, arguments);\r\n\t\tthis.error = null;\r\n\t\t\r\n\t\t// Checks for cell at preview point (with grid)\r\n\t\tif (cell == null && this.currentPoint != null)\r\n\t\t{\r\n\t\t\tcell = this.graph.getCellAt(this.currentPoint.x, this.currentPoint.y);\r\n\t\t}\r\n\t\t\r\n\t\t// Uses connectable parent vertex if one exists\r\n\t\tif (cell != null && !this.graph.isCellConnectable(cell))\r\n\t\t{\r\n\t\t\tvar parent = this.graph.getModel().getParent(cell);\r\n\t\t\t\r\n\t\t\tif (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent))\r\n\t\t\t{\r\n\t\t\t\tcell = parent;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif ((this.graph.isSwimlane(cell) && this.currentPoint != null &&\r\n\t\t\tthis.graph.hitsSwimlaneContent(cell, this.currentPoint.x, this.currentPoint.y)) ||\r\n\t\t\t!this.isConnectableCell(cell))\r\n\t\t{\r\n\t\t\tcell = null;\r\n\t\t}\r\n\t\t\r\n\t\tif (cell != null)\r\n\t\t{\r\n\t\t\tif (this.isConnecting())\r\n\t\t\t{\r\n\t\t\t\tif (this.previous != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.error = this.validateConnection(this.previous.cell, cell);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (this.error != null && this.error.length == 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tcell = null;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t// Enables create target inside groups\r\n\t\t\t\t\t\tif (this.isCreateTarget(me.getEvent()))\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tthis.error = null;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if (!this.isValidSource(cell, me))\r\n\t\t\t{\r\n\t\t\t\tcell = null;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (this.isConnecting() && !this.isCreateTarget(me.getEvent()) &&\r\n\t\t\t\t!this.graph.allowDanglingEdges)\r\n\t\t{\r\n\t\t\tthis.error = '';\r\n\t\t}\r\n\r\n\t\treturn cell;\r\n\t});\r\n\r\n\t// Sets the highlight color according to validateConnection\r\n\tmarker.isValidState = mxUtils.bind(this, function(state)\r\n\t{\r\n\t\tif (this.isConnecting())\r\n\t\t{\r\n\t\t\treturn this.error == null;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn mxCellMarker.prototype.isValidState.apply(marker, arguments);\r\n\t\t}\r\n\t});\r\n\r\n\t// Overrides to use marker color only in highlight mode or for\r\n\t// target selection\r\n\tmarker.getMarkerColor = mxUtils.bind(this, function(evt, state, isValid)\r\n\t{\r\n\t\treturn (this.connectImage == null || this.isConnecting()) ?\r\n\t\t\tmxCellMarker.prototype.getMarkerColor.apply(marker, arguments) :\r\n\t\t\tnull;\r\n\t});\r\n\r\n\t// Overrides to use hotspot only for source selection otherwise\r\n\t// intersects always returns true when over a cell\r\n\tmarker.intersects = mxUtils.bind(this, function(state, evt)\r\n\t{\r\n\t\tif (this.connectImage != null || this.isConnecting())\r\n\t\t{\r\n\t\t\treturn true;\r\n\t\t}\r\n\t\t\r\n\t\treturn mxCellMarker.prototype.intersects.apply(marker, arguments);\r\n\t});\r\n\r\n\treturn marker;\r\n};\r\n\r\n/**\r\n * Function: start\r\n * \r\n * Starts a new connection for the given state and coordinates.\r\n */\r\nmxConnectionHandler.prototype.start = function(state, x, y, edgeState)\r\n{\r\n\tthis.previous = state;\r\n\tthis.first = new mxPoint(x, y);\r\n\tthis.edgeState = (edgeState != null) ? edgeState : this.createEdgeState(null);\r\n\t\r\n\t// Marks the source state\r\n\tthis.marker.currentColor = this.marker.validColor;\r\n\tthis.marker.markedState = state;\r\n\tthis.marker.mark();\r\n\r\n\tthis.fireEvent(new mxEventObject(mxEvent.START, 'state', this.previous));\r\n};\r\n\r\n/**\r\n * Function: isConnecting\r\n * \r\n * Returns true if the source terminal has been clicked and a new\r\n * connection is currently being previewed.\r\n */\r\nmxConnectionHandler.prototype.isConnecting = function()\r\n{\r\n\treturn this.first != null && this.shape != null;\r\n};\r\n\r\n/**\r\n * Function: isValidSource\r\n * \r\n * Returns <mxGraph.isValidSource> for the given source terminal.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> that represents the source terminal.\r\n * me - <mxMouseEvent> that is associated with this call.\r\n */\r\nmxConnectionHandler.prototype.isValidSource = function(cell, me)\r\n{\r\n\treturn this.graph.isValidSource(cell);\r\n};\r\n\r\n/**\r\n * Function: isValidTarget\r\n * \r\n * Returns true. The call to <mxGraph.isValidTarget> is implicit by calling\r\n * <mxGraph.getEdgeValidationError> in <validateConnection>. This is an\r\n * additional hook for disabling certain targets in this specific handler.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> that represents the target terminal.\r\n */\r\nmxConnectionHandler.prototype.isValidTarget = function(cell)\r\n{\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: validateConnection\r\n * \r\n * Returns the error message or an empty string if the connection for the\r\n * given source target pair is not valid. Otherwise it returns null. This\r\n * implementation uses <mxGraph.getEdgeValidationError>.\r\n * \r\n * Parameters:\r\n * \r\n * source - <mxCell> that represents the source terminal.\r\n * target - <mxCell> that represents the target terminal.\r\n */\r\nmxConnectionHandler.prototype.validateConnection = function(source, target)\r\n{\r\n\tif (!this.isValidTarget(target))\r\n\t{\r\n\t\treturn '';\r\n\t}\r\n\t\r\n\treturn this.graph.getEdgeValidationError(null, source, target);\r\n};\r\n\r\n/**\r\n * Function: getConnectImage\r\n * \r\n * Hook to return the <mxImage> used for the connection icon of the given\r\n * <mxCellState>. This implementation returns <connectImage>.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose connect image should be returned.\r\n */\r\nmxConnectionHandler.prototype.getConnectImage = function(state)\r\n{\r\n\treturn this.connectImage;\r\n};\r\n\r\n/**\r\n * Function: isMoveIconToFrontForState\r\n * \r\n * Returns true if the state has a HTML label in the graph's container, otherwise\r\n * it returns <moveIconFront>.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose connect icons should be returned.\r\n */\r\nmxConnectionHandler.prototype.isMoveIconToFrontForState = function(state)\r\n{\r\n\tif (state.text != null && state.text.node.parentNode == this.graph.container)\r\n\t{\r\n\t\treturn true;\r\n\t}\r\n\t\r\n\treturn this.moveIconFront;\r\n};\r\n\r\n/**\r\n * Function: createIcons\r\n * \r\n * Creates the array <mxImageShapes> that represent the connect icons for\r\n * the given <mxCellState>.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> whose connect icons should be returned.\r\n */\r\nmxConnectionHandler.prototype.createIcons = function(state)\r\n{\r\n\tvar image = this.getConnectImage(state);\r\n\t\r\n\tif (image != null && state != null)\r\n\t{\r\n\t\tthis.iconState = state;\r\n\t\tvar icons = [];\r\n\r\n\t\t// Cannot use HTML for the connect icons because the icon receives all\r\n\t\t// mouse move events in IE, must use VML and SVG instead even if the\r\n\t\t// connect-icon appears behind the selection border and the selection\r\n\t\t// border consumes the events before the icon gets a chance\r\n\t\tvar bounds = new mxRectangle(0, 0, image.width, image.height);\r\n\t\tvar icon = new mxImageShape(bounds, image.src, null, null, 0);\r\n\t\ticon.preserveImageAspect = false;\r\n\t\t\r\n\t\tif (this.isMoveIconToFrontForState(state))\r\n\t\t{\r\n\t\t\ticon.dialect = mxConstants.DIALECT_STRICTHTML;\r\n\t\t\ticon.init(this.graph.container);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\ticon.dialect = (this.graph.dialect == mxConstants.DIALECT_SVG) ?\r\n\t\t\t\tmxConstants.DIALECT_SVG : mxConstants.DIALECT_VML;\r\n\t\t\ticon.init(this.graph.getView().getOverlayPane());\r\n\r\n\t\t\t// Move the icon back in the overlay pane\r\n\t\t\tif (this.moveIconBack && icon.node.previousSibling != null)\r\n\t\t\t{\r\n\t\t\t\ticon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\ticon.node.style.cursor = mxConstants.CURSOR_CONNECT;\r\n\r\n\t\t// Events transparency\r\n\t\tvar getState = mxUtils.bind(this, function()\r\n\t\t{\r\n\t\t\treturn (this.currentState != null) ? this.currentState : state;\r\n\t\t});\r\n\t\t\r\n\t\t// Updates the local icon before firing the mouse down event.\r\n\t\tvar mouseDown = mxUtils.bind(this, function(evt)\r\n\t\t{\r\n\t\t\tif (!mxEvent.isConsumed(evt))\r\n\t\t\t{\r\n\t\t\t\tthis.icon = icon;\r\n\t\t\t\tthis.graph.fireMouseEvent(mxEvent.MOUSE_DOWN,\r\n\t\t\t\t\tnew mxMouseEvent(evt, getState()));\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tmxEvent.redirectMouseEvents(icon.node, this.graph, getState, mouseDown);\r\n\t\t\r\n\t\ticons.push(icon);\r\n\t\tthis.redrawIcons(icons, this.iconState);\r\n\t\t\r\n\t\treturn icons;\r\n\t}\r\n\t\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: redrawIcons\r\n * \r\n * Redraws the given array of <mxImageShapes>.\r\n * \r\n * Parameters:\r\n * \r\n * icons - Optional array of <mxImageShapes> to be redrawn.\r\n */\r\nmxConnectionHandler.prototype.redrawIcons = function(icons, state)\r\n{\r\n\tif (icons != null && icons[0] != null && state != null)\r\n\t{\r\n\t\tvar pos = this.getIconPosition(icons[0], state);\r\n\t\ticons[0].bounds.x = pos.x;\r\n\t\ticons[0].bounds.y = pos.y;\r\n\t\ticons[0].redraw();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: redrawIcons\r\n * \r\n * Redraws the given array of <mxImageShapes>.\r\n * \r\n * Parameters:\r\n * \r\n * icons - Optional array of <mxImageShapes> to be redrawn.\r\n */\r\nmxConnectionHandler.prototype.getIconPosition = function(icon, state)\r\n{\r\n\tvar scale = this.graph.getView().scale;\r\n\tvar cx = state.getCenterX();\r\n\tvar cy = state.getCenterY();\r\n\t\r\n\tif (this.graph.isSwimlane(state.cell))\r\n\t{\r\n\t\tvar size = this.graph.getStartSize(state.cell);\r\n\t\t\r\n\t\tcx = (size.width != 0) ? state.x + size.width * scale / 2 : cx;\r\n\t\tcy = (size.height != 0) ? state.y + size.height * scale / 2 : cy;\r\n\t\t\r\n\t\tvar alpha = mxUtils.toRadians(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0);\r\n\t\t\r\n\t\tif (alpha != 0)\r\n\t\t{\r\n\t\t\tvar cos = Math.cos(alpha);\r\n\t\t\tvar sin = Math.sin(alpha);\r\n\t\t\tvar ct = new mxPoint(state.getCenterX(), state.getCenterY());\r\n\t\t\tvar pt = mxUtils.getRotatedPoint(new mxPoint(cx, cy), cos, sin, ct);\r\n\t\t\tcx = pt.x;\r\n\t\t\tcy = pt.y;\r\n\t\t}\r\n\t}\r\n\r\n\treturn new mxPoint(cx - icon.bounds.width / 2,\r\n\t\t\tcy - icon.bounds.height / 2);\r\n};\r\n\r\n/**\r\n * Function: destroyIcons\r\n * \r\n * Destroys the connect icons and resets the respective state.\r\n */\r\nmxConnectionHandler.prototype.destroyIcons = function()\r\n{\r\n\tif (this.icons != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.icons.length; i++)\r\n\t\t{\r\n\t\t\tthis.icons[i].destroy();\r\n\t\t}\r\n\t\t\r\n\t\tthis.icons = null;\r\n\t\tthis.icon = null;\r\n\t\tthis.selectedIcon = null;\r\n\t\tthis.iconState = null;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isStartEvent\r\n * \r\n * Returns true if the given mouse down event should start this handler. The\r\n * This implementation returns true if the event does not force marquee\r\n * selection, and the currentConstraint and currentFocus of the\r\n * <constraintHandler> are not null, or <previous> and <error> are not null and\r\n * <icons> is null or <icons> and <icon> are not null.\r\n */\r\nmxConnectionHandler.prototype.isStartEvent = function(me)\r\n{\r\n\treturn ((this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null) ||\r\n\t\t(this.previous != null && this.error == null && (this.icons == null || (this.icons != null &&\r\n\t\tthis.icon != null))));\r\n};\r\n\r\n/**\r\n * Function: mouseDown\r\n * \r\n * Handles the event by initiating a new connection.\r\n */\r\nmxConnectionHandler.prototype.mouseDown = function(sender, me)\r\n{\r\n\tthis.mouseDownCounter++;\r\n\t\r\n\tif (this.isEnabled() && this.graph.isEnabled() && !me.isConsumed() &&\r\n\t\t!this.isConnecting() && this.isStartEvent(me))\r\n\t{\r\n\t\tif (this.constraintHandler.currentConstraint != null &&\r\n\t\t\tthis.constraintHandler.currentFocus != null &&\r\n\t\t\tthis.constraintHandler.currentPoint != null)\r\n\t\t{\r\n\t\t\tthis.sourceConstraint = this.constraintHandler.currentConstraint;\r\n\t\t\tthis.previous = this.constraintHandler.currentFocus;\r\n\t\t\tthis.first = this.constraintHandler.currentPoint.clone();\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Stores the location of the initial mousedown\r\n\t\t\tthis.first = new mxPoint(me.getGraphX(), me.getGraphY());\r\n\t\t}\r\n\t\r\n\t\tthis.edgeState = this.createEdgeState(me);\r\n\t\tthis.mouseDownCounter = 1;\r\n\t\t\r\n\t\tif (this.waypointsEnabled && this.shape == null)\r\n\t\t{\r\n\t\t\tthis.waypoints = null;\r\n\t\t\tthis.shape = this.createShape();\r\n\t\t\t\r\n\t\t\tif (this.edgeState != null)\r\n\t\t\t{\r\n\t\t\t\tthis.shape.apply(this.edgeState);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Stores the starting point in the geometry of the preview\r\n\t\tif (this.previous == null && this.edgeState != null)\r\n\t\t{\r\n\t\t\tvar pt = this.graph.getPointForEvent(me.getEvent());\r\n\t\t\tthis.edgeState.cell.geometry.setTerminalPoint(pt, true);\r\n\t\t}\r\n\t\t\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.START, 'state', this.previous));\r\n\r\n\t\tme.consume();\r\n\t}\r\n\r\n\tthis.selectedIcon = this.icon;\r\n\tthis.icon = null;\r\n};\r\n\r\n/**\r\n * Function: isImmediateConnectSource\r\n * \r\n * Returns true if a tap on the given source state should immediately start\r\n * connecting. This implementation returns true if the state is not movable\r\n * in the graph. \r\n */\r\nmxConnectionHandler.prototype.isImmediateConnectSource = function(state)\r\n{\r\n\treturn !this.graph.isCellMovable(state.cell);\r\n};\r\n\r\n/**\r\n * Function: createEdgeState\r\n * \r\n * Hook to return an <mxCellState> which may be used during the preview.\r\n * This implementation returns null.\r\n * \r\n * Use the following code to create a preview for an existing edge style:\r\n * \r\n * (code)\r\n * graph.connectionHandler.createEdgeState = function(me)\r\n * {\r\n *   var edge = graph.createEdge(null, null, null, null, null, 'edgeStyle=elbowEdgeStyle');\r\n *   \r\n *   return new mxCellState(this.graph.view, edge, this.graph.getCellStyle(edge));\r\n * };\r\n * (end)\r\n */\r\nmxConnectionHandler.prototype.createEdgeState = function(me)\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: isOutlineConnectEvent\r\n * \r\n * Returns true if <outlineConnect> is true and the source of the event is the outline shape\r\n * or shift is pressed.\r\n */\r\nmxConnectionHandler.prototype.isOutlineConnectEvent = function(me)\r\n{\r\n\tvar offset = mxUtils.getOffset(this.graph.container);\r\n\tvar evt = me.getEvent();\r\n\t\r\n\tvar clientX = mxEvent.getClientX(evt);\r\n\tvar clientY = mxEvent.getClientY(evt);\r\n\t\r\n\tvar doc = document.documentElement;\r\n\tvar left = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);\r\n\tvar top = (window.pageYOffset || doc.scrollTop)  - (doc.clientTop || 0);\r\n\t\r\n\tvar gridX = this.currentPoint.x - this.graph.container.scrollLeft + offset.x - left;\r\n\tvar gridY = this.currentPoint.y - this.graph.container.scrollTop + offset.y - top;\r\n\r\n\treturn this.outlineConnect && !mxEvent.isShiftDown(me.getEvent()) &&\r\n\t\t(me.isSource(this.marker.highlight.shape) ||\r\n\t\t(mxEvent.isAltDown(me.getEvent()) && me.getState() != null) ||\r\n\t\tthis.marker.highlight.isHighlightAt(clientX, clientY) ||\r\n\t\t((gridX != clientX || gridY != clientY) && me.getState() == null &&\r\n\t\tthis.marker.highlight.isHighlightAt(gridX, gridY)));\r\n};\r\n\r\n/**\r\n * Function: updateCurrentState\r\n * \r\n * Updates the current state for a given mouse move event by using\r\n * the <marker>.\r\n */\r\nmxConnectionHandler.prototype.updateCurrentState = function(me, point)\r\n{\r\n\tthis.constraintHandler.update(me, this.first == null, false, (this.first == null ||\r\n\t\tme.isSource(this.marker.highlight.shape)) ? null : point);\r\n\t\r\n\tif (this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null)\r\n\t{\r\n\t\t// Handles special case where grid is large and connection point is at actual point in which\r\n\t\t// case the outline is not followed as long as we're < gridSize / 2 away from that point\r\n\t\tif (this.marker.highlight != null && this.marker.highlight.state != null &&\r\n\t\t\tthis.marker.highlight.state.cell == this.constraintHandler.currentFocus.cell)\r\n\t\t{\r\n\t\t\t// Direct repaint needed if cell already highlighted\r\n\t\t\tif (this.marker.highlight.shape.stroke != 'transparent')\r\n\t\t\t{\r\n\t\t\t\tthis.marker.highlight.shape.stroke = 'transparent';\r\n\t\t\t\tthis.marker.highlight.repaint();\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.marker.markCell(this.constraintHandler.currentFocus.cell, 'transparent');\r\n\t\t}\r\n\r\n\t\t// Updates validation state\r\n\t\tif (this.previous != null)\r\n\t\t{\r\n\t\t\tthis.error = this.validateConnection(this.previous.cell, this.constraintHandler.currentFocus.cell);\r\n\t\t\t\r\n\t\t\tif (this.error == null)\r\n\t\t\t{\r\n\t\t\t\tthis.currentState = this.constraintHandler.currentFocus;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.constraintHandler.reset();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tif (this.graph.isIgnoreTerminalEvent(me.getEvent()))\r\n\t\t{\r\n\t\t\tthis.marker.reset();\r\n\t\t\tthis.currentState = null;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.marker.process(me);\r\n\t\t\tthis.currentState = this.marker.getValidState();\r\n\t\t\t\r\n\t\t\tif (this.currentState != null && !this.isCellEnabled(this.currentState.cell))\r\n\t\t\t{\r\n\t\t\t\tthis.currentState = null;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tvar outline = this.isOutlineConnectEvent(me);\r\n\t\t\r\n\t\tif (this.currentState != null && outline)\r\n\t\t{\r\n\t\t\t// Handles special case where mouse is on outline away from actual end point\r\n\t\t\t// in which case the grid is ignored and mouse point is used instead\r\n\t\t\tif (me.isSource(this.marker.highlight.shape))\r\n\t\t\t{\r\n\t\t\t\tpoint = new mxPoint(me.getGraphX(), me.getGraphY());\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar constraint = this.graph.getOutlineConstraint(point, this.currentState, me);\r\n\t\t\tthis.constraintHandler.setFocus(me, this.currentState, false);\r\n\t\t\tthis.constraintHandler.currentConstraint = constraint;\r\n\t\t\tthis.constraintHandler.currentPoint = point;\r\n\t\t}\r\n\r\n\t\tif (this.outlineConnect)\r\n\t\t{\r\n\t\t\tif (this.marker.highlight != null && this.marker.highlight.shape != null)\r\n\t\t\t{\r\n\t\t\t\tvar s = this.graph.view.scale;\r\n\t\t\t\t\r\n\t\t\t\tif (this.constraintHandler.currentConstraint != null &&\r\n\t\t\t\t\tthis.constraintHandler.currentFocus != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.marker.highlight.shape.stroke = mxConstants.OUTLINE_HIGHLIGHT_COLOR;\r\n\t\t\t\t\tthis.marker.highlight.shape.strokewidth = mxConstants.OUTLINE_HIGHLIGHT_STROKEWIDTH / s / s;\r\n\t\t\t\t\tthis.marker.highlight.repaint();\r\n\t\t\t\t} \r\n\t\t\t\telse if (this.marker.hasValidState())\r\n\t\t\t\t{\r\n\t\t\t\t\t// Handles special case where actual end point of edge and current mouse point\r\n\t\t\t\t\t// are not equal (due to grid snapping) and there is no hit on shape or highlight\r\n\t\t\t\t\tif (this.marker.getValidState() != me.getState())\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.marker.highlight.shape.stroke = 'transparent';\r\n\t\t\t\t\t\tthis.currentState = null;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.marker.highlight.shape.stroke = mxConstants.DEFAULT_VALID_COLOR;\r\n\t\t\t\t\t}\r\n\t\r\n\t\t\t\t\tthis.marker.highlight.shape.strokewidth = mxConstants.HIGHLIGHT_STROKEWIDTH / s / s;\r\n\t\t\t\t\tthis.marker.highlight.repaint();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isCellEnabled\r\n * \r\n * Returns true if the given cell does not allow new connections to be created.\r\n */\r\nmxConnectionHandler.prototype.isCellEnabled = function(cell)\r\n{\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: convertWaypoint\r\n * \r\n * Converts the given point from screen coordinates to model coordinates.\r\n */\r\nmxConnectionHandler.prototype.convertWaypoint = function(point)\r\n{\r\n\tvar scale = this.graph.getView().getScale();\r\n\tvar tr = this.graph.getView().getTranslate();\r\n\t\r\n\tpoint.x = point.x / scale - tr.x;\r\n\tpoint.y = point.y / scale - tr.y;\r\n};\r\n\r\n/**\r\n * Function: snapToPreview\r\n * \r\n * Called to snap the given point to the current preview. This snaps to the\r\n * first point of the preview if alt is not pressed.\r\n */\r\nmxConnectionHandler.prototype.snapToPreview = function(me, point)\r\n{\r\n\tif (!mxEvent.isAltDown(me.getEvent()) && this.previous != null)\r\n\t{\r\n\t\tvar tol = this.graph.gridSize * this.graph.view.scale / 2;\t\r\n\t\tvar tmp = (this.sourceConstraint != null) ? this.first :\r\n\t\t\tnew mxPoint(this.previous.getCenterX(), this.previous.getCenterY());\r\n\r\n\t\tif (Math.abs(tmp.x - me.getGraphX()) < tol)\r\n\t\t{\r\n\t\t\tpoint.x = tmp.x;\r\n\t\t}\r\n\t\t\r\n\t\tif (Math.abs(tmp.y - me.getGraphY()) < tol)\r\n\t\t{\r\n\t\t\tpoint.y = tmp.y;\r\n\t\t}\r\n\t}\t\r\n};\r\n\r\n/**\r\n * Function: mouseMove\r\n * \r\n * Handles the event by updating the preview edge or by highlighting\r\n * a possible source or target terminal.\r\n */\r\nmxConnectionHandler.prototype.mouseMove = function(sender, me)\r\n{\r\n\tif (!me.isConsumed() && (this.ignoreMouseDown || this.first != null || !this.graph.isMouseDown))\r\n\t{\r\n\t\t// Handles special case when handler is disabled during highlight\r\n\t\tif (!this.isEnabled() && this.currentState != null)\r\n\t\t{\r\n\t\t\tthis.destroyIcons();\r\n\t\t\tthis.currentState = null;\r\n\t\t}\r\n\r\n\t\tvar view = this.graph.getView();\r\n\t\tvar scale = view.scale;\r\n\t\tvar tr = view.translate;\r\n\t\tvar point = new mxPoint(me.getGraphX(), me.getGraphY());\r\n\t\tthis.error = null;\r\n\r\n\t\tif (this.graph.isGridEnabledEvent(me.getEvent()))\r\n\t\t{\r\n\t\t\tpoint = new mxPoint((this.graph.snap(point.x / scale - tr.x) + tr.x) * scale,\r\n\t\t\t\t(this.graph.snap(point.y / scale - tr.y) + tr.y) * scale);\r\n\t\t}\r\n\t\t\r\n\t\tthis.snapToPreview(me, point);\r\n\t\tthis.currentPoint = point;\r\n\t\t\r\n\t\tif ((this.first != null || (this.isEnabled() && this.graph.isEnabled())) &&\r\n\t\t\t(this.shape != null || this.first == null ||\r\n\t\t\tMath.abs(me.getGraphX() - this.first.x) > this.graph.tolerance ||\r\n\t\t\tMath.abs(me.getGraphY() - this.first.y) > this.graph.tolerance))\r\n\t\t{\r\n\t\t\tthis.updateCurrentState(me, point);\r\n\t\t}\r\n\r\n\t\tif (this.first != null)\r\n\t\t{\r\n\t\t\tvar constraint = null;\r\n\t\t\tvar current = point;\r\n\t\t\t\r\n\t\t\t// Uses the current point from the constraint handler if available\r\n\t\t\tif (this.constraintHandler.currentConstraint != null &&\r\n\t\t\t\tthis.constraintHandler.currentFocus != null &&\r\n\t\t\t\tthis.constraintHandler.currentPoint != null)\r\n\t\t\t{\r\n\t\t\t\tconstraint = this.constraintHandler.currentConstraint;\r\n\t\t\t\tcurrent = this.constraintHandler.currentPoint.clone();\r\n\t\t\t}\r\n\t\t\telse if (this.previous != null && !this.graph.isIgnoreTerminalEvent(me.getEvent()) &&\r\n\t\t\t\tmxEvent.isShiftDown(me.getEvent()))\r\n\t\t\t{\r\n\t\t\t\tif (Math.abs(this.previous.getCenterX() - point.x) <\r\n\t\t\t\t\tMath.abs(this.previous.getCenterY() - point.y))\r\n\t\t\t\t{\r\n\t\t\t\t\tpoint.x = this.previous.getCenterX();\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tpoint.y = this.previous.getCenterY();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar pt2 = this.first;\r\n\t\t\t\r\n\t\t\t// Moves the connect icon with the mouse\r\n\t\t\tif (this.selectedIcon != null)\r\n\t\t\t{\r\n\t\t\t\tvar w = this.selectedIcon.bounds.width;\r\n\t\t\t\tvar h = this.selectedIcon.bounds.height;\r\n\t\t\t\t\r\n\t\t\t\tif (this.currentState != null && this.targetConnectImage)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar pos = this.getIconPosition(this.selectedIcon, this.currentState);\r\n\t\t\t\t\tthis.selectedIcon.bounds.x = pos.x;\r\n\t\t\t\t\tthis.selectedIcon.bounds.y = pos.y;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tvar bounds = new mxRectangle(me.getGraphX() + this.connectIconOffset.x,\r\n\t\t\t\t\t\tme.getGraphY() + this.connectIconOffset.y, w, h);\r\n\t\t\t\t\tthis.selectedIcon.bounds = bounds;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.selectedIcon.redraw();\r\n\t\t\t}\r\n\r\n\t\t\t// Uses edge state to compute the terminal points\r\n\t\t\tif (this.edgeState != null)\r\n\t\t\t{\r\n\t\t\t\tthis.updateEdgeState(current, constraint);\r\n\t\t\t\tcurrent = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 1];\r\n\t\t\t\tpt2 = this.edgeState.absolutePoints[0];\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tif (this.currentState != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (this.constraintHandler.currentConstraint == null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar tmp = this.getTargetPerimeterPoint(this.currentState, me);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (tmp != null)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tcurrent = tmp;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Computes the source perimeter point\r\n\t\t\t\tif (this.sourceConstraint == null && this.previous != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar next = (this.waypoints != null && this.waypoints.length > 0) ?\r\n\t\t\t\t\t\t\tthis.waypoints[0] : current;\r\n\t\t\t\t\tvar tmp = this.getSourcePerimeterPoint(this.previous, next, me);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (tmp != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tpt2 = tmp;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// Makes sure the cell under the mousepointer can be detected\r\n\t\t\t// by moving the preview shape away from the mouse. This\r\n\t\t\t// makes sure the preview shape does not prevent the detection\r\n\t\t\t// of the cell under the mousepointer even for slow gestures.\r\n\t\t\tif (this.currentState == null && this.movePreviewAway)\r\n\t\t\t{\r\n\t\t\t\tvar tmp = pt2; \r\n\t\t\t\t\r\n\t\t\t\tif (this.edgeState != null && this.edgeState.absolutePoints.length >= 2)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar tmp2 = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 2];\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (tmp2 != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ttmp = tmp2;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tvar dx = current.x - tmp.x;\r\n\t\t\t\tvar dy = current.y - tmp.y;\r\n\t\t\t\t\r\n\t\t\t\tvar len = Math.sqrt(dx * dx + dy * dy);\r\n\t\t\t\t\r\n\t\t\t\tif (len == 0)\r\n\t\t\t\t{\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Stores old point to reuse when creating edge\r\n\t\t\t\tthis.originalPoint = current.clone();\r\n\t\t\t\tcurrent.x -= dx * 4 / len;\r\n\t\t\t\tcurrent.y -= dy * 4 / len;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.originalPoint = null;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Creates the preview shape (lazy)\r\n\t\t\tif (this.shape == null)\r\n\t\t\t{\r\n\t\t\t\tvar dx = Math.abs(me.getGraphX() - this.first.x);\r\n\t\t\t\tvar dy = Math.abs(me.getGraphY() - this.first.y);\r\n\r\n\t\t\t\tif (dx > this.graph.tolerance || dy > this.graph.tolerance)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.shape = this.createShape();\r\n\r\n\t\t\t\t\tif (this.edgeState != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.shape.apply(this.edgeState);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Revalidates current connection\r\n\t\t\t\t\tthis.updateCurrentState(me, point);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// Updates the points in the preview edge\r\n\t\t\tif (this.shape != null)\r\n\t\t\t{\r\n\t\t\t\tif (this.edgeState != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.shape.points = this.edgeState.absolutePoints;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tvar pts = [pt2];\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (this.waypoints != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tpts = pts.concat(this.waypoints);\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tpts.push(current);\r\n\t\t\t\t\tthis.shape.points = pts;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.drawPreview();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Makes sure endpoint of edge is visible during connect\r\n\t\t\tif (this.cursor != null)\r\n\t\t\t{\r\n\t\t\t\tthis.graph.container.style.cursor = this.cursor;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tmxEvent.consume(me.getEvent());\r\n\t\t\tme.consume();\r\n\t\t}\r\n\t\telse if (!this.isEnabled() || !this.graph.isEnabled())\r\n\t\t{\r\n\t\t\tthis.constraintHandler.reset();\r\n\t\t}\r\n\t\telse if (this.previous != this.currentState && this.edgeState == null)\r\n\t\t{\r\n\t\t\tthis.destroyIcons();\r\n\t\t\t\r\n\t\t\t// Sets the cursor on the current shape\t\t\t\t\r\n\t\t\tif (this.currentState != null && this.error == null && this.constraintHandler.currentConstraint == null)\r\n\t\t\t{\r\n\t\t\t\tthis.icons = this.createIcons(this.currentState);\r\n\r\n\t\t\t\tif (this.icons == null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.currentState.setCursor(mxConstants.CURSOR_CONNECT);\r\n\t\t\t\t\tme.consume();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tthis.previous = this.currentState;\r\n\t\t}\r\n\t\telse if (this.previous == this.currentState && this.currentState != null && this.icons == null &&\r\n\t\t\t!this.graph.isMouseDown)\r\n\t\t{\r\n\t\t\t// Makes sure that no cursors are changed\r\n\t\t\tme.consume();\r\n\t\t}\r\n\r\n\t\tif (!this.graph.isMouseDown && this.currentState != null && this.icons != null)\r\n\t\t{\r\n\t\t\tvar hitsIcon = false;\r\n\t\t\tvar target = me.getSource();\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < this.icons.length && !hitsIcon; i++)\r\n\t\t\t{\r\n\t\t\t\thitsIcon = target == this.icons[i].node || target.parentNode == this.icons[i].node;\r\n\t\t\t}\r\n\r\n\t\t\tif (!hitsIcon)\r\n\t\t\t{\r\n\t\t\t\tthis.updateIcons(this.currentState, this.icons, me);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.constraintHandler.reset();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: updateEdgeState\r\n * \r\n * Updates <edgeState>.\r\n */\r\nmxConnectionHandler.prototype.updateEdgeState = function(current, constraint)\r\n{\r\n\t// TODO: Use generic method for writing constraint to style\r\n\tif (this.sourceConstraint != null && this.sourceConstraint.point != null)\r\n\t{\r\n\t\tthis.edgeState.style[mxConstants.STYLE_EXIT_X] = this.sourceConstraint.point.x;\r\n\t\tthis.edgeState.style[mxConstants.STYLE_EXIT_Y] = this.sourceConstraint.point.y;\r\n\t}\r\n\r\n\tif (constraint != null && constraint.point != null)\r\n\t{\r\n\t\tthis.edgeState.style[mxConstants.STYLE_ENTRY_X] = constraint.point.x;\r\n\t\tthis.edgeState.style[mxConstants.STYLE_ENTRY_Y] = constraint.point.y;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tdelete this.edgeState.style[mxConstants.STYLE_ENTRY_X];\r\n\t\tdelete this.edgeState.style[mxConstants.STYLE_ENTRY_Y];\r\n\t}\r\n\t\r\n\tthis.edgeState.absolutePoints = [null, (this.currentState != null) ? null : current];\r\n\tthis.graph.view.updateFixedTerminalPoint(this.edgeState, this.previous, true, this.sourceConstraint);\r\n\t\r\n\tif (this.currentState != null)\r\n\t{\r\n\t\tif (constraint == null)\r\n\t\t{\r\n\t\t\tconstraint = this.graph.getConnectionConstraint(this.edgeState, this.previous, false);\r\n\t\t}\r\n\t\t\r\n\t\tthis.edgeState.setAbsoluteTerminalPoint(null, false);\r\n\t\tthis.graph.view.updateFixedTerminalPoint(this.edgeState, this.currentState, false, constraint);\r\n\t}\r\n\t\r\n\t// Scales and translates the waypoints to the model\r\n\tvar realPoints = null;\r\n\t\r\n\tif (this.waypoints != null)\r\n\t{\r\n\t\trealPoints = [];\r\n\t\t\r\n\t\tfor (var i = 0; i < this.waypoints.length; i++)\r\n\t\t{\r\n\t\t\tvar pt = this.waypoints[i].clone();\r\n\t\t\tthis.convertWaypoint(pt);\r\n\t\t\trealPoints[i] = pt;\r\n\t\t}\r\n\t}\r\n\t\r\n\tthis.graph.view.updatePoints(this.edgeState, realPoints, this.previous, this.currentState);\r\n\tthis.graph.view.updateFloatingTerminalPoints(this.edgeState, this.previous, this.currentState);\r\n};\r\n\r\n/**\r\n * Function: getTargetPerimeterPoint\r\n * \r\n * Returns the perimeter point for the given target state.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> that represents the target cell state.\r\n * me - <mxMouseEvent> that represents the mouse move.\r\n */\r\nmxConnectionHandler.prototype.getTargetPerimeterPoint = function(state, me)\r\n{\r\n\tvar result = null;\r\n\tvar view = state.view;\r\n\tvar targetPerimeter = view.getPerimeterFunction(state);\r\n\t\r\n\tif (targetPerimeter != null)\r\n\t{\r\n\t\tvar next = (this.waypoints != null && this.waypoints.length > 0) ?\r\n\t\t\t\tthis.waypoints[this.waypoints.length - 1] :\r\n\t\t\t\tnew mxPoint(this.previous.getCenterX(), this.previous.getCenterY());\r\n\t\tvar tmp = targetPerimeter(view.getPerimeterBounds(state),\r\n\t\t\tthis.edgeState, next, false);\r\n\t\t\t\r\n\t\tif (tmp != null)\r\n\t\t{\r\n\t\t\tresult = tmp;\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tresult = new mxPoint(state.getCenterX(), state.getCenterY());\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: getSourcePerimeterPoint\r\n * \r\n * Hook to update the icon position(s) based on a mouseOver event. This is\r\n * an empty implementation.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> that represents the target cell state.\r\n * next - <mxPoint> that represents the next point along the previewed edge.\r\n * me - <mxMouseEvent> that represents the mouse move.\r\n */\r\nmxConnectionHandler.prototype.getSourcePerimeterPoint = function(state, next, me)\r\n{\r\n\tvar result = null;\r\n\tvar view = state.view;\r\n\tvar sourcePerimeter = view.getPerimeterFunction(state);\r\n\tvar c = new mxPoint(state.getCenterX(), state.getCenterY());\r\n\t\r\n\tif (sourcePerimeter != null)\r\n\t{\r\n\t\tvar theta = mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0);\r\n\t\tvar rad = -theta * (Math.PI / 180);\r\n\t\t\r\n\t\tif (theta != 0)\r\n\t\t{\r\n\t\t\tnext = mxUtils.getRotatedPoint(new mxPoint(next.x, next.y), Math.cos(rad), Math.sin(rad), c);\r\n\t\t}\r\n\t\t\r\n\t\tvar tmp = sourcePerimeter(view.getPerimeterBounds(state), state, next, false);\r\n\t\t\t\r\n\t\tif (tmp != null)\r\n\t\t{\r\n\t\t\tif (theta != 0)\r\n\t\t\t{\r\n\t\t\t\ttmp = mxUtils.getRotatedPoint(new mxPoint(tmp.x, tmp.y), Math.cos(-rad), Math.sin(-rad), c);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tresult = tmp;\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tresult = c;\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n\r\n/**\r\n * Function: updateIcons\r\n * \r\n * Hook to update the icon position(s) based on a mouseOver event. This is\r\n * an empty implementation.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> under the mouse.\r\n * icons - Array of currently displayed icons.\r\n * me - <mxMouseEvent> that contains the mouse event.\r\n */\r\nmxConnectionHandler.prototype.updateIcons = function(state, icons, me)\r\n{\r\n\t// empty\r\n};\r\n\r\n/**\r\n * Function: isStopEvent\r\n * \r\n * Returns true if the given mouse up event should stop this handler. The\r\n * connection will be created if <error> is null. Note that this is only\r\n * called if <waypointsEnabled> is true. This implemtation returns true\r\n * if there is a cell state in the given event.\r\n */\r\nmxConnectionHandler.prototype.isStopEvent = function(me)\r\n{\r\n\treturn me.getState() != null;\r\n};\r\n\r\n/**\r\n * Function: addWaypoint\r\n * \r\n * Adds the waypoint for the given event to <waypoints>.\r\n */\r\nmxConnectionHandler.prototype.addWaypointForEvent = function(me)\r\n{\r\n\tvar point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY());\r\n\tvar dx = Math.abs(point.x - this.first.x);\r\n\tvar dy = Math.abs(point.y - this.first.y);\r\n\tvar addPoint = this.waypoints != null || (this.mouseDownCounter > 1 &&\r\n\t\t\t(dx > this.graph.tolerance || dy > this.graph.tolerance));\r\n\r\n\tif (addPoint)\r\n\t{\r\n\t\tif (this.waypoints == null)\r\n\t\t{\r\n\t\t\tthis.waypoints = [];\r\n\t\t}\r\n\t\t\r\n\t\tvar scale = this.graph.view.scale;\r\n\t\tvar point = new mxPoint(this.graph.snap(me.getGraphX() / scale) * scale,\r\n\t\t\t\tthis.graph.snap(me.getGraphY() / scale) * scale);\r\n\t\tthis.waypoints.push(point);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: checkConstraints\r\n * \r\n * Returns true if the connection for the given constraints is valid. This\r\n * implementation returns true if the constraints are not pointing to the\r\n * same fixed connection point.\r\n */\r\nmxConnectionHandler.prototype.checkConstraints = function(c1, c2)\r\n{\r\n\treturn (c1 == null || c2 == null || c1.point == null || c2.point == null ||\r\n\t\t!c1.point.equals(c2.point) || c1.dx != c2.dx || c1.dy != c2.dy ||\r\n\t\tc1.perimeter != c2.perimeter);\r\n};\r\n\r\n/**\r\n * Function: mouseUp\r\n * \r\n * Handles the event by inserting the new connection.\r\n */\r\nmxConnectionHandler.prototype.mouseUp = function(sender, me)\r\n{\r\n\tif (!me.isConsumed() && this.isConnecting())\r\n\t{\r\n\t\tif (this.waypointsEnabled && !this.isStopEvent(me))\r\n\t\t{\r\n\t\t\tthis.addWaypointForEvent(me);\r\n\t\t\tme.consume();\r\n\t\t\t\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tvar c1 = this.sourceConstraint;\r\n\t\tvar c2 = this.constraintHandler.currentConstraint;\r\n\r\n\t\tvar source = (this.previous != null) ? this.previous.cell : null;\r\n\t\tvar target = null;\r\n\t\t\r\n\t\tif (this.constraintHandler.currentConstraint != null &&\r\n\t\t\tthis.constraintHandler.currentFocus != null)\r\n\t\t{\r\n\t\t\ttarget = this.constraintHandler.currentFocus.cell;\r\n\t\t}\r\n\t\t\r\n\t\tif (target == null && this.currentState != null)\r\n\t\t{\r\n\t\t\ttarget = this.currentState.cell;\r\n\t\t}\r\n\t\t\r\n\t\t// Inserts the edge if no validation error exists and if constraints differ\r\n\t\tif (this.error == null && (source == null || target == null ||\r\n\t\t\tsource != target || this.checkConstraints(c1, c2)))\r\n\t\t{\r\n\t\t\tthis.connect(source, target, me.getEvent(), me.getCell());\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Selects the source terminal for self-references\r\n\t\t\tif (this.previous != null && this.marker.validState != null &&\r\n\t\t\t\tthis.previous.cell == this.marker.validState.cell)\r\n\t\t\t{\r\n\t\t\t\tthis.graph.selectCellForEvent(this.marker.source, me.getEvent());\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Displays the error message if it is not an empty string,\r\n\t\t\t// for empty error messages, the event is silently dropped\r\n\t\t\tif (this.error != null && this.error.length > 0)\r\n\t\t\t{\r\n\t\t\t\tthis.graph.validationAlert(this.error);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Redraws the connect icons and resets the handler state\r\n\t\tthis.destroyIcons();\r\n\t\tme.consume();\r\n\t}\r\n\r\n\tif (this.first != null)\r\n\t{\r\n\t\tthis.reset();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: reset\r\n * \r\n * Resets the state of this handler.\r\n */\r\nmxConnectionHandler.prototype.reset = function()\r\n{\r\n\tif (this.shape != null)\r\n\t{\r\n\t\tthis.shape.destroy();\r\n\t\tthis.shape = null;\r\n\t}\r\n\t\r\n\t// Resets the cursor on the container\r\n\tif (this.cursor != null && this.graph.container != null)\r\n\t{\r\n\t\tthis.graph.container.style.cursor = '';\r\n\t}\r\n\t\r\n\tthis.destroyIcons();\r\n\tthis.marker.reset();\r\n\tthis.constraintHandler.reset();\r\n\tthis.originalPoint = null;\r\n\tthis.currentPoint = null;\r\n\tthis.edgeState = null;\r\n\tthis.previous = null;\r\n\tthis.error = null;\r\n\tthis.sourceConstraint = null;\r\n\tthis.mouseDownCounter = 0;\r\n\tthis.first = null;\r\n\r\n\tthis.fireEvent(new mxEventObject(mxEvent.RESET));\r\n};\r\n\r\n/**\r\n * Function: drawPreview\r\n * \r\n * Redraws the preview edge using the color and width returned by\r\n * <getEdgeColor> and <getEdgeWidth>.\r\n */\r\nmxConnectionHandler.prototype.drawPreview = function()\r\n{\r\n\tthis.updatePreview(this.error == null);\r\n\tthis.shape.redraw();\r\n};\r\n\r\n/**\r\n * Function: getEdgeColor\r\n * \r\n * Returns the color used to draw the preview edge. This returns green if\r\n * there is no edge validation error and red otherwise.\r\n * \r\n * Parameters:\r\n * \r\n * valid - Boolean indicating if the color for a valid edge should be\r\n * returned.\r\n */\r\nmxConnectionHandler.prototype.updatePreview = function(valid)\r\n{\r\n\tthis.shape.strokewidth = this.getEdgeWidth(valid);\r\n\tthis.shape.stroke = this.getEdgeColor(valid);\r\n};\r\n\r\n/**\r\n * Function: getEdgeColor\r\n * \r\n * Returns the color used to draw the preview edge. This returns green if\r\n * there is no edge validation error and red otherwise.\r\n * \r\n * Parameters:\r\n * \r\n * valid - Boolean indicating if the color for a valid edge should be\r\n * returned.\r\n */\r\nmxConnectionHandler.prototype.getEdgeColor = function(valid)\r\n{\r\n\treturn (valid) ? mxConstants.VALID_COLOR : mxConstants.INVALID_COLOR;\r\n};\r\n\t\r\n/**\r\n * Function: getEdgeWidth\r\n * \r\n * Returns the width used to draw the preview edge. This returns 3 if\r\n * there is no edge validation error and 1 otherwise.\r\n * \r\n * Parameters:\r\n * \r\n * valid - Boolean indicating if the width for a valid edge should be\r\n * returned.\r\n */\r\nmxConnectionHandler.prototype.getEdgeWidth = function(valid)\r\n{\r\n\treturn (valid) ? 3 : 1;\r\n};\r\n\r\n/**\r\n * Function: connect\r\n * \r\n * Connects the given source and target using a new edge. This\r\n * implementation uses <createEdge> to create the edge.\r\n * \r\n * Parameters:\r\n * \r\n * source - <mxCell> that represents the source terminal.\r\n * target - <mxCell> that represents the target terminal.\r\n * evt - Mousedown event of the connect gesture.\r\n * dropTarget - <mxCell> that represents the cell under the mouse when it was\r\n * released.\r\n */\r\nmxConnectionHandler.prototype.connect = function(source, target, evt, dropTarget)\r\n{\r\n\tif (target != null || this.isCreateTarget(evt) || this.graph.allowDanglingEdges)\r\n\t{\r\n\t\t// Uses the common parent of source and target or\r\n\t\t// the default parent to insert the edge\r\n\t\tvar model = this.graph.getModel();\r\n\t\tvar terminalInserted = false;\r\n\t\tvar edge = null;\r\n\r\n\t\tmodel.beginUpdate();\r\n\t\ttry\r\n\t\t{\r\n\t\t\tif (source != null && target == null && !this.graph.isIgnoreTerminalEvent(evt) && this.isCreateTarget(evt))\r\n\t\t\t{\r\n\t\t\t\ttarget = this.createTargetVertex(evt, source);\r\n\t\t\t\t\r\n\t\t\t\tif (target != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tdropTarget = this.graph.getDropTarget([target], evt, dropTarget);\r\n\t\t\t\t\tterminalInserted = true;\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Disables edges as drop targets if the target cell was created\r\n\t\t\t\t\t// FIXME: Should not shift if vertex was aligned (same in Java)\r\n\t\t\t\t\tif (dropTarget == null || !this.graph.getModel().isEdge(dropTarget))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar pstate = this.graph.getView().getState(dropTarget);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (pstate != null)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar tmp = model.getGeometry(target);\r\n\t\t\t\t\t\t\ttmp.x -= pstate.origin.x;\r\n\t\t\t\t\t\t\ttmp.y -= pstate.origin.y;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tdropTarget = this.graph.getDefaultParent();\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\tthis.graph.addCell(target, dropTarget);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tvar parent = this.graph.getDefaultParent();\r\n\r\n\t\t\tif (source != null && target != null &&\r\n\t\t\t\tmodel.getParent(source) == model.getParent(target) &&\r\n\t\t\t\tmodel.getParent(model.getParent(source)) != model.getRoot())\r\n\t\t\t{\r\n\t\t\t\tparent = model.getParent(source);\r\n\r\n\t\t\t\tif ((source.geometry != null && source.geometry.relative) &&\r\n\t\t\t\t\t(target.geometry != null && target.geometry.relative))\r\n\t\t\t\t{\r\n\t\t\t\t\tparent = model.getParent(parent);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Uses the value of the preview edge state for inserting\r\n\t\t\t// the new edge into the graph\r\n\t\t\tvar value = null;\r\n\t\t\tvar style = null;\r\n\t\t\t\r\n\t\t\tif (this.edgeState != null)\r\n\t\t\t{\r\n\t\t\t\tvalue = this.edgeState.cell.value;\r\n\t\t\t\tstyle = this.edgeState.cell.style;\r\n\t\t\t}\r\n\r\n\t\t\tedge = this.insertEdge(parent, null, value, source, target, style);\r\n\t\t\t\r\n\t\t\tif (edge != null)\r\n\t\t\t{\r\n\t\t\t\t// Updates the connection constraints\r\n\t\t\t\tthis.graph.setConnectionConstraint(edge, source, true, this.sourceConstraint);\r\n\t\t\t\tthis.graph.setConnectionConstraint(edge, target, false, this.constraintHandler.currentConstraint);\r\n\t\t\t\t\r\n\t\t\t\t// Uses geometry of the preview edge state\r\n\t\t\t\tif (this.edgeState != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tmodel.setGeometry(edge, this.edgeState.cell.geometry);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tvar parent = model.getParent(source);\r\n\t\t\t\t\r\n\t\t\t\t// Inserts edge before source\r\n\t\t\t\tif (this.isInsertBefore(edge, source, target, evt, dropTarget))\r\n\t\t\t\t{\r\n\t\t\t\t\tvar index = null;\r\n\t\t\t\t\tvar tmp = source;\r\n\r\n\t\t\t\t\twhile (tmp.parent != null && tmp.geometry != null &&\r\n\t\t\t\t\t\ttmp.geometry.relative && tmp.parent != edge.parent)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ttmp = this.graph.model.getParent(tmp);\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif (tmp != null && tmp.parent != null && tmp.parent == edge.parent)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tmodel.add(parent, edge, tmp.parent.getIndex(tmp));\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Makes sure the edge has a non-null, relative geometry\r\n\t\t\t\tvar geo = model.getGeometry(edge);\r\n\r\n\t\t\t\tif (geo == null)\r\n\t\t\t\t{\r\n\t\t\t\t\tgeo = new mxGeometry();\r\n\t\t\t\t\tgeo.relative = true;\r\n\t\t\t\t\t\r\n\t\t\t\t\tmodel.setGeometry(edge, geo);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Uses scaled waypoints in geometry\r\n\t\t\t\tif (this.waypoints != null && this.waypoints.length > 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar s = this.graph.view.scale;\r\n\t\t\t\t\tvar tr = this.graph.view.translate;\r\n\t\t\t\t\tgeo.points = [];\r\n\t\t\t\t\t\r\n\t\t\t\t\tfor (var i = 0; i < this.waypoints.length; i++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar pt = this.waypoints[i];\r\n\t\t\t\t\t\tgeo.points.push(new mxPoint(pt.x / s - tr.x, pt.y / s - tr.y));\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (target == null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar t = this.graph.view.translate;\r\n\t\t\t\t\tvar s = this.graph.view.scale;\r\n\t\t\t\t\tvar pt = (this.originalPoint != null) ?\r\n\t\t\t\t\t\t\tnew mxPoint(this.originalPoint.x / s - t.x, this.originalPoint.y / s - t.y) :\r\n\t\t\t\t\t\tnew mxPoint(this.currentPoint.x / s - t.x, this.currentPoint.y / s - t.y);\r\n\t\t\t\t\tpt.x -= this.graph.panDx / this.graph.view.scale;\r\n\t\t\t\t\tpt.y -= this.graph.panDy / this.graph.view.scale;\r\n\t\t\t\t\tgeo.setTerminalPoint(pt, false);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.fireEvent(new mxEventObject(mxEvent.CONNECT, 'cell', edge, 'terminal', target,\r\n\t\t\t\t\t'event', evt, 'target', dropTarget, 'terminalInserted', terminalInserted));\r\n\t\t\t}\r\n\t\t}\r\n\t\tcatch (e)\r\n\t\t{\r\n\t\t\tmxLog.show();\r\n\t\t\tmxLog.debug(e.message);\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tmodel.endUpdate();\r\n\t\t}\r\n\t\t\r\n\t\tif (this.select)\r\n\t\t{\r\n\t\t\tthis.selectCells(edge, (terminalInserted) ? target : null);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: selectCells\r\n * \r\n * Selects the given edge after adding a new connection. The target argument\r\n * contains the target vertex if one has been inserted.\r\n */\r\nmxConnectionHandler.prototype.selectCells = function(edge, target)\r\n{\r\n\tthis.graph.setSelectionCell(edge);\r\n};\r\n\r\n/**\r\n * Function: insertEdge\r\n * \r\n * Creates, inserts and returns the new edge for the given parameters. This\r\n * implementation does only use <createEdge> if <factoryMethod> is defined,\r\n * otherwise <mxGraph.insertEdge> will be used.\r\n */\r\nmxConnectionHandler.prototype.insertEdge = function(parent, id, value, source, target, style)\r\n{\r\n\tif (this.factoryMethod == null)\r\n\t{\r\n\t\treturn this.graph.insertEdge(parent, id, value, source, target, style);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar edge = this.createEdge(value, source, target, style);\r\n\t\tedge = this.graph.addEdge(edge, parent, source, target);\r\n\t\t\r\n\t\treturn edge;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createTargetVertex\r\n * \r\n * Hook method for creating new vertices on the fly if no target was\r\n * under the mouse. This is only called if <createTarget> is true and\r\n * returns null.\r\n * \r\n * Parameters:\r\n * \r\n * evt - Mousedown event of the connect gesture.\r\n * source - <mxCell> that represents the source terminal.\r\n */\r\nmxConnectionHandler.prototype.createTargetVertex = function(evt, source)\r\n{\r\n\t// Uses the first non-relative source\r\n\tvar geo = this.graph.getCellGeometry(source);\r\n\t\r\n\twhile (geo != null && geo.relative)\r\n\t{\r\n\t\tsource = this.graph.getModel().getParent(source);\r\n\t\tgeo = this.graph.getCellGeometry(source);\r\n\t}\r\n\t\r\n\tvar clone = this.graph.cloneCell(source);\r\n\tvar geo = this.graph.getModel().getGeometry(clone);\r\n\t\r\n\tif (geo != null)\r\n\t{\r\n\t\tvar t = this.graph.view.translate;\r\n\t\tvar s = this.graph.view.scale;\r\n\t\tvar point = new mxPoint(this.currentPoint.x / s - t.x, this.currentPoint.y / s - t.y);\r\n\t\tgeo.x = Math.round(point.x - geo.width / 2 - this.graph.panDx / s);\r\n\t\tgeo.y = Math.round(point.y - geo.height / 2 - this.graph.panDy / s);\r\n\r\n\t\t// Aligns with source if within certain tolerance\r\n\t\tvar tol = this.getAlignmentTolerance();\r\n\t\t\r\n\t\tif (tol > 0)\r\n\t\t{\r\n\t\t\tvar sourceState = this.graph.view.getState(source);\r\n\t\t\t\r\n\t\t\tif (sourceState != null)\r\n\t\t\t{\r\n\t\t\t\tvar x = sourceState.x / s - t.x;\r\n\t\t\t\tvar y = sourceState.y / s - t.y;\r\n\t\t\t\t\r\n\t\t\t\tif (Math.abs(x - geo.x) <= tol)\r\n\t\t\t\t{\r\n\t\t\t\t\tgeo.x = Math.round(x);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (Math.abs(y - geo.y) <= tol)\r\n\t\t\t\t{\r\n\t\t\t\t\tgeo.y = Math.round(y);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn clone;\t\t\r\n};\r\n\r\n/**\r\n * Function: getAlignmentTolerance\r\n * \r\n * Returns the tolerance for aligning new targets to sources. This returns the grid size / 2.\r\n */\r\nmxConnectionHandler.prototype.getAlignmentTolerance = function(evt)\r\n{\r\n\treturn (this.graph.isGridEnabled()) ? this.graph.gridSize / 2 : this.graph.tolerance;\r\n};\r\n\r\n/**\r\n * Function: createEdge\r\n * \r\n * Creates and returns a new edge using <factoryMethod> if one exists. If\r\n * no factory method is defined, then a new default edge is returned. The\r\n * source and target arguments are informal, the actual connection is\r\n * setup later by the caller of this function.\r\n * \r\n * Parameters:\r\n * \r\n * value - Value to be used for creating the edge.\r\n * source - <mxCell> that represents the source terminal.\r\n * target - <mxCell> that represents the target terminal.\r\n * style - Optional style from the preview edge.\r\n */\r\nmxConnectionHandler.prototype.createEdge = function(value, source, target, style)\r\n{\r\n\tvar edge = null;\r\n\t\r\n\t// Creates a new edge using the factoryMethod\r\n\tif (this.factoryMethod != null)\r\n\t{\r\n\t\tedge = this.factoryMethod(source, target, style);\r\n\t}\r\n\t\r\n\tif (edge == null)\r\n\t{\r\n\t\tedge = new mxCell(value || '');\r\n\t\tedge.setEdge(true);\r\n\t\tedge.setStyle(style);\r\n\t\t\r\n\t\tvar geo = new mxGeometry();\r\n\t\tgeo.relative = true;\r\n\t\tedge.setGeometry(geo);\r\n\t}\r\n\r\n\treturn edge;\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Destroys the handler and all its resources and DOM nodes. This should be\r\n * called on all instances. It is called automatically for the built-in\r\n * instance created for each <mxGraph>.\r\n */\r\nmxConnectionHandler.prototype.destroy = function()\r\n{\r\n\tthis.graph.removeMouseListener(this);\r\n\t\r\n\tif (this.shape != null)\r\n\t{\r\n\t\tthis.shape.destroy();\r\n\t\tthis.shape = null;\r\n\t}\r\n\t\r\n\tif (this.marker != null)\r\n\t{\r\n\t\tthis.marker.destroy();\r\n\t\tthis.marker = null;\r\n\t}\r\n\r\n\tif (this.constraintHandler != null)\r\n\t{\r\n\t\tthis.constraintHandler.destroy();\r\n\t\tthis.constraintHandler = null;\r\n\t}\r\n\r\n\tif (this.changeHandler != null)\r\n\t{\r\n\t\tthis.graph.getModel().removeListener(this.changeHandler);\r\n\t\tthis.graph.getView().removeListener(this.changeHandler);\r\n\t\tthis.changeHandler = null;\r\n\t}\r\n\t\r\n\tif (this.drillHandler != null)\r\n\t{\r\n\t\tthis.graph.removeListener(this.drillHandler);\r\n\t\tthis.graph.getView().removeListener(this.drillHandler);\r\n\t\tthis.drillHandler = null;\r\n\t}\r\n\t\r\n\tif (this.escapeHandler != null)\r\n\t{\r\n\t\tthis.graph.removeListener(this.escapeHandler);\r\n\t\tthis.escapeHandler = null;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxConstraintHandler\r\n *\r\n * Handles constraints on connection targets. This class is in charge of\r\n * showing fixed points when the mouse is over a vertex and handles constraints\r\n * to establish new connections.\r\n *\r\n * Constructor: mxConstraintHandler\r\n *\r\n * Constructs an new constraint handler.\r\n * \r\n * Parameters:\r\n * \r\n * graph - Reference to the enclosing <mxGraph>.\r\n * factoryMethod - Optional function to create the edge. The function takes\r\n * the source and target <mxCell> as the first and second argument and\r\n * returns the <mxCell> that represents the new edge.\r\n */\r\nfunction mxConstraintHandler(graph)\r\n{\r\n\tthis.graph = graph;\r\n\t\r\n\t// Adds a graph model listener to update the current focus on changes\r\n\tthis.resetHandler = mxUtils.bind(this, function(sender, evt)\r\n\t{\r\n\t\tif (this.currentFocus != null && this.graph.view.getState(this.currentFocus.cell) == null)\r\n\t\t{\r\n\t\t\tthis.reset();\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.redraw();\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.graph.model.addListener(mxEvent.CHANGE, this.resetHandler);\r\n\tthis.graph.view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.resetHandler);\r\n\tthis.graph.view.addListener(mxEvent.TRANSLATE, this.resetHandler);\r\n\tthis.graph.view.addListener(mxEvent.SCALE, this.resetHandler);\r\n\tthis.graph.addListener(mxEvent.ROOT, this.resetHandler);\r\n};\r\n\r\n/**\r\n * Variable: pointImage\r\n * \r\n * <mxImage> to be used as the image for fixed connection points.\r\n */\r\nmxConstraintHandler.prototype.pointImage = new mxImage(mxClient.imageBasePath + '/point.gif', 5, 5);\r\n\r\n/**\r\n * Variable: graph\r\n * \r\n * Reference to the enclosing <mxGraph>.\r\n */\r\nmxConstraintHandler.prototype.graph = null;\r\n\r\n/**\r\n * Variable: enabled\r\n * \r\n * Specifies if events are handled. Default is true.\r\n */\r\nmxConstraintHandler.prototype.enabled = true;\r\n\r\n/**\r\n * Variable: highlightColor\r\n * \r\n * Specifies the color for the highlight. Default is <mxConstants.DEFAULT_VALID_COLOR>.\r\n */\r\nmxConstraintHandler.prototype.highlightColor = mxConstants.DEFAULT_VALID_COLOR;\r\n\r\n/**\r\n * Function: isEnabled\r\n * \r\n * Returns true if events are handled. This implementation\r\n * returns <enabled>.\r\n */\r\nmxConstraintHandler.prototype.isEnabled = function()\r\n{\r\n\treturn this.enabled;\r\n};\r\n\t\r\n/**\r\n * Function: setEnabled\r\n * \r\n * Enables or disables event handling. This implementation\r\n * updates <enabled>.\r\n * \r\n * Parameters:\r\n * \r\n * enabled - Boolean that specifies the new enabled state.\r\n */\r\nmxConstraintHandler.prototype.setEnabled = function(enabled)\r\n{\r\n\tthis.enabled = enabled;\r\n};\r\n\r\n/**\r\n * Function: reset\r\n * \r\n * Resets the state of this handler.\r\n */\r\nmxConstraintHandler.prototype.reset = function()\r\n{\r\n\tif (this.focusIcons != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.focusIcons.length; i++)\r\n\t\t{\r\n\t\t\tthis.focusIcons[i].destroy();\r\n\t\t}\r\n\t\t\r\n\t\tthis.focusIcons = null;\r\n\t}\r\n\t\r\n\tif (this.focusHighlight != null)\r\n\t{\r\n\t\tthis.focusHighlight.destroy();\r\n\t\tthis.focusHighlight = null;\r\n\t}\r\n\t\r\n\tthis.currentConstraint = null;\r\n\tthis.currentFocusArea = null;\r\n\tthis.currentPoint = null;\r\n\tthis.currentFocus = null;\r\n\tthis.focusPoints = null;\r\n};\r\n\r\n/**\r\n * Function: getTolerance\r\n * \r\n * Returns the tolerance to be used for intersecting connection points. This\r\n * implementation returns <mxGraph.tolerance>.\r\n * \r\n * Parameters:\r\n * \r\n * me - <mxMouseEvent> whose tolerance should be returned.\r\n */\r\nmxConstraintHandler.prototype.getTolerance = function(me)\r\n{\r\n\treturn this.graph.getTolerance();\r\n};\r\n\r\n/**\r\n * Function: getImageForConstraint\r\n * \r\n * Returns the tolerance to be used for intersecting connection points.\r\n */\r\nmxConstraintHandler.prototype.getImageForConstraint = function(state, constraint, point)\r\n{\r\n\treturn this.pointImage;\r\n};\r\n\r\n/**\r\n * Function: isEventIgnored\r\n * \r\n * Returns true if the given <mxMouseEvent> should be ignored in <update>. This\r\n * implementation always returns false.\r\n */\r\nmxConstraintHandler.prototype.isEventIgnored = function(me, source)\r\n{\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: isStateIgnored\r\n * \r\n * Returns true if the given state should be ignored. This always returns false.\r\n */\r\nmxConstraintHandler.prototype.isStateIgnored = function(state, source)\r\n{\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: destroyIcons\r\n * \r\n * Destroys the <focusIcons> if they exist.\r\n */\r\nmxConstraintHandler.prototype.destroyIcons = function()\r\n{\r\n\tif (this.focusIcons != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.focusIcons.length; i++)\r\n\t\t{\r\n\t\t\tthis.focusIcons[i].destroy();\r\n\t\t}\r\n\t\t\r\n\t\tthis.focusIcons = null;\r\n\t\tthis.focusPoints = null;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: destroyFocusHighlight\r\n * \r\n * Destroys the <focusHighlight> if one exists.\r\n */\r\nmxConstraintHandler.prototype.destroyFocusHighlight = function()\r\n{\r\n\tif (this.focusHighlight != null)\r\n\t{\r\n\t\tthis.focusHighlight.destroy();\r\n\t\tthis.focusHighlight = null;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isKeepFocusEvent\r\n * \r\n * Returns true if the current focused state should not be changed for the given event.\r\n * This returns true if shift and alt are pressed.\r\n */\r\nmxConstraintHandler.prototype.isKeepFocusEvent = function(me)\r\n{\r\n\treturn mxEvent.isShiftDown(me.getEvent());\r\n};\r\n\r\n/**\r\n * Function: getCellForEvent\r\n * \r\n * Returns the cell for the given event.\r\n */\r\nmxConstraintHandler.prototype.getCellForEvent = function(me, point)\r\n{\r\n\tvar cell = me.getCell();\r\n\t\r\n\t// Gets cell under actual point if different from event location\r\n\tif (cell == null && point != null && (me.getGraphX() != point.x || me.getGraphY() != point.y))\r\n\t{\r\n\t\tcell = this.graph.getCellAt(point.x, point.y);\r\n\t}\r\n\t\r\n\t// Uses connectable parent vertex if one exists\r\n\tif (cell != null && !this.graph.isCellConnectable(cell))\r\n\t{\r\n\t\tvar parent = this.graph.getModel().getParent(cell);\r\n\t\t\r\n\t\tif (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent))\r\n\t\t{\r\n\t\t\tcell = parent;\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn (this.graph.isCellLocked(cell)) ? null : cell;\r\n};\r\n\r\n/**\r\n * Function: update\r\n * \r\n * Updates the state of this handler based on the given <mxMouseEvent>.\r\n * Source is a boolean indicating if the cell is a source or target.\r\n */\r\nmxConstraintHandler.prototype.update = function(me, source, existingEdge, point)\r\n{\r\n\tif (this.isEnabled() && !this.isEventIgnored(me))\r\n\t{\r\n\t\t// Lazy installation of mouseleave handler\r\n\t\tif (this.mouseleaveHandler == null && this.graph.container != null)\r\n\t\t{\r\n\t\t\tthis.mouseleaveHandler = mxUtils.bind(this, function()\r\n\t\t\t{\r\n\t\t\t\tthis.reset();\r\n\t\t\t});\r\n\r\n\t\t\tmxEvent.addListener(this.graph.container, 'mouseleave', this.resetHandler);\t\r\n\t\t}\r\n\t\t\r\n\t\tvar tol = this.getTolerance(me);\r\n\t\tvar x = (point != null) ? point.x : me.getGraphX();\r\n\t\tvar y = (point != null) ? point.y : me.getGraphY();\r\n\t\tvar grid = new mxRectangle(x - tol, y - tol, 2 * tol, 2 * tol);\r\n\t\tvar mouse = new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol);\r\n\t\tvar state = this.graph.view.getState(this.getCellForEvent(me, point));\r\n\r\n\t\t// Keeps focus icons visible while over vertex bounds and no other cell under mouse or shift is pressed\r\n\t\tif (!this.isKeepFocusEvent(me) && (this.currentFocusArea == null || this.currentFocus == null ||\r\n\t\t\t(state != null) || !this.graph.getModel().isVertex(this.currentFocus.cell) ||\r\n\t\t\t!mxUtils.intersects(this.currentFocusArea, mouse)) && (state != this.currentFocus))\r\n\t\t{\r\n\t\t\tthis.currentFocusArea = null;\r\n\t\t\tthis.currentFocus = null;\r\n\t\t\tthis.setFocus(me, state, source);\r\n\t\t}\r\n\r\n\t\tthis.currentConstraint = null;\r\n\t\tthis.currentPoint = null;\r\n\t\tvar minDistSq = null;\r\n\t\t\r\n\t\tif (this.focusIcons != null && this.constraints != null &&\r\n\t\t\t(state == null || this.currentFocus == state))\r\n\t\t{\r\n\t\t\tvar cx = mouse.getCenterX();\r\n\t\t\tvar cy = mouse.getCenterY();\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < this.focusIcons.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar dx = cx - this.focusIcons[i].bounds.getCenterX();\r\n\t\t\t\tvar dy = cy - this.focusIcons[i].bounds.getCenterY();\r\n\t\t\t\tvar tmp = dx * dx + dy * dy;\r\n\t\t\t\t\r\n\t\t\t\tif ((this.intersects(this.focusIcons[i], mouse, source, existingEdge) || (point != null &&\r\n\t\t\t\t\tthis.intersects(this.focusIcons[i], grid, source, existingEdge))) &&\r\n\t\t\t\t\t(minDistSq == null || tmp < minDistSq))\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.currentConstraint = this.constraints[i];\r\n\t\t\t\t\tthis.currentPoint = this.focusPoints[i];\r\n\t\t\t\t\tminDistSq = tmp;\r\n\t\t\t\t\t\r\n\t\t\t\t\tvar tmp = this.focusIcons[i].bounds.clone();\r\n\t\t\t\t\ttmp.grow(mxConstants.HIGHLIGHT_SIZE + 1);\r\n\t\t\t\t\ttmp.width -= 1;\r\n\t\t\t\t\ttmp.height -= 1;\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (this.focusHighlight == null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar hl = this.createHighlightShape();\r\n\t\t\t\t\t\thl.dialect = (this.graph.dialect == mxConstants.DIALECT_SVG) ?\r\n\t\t\t\t\t\t\t\tmxConstants.DIALECT_SVG : mxConstants.DIALECT_VML;\r\n\t\t\t\t\t\thl.pointerEvents = false;\r\n\r\n\t\t\t\t\t\thl.init(this.graph.getView().getOverlayPane());\r\n\t\t\t\t\t\tthis.focusHighlight = hl;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tvar getState = mxUtils.bind(this, function()\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\treturn (this.currentFocus != null) ? this.currentFocus : state;\r\n\t\t\t\t\t\t});\r\n\t\r\n\t\t\t\t\t\tmxEvent.redirectMouseEvents(hl.node, this.graph, getState);\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tthis.focusHighlight.bounds = tmp;\r\n\t\t\t\t\tthis.focusHighlight.redraw();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (this.currentConstraint == null)\r\n\t\t{\r\n\t\t\tthis.destroyFocusHighlight();\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.currentConstraint = null;\r\n\t\tthis.currentFocus = null;\r\n\t\tthis.currentPoint = null;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: redraw\r\n * \r\n * Transfers the focus to the given state as a source or target terminal. If\r\n * the handler is not enabled then the outline is painted, but the constraints\r\n * are ignored.\r\n */\r\nmxConstraintHandler.prototype.redraw = function()\r\n{\r\n\tif (this.currentFocus != null && this.constraints != null && this.focusIcons != null)\r\n\t{\r\n\t\tvar state = this.graph.view.getState(this.currentFocus.cell);\r\n\t\tthis.currentFocus = state;\r\n\t\tthis.currentFocusArea = new mxRectangle(state.x, state.y, state.width, state.height);\r\n\t\t\r\n\t\tfor (var i = 0; i < this.constraints.length; i++)\r\n\t\t{\r\n\t\t\tvar cp = this.graph.getConnectionPoint(state, this.constraints[i]);\r\n\t\t\tvar img = this.getImageForConstraint(state, this.constraints[i], cp);\r\n\r\n\t\t\tvar bounds = new mxRectangle(Math.round(cp.x - img.width / 2),\r\n\t\t\t\tMath.round(cp.y - img.height / 2), img.width, img.height);\r\n\t\t\tthis.focusIcons[i].bounds = bounds;\r\n\t\t\tthis.focusIcons[i].redraw();\r\n\t\t\tthis.currentFocusArea.add(this.focusIcons[i].bounds);\r\n\t\t\tthis.focusPoints[i] = cp;\r\n\t\t}\r\n\t}\t\r\n};\r\n\r\n/**\r\n * Function: setFocus\r\n * \r\n * Transfers the focus to the given state as a source or target terminal. If\r\n * the handler is not enabled then the outline is painted, but the constraints\r\n * are ignored.\r\n */\r\nmxConstraintHandler.prototype.setFocus = function(me, state, source)\r\n{\r\n\tthis.constraints = (state != null && !this.isStateIgnored(state, source) &&\r\n\t\tthis.graph.isCellConnectable(state.cell)) ? ((this.isEnabled()) ?\r\n\t\t(this.graph.getAllConnectionConstraints(state, source) || []) : []) : null;\r\n\r\n\t// Only uses cells which have constraints\r\n\tif (this.constraints != null)\r\n\t{\r\n\t\tthis.currentFocus = state;\r\n\t\tthis.currentFocusArea = new mxRectangle(state.x, state.y, state.width, state.height);\r\n\t\t\r\n\t\tif (this.focusIcons != null)\r\n\t\t{\r\n\t\t\tfor (var i = 0; i < this.focusIcons.length; i++)\r\n\t\t\t{\r\n\t\t\t\tthis.focusIcons[i].destroy();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.focusIcons = null;\r\n\t\t\tthis.focusPoints = null;\r\n\t\t}\r\n\t\t\r\n\t\tthis.focusPoints = [];\r\n\t\tthis.focusIcons = [];\r\n\t\t\r\n\t\tfor (var i = 0; i < this.constraints.length; i++)\r\n\t\t{\r\n\t\t\tvar cp = this.graph.getConnectionPoint(state, this.constraints[i]);\r\n\t\t\tvar img = this.getImageForConstraint(state, this.constraints[i], cp);\r\n\r\n\t\t\tvar src = img.src;\r\n\t\t\tvar bounds = new mxRectangle(Math.round(cp.x - img.width / 2),\r\n\t\t\t\tMath.round(cp.y - img.height / 2), img.width, img.height);\r\n\t\t\tvar icon = new mxImageShape(bounds, src);\r\n\t\t\ticon.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?\r\n\t\t\t\t\tmxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;\r\n\t\t\ticon.preserveImageAspect = false;\r\n\t\t\ticon.init(this.graph.getView().getDecoratorPane());\r\n\t\t\t\r\n\t\t\t// Fixes lost event tracking for images in quirks / IE8 standards\r\n\t\t\tif (mxClient.IS_QUIRKS || document.documentMode == 8)\r\n\t\t\t{\r\n\t\t\t\tmxEvent.addListener(icon.node, 'dragstart', function(evt)\r\n\t\t\t\t{\r\n\t\t\t\t\tmxEvent.consume(evt);\r\n\t\t\t\t\t\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Move the icon behind all other overlays\r\n\t\t\tif (icon.node.previousSibling != null)\r\n\t\t\t{\r\n\t\t\t\ticon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild);\r\n\t\t\t}\r\n\r\n\t\t\tvar getState = mxUtils.bind(this, function()\r\n\t\t\t{\r\n\t\t\t\treturn (this.currentFocus != null) ? this.currentFocus : state;\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\ticon.redraw();\r\n\r\n\t\t\tmxEvent.redirectMouseEvents(icon.node, this.graph, getState);\r\n\t\t\tthis.currentFocusArea.add(icon.bounds);\r\n\t\t\tthis.focusIcons.push(icon);\r\n\t\t\tthis.focusPoints.push(cp);\r\n\t\t}\r\n\t\t\r\n\t\tthis.currentFocusArea.grow(this.getTolerance(me));\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.destroyIcons();\r\n\t\tthis.destroyFocusHighlight();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createHighlightShape\r\n * \r\n * Create the shape used to paint the highlight.\r\n * \r\n * Returns true if the given icon intersects the given point.\r\n */\r\nmxConstraintHandler.prototype.createHighlightShape = function()\r\n{\r\n\tvar hl = new mxRectangleShape(null, this.highlightColor, this.highlightColor, mxConstants.HIGHLIGHT_STROKEWIDTH);\r\n\thl.opacity = mxConstants.HIGHLIGHT_OPACITY;\r\n\t\r\n\treturn hl;\r\n};\r\n\r\n/**\r\n * Function: intersects\r\n * \r\n * Returns true if the given icon intersects the given rectangle.\r\n */\r\nmxConstraintHandler.prototype.intersects = function(icon, mouse, source, existingEdge)\r\n{\r\n\treturn mxUtils.intersects(icon.bounds, mouse);\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Destroy this handler.\r\n */\r\nmxConstraintHandler.prototype.destroy = function()\r\n{\r\n\tthis.reset();\r\n\t\r\n\tif (this.resetHandler != null)\r\n\t{\r\n\t\tthis.graph.model.removeListener(this.resetHandler);\r\n\t\tthis.graph.view.removeListener(this.resetHandler);\r\n\t\tthis.graph.removeListener(this.resetHandler);\r\n\t\tthis.resetHandler = null;\r\n\t}\r\n\t\r\n\tif (this.mouseleaveHandler != null && this.graph.container != null)\r\n\t{\r\n\t\tmxEvent.removeListener(this.graph.container, 'mouseleave', this.mouseleaveHandler);\r\n\t\tthis.mouseleaveHandler = null;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2016, JGraph Ltd\r\n * Copyright (c) 2006-2016, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxRubberband\r\n * \r\n * Event handler that selects rectangular regions. This is not built-into\r\n * <mxGraph>. To enable rubberband selection in a graph, use the following code.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * var rubberband = new mxRubberband(graph);\r\n * (end)\r\n * \r\n * Constructor: mxRubberband\r\n * \r\n * Constructs an event handler that selects rectangular regions in the graph\r\n * using rubberband selection.\r\n */\r\nfunction mxRubberband(graph)\r\n{\r\n\tif (graph != null)\r\n\t{\r\n\t\tthis.graph = graph;\r\n\t\tthis.graph.addMouseListener(this);\r\n\r\n\t\t// Handles force rubberband event\r\n\t\tthis.forceRubberbandHandler = mxUtils.bind(this, function(sender, evt)\r\n\t\t{\r\n\t\t\tvar evtName = evt.getProperty('eventName');\r\n\t\t\tvar me = evt.getProperty('event');\r\n\t\t\t\r\n\t\t\tif (evtName == mxEvent.MOUSE_DOWN && this.isForceRubberbandEvent(me))\r\n\t\t\t{\r\n\t\t\t\tvar offset = mxUtils.getOffset(this.graph.container);\r\n\t\t\t\tvar origin = mxUtils.getScrollOrigin(this.graph.container);\r\n\t\t\t\torigin.x -= offset.x;\r\n\t\t\t\torigin.y -= offset.y;\r\n\t\t\t\tthis.start(me.getX() + origin.x, me.getY() + origin.y);\r\n\t\t\t\tme.consume(false);\r\n\t\t\t}\r\n\t\t});\r\n\t\t\r\n\t\tthis.graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.forceRubberbandHandler);\r\n\t\t\r\n\t\t// Repaints the marquee after autoscroll\r\n\t\tthis.panHandler = mxUtils.bind(this, function()\r\n\t\t{\r\n\t\t\tthis.repaint();\r\n\t\t});\r\n\t\t\r\n\t\tthis.graph.addListener(mxEvent.PAN, this.panHandler);\r\n\t\t\r\n\t\t// Does not show menu if any touch gestures take place after the trigger\r\n\t\tthis.gestureHandler = mxUtils.bind(this, function(sender, eo)\r\n\t\t{\r\n\t\t\tif (this.first != null)\r\n\t\t\t{\r\n\t\t\t\tthis.reset();\r\n\t\t\t}\r\n\t\t});\r\n\t\t\r\n\t\tthis.graph.addListener(mxEvent.GESTURE, this.gestureHandler);\r\n\t\t\r\n\t\t// Automatic deallocation of memory\r\n\t\tif (mxClient.IS_IE)\r\n\t\t{\r\n\t\t\tmxEvent.addListener(window, 'unload',\r\n\t\t\t\tmxUtils.bind(this, function()\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.destroy();\r\n\t\t\t\t})\r\n\t\t\t);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Variable: defaultOpacity\r\n * \r\n * Specifies the default opacity to be used for the rubberband div. Default\r\n * is 20.\r\n */\r\nmxRubberband.prototype.defaultOpacity = 20;\r\n\r\n/**\r\n * Variable: enabled\r\n * \r\n * Specifies if events are handled. Default is true.\r\n */\r\nmxRubberband.prototype.enabled = true;\r\n\r\n/**\r\n * Variable: div\r\n * \r\n * Holds the DIV element which is currently visible.\r\n */\r\nmxRubberband.prototype.div = null;\r\n\r\n/**\r\n * Variable: sharedDiv\r\n * \r\n * Holds the DIV element which is used to display the rubberband.\r\n */\r\nmxRubberband.prototype.sharedDiv = null;\r\n\r\n/**\r\n * Variable: currentX\r\n * \r\n * Holds the value of the x argument in the last call to <update>.\r\n */\r\nmxRubberband.prototype.currentX = 0;\r\n\r\n/**\r\n * Variable: currentY\r\n * \r\n * Holds the value of the y argument in the last call to <update>.\r\n */\r\nmxRubberband.prototype.currentY = 0;\r\n\r\n/**\r\n * Variable: fadeOut\r\n * \r\n * Optional fade out effect. Default is false.\r\n */\r\nmxRubberband.prototype.fadeOut = false;\r\n\r\n/**\r\n * Function: isEnabled\r\n * \r\n * Returns true if events are handled. This implementation returns\r\n * <enabled>.\r\n */\r\nmxRubberband.prototype.isEnabled = function()\r\n{\r\n\treturn this.enabled;\r\n};\r\n\t\t\r\n/**\r\n * Function: setEnabled\r\n * \r\n * Enables or disables event handling. This implementation updates\r\n * <enabled>.\r\n */\r\nmxRubberband.prototype.setEnabled = function(enabled)\r\n{\r\n\tthis.enabled = enabled;\r\n};\r\n\r\n/**\r\n * Function: isForceRubberbandEvent\r\n * \r\n * Returns true if the given <mxMouseEvent> should start rubberband selection.\r\n * This implementation returns true if the alt key is pressed.\r\n */\r\nmxRubberband.prototype.isForceRubberbandEvent = function(me)\r\n{\r\n\treturn mxEvent.isAltDown(me.getEvent());\r\n};\r\n\r\n/**\r\n * Function: mouseDown\r\n * \r\n * Handles the event by initiating a rubberband selection. By consuming the\r\n * event all subsequent events of the gesture are redirected to this\r\n * handler.\r\n */\r\nmxRubberband.prototype.mouseDown = function(sender, me)\r\n{\r\n\tif (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled() &&\r\n\t\tme.getState() == null && !mxEvent.isMultiTouchEvent(me.getEvent()))\r\n\t{\r\n\t\tvar offset = mxUtils.getOffset(this.graph.container);\r\n\t\tvar origin = mxUtils.getScrollOrigin(this.graph.container);\r\n\t\torigin.x -= offset.x;\r\n\t\torigin.y -= offset.y;\r\n\t\tthis.start(me.getX() + origin.x, me.getY() + origin.y);\r\n\r\n\t\t// Does not prevent the default for this event so that the\r\n\t\t// event processing chain is still executed even if we start\r\n\t\t// rubberbanding. This is required eg. in ExtJs to hide the\r\n\t\t// current context menu. In mouseMove we'll make sure we're\r\n\t\t// not selecting anything while we're rubberbanding.\r\n\t\tme.consume(false);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: start\r\n * \r\n * Sets the start point for the rubberband selection.\r\n */\r\nmxRubberband.prototype.start = function(x, y)\r\n{\r\n\tthis.first = new mxPoint(x, y);\r\n\r\n\tvar container = this.graph.container;\r\n\t\r\n\tfunction createMouseEvent(evt)\r\n\t{\r\n\t\tvar me = new mxMouseEvent(evt);\r\n\t\tvar pt = mxUtils.convertPoint(container, me.getX(), me.getY());\r\n\t\t\r\n\t\tme.graphX = pt.x;\r\n\t\tme.graphY = pt.y;\r\n\t\t\r\n\t\treturn me;\r\n\t};\r\n\r\n\tthis.dragHandler = mxUtils.bind(this, function(evt)\r\n\t{\r\n\t\tthis.mouseMove(this.graph, createMouseEvent(evt));\r\n\t});\r\n\r\n\tthis.dropHandler = mxUtils.bind(this, function(evt)\r\n\t{\r\n\t\tthis.mouseUp(this.graph, createMouseEvent(evt));\r\n\t});\r\n\r\n\t// Workaround for rubberband stopping if the mouse leaves the container in Firefox\r\n\tif (mxClient.IS_FF)\r\n\t{\r\n\t\tmxEvent.addGestureListeners(document, null, this.dragHandler, this.dropHandler);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: mouseMove\r\n * \r\n * Handles the event by updating therubberband selection.\r\n */\r\nmxRubberband.prototype.mouseMove = function(sender, me)\r\n{\r\n\tif (!me.isConsumed() && this.first != null)\r\n\t{\r\n\t\tvar origin = mxUtils.getScrollOrigin(this.graph.container);\r\n\t\tvar offset = mxUtils.getOffset(this.graph.container);\r\n\t\torigin.x -= offset.x;\r\n\t\torigin.y -= offset.y;\r\n\t\tvar x = me.getX() + origin.x;\r\n\t\tvar y = me.getY() + origin.y;\r\n\t\tvar dx = this.first.x - x;\r\n\t\tvar dy = this.first.y - y;\r\n\t\tvar tol = this.graph.tolerance;\r\n\t\t\r\n\t\tif (this.div != null || Math.abs(dx) > tol ||  Math.abs(dy) > tol)\r\n\t\t{\r\n\t\t\tif (this.div == null)\r\n\t\t\t{\r\n\t\t\t\tthis.div = this.createShape();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Clears selection while rubberbanding. This is required because\r\n\t\t\t// the event is not consumed in mouseDown.\r\n\t\t\tmxUtils.clearSelection();\r\n\t\t\t\r\n\t\t\tthis.update(x, y);\r\n\t\t\tme.consume();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createShape\r\n * \r\n * Creates the rubberband selection shape.\r\n */\r\nmxRubberband.prototype.createShape = function()\r\n{\r\n\tif (this.sharedDiv == null)\r\n\t{\r\n\t\tthis.sharedDiv = document.createElement('div');\r\n\t\tthis.sharedDiv.className = 'mxRubberband';\r\n\t\tmxUtils.setOpacity(this.sharedDiv, this.defaultOpacity);\r\n\t}\r\n\r\n\tthis.graph.container.appendChild(this.sharedDiv);\r\n\tvar result = this.sharedDiv;\r\n\t\r\n\tif (mxClient.IS_SVG && (!mxClient.IS_IE || document.documentMode >= 10) && this.fadeOut)\r\n\t{\r\n\t\tthis.sharedDiv = null;\r\n\t}\r\n\t\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: isActive\r\n * \r\n * Returns true if this handler is active.\r\n */\r\nmxRubberband.prototype.isActive = function(sender, me)\r\n{\r\n\treturn this.div != null && this.div.style.display != 'none';\r\n};\r\n\r\n/**\r\n * Function: mouseUp\r\n * \r\n * Handles the event by selecting the region of the rubberband using\r\n * <mxGraph.selectRegion>.\r\n */\r\nmxRubberband.prototype.mouseUp = function(sender, me)\r\n{\r\n\tvar active = this.isActive();\r\n\tthis.reset();\r\n\t\r\n\tif (active)\r\n\t{\r\n\t\tthis.execute(me.getEvent());\r\n\t\tme.consume();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Resets the state of this handler and selects the current region\r\n * for the given event.\r\n */\r\nmxRubberband.prototype.execute = function(evt)\r\n{\r\n\tvar rect = new mxRectangle(this.x, this.y, this.width, this.height);\r\n\tthis.graph.selectRegion(rect, evt);\r\n};\r\n\r\n/**\r\n * Function: reset\r\n * \r\n * Resets the state of the rubberband selection.\r\n */\r\nmxRubberband.prototype.reset = function()\r\n{\r\n\tif (this.div != null)\r\n\t{\r\n\t\tif (mxClient.IS_SVG && (!mxClient.IS_IE || document.documentMode >= 10) && this.fadeOut)\r\n\t\t{\r\n\t\t\tvar temp = this.div;\r\n\t\t\tmxUtils.setPrefixedStyle(temp.style, 'transition', 'all 0.2s linear');\r\n\t\t\ttemp.style.pointerEvents = 'none';\r\n\t\t\ttemp.style.opacity = 0;\r\n\t\t    \r\n\t\t    window.setTimeout(function()\r\n\t\t    \t{\r\n\t\t    \t\ttemp.parentNode.removeChild(temp);\r\n\t\t    \t}, 200);\t\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.div.parentNode.removeChild(this.div);\r\n\t\t}\r\n\t}\r\n\r\n\tmxEvent.removeGestureListeners(document, null, this.dragHandler, this.dropHandler);\r\n\tthis.dragHandler = null;\r\n\tthis.dropHandler = null;\r\n\t\r\n\tthis.currentX = 0;\r\n\tthis.currentY = 0;\r\n\tthis.first = null;\r\n\tthis.div = null;\r\n};\r\n\r\n/**\r\n * Function: update\r\n * \r\n * Sets <currentX> and <currentY> and calls <repaint>.\r\n */\r\nmxRubberband.prototype.update = function(x, y)\r\n{\r\n\tthis.currentX = x;\r\n\tthis.currentY = y;\r\n\t\r\n\tthis.repaint();\r\n};\r\n\r\n/**\r\n * Function: repaint\r\n * \r\n * Computes the bounding box and updates the style of the <div>.\r\n */\r\nmxRubberband.prototype.repaint = function()\r\n{\r\n\tif (this.div != null)\r\n\t{\r\n\t\tvar x = this.currentX - this.graph.panDx;\r\n\t\tvar y = this.currentY - this.graph.panDy;\r\n\t\t\r\n\t\tthis.x = Math.min(this.first.x, x);\r\n\t\tthis.y = Math.min(this.first.y, y);\r\n\t\tthis.width = Math.max(this.first.x, x) - this.x;\r\n\t\tthis.height =  Math.max(this.first.y, y) - this.y;\r\n\r\n\t\tvar dx = (mxClient.IS_VML) ? this.graph.panDx : 0;\r\n\t\tvar dy = (mxClient.IS_VML) ? this.graph.panDy : 0;\r\n\t\t\r\n\t\tthis.div.style.left = (this.x + dx) + 'px';\r\n\t\tthis.div.style.top = (this.y + dy) + 'px';\r\n\t\tthis.div.style.width = Math.max(1, this.width) + 'px';\r\n\t\tthis.div.style.height = Math.max(1, this.height) + 'px';\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Destroys the handler and all its resources and DOM nodes. This does\r\n * normally not need to be called, it is called automatically when the\r\n * window unloads.\r\n */\r\nmxRubberband.prototype.destroy = function()\r\n{\r\n\tif (!this.destroyed)\r\n\t{\r\n\t\tthis.destroyed = true;\r\n\t\tthis.graph.removeMouseListener(this);\r\n\t\tthis.graph.removeListener(this.forceRubberbandHandler);\r\n\t\tthis.graph.removeListener(this.panHandler);\r\n\t\tthis.reset();\r\n\t\t\r\n\t\tif (this.sharedDiv != null)\r\n\t\t{\r\n\t\t\tthis.sharedDiv = null;\r\n\t\t}\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxHandle\r\n * \r\n * Implements a single custom handle for vertices.\r\n * \r\n * Constructor: mxHandle\r\n * \r\n * Constructs a new handle for the given state.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> of the cell to be handled.\r\n */\r\nfunction mxHandle(state, cursor, image)\r\n{\r\n\tthis.graph = state.view.graph;\r\n\tthis.state = state;\r\n\tthis.cursor = (cursor != null) ? cursor : this.cursor;\r\n\tthis.image = (image != null) ? image : this.image;\r\n\tthis.init();\r\n};\r\n\r\n/**\r\n * Variable: cursor\r\n * \r\n * Specifies the cursor to be used for this handle. Default is 'default'.\r\n */\r\nmxHandle.prototype.cursor = 'default';\r\n\r\n/**\r\n * Variable: image\r\n * \r\n * Specifies the <mxImage> to be used to render the handle. Default is null.\r\n */\r\nmxHandle.prototype.image = null;\r\n\r\n/**\r\n * Variable: image\r\n * \r\n * Specifies the <mxImage> to be used to render the handle. Default is null.\r\n */\r\nmxHandle.prototype.ignoreGrid = false;\r\n\r\n/**\r\n * Function: getPosition\r\n * \r\n * Hook for subclassers to return the current position of the handle.\r\n */\r\nmxHandle.prototype.getPosition = function(bounds) { };\r\n\r\n/**\r\n * Function: setPosition\r\n * \r\n * Hooks for subclassers to update the style in the <state>.\r\n */\r\nmxHandle.prototype.setPosition = function(bounds, pt, me) { };\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Hook for subclassers to execute the handle.\r\n */\r\nmxHandle.prototype.execute = function() { };\r\n\r\n/**\r\n * Function: copyStyle\r\n * \r\n * Sets the cell style with the given name to the corresponding value in <state>.\r\n */\r\nmxHandle.prototype.copyStyle = function(key)\r\n{\r\n\tthis.graph.setCellStyles(key, this.state.style[key], [this.state.cell]);\r\n};\r\n\r\n/**\r\n * Function: processEvent\r\n * \r\n * Processes the given <mxMouseEvent> and invokes <setPosition>.\r\n */\r\nmxHandle.prototype.processEvent = function(me)\r\n{\r\n\tvar scale = this.graph.view.scale;\r\n\tvar tr = this.graph.view.translate;\r\n\tvar pt = new mxPoint(me.getGraphX() / scale - tr.x, me.getGraphY() / scale - tr.y);\r\n\t\r\n\t// Center shape on mouse cursor\r\n\tif (this.shape != null && this.shape.bounds != null)\r\n\t{\r\n\t\tpt.x -= this.shape.bounds.width / scale / 4;\r\n\t\tpt.y -= this.shape.bounds.height / scale / 4;\r\n\t}\r\n\r\n\t// Snaps to grid for the rotated position then applies the rotation for the direction after that\r\n\tvar alpha1 = -mxUtils.toRadians(this.getRotation());\r\n\tvar alpha2 = -mxUtils.toRadians(this.getTotalRotation()) - alpha1;\r\n\tpt = this.flipPoint(this.rotatePoint(this.snapPoint(this.rotatePoint(pt, alpha1),\r\n\t\t\tthis.ignoreGrid || !this.graph.isGridEnabledEvent(me.getEvent())), alpha2));\r\n\tthis.setPosition(this.state.getPaintBounds(), pt, me);\r\n\tthis.positionChanged();\r\n\tthis.redraw();\r\n};\r\n\r\n/**\r\n * Function: positionChanged\r\n * \r\n * Called after <setPosition> has been called in <processEvent>. This repaints\r\n * the state using <mxCellRenderer>.\r\n */\r\nmxHandle.prototype.positionChanged = function()\r\n{\r\n\tif (this.state.text != null)\r\n\t{\r\n\t\tthis.state.text.apply(this.state);\r\n\t}\r\n\t\r\n\tif (this.state.shape != null)\r\n\t{\r\n\t\tthis.state.shape.apply(this.state);\r\n\t}\r\n\t\r\n\tthis.graph.cellRenderer.redraw(this.state, true);\r\n};\r\n\r\n/**\r\n * Function: getRotation\r\n * \r\n * Returns the rotation defined in the style of the cell.\r\n */\r\nmxHandle.prototype.getRotation = function()\r\n{\r\n\tif (this.state.shape != null)\r\n\t{\r\n\t\treturn this.state.shape.getRotation();\r\n\t}\r\n\t\r\n\treturn 0;\r\n};\r\n\r\n/**\r\n * Function: getTotalRotation\r\n * \r\n * Returns the rotation from the style and the rotation from the direction of\r\n * the cell.\r\n */\r\nmxHandle.prototype.getTotalRotation = function()\r\n{\r\n\tif (this.state.shape != null)\r\n\t{\r\n\t\treturn this.state.shape.getShapeRotation();\r\n\t}\r\n\t\r\n\treturn 0;\r\n};\r\n\r\n/**\r\n * Function: init\r\n * \r\n * Creates and initializes the shapes required for this handle.\r\n */\r\nmxHandle.prototype.init = function()\r\n{\r\n\tvar html = this.isHtmlRequired();\r\n\t\r\n\tif (this.image != null)\r\n\t{\r\n\t\tthis.shape = new mxImageShape(new mxRectangle(0, 0, this.image.width, this.image.height), this.image.src);\r\n\t\tthis.shape.preserveImageAspect = false;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.shape = this.createShape(html);\r\n\t}\r\n\t\r\n\tthis.initShape(html);\r\n};\r\n\r\n/**\r\n * Function: createShape\r\n * \r\n * Creates and returns the shape for this handle.\r\n */\r\nmxHandle.prototype.createShape = function(html)\r\n{\r\n\tvar bounds = new mxRectangle(0, 0, mxConstants.HANDLE_SIZE, mxConstants.HANDLE_SIZE);\r\n\t\r\n\treturn new mxRectangleShape(bounds, mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);\r\n};\r\n\r\n/**\r\n * Function: initShape\r\n * \r\n * Initializes <shape> and sets its cursor.\r\n */\r\nmxHandle.prototype.initShape = function(html)\r\n{\r\n\tif (html && this.shape.isHtmlAllowed())\r\n\t{\r\n\t\tthis.shape.dialect = mxConstants.DIALECT_STRICTHTML;\r\n\t\tthis.shape.init(this.graph.container);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;\r\n\t\t\r\n\t\tif (this.cursor != null)\r\n\t\t{\r\n\t\t\tthis.shape.init(this.graph.getView().getOverlayPane());\r\n\t\t}\r\n\t}\r\n\r\n\tmxEvent.redirectMouseEvents(this.shape.node, this.graph, this.state);\r\n\tthis.shape.node.style.cursor = this.cursor;\r\n};\r\n\r\n/**\r\n * Function: redraw\r\n * \r\n * Renders the shape for this handle.\r\n */\r\nmxHandle.prototype.redraw = function()\r\n{\r\n\tif (this.shape != null && this.state.shape != null)\r\n\t{\r\n\t\tvar pt = this.getPosition(this.state.getPaintBounds());\r\n\t\t\r\n\t\tif (pt != null)\r\n\t\t{\r\n\t\t\tvar alpha = mxUtils.toRadians(this.getTotalRotation());\r\n\t\t\tpt = this.rotatePoint(this.flipPoint(pt), alpha);\r\n\t\r\n\t\t\tvar scale = this.graph.view.scale;\r\n\t\t\tvar tr = this.graph.view.translate;\r\n\t\t\tthis.shape.bounds.x = Math.floor((pt.x + tr.x) * scale - this.shape.bounds.width / 2);\r\n\t\t\tthis.shape.bounds.y = Math.floor((pt.y + tr.y) * scale - this.shape.bounds.height / 2);\r\n\t\t\t\r\n\t\t\t// Needed to force update of text bounds\r\n\t\t\tthis.shape.redraw();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isHtmlRequired\r\n * \r\n * Returns true if this handle should be rendered in HTML. This returns true if\r\n * the text node is in the graph container.\r\n */\r\nmxHandle.prototype.isHtmlRequired = function()\r\n{\r\n\treturn this.state.text != null && this.state.text.node.parentNode == this.graph.container;\r\n};\r\n\r\n/**\r\n * Function: rotatePoint\r\n * \r\n * Rotates the point by the given angle.\r\n */\r\nmxHandle.prototype.rotatePoint = function(pt, alpha)\r\n{\r\n\tvar bounds = this.state.getCellBounds();\r\n\tvar cx = new mxPoint(bounds.getCenterX(), bounds.getCenterY());\r\n\tvar cos = Math.cos(alpha);\r\n\tvar sin = Math.sin(alpha); \r\n\r\n\treturn mxUtils.getRotatedPoint(pt, cos, sin, cx);\r\n};\r\n\r\n/**\r\n * Function: flipPoint\r\n * \r\n * Flips the given point vertically and/or horizontally.\r\n */\r\nmxHandle.prototype.flipPoint = function(pt)\r\n{\r\n\tif (this.state.shape != null)\r\n\t{\r\n\t\tvar bounds = this.state.getCellBounds();\r\n\t\t\r\n\t\tif (this.state.shape.flipH)\r\n\t\t{\r\n\t\t\tpt.x = 2 * bounds.x + bounds.width - pt.x;\r\n\t\t}\r\n\t\t\r\n\t\tif (this.state.shape.flipV)\r\n\t\t{\r\n\t\t\tpt.y = 2 * bounds.y + bounds.height - pt.y;\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn pt;\r\n};\r\n\r\n/**\r\n * Function: snapPoint\r\n * \r\n * Snaps the given point to the grid if ignore is false. This modifies\r\n * the given point in-place and also returns it.\r\n */\r\nmxHandle.prototype.snapPoint = function(pt, ignore)\r\n{\r\n\tif (!ignore)\r\n\t{\r\n\t\tpt.x = this.graph.snap(pt.x);\r\n\t\tpt.y = this.graph.snap(pt.y);\r\n\t}\r\n\t\r\n\treturn pt;\r\n};\r\n\r\n/**\r\n * Function: setVisible\r\n * \r\n * Shows or hides this handle.\r\n */\r\nmxHandle.prototype.setVisible = function(visible)\r\n{\r\n\tif (this.shape != null && this.shape.node != null)\r\n\t{\r\n\t\tthis.shape.node.style.display = (visible) ? '' : 'none';\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: reset\r\n * \r\n * Resets the state of this handle by setting its visibility to true.\r\n */\r\nmxHandle.prototype.reset = function()\r\n{\r\n\tthis.setVisible(true);\r\n\tthis.state.style = this.graph.getCellStyle(this.state.cell);\r\n\tthis.positionChanged();\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Destroys this handle.\r\n */\r\nmxHandle.prototype.destroy = function()\r\n{\r\n\tif (this.shape != null)\r\n\t{\r\n\t\tthis.shape.destroy();\r\n\t\tthis.shape = null;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxVertexHandler\r\n * \r\n * Event handler for resizing cells. This handler is automatically created in\r\n * <mxGraph.createHandler>.\r\n * \r\n * Constructor: mxVertexHandler\r\n * \r\n * Constructs an event handler that allows to resize vertices\r\n * and groups.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> of the cell to be resized.\r\n */\r\nfunction mxVertexHandler(state)\r\n{\r\n\tif (state != null)\r\n\t{\r\n\t\tthis.state = state;\r\n\t\tthis.init();\r\n\t\t\r\n\t\t// Handles escape keystrokes\r\n\t\tthis.escapeHandler = mxUtils.bind(this, function(sender, evt)\r\n\t\t{\r\n\t\t\tif (this.livePreview && this.index != null)\r\n\t\t\t{\r\n\t\t\t\t// Redraws the live preview\r\n\t\t\t\tthis.state.view.graph.cellRenderer.redraw(this.state, true);\r\n\t\t\t\t\r\n\t\t\t\t// Redraws connected edges\r\n\t\t\t\tthis.state.view.invalidate(this.state.cell);\r\n\t\t\t\tthis.state.invalid = false;\r\n\t\t\t\tthis.state.view.validate();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.reset();\r\n\t\t});\r\n\t\t\r\n\t\tthis.state.view.graph.addListener(mxEvent.ESCAPE, this.escapeHandler);\r\n\t}\r\n};\r\n\r\n/**\r\n * Variable: graph\r\n * \r\n * Reference to the enclosing <mxGraph>.\r\n */\r\nmxVertexHandler.prototype.graph = null;\r\n\r\n/**\r\n * Variable: state\r\n * \r\n * Reference to the <mxCellState> being modified.\r\n */\r\nmxVertexHandler.prototype.state = null;\r\n\r\n/**\r\n * Variable: singleSizer\r\n * \r\n * Specifies if only one sizer handle at the bottom, right corner should be\r\n * used. Default is false.\r\n */\r\nmxVertexHandler.prototype.singleSizer = false;\r\n\r\n/**\r\n * Variable: index\r\n * \r\n * Holds the index of the current handle.\r\n */\r\nmxVertexHandler.prototype.index = null;\r\n\r\n/**\r\n * Variable: allowHandleBoundsCheck\r\n * \r\n * Specifies if the bounds of handles should be used for hit-detection in IE or\r\n * if <tolerance> > 0. Default is true.\r\n */\r\nmxVertexHandler.prototype.allowHandleBoundsCheck = true;\r\n\r\n/**\r\n * Variable: handleImage\r\n * \r\n * Optional <mxImage> to be used as handles. Default is null.\r\n */\r\nmxVertexHandler.prototype.handleImage = null;\r\n\r\n/**\r\n * Variable: tolerance\r\n * \r\n * Optional tolerance for hit-detection in <getHandleForEvent>. Default is 0.\r\n */\r\nmxVertexHandler.prototype.tolerance = 0;\r\n\r\n/**\r\n * Variable: rotationEnabled\r\n * \r\n * Specifies if a rotation handle should be visible. Default is false.\r\n */\r\nmxVertexHandler.prototype.rotationEnabled = false;\r\n\r\n/**\r\n * Variable: parentHighlightEnabled\r\n * \r\n * Specifies if the parent should be highlighted if a child cell is selected.\r\n * Default is false.\r\n */\r\nmxVertexHandler.prototype.parentHighlightEnabled = false;\r\n\r\n/**\r\n * Variable: rotationRaster\r\n * \r\n * Specifies if rotation steps should be \"rasterized\" depening on the distance\r\n * to the handle. Default is true.\r\n */\r\nmxVertexHandler.prototype.rotationRaster = true;\r\n\r\n/**\r\n * Variable: rotationCursor\r\n * \r\n * Specifies the cursor for the rotation handle. Default is 'crosshair'.\r\n */\r\nmxVertexHandler.prototype.rotationCursor = 'crosshair';\r\n\r\n/**\r\n * Variable: livePreview\r\n * \r\n * Specifies if resize should change the cell in-place. This is an experimental\r\n * feature for non-touch devices. Default is false.\r\n */\r\nmxVertexHandler.prototype.livePreview = false;\r\n\r\n/**\r\n * Variable: manageSizers\r\n * \r\n * Specifies if sizers should be hidden and spaced if the vertex is small.\r\n * Default is false.\r\n */\r\nmxVertexHandler.prototype.manageSizers = false;\r\n\r\n/**\r\n * Variable: constrainGroupByChildren\r\n * \r\n * Specifies if the size of groups should be constrained by the children.\r\n * Default is false.\r\n */\r\nmxVertexHandler.prototype.constrainGroupByChildren = false;\r\n\r\n/**\r\n * Variable: rotationHandleVSpacing\r\n * \r\n * Vertical spacing for rotation icon. Default is -16.\r\n */\r\nmxVertexHandler.prototype.rotationHandleVSpacing = -16;\r\n\r\n/**\r\n * Variable: horizontalOffset\r\n * \r\n * The horizontal offset for the handles. This is updated in <redrawHandles>\r\n * if <manageSizers> is true and the sizers are offset horizontally.\r\n */\r\nmxVertexHandler.prototype.horizontalOffset = 0;\r\n\r\n/**\r\n * Variable: verticalOffset\r\n * \r\n * The horizontal offset for the handles. This is updated in <redrawHandles>\r\n * if <manageSizers> is true and the sizers are offset vertically.\r\n */\r\nmxVertexHandler.prototype.verticalOffset = 0;\r\n\r\n/**\r\n * Function: init\r\n * \r\n * Initializes the shapes required for this vertex handler.\r\n */\r\nmxVertexHandler.prototype.init = function()\r\n{\r\n\tthis.graph = this.state.view.graph;\r\n\tthis.selectionBounds = this.getSelectionBounds(this.state);\r\n\tthis.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y, this.selectionBounds.width, this.selectionBounds.height);\r\n\tthis.selectionBorder = this.createSelectionShape(this.bounds);\r\n\t// VML dialect required here for event transparency in IE\r\n\tthis.selectionBorder.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;\r\n\tthis.selectionBorder.pointerEvents = false;\r\n\tthis.selectionBorder.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0');\r\n\tthis.selectionBorder.init(this.graph.getView().getOverlayPane());\r\n\tmxEvent.redirectMouseEvents(this.selectionBorder.node, this.graph, this.state);\r\n\t\r\n\tif (this.graph.isCellMovable(this.state.cell))\r\n\t{\r\n\t\tthis.selectionBorder.setCursor(mxConstants.CURSOR_MOVABLE_VERTEX);\r\n\t}\r\n\r\n\t// Adds the sizer handles\r\n\tif (mxGraphHandler.prototype.maxCells <= 0 || this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells)\r\n\t{\r\n\t\tvar resizable = this.graph.isCellResizable(this.state.cell);\r\n\t\tthis.sizers = [];\r\n\r\n\t\tif (resizable || (this.graph.isLabelMovable(this.state.cell) &&\r\n\t\t\tthis.state.width >= 2 && this.state.height >= 2))\r\n\t\t{\r\n\t\t\tvar i = 0;\r\n\r\n\t\t\tif (resizable)\r\n\t\t\t{\r\n\t\t\t\tif (!this.singleSizer)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.sizers.push(this.createSizer('nw-resize', i++));\r\n\t\t\t\t\tthis.sizers.push(this.createSizer('n-resize', i++));\r\n\t\t\t\t\tthis.sizers.push(this.createSizer('ne-resize', i++));\r\n\t\t\t\t\tthis.sizers.push(this.createSizer('w-resize', i++));\r\n\t\t\t\t\tthis.sizers.push(this.createSizer('e-resize', i++));\r\n\t\t\t\t\tthis.sizers.push(this.createSizer('sw-resize', i++));\r\n\t\t\t\t\tthis.sizers.push(this.createSizer('s-resize', i++));\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.sizers.push(this.createSizer('se-resize', i++));\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar geo = this.graph.model.getGeometry(this.state.cell);\r\n\t\t\t\r\n\t\t\tif (geo != null && !geo.relative && !this.graph.isSwimlane(this.state.cell) &&\r\n\t\t\t\tthis.graph.isLabelMovable(this.state.cell))\r\n\t\t\t{\r\n\t\t\t\t// Marks this as the label handle for getHandleForEvent\r\n\t\t\t\tthis.labelShape = this.createSizer(mxConstants.CURSOR_LABEL_HANDLE, mxEvent.LABEL_HANDLE, mxConstants.LABEL_HANDLE_SIZE, mxConstants.LABEL_HANDLE_FILLCOLOR);\r\n\t\t\t\tthis.sizers.push(this.labelShape);\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (this.graph.isCellMovable(this.state.cell) && !this.graph.isCellResizable(this.state.cell) &&\r\n\t\t\tthis.state.width < 2 && this.state.height < 2)\r\n\t\t{\r\n\t\t\tthis.labelShape = this.createSizer(mxConstants.CURSOR_MOVABLE_VERTEX,\r\n\t\t\t\tmxEvent.LABEL_HANDLE, null, mxConstants.LABEL_HANDLE_FILLCOLOR);\r\n\t\t\tthis.sizers.push(this.labelShape);\r\n\t\t}\r\n\t}\r\n\t\r\n\t// Adds the rotation handler\r\n\tif (this.isRotationHandleVisible())\r\n\t{\r\n\t\tthis.rotationShape = this.createSizer(this.rotationCursor, mxEvent.ROTATION_HANDLE,\r\n\t\t\tmxConstants.HANDLE_SIZE + 3, mxConstants.HANDLE_FILLCOLOR);\r\n\t\tthis.sizers.push(this.rotationShape);\r\n\t}\r\n\r\n\tthis.customHandles = this.createCustomHandles();\r\n\tthis.redraw();\r\n\t\r\n\tif (this.constrainGroupByChildren)\r\n\t{\r\n\t\tthis.updateMinBounds();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isRotationHandleVisible\r\n * \r\n * Returns true if the rotation handle should be showing.\r\n */\r\nmxVertexHandler.prototype.isRotationHandleVisible = function()\r\n{\r\n\treturn this.graph.isEnabled() && this.rotationEnabled && this.graph.isCellRotatable(this.state.cell) &&\r\n\t\t(mxGraphHandler.prototype.maxCells <= 0 || this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells) &&\r\n\t\tthis.state.width >= 2 && this.state.height >= 2;\r\n};\r\n\r\n/**\r\n * Function: isConstrainedEvent\r\n * \r\n * Returns true if the aspect ratio if the cell should be maintained.\r\n */\r\nmxVertexHandler.prototype.isConstrainedEvent = function(me)\r\n{\r\n\treturn mxEvent.isShiftDown(me.getEvent()) || this.state.style[mxConstants.STYLE_ASPECT] == 'fixed';\r\n};\r\n\r\n/**\r\n * Function: isCenteredEvent\r\n * \r\n * Returns true if the center of the vertex should be maintained during the resize.\r\n */\r\nmxVertexHandler.prototype.isCenteredEvent = function(state, me)\r\n{\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: createCustomHandles\r\n * \r\n * Returns an array of custom handles. This implementation returns null.\r\n */\r\nmxVertexHandler.prototype.createCustomHandles = function()\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: updateMinBounds\r\n * \r\n * Initializes the shapes required for this vertex handler.\r\n */\r\nmxVertexHandler.prototype.updateMinBounds = function()\r\n{\r\n\tvar children = this.graph.getChildCells(this.state.cell);\r\n\t\r\n\tif (children.length > 0)\r\n\t{\r\n\t\tthis.minBounds = this.graph.view.getBounds(children);\r\n\t\t\r\n\t\tif (this.minBounds != null)\r\n\t\t{\r\n\t\t\tvar s = this.state.view.scale;\r\n\t\t\tvar t = this.state.view.translate;\r\n\r\n\t\t\tthis.minBounds.x -= this.state.x;\r\n\t\t\tthis.minBounds.y -= this.state.y;\r\n\t\t\tthis.minBounds.x /= s;\r\n\t\t\tthis.minBounds.y /= s;\r\n\t\t\tthis.minBounds.width /= s;\r\n\t\t\tthis.minBounds.height /= s;\r\n\t\t\tthis.x0 = this.state.x / s - t.x;\r\n\t\t\tthis.y0 = this.state.y / s - t.y;\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getSelectionBounds\r\n * \r\n * Returns the mxRectangle that defines the bounds of the selection\r\n * border.\r\n */\r\nmxVertexHandler.prototype.getSelectionBounds = function(state)\r\n{\r\n\treturn new mxRectangle(Math.round(state.x), Math.round(state.y), Math.round(state.width), Math.round(state.height));\r\n};\r\n\r\n/**\r\n * Function: createParentHighlightShape\r\n * \r\n * Creates the shape used to draw the selection border.\r\n */\r\nmxVertexHandler.prototype.createParentHighlightShape = function(bounds)\r\n{\r\n\treturn this.createSelectionShape(bounds);\r\n};\r\n\r\n/**\r\n * Function: createSelectionShape\r\n * \r\n * Creates the shape used to draw the selection border.\r\n */\r\nmxVertexHandler.prototype.createSelectionShape = function(bounds)\r\n{\r\n\tvar shape = new mxRectangleShape(bounds, null, this.getSelectionColor());\r\n\tshape.strokewidth = this.getSelectionStrokeWidth();\r\n\tshape.isDashed = this.isSelectionDashed();\r\n\t\r\n\treturn shape;\r\n};\r\n\r\n/**\r\n * Function: getSelectionColor\r\n * \r\n * Returns <mxConstants.VERTEX_SELECTION_COLOR>.\r\n */\r\nmxVertexHandler.prototype.getSelectionColor = function()\r\n{\r\n\treturn mxConstants.VERTEX_SELECTION_COLOR;\r\n};\r\n\r\n/**\r\n * Function: getSelectionStrokeWidth\r\n * \r\n * Returns <mxConstants.VERTEX_SELECTION_STROKEWIDTH>.\r\n */\r\nmxVertexHandler.prototype.getSelectionStrokeWidth = function()\r\n{\r\n\treturn mxConstants.VERTEX_SELECTION_STROKEWIDTH;\r\n};\r\n\r\n/**\r\n * Function: isSelectionDashed\r\n * \r\n * Returns <mxConstants.VERTEX_SELECTION_DASHED>.\r\n */\r\nmxVertexHandler.prototype.isSelectionDashed = function()\r\n{\r\n\treturn mxConstants.VERTEX_SELECTION_DASHED;\r\n};\r\n\r\n/**\r\n * Function: createSizer\r\n * \r\n * Creates a sizer handle for the specified cursor and index and returns\r\n * the new <mxRectangleShape> that represents the handle.\r\n */\r\nmxVertexHandler.prototype.createSizer = function(cursor, index, size, fillColor)\r\n{\r\n\tsize = size || mxConstants.HANDLE_SIZE;\r\n\t\r\n\tvar bounds = new mxRectangle(0, 0, size, size);\r\n\tvar sizer = this.createSizerShape(bounds, index, fillColor);\r\n\r\n\tif (sizer.isHtmlAllowed() && this.state.text != null && this.state.text.node.parentNode == this.graph.container)\r\n\t{\r\n\t\tsizer.bounds.height -= 1;\r\n\t\tsizer.bounds.width -= 1;\r\n\t\tsizer.dialect = mxConstants.DIALECT_STRICTHTML;\r\n\t\tsizer.init(this.graph.container);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tsizer.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?\r\n\t\t\t\tmxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;\r\n\t\tsizer.init(this.graph.getView().getOverlayPane());\r\n\t}\r\n\r\n\tmxEvent.redirectMouseEvents(sizer.node, this.graph, this.state);\r\n\t\r\n\tif (this.graph.isEnabled())\r\n\t{\r\n\t\tsizer.setCursor(cursor);\r\n\t}\r\n\t\r\n\tif (!this.isSizerVisible(index))\r\n\t{\r\n\t\tsizer.visible = false;\r\n\t}\r\n\t\r\n\treturn sizer;\r\n};\r\n\r\n/**\r\n * Function: isSizerVisible\r\n * \r\n * Returns true if the sizer for the given index is visible.\r\n * This returns true for all given indices.\r\n */\r\nmxVertexHandler.prototype.isSizerVisible = function(index)\r\n{\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: createSizerShape\r\n * \r\n * Creates the shape used for the sizer handle for the specified bounds an\r\n * index. Only images and rectangles should be returned if support for HTML\r\n * labels with not foreign objects is required.\r\n */\r\nmxVertexHandler.prototype.createSizerShape = function(bounds, index, fillColor)\r\n{\r\n\tif (this.handleImage != null)\r\n\t{\r\n\t\tbounds = new mxRectangle(bounds.x, bounds.y, this.handleImage.width, this.handleImage.height);\r\n\t\tvar shape = new mxImageShape(bounds, this.handleImage.src);\r\n\t\t\r\n\t\t// Allows HTML rendering of the images\r\n\t\tshape.preserveImageAspect = false;\r\n\r\n\t\treturn shape;\r\n\t}\r\n\telse if (index == mxEvent.ROTATION_HANDLE)\r\n\t{\r\n\t\treturn new mxEllipse(bounds, fillColor || mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);\r\n\t}\r\n\telse\r\n\t{\r\n\t\treturn new mxRectangleShape(bounds, fillColor || mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createBounds\r\n * \r\n * Helper method to create an <mxRectangle> around the given centerpoint\r\n * with a width and height of 2*s or 6, if no s is given.\r\n */\r\nmxVertexHandler.prototype.moveSizerTo = function(shape, x, y)\r\n{\r\n\tif (shape != null)\r\n\t{\r\n\t\tshape.bounds.x = Math.floor(x - shape.bounds.width / 2);\r\n\t\tshape.bounds.y = Math.floor(y - shape.bounds.height / 2);\r\n\t\t\r\n\t\t// Fixes visible inactive handles in VML\r\n\t\tif (shape.node != null && shape.node.style.display != 'none')\r\n\t\t{\r\n\t\t\tshape.redraw();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getHandleForEvent\r\n * \r\n * Returns the index of the handle for the given event. This returns the index\r\n * of the sizer from where the event originated or <mxEvent.LABEL_INDEX>.\r\n */\r\nmxVertexHandler.prototype.getHandleForEvent = function(me)\r\n{\r\n\t// Connection highlight may consume events before they reach sizer handle\r\n\tvar tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.tolerance : 1;\r\n\tvar hit = (this.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)) ?\r\n\t\tnew mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null;\r\n\t\r\n\tfunction checkShape(shape)\r\n\t{\r\n\t\treturn shape != null && (me.isSource(shape) || (hit != null && mxUtils.intersects(shape.bounds, hit) &&\r\n\t\t\tshape.node.style.display != 'none' && shape.node.style.visibility != 'hidden'));\r\n\t}\r\n\r\n\tif (this.customHandles != null && this.isCustomHandleEvent(me))\r\n\t{\r\n\t\t// Inverse loop order to match display order\r\n\t\tfor (var i = this.customHandles.length - 1; i >= 0; i--)\r\n\t\t{\r\n\t\t\tif (checkShape(this.customHandles[i].shape))\r\n\t\t\t{\r\n\t\t\t\t// LATER: Return reference to active shape\r\n\t\t\t\treturn mxEvent.CUSTOM_HANDLE - i;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tif (checkShape(this.rotationShape))\r\n\t{\r\n\t\treturn mxEvent.ROTATION_HANDLE;\r\n\t}\r\n\telse if (checkShape(this.labelShape))\r\n\t{\r\n\t\treturn mxEvent.LABEL_HANDLE;\r\n\t}\r\n\t\r\n\tif (this.sizers != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.sizers.length; i++)\r\n\t\t{\r\n\t\t\tif (checkShape(this.sizers[i]))\r\n\t\t\t{\r\n\t\t\t\treturn i;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: isCustomHandleEvent\r\n * \r\n * Returns true if the given event allows custom handles to be changed. This\r\n * implementation returns true.\r\n */\r\nmxVertexHandler.prototype.isCustomHandleEvent = function(me)\r\n{\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: mouseDown\r\n * \r\n * Handles the event if a handle has been clicked. By consuming the\r\n * event all subsequent events of the gesture are redirected to this\r\n * handler.\r\n */\r\nmxVertexHandler.prototype.mouseDown = function(sender, me)\r\n{\r\n\tvar tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.tolerance : 0;\r\n\t\r\n\tif (!me.isConsumed() && this.graph.isEnabled() && (tol > 0 || me.getState() == this.state))\r\n\t{\r\n\t\tvar handle = this.getHandleForEvent(me);\r\n\r\n\t\tif (handle != null)\r\n\t\t{\r\n\t\t\tthis.start(me.getGraphX(), me.getGraphY(), handle);\r\n\t\t\tme.consume();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isLivePreviewBorder\r\n * \r\n * Called if <livePreview> is enabled to check if a border should be painted.\r\n * This implementation returns true if the shape is transparent.\r\n */\r\nmxVertexHandler.prototype.isLivePreviewBorder = function()\r\n{\r\n\treturn this.state.shape != null && this.state.shape.fill == null && this.state.shape.stroke == null;\r\n};\r\n\r\n/**\r\n * Function: start\r\n * \r\n * Starts the handling of the mouse gesture.\r\n */\r\nmxVertexHandler.prototype.start = function(x, y, index)\r\n{\r\n\tthis.inTolerance = true;\r\n\tthis.childOffsetX = 0;\r\n\tthis.childOffsetY = 0;\r\n\tthis.index = index;\r\n\tthis.startX = x;\r\n\tthis.startY = y;\r\n\t\r\n\t// Saves reference to parent state\r\n\tvar model = this.state.view.graph.model;\r\n\tvar parent = model.getParent(this.state.cell);\r\n\t\r\n\tif (this.state.view.currentRoot != parent && (model.isVertex(parent) || model.isEdge(parent)))\r\n\t{\r\n\t\tthis.parentState = this.state.view.graph.view.getState(parent);\r\n\t}\r\n\t\r\n\t// Creates a preview that can be on top of any HTML label\r\n\tthis.selectionBorder.node.style.display = (index == mxEvent.ROTATION_HANDLE) ? 'inline' : 'none';\r\n\t\r\n\t// Creates the border that represents the new bounds\r\n\tif (!this.livePreview || this.isLivePreviewBorder())\r\n\t{\r\n\t\tthis.preview = this.createSelectionShape(this.bounds);\r\n\t\t\r\n\t\tif (!(mxClient.IS_SVG && Number(this.state.style[mxConstants.STYLE_ROTATION] || '0') != 0) &&\r\n\t\t\tthis.state.text != null && this.state.text.node.parentNode == this.graph.container)\r\n\t\t{\r\n\t\t\tthis.preview.dialect = mxConstants.DIALECT_STRICTHTML;\r\n\t\t\tthis.preview.init(this.graph.container);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.preview.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?\r\n\t\t\t\t\tmxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;\r\n\t\t\tthis.preview.init(this.graph.view.getOverlayPane());\r\n\t\t}\r\n\t}\r\n\t\r\n\t// Prepares the handles for live preview\r\n\tif (this.livePreview)\r\n\t{\r\n\t\tthis.hideSizers();\r\n\t\t\r\n\t\tif (index == mxEvent.ROTATION_HANDLE)\r\n\t\t{\r\n\t\t\tthis.rotationShape.node.style.display = '';\r\n\t\t}\r\n\t\telse if (index == mxEvent.LABEL_HANDLE)\r\n\t\t{\r\n\t\t\tthis.labelShape.node.style.display = '';\r\n\t\t}\r\n\t\telse if (this.sizers != null && this.sizers[index] != null)\r\n\t\t{\r\n\t\t\tthis.sizers[index].node.style.display = '';\r\n\t\t}\r\n\t\telse if (index <= mxEvent.CUSTOM_HANDLE && this.customHandles != null)\r\n\t\t{\r\n\t\t\tthis.customHandles[mxEvent.CUSTOM_HANDLE - index].setVisible(true);\r\n\t\t}\r\n\t\t\r\n\t\t// Gets the array of connected edge handlers for redrawing\r\n\t\tvar edges = this.graph.getEdges(this.state.cell);\r\n\t\tthis.edgeHandlers = [];\r\n\t\t\r\n\t\tfor (var i = 0; i < edges.length; i++)\r\n\t\t{\r\n\t\t\tvar handler = this.graph.selectionCellsHandler.getHandler(edges[i]);\r\n\t\t\t\r\n\t\t\tif (handler != null)\r\n\t\t\t{\r\n\t\t\t\tthis.edgeHandlers.push(handler);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: hideHandles\r\n * \r\n * Shortcut to <hideSizers>.\r\n */\r\nmxVertexHandler.prototype.setHandlesVisible = function(visible)\r\n{\r\n\tif (this.sizers != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.sizers.length; i++)\r\n\t\t{\r\n\t\t\tthis.sizers[i].node.style.display = (visible) ? '' : 'none';\r\n\t\t}\r\n\t}\r\n\r\n\tif (this.customHandles != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.customHandles.length; i++)\r\n\t\t{\r\n\t\t\tthis.customHandles[i].setVisible(visible);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: hideSizers\r\n * \r\n * Hides all sizers except.\r\n * \r\n * Starts the handling of the mouse gesture.\r\n */\r\nmxVertexHandler.prototype.hideSizers = function()\r\n{\r\n\tthis.setHandlesVisible(false);\r\n};\r\n\r\n/**\r\n * Function: checkTolerance\r\n * \r\n * Checks if the coordinates for the given event are within the\r\n * <mxGraph.tolerance>. If the event is a mouse event then the tolerance is\r\n * ignored.\r\n */\r\nmxVertexHandler.prototype.checkTolerance = function(me)\r\n{\r\n\tif (this.inTolerance && this.startX != null && this.startY != null)\r\n\t{\r\n\t\tif (mxEvent.isMouseEvent(me.getEvent()) ||\r\n\t\t\tMath.abs(me.getGraphX() - this.startX) > this.graph.tolerance ||\r\n\t\t\tMath.abs(me.getGraphY() - this.startY) > this.graph.tolerance)\r\n\t\t{\r\n\t\t\tthis.inTolerance = false;\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: updateHint\r\n * \r\n * Hook for subclassers do show details while the handler is active.\r\n */\r\nmxVertexHandler.prototype.updateHint = function(me) { };\r\n\r\n/**\r\n * Function: removeHint\r\n * \r\n * Hooks for subclassers to hide details when the handler gets inactive.\r\n */\r\nmxVertexHandler.prototype.removeHint = function() { };\r\n\r\n/**\r\n * Function: roundAngle\r\n * \r\n * Hook for rounding the angle. This uses Math.round.\r\n */\r\nmxVertexHandler.prototype.roundAngle = function(angle)\r\n{\r\n\treturn Math.round(angle * 10) / 10;\r\n};\r\n\r\n/**\r\n * Function: roundLength\r\n * \r\n * Hook for rounding the unscaled width or height. This uses Math.round.\r\n */\r\nmxVertexHandler.prototype.roundLength = function(length)\r\n{\r\n\treturn Math.round(length);\r\n};\r\n\r\n/**\r\n * Function: mouseMove\r\n * \r\n * Handles the event by updating the preview.\r\n */\r\nmxVertexHandler.prototype.mouseMove = function(sender, me)\r\n{\r\n\tif (!me.isConsumed() && this.index != null)\r\n\t{\r\n\t\t// Checks tolerance for ignoring single clicks\r\n\t\tthis.checkTolerance(me);\r\n\r\n\t\tif (!this.inTolerance)\r\n\t\t{\r\n\t\t\tif (this.index <= mxEvent.CUSTOM_HANDLE)\r\n\t\t\t{\r\n\t\t\t\tif (this.customHandles != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.customHandles[mxEvent.CUSTOM_HANDLE - this.index].processEvent(me);\r\n\t\t\t\t\tthis.customHandles[mxEvent.CUSTOM_HANDLE - this.index].active = true;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if (this.index == mxEvent.LABEL_HANDLE)\r\n\t\t\t{\r\n\t\t\t\tthis.moveLabel(me);\r\n\t\t\t}\r\n\t\t\telse if (this.index == mxEvent.ROTATION_HANDLE)\r\n\t\t\t{\r\n\t\t\t\tthis.rotateVertex(me);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.resizeVertex(me);\r\n\t\t\t}\r\n\r\n\t\t\tthis.updateHint(me);\r\n\t\t}\r\n\t\t\r\n\t\tme.consume();\r\n\t}\r\n\t// Workaround for disabling the connect highlight when over handle\r\n\telse if (!this.graph.isMouseDown && this.getHandleForEvent(me) != null)\r\n\t{\r\n\t\tme.consume(false);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: rotateVertex\r\n * \r\n * Rotates the vertex.\r\n */\r\nmxVertexHandler.prototype.moveLabel = function(me)\r\n{\r\n\tvar point = new mxPoint(me.getGraphX(), me.getGraphY());\r\n\tvar tr = this.graph.view.translate;\r\n\tvar scale = this.graph.view.scale;\r\n\t\r\n\tif (this.graph.isGridEnabledEvent(me.getEvent()))\r\n\t{\r\n\t\tpoint.x = (this.graph.snap(point.x / scale - tr.x) + tr.x) * scale;\r\n\t\tpoint.y = (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale;\r\n\t}\r\n\r\n\tvar index = (this.rotationShape != null) ? this.sizers.length - 2 : this.sizers.length - 1;\r\n\tthis.moveSizerTo(this.sizers[index], point.x, point.y);\r\n};\r\n\r\n/**\r\n * Function: rotateVertex\r\n * \r\n * Rotates the vertex.\r\n */\r\nmxVertexHandler.prototype.rotateVertex = function(me)\r\n{\r\n\tvar point = new mxPoint(me.getGraphX(), me.getGraphY());\r\n\tvar dx = this.state.x + this.state.width / 2 - point.x;\r\n\tvar dy = this.state.y + this.state.height / 2 - point.y;\r\n\tthis.currentAlpha = (dx != 0) ? Math.atan(dy / dx) * 180 / Math.PI + 90 : ((dy < 0) ? 180 : 0);\r\n\t\r\n\tif (dx > 0)\r\n\t{\r\n\t\tthis.currentAlpha -= 180;\r\n\t}\r\n\r\n\t// Rotation raster\r\n\tif (this.rotationRaster && this.graph.isGridEnabledEvent(me.getEvent()))\r\n\t{\r\n\t\tvar dx = point.x - this.state.getCenterX();\r\n\t\tvar dy = point.y - this.state.getCenterY();\r\n\t\tvar dist = Math.abs(Math.sqrt(dx * dx + dy * dy) - 20) * 3;\r\n\t\tvar raster = Math.max(1, 5 * Math.min(3, Math.max(0, Math.round(80 / Math.abs(dist)))));\r\n\t\t\r\n\t\tthis.currentAlpha = Math.round(this.currentAlpha / raster) * raster;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.currentAlpha = this.roundAngle(this.currentAlpha);\r\n\t}\r\n\r\n\tthis.selectionBorder.rotation = this.currentAlpha;\r\n\tthis.selectionBorder.redraw();\r\n\t\t\t\t\t\r\n\tif (this.livePreview)\r\n\t{\r\n\t\tthis.redrawHandles();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: rotateVertex\r\n * \r\n * Rotates the vertex.\r\n */\r\nmxVertexHandler.prototype.resizeVertex = function(me)\r\n{\r\n\tvar ct = new mxPoint(this.state.getCenterX(), this.state.getCenterY());\r\n\tvar alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0');\r\n\tvar point = new mxPoint(me.getGraphX(), me.getGraphY());\r\n\tvar tr = this.graph.view.translate;\r\n\tvar scale = this.graph.view.scale;\r\n\tvar cos = Math.cos(-alpha);\r\n\tvar sin = Math.sin(-alpha);\r\n\t\r\n\tvar dx = point.x - this.startX;\r\n\tvar dy = point.y - this.startY;\r\n\r\n\t// Rotates vector for mouse gesture\r\n\tvar tx = cos * dx - sin * dy;\r\n\tvar ty = sin * dx + cos * dy;\r\n\t\r\n\tdx = tx;\r\n\tdy = ty;\r\n\r\n\tvar geo = this.graph.getCellGeometry(this.state.cell);\r\n\tthis.unscaledBounds = this.union(geo, dx / scale, dy / scale, this.index,\r\n\t\tthis.graph.isGridEnabledEvent(me.getEvent()), 1,\r\n\t\tnew mxPoint(0, 0), this.isConstrainedEvent(me),\r\n\t\tthis.isCenteredEvent(this.state, me));\r\n\t\r\n\t// Keeps vertex within maximum graph or parent bounds\r\n\tif (!geo.relative)\r\n\t{\r\n\t\tvar max = this.graph.getMaximumGraphBounds();\r\n\t\t\r\n\t\t// Handles child cells\r\n\t\tif (max != null && this.parentState != null)\r\n\t\t{\r\n\t\t\tmax = mxRectangle.fromRectangle(max);\r\n\t\t\t\r\n\t\t\tmax.x -= (this.parentState.x - tr.x * scale) / scale;\r\n\t\t\tmax.y -= (this.parentState.y - tr.y * scale) / scale;\r\n\t\t}\r\n\t\t\r\n\t\tif (this.graph.isConstrainChild(this.state.cell))\r\n\t\t{\r\n\t\t\tvar tmp = this.graph.getCellContainmentArea(this.state.cell);\r\n\t\t\t\r\n\t\t\tif (tmp != null)\r\n\t\t\t{\r\n\t\t\t\tvar overlap = this.graph.getOverlap(this.state.cell);\r\n\t\t\t\t\r\n\t\t\t\tif (overlap > 0)\r\n\t\t\t\t{\r\n\t\t\t\t\ttmp = mxRectangle.fromRectangle(tmp);\r\n\t\t\t\t\t\r\n\t\t\t\t\ttmp.x -= tmp.width * overlap;\r\n\t\t\t\t\ttmp.y -= tmp.height * overlap;\r\n\t\t\t\t\ttmp.width += 2 * tmp.width * overlap;\r\n\t\t\t\t\ttmp.height += 2 * tmp.height * overlap;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (max == null)\r\n\t\t\t\t{\r\n\t\t\t\t\tmax = tmp;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tmax = mxRectangle.fromRectangle(max);\r\n\t\t\t\t\tmax.intersect(tmp);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\r\n\t\tif (max != null)\r\n\t\t{\r\n\t\t\tif (this.unscaledBounds.x < max.x)\r\n\t\t\t{\r\n\t\t\t\tthis.unscaledBounds.width -= max.x - this.unscaledBounds.x;\r\n\t\t\t\tthis.unscaledBounds.x = max.x;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (this.unscaledBounds.y < max.y)\r\n\t\t\t{\r\n\t\t\t\tthis.unscaledBounds.height -= max.y - this.unscaledBounds.y;\r\n\t\t\t\tthis.unscaledBounds.y = max.y;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (this.unscaledBounds.x + this.unscaledBounds.width > max.x + max.width)\r\n\t\t\t{\r\n\t\t\t\tthis.unscaledBounds.width -= this.unscaledBounds.x +\r\n\t\t\t\t\tthis.unscaledBounds.width - max.x - max.width;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (this.unscaledBounds.y + this.unscaledBounds.height > max.y + max.height)\r\n\t\t\t{\r\n\t\t\t\tthis.unscaledBounds.height -= this.unscaledBounds.y +\r\n\t\t\t\t\tthis.unscaledBounds.height - max.y - max.height;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\tthis.bounds = new mxRectangle(((this.parentState != null) ? this.parentState.x : tr.x * scale) +\r\n\t\t(this.unscaledBounds.x) * scale, ((this.parentState != null) ? this.parentState.y : tr.y * scale) +\r\n\t\t(this.unscaledBounds.y) * scale, this.unscaledBounds.width * scale, this.unscaledBounds.height * scale);\r\n\r\n\tif (geo.relative && this.parentState != null)\r\n\t{\r\n\t\tthis.bounds.x += this.state.x - this.parentState.x;\r\n\t\tthis.bounds.y += this.state.y - this.parentState.y;\r\n\t}\r\n\r\n\tcos = Math.cos(alpha);\r\n\tsin = Math.sin(alpha);\r\n\t\r\n\tvar c2 = new mxPoint(this.bounds.getCenterX(), this.bounds.getCenterY());\r\n\r\n\tvar dx = c2.x - ct.x;\r\n\tvar dy = c2.y - ct.y;\r\n\t\r\n\tvar dx2 = cos * dx - sin * dy;\r\n\tvar dy2 = sin * dx + cos * dy;\r\n\t\r\n\tvar dx3 = dx2 - dx;\r\n\tvar dy3 = dy2 - dy;\r\n\t\r\n\tvar dx4 = this.bounds.x - this.state.x;\r\n\tvar dy4 = this.bounds.y - this.state.y;\r\n\t\r\n\tvar dx5 = cos * dx4 - sin * dy4;\r\n\tvar dy5 = sin * dx4 + cos * dy4;\r\n\t\r\n\tthis.bounds.x += dx3;\r\n\tthis.bounds.y += dy3;\r\n\t\r\n\t// Rounds unscaled bounds to int\r\n\tthis.unscaledBounds.x = this.roundLength(this.unscaledBounds.x + dx3 / scale);\r\n\tthis.unscaledBounds.y = this.roundLength(this.unscaledBounds.y + dy3 / scale);\r\n\tthis.unscaledBounds.width = this.roundLength(this.unscaledBounds.width);\r\n\tthis.unscaledBounds.height = this.roundLength(this.unscaledBounds.height);\r\n\t\r\n\t// Shifts the children according to parent offset\r\n\tif (!this.graph.isCellCollapsed(this.state.cell) && (dx3 != 0 || dy3 != 0))\r\n\t{\r\n\t\tthis.childOffsetX = this.state.x - this.bounds.x + dx5;\r\n\t\tthis.childOffsetY = this.state.y - this.bounds.y + dy5;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.childOffsetX = 0;\r\n\t\tthis.childOffsetY = 0;\r\n\t}\r\n\t\r\n\tif (this.livePreview)\r\n\t{\r\n\t\tthis.updateLivePreview(me);\r\n\t}\r\n\t\r\n\tif (this.preview != null)\r\n\t{\r\n\t\tthis.drawPreview();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: updateLivePreview\r\n * \r\n * Repaints the live preview.\r\n */\r\nmxVertexHandler.prototype.updateLivePreview = function(me)\r\n{\r\n\t// TODO: Apply child offset to children in live preview\r\n\tvar scale = this.graph.view.scale;\r\n\tvar tr = this.graph.view.translate;\r\n\t\r\n\t// Saves current state\r\n\tvar tempState = this.state.clone();\r\n\r\n\t// Temporarily changes size and origin\r\n\tthis.state.x = this.bounds.x;\r\n\tthis.state.y = this.bounds.y;\r\n\tthis.state.origin = new mxPoint(this.state.x / scale - tr.x, this.state.y / scale - tr.y);\r\n\tthis.state.width = this.bounds.width;\r\n\tthis.state.height = this.bounds.height;\r\n\t\r\n\t// Needed to force update of text bounds\r\n\tthis.state.unscaledWidth = null;\r\n\t\r\n\t// Redraws cell and handles\r\n\tvar off = this.state.absoluteOffset;\r\n\toff = new mxPoint(off.x, off.y);\r\n\r\n\t// Required to store and reset absolute offset for updating label position\r\n\tthis.state.absoluteOffset.x = 0;\r\n\tthis.state.absoluteOffset.y = 0;\r\n\tvar geo = this.graph.getCellGeometry(this.state.cell);\t\t\t\t\r\n\r\n\tif (geo != null)\r\n\t{\r\n\t\tvar offset = geo.offset || this.EMPTY_POINT;\r\n\r\n\t\tif (offset != null && !geo.relative)\r\n\t\t{\r\n\t\t\tthis.state.absoluteOffset.x = this.state.view.scale * offset.x;\r\n\t\t\tthis.state.absoluteOffset.y = this.state.view.scale * offset.y;\r\n\t\t}\r\n\t\t\r\n\t\tthis.state.view.updateVertexLabelOffset(this.state);\r\n\t}\r\n\t\r\n\t// Draws the live preview\r\n\tthis.state.view.graph.cellRenderer.redraw(this.state, true);\r\n\t\r\n\t// Redraws connected edges TODO: Include child edges\r\n\tthis.state.view.invalidate(this.state.cell);\r\n\tthis.state.invalid = false;\r\n\tthis.state.view.validate();\r\n\tthis.redrawHandles();\r\n\t\r\n\t// Restores current state\r\n\tthis.state.setState(tempState);\r\n};\r\n\r\n/**\r\n * Function: mouseUp\r\n * \r\n * Handles the event by applying the changes to the geometry.\r\n */\r\nmxVertexHandler.prototype.mouseUp = function(sender, me)\r\n{\r\n\tif (this.index != null && this.state != null)\r\n\t{\r\n\t\tvar point = new mxPoint(me.getGraphX(), me.getGraphY());\r\n\r\n\t\tthis.graph.getModel().beginUpdate();\r\n\t\ttry\r\n\t\t{\r\n\t\t\tif (this.index <= mxEvent.CUSTOM_HANDLE)\r\n\t\t\t{\r\n\t\t\t\tif (this.customHandles != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.customHandles[mxEvent.CUSTOM_HANDLE - this.index].active = false;\r\n\t\t\t\t\tthis.customHandles[mxEvent.CUSTOM_HANDLE - this.index].execute();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if (this.index == mxEvent.ROTATION_HANDLE)\r\n\t\t\t{\r\n\t\t\t\tif (this.currentAlpha != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar delta = this.currentAlpha - (this.state.style[mxConstants.STYLE_ROTATION] || 0);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (delta != 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.rotateCell(this.state.cell, delta);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.rotateClick();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tvar gridEnabled = this.graph.isGridEnabledEvent(me.getEvent());\r\n\t\t\t\tvar alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0');\r\n\t\t\t\tvar cos = Math.cos(-alpha);\r\n\t\t\t\tvar sin = Math.sin(-alpha);\r\n\t\t\t\t\r\n\t\t\t\tvar dx = point.x - this.startX;\r\n\t\t\t\tvar dy = point.y - this.startY;\r\n\t\t\t\t\r\n\t\t\t\t// Rotates vector for mouse gesture\r\n\t\t\t\tvar tx = cos * dx - sin * dy;\r\n\t\t\t\tvar ty = sin * dx + cos * dy;\r\n\t\t\t\t\r\n\t\t\t\tdx = tx;\r\n\t\t\t\tdy = ty;\r\n\t\t\t\t\r\n\t\t\t\tvar s = this.graph.view.scale;\r\n\t\t\t\tvar recurse = this.isRecursiveResize(this.state, me);\r\n\t\t\t\tthis.resizeCell(this.state.cell, this.roundLength(dx / s), this.roundLength(dy / s),\r\n\t\t\t\t\tthis.index, gridEnabled, this.isConstrainedEvent(me), recurse);\r\n\t\t\t}\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tthis.graph.getModel().endUpdate();\r\n\t\t}\r\n\r\n\t\tme.consume();\r\n\t\tthis.reset();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: rotateCell\r\n * \r\n * Rotates the given cell to the given rotation.\r\n */\r\nmxVertexHandler.prototype.isRecursiveResize = function(state, me)\r\n{\r\n\treturn this.graph.isRecursiveResize(this.state);\r\n};\r\n\r\n/**\r\n * Function: rotateClick\r\n * \r\n * Hook for subclassers to implement a single click on the rotation handle.\r\n * This code is executed as part of the model transaction. This implementation\r\n * is empty.\r\n */\r\nmxVertexHandler.prototype.rotateClick = function() { };\r\n\r\n/**\r\n * Function: rotateCell\r\n * \r\n * Rotates the given cell and its children by the given angle in degrees.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> to be rotated.\r\n * angle - Angle in degrees.\r\n */\r\nmxVertexHandler.prototype.rotateCell = function(cell, angle, parent)\r\n{\r\n\tif (angle != 0)\r\n\t{\r\n\t\tvar model = this.graph.getModel();\r\n\r\n\t\tif (model.isVertex(cell) || model.isEdge(cell))\r\n\t\t{\r\n\t\t\tif (!model.isEdge(cell))\r\n\t\t\t{\r\n\t\t\t\tvar state = this.graph.view.getState(cell);\r\n\t\t\t\tvar style = (state != null) ? state.style : this.graph.getCellStyle(cell);\r\n\t\t\r\n\t\t\t\tif (style != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar total = (style[mxConstants.STYLE_ROTATION] || 0) + angle;\r\n\t\t\t\t\tthis.graph.setCellStyles(mxConstants.STYLE_ROTATION, total, [cell]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar geo = this.graph.getCellGeometry(cell);\r\n\t\t\t\r\n\t\t\tif (geo != null)\r\n\t\t\t{\r\n\t\t\t\tvar pgeo = this.graph.getCellGeometry(parent);\r\n\t\t\t\t\r\n\t\t\t\tif (pgeo != null && !model.isEdge(parent))\r\n\t\t\t\t{\r\n\t\t\t\t\tgeo = geo.clone();\r\n\t\t\t\t\tgeo.rotate(angle, new mxPoint(pgeo.width / 2, pgeo.height / 2));\r\n\t\t\t\t\tmodel.setGeometry(cell, geo);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif ((model.isVertex(cell) && !geo.relative) || model.isEdge(cell))\r\n\t\t\t\t{\r\n\t\t\t\t\t// Recursive rotation\r\n\t\t\t\t\tvar childCount = model.getChildCount(cell);\r\n\t\t\t\t\t\r\n\t\t\t\t\tfor (var i = 0; i < childCount; i++)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.rotateCell(model.getChildAt(cell, i), angle, cell);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: reset\r\n * \r\n * Resets the state of this handler.\r\n */\r\nmxVertexHandler.prototype.reset = function()\r\n{\r\n\tif (this.sizers != null && this.index != null && this.sizers[this.index] != null &&\r\n\t\tthis.sizers[this.index].node.style.display == 'none')\r\n\t{\r\n\t\tthis.sizers[this.index].node.style.display = '';\r\n\t}\r\n\r\n\tthis.currentAlpha = null;\r\n\tthis.inTolerance = null;\r\n\tthis.index = null;\r\n\r\n\t// TODO: Reset and redraw cell states for live preview\r\n\tif (this.preview != null)\r\n\t{\r\n\t\tthis.preview.destroy();\r\n\t\tthis.preview = null;\r\n\t}\r\n\r\n\tif (this.livePreview && this.sizers != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.sizers.length; i++)\r\n\t\t{\r\n\t\t\tif (this.sizers[i] != null)\r\n\t\t\t{\r\n\t\t\t\tthis.sizers[i].node.style.display = '';\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tif (this.customHandles != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.customHandles.length; i++)\r\n\t\t{\r\n\t\t\tif (this.customHandles[i].active)\r\n\t\t\t{\r\n\t\t\t\tthis.customHandles[i].active = false;\r\n\t\t\t\tthis.customHandles[i].reset();\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.customHandles[i].setVisible(true);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\t// Checks if handler has been destroyed\r\n\tif (this.selectionBorder != null)\r\n\t{\r\n\t\tthis.selectionBorder.node.style.display = 'inline';\r\n\t\tthis.selectionBounds = this.getSelectionBounds(this.state);\r\n\t\tthis.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y,\r\n\t\t\tthis.selectionBounds.width, this.selectionBounds.height);\r\n\t\tthis.drawPreview();\r\n\t}\r\n\r\n\tthis.removeHint();\r\n\tthis.redrawHandles();\r\n\tthis.edgeHandlers = null;\r\n\tthis.unscaledBounds = null;\r\n};\r\n\r\n/**\r\n * Function: resizeCell\r\n * \r\n * Uses the given vector to change the bounds of the given cell\r\n * in the graph using <mxGraph.resizeCell>.\r\n */\r\nmxVertexHandler.prototype.resizeCell = function(cell, dx, dy, index, gridEnabled, constrained, recurse)\r\n{\r\n\tvar geo = this.graph.model.getGeometry(cell);\r\n\t\r\n\tif (geo != null)\r\n\t{\r\n\t\tif (index == mxEvent.LABEL_HANDLE)\r\n\t\t{\r\n\t\t\tvar scale = this.graph.view.scale;\r\n\t\t\tdx = Math.round((this.labelShape.bounds.getCenterX() - this.startX) / scale);\r\n\t\t\tdy = Math.round((this.labelShape.bounds.getCenterY() - this.startY) / scale);\r\n\t\t\t\r\n\t\t\tgeo = geo.clone();\r\n\t\t\t\r\n\t\t\tif (geo.offset == null)\r\n\t\t\t{\r\n\t\t\t\tgeo.offset = new mxPoint(dx, dy);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tgeo.offset.x += dx;\r\n\t\t\t\tgeo.offset.y += dy;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.graph.model.setGeometry(cell, geo);\r\n\t\t}\r\n\t\telse if (this.unscaledBounds != null)\r\n\t\t{\r\n\t\t\tvar scale = this.graph.view.scale;\r\n\r\n\t\t\tif (this.childOffsetX != 0 || this.childOffsetY != 0)\r\n\t\t\t{\r\n\t\t\t\tthis.moveChildren(cell, Math.round(this.childOffsetX / scale), Math.round(this.childOffsetY / scale));\r\n\t\t\t}\r\n\r\n\t\t\tthis.graph.resizeCell(cell, this.unscaledBounds, recurse);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: moveChildren\r\n * \r\n * Moves the children of the given cell by the given vector.\r\n */\r\nmxVertexHandler.prototype.moveChildren = function(cell, dx, dy)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\tvar childCount = model.getChildCount(cell);\r\n\t\r\n\tfor (var i = 0; i < childCount; i++)\r\n\t{\r\n\t\tvar child = model.getChildAt(cell, i);\r\n\t\tvar geo = this.graph.getCellGeometry(child);\r\n\t\t\r\n\t\tif (geo != null)\r\n\t\t{\r\n\t\t\tgeo = geo.clone();\r\n\t\t\tgeo.translate(dx, dy);\r\n\t\t\tmodel.setGeometry(child, geo);\r\n\t\t}\r\n\t}\r\n};\r\n/**\r\n * Function: union\r\n * \r\n * Returns the union of the given bounds and location for the specified\r\n * handle index.\r\n * \r\n * To override this to limit the size of vertex via a minWidth/-Height style,\r\n * the following code can be used.\r\n * \r\n * (code)\r\n * var vertexHandlerUnion = mxVertexHandler.prototype.union;\r\n * mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr, constrained)\r\n * {\r\n *   var result = vertexHandlerUnion.apply(this, arguments);\r\n *   \r\n *   result.width = Math.max(result.width, mxUtils.getNumber(this.state.style, 'minWidth', 0));\r\n *   result.height = Math.max(result.height, mxUtils.getNumber(this.state.style, 'minHeight', 0));\r\n *   \r\n *   return result;\r\n * };\r\n * (end)\r\n * \r\n * The minWidth/-Height style can then be used as follows:\r\n * \r\n * (code)\r\n * graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30, 'minWidth=100;minHeight=100;');\r\n * (end)\r\n * \r\n * To override this to update the height for a wrapped text if the width of a vertex is\r\n * changed, the following can be used.\r\n * \r\n * (code)\r\n * var mxVertexHandlerUnion = mxVertexHandler.prototype.union;\r\n * mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr, constrained)\r\n * {\r\n *   var result = mxVertexHandlerUnion.apply(this, arguments);\r\n *   var s = this.state;\r\n *   \r\n *   if (this.graph.isHtmlLabel(s.cell) && (index == 3 || index == 4) &&\r\n *       s.text != null && s.style[mxConstants.STYLE_WHITE_SPACE] == 'wrap')\r\n *   {\r\n *     var label = this.graph.getLabel(s.cell);\r\n *     var fontSize = mxUtils.getNumber(s.style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE);\r\n *     var ww = result.width / s.view.scale - s.text.spacingRight - s.text.spacingLeft\r\n *     \r\n *     result.height = mxUtils.getSizeForString(label, fontSize, s.style[mxConstants.STYLE_FONTFAMILY], ww).height;\r\n *   }\r\n *   \r\n *   return result;\r\n * };\r\n * (end)\r\n */\r\nmxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr, constrained, centered)\r\n{\r\n\tif (this.singleSizer)\r\n\t{\r\n\t\tvar x = bounds.x + bounds.width + dx;\r\n\t\tvar y = bounds.y + bounds.height + dy;\r\n\t\t\r\n\t\tif (gridEnabled)\r\n\t\t{\r\n\t\t\tx = this.graph.snap(x / scale) * scale;\r\n\t\t\ty = this.graph.snap(y / scale) * scale;\r\n\t\t}\r\n\t\t\r\n\t\tvar rect = new mxRectangle(bounds.x, bounds.y, 0, 0);\r\n\t\trect.add(new mxRectangle(x, y, 0, 0));\r\n\t\t\r\n\t\treturn rect;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar w0 = bounds.width;\r\n\t\tvar h0 = bounds.height;\r\n\t\tvar left = bounds.x - tr.x * scale;\r\n\t\tvar right = left + w0;\r\n\t\tvar top = bounds.y - tr.y * scale;\r\n\t\tvar bottom = top + h0;\r\n\t\t\r\n\t\tvar cx = left + w0 / 2;\r\n\t\tvar cy = top + h0 / 2;\r\n\t\t\r\n\t\tif (index > 4 /* Bottom Row */)\r\n\t\t{\r\n\t\t\tbottom = bottom + dy;\r\n\t\t\t\r\n\t\t\tif (gridEnabled)\r\n\t\t\t{\r\n\t\t\t\tbottom = this.graph.snap(bottom / scale) * scale;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (index < 3 /* Top Row */)\r\n\t\t{\r\n\t\t\ttop = top + dy;\r\n\t\t\t\r\n\t\t\tif (gridEnabled)\r\n\t\t\t{\r\n\t\t\t\ttop = this.graph.snap(top / scale) * scale;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (index == 0 || index == 3 || index == 5 /* Left */)\r\n\t\t{\r\n\t\t\tleft += dx;\r\n\t\t\t\r\n\t\t\tif (gridEnabled)\r\n\t\t\t{\r\n\t\t\t\tleft = this.graph.snap(left / scale) * scale;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (index == 2 || index == 4 || index == 7 /* Right */)\r\n\t\t{\r\n\t\t\tright += dx;\r\n\t\t\t\r\n\t\t\tif (gridEnabled)\r\n\t\t\t{\r\n\t\t\t\tright = this.graph.snap(right / scale) * scale;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tvar width = right - left;\r\n\t\tvar height = bottom - top;\r\n\r\n\t\tif (constrained)\r\n\t\t{\r\n\t\t\tvar geo = this.graph.getCellGeometry(this.state.cell);\r\n\r\n\t\t\tif (geo != null)\r\n\t\t\t{\r\n\t\t\t\tvar aspect = geo.width / geo.height;\r\n\t\t\t\t\r\n\t\t\t\tif (index== 1 || index== 2 || index == 7 || index == 6)\r\n\t\t\t\t{\r\n\t\t\t\t\twidth = height * aspect;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\theight = width / aspect;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (index == 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tleft = right - width;\r\n\t\t\t\t\ttop = bottom - height;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (centered)\r\n\t\t{\r\n\t\t\twidth += (width - w0);\r\n\t\t\theight += (height - h0);\r\n\t\t\t\r\n\t\t\tvar cdx = cx - (left + width / 2);\r\n\t\t\tvar cdy = cy - (top + height / 2);\r\n\r\n\t\t\tleft += cdx;\r\n\t\t\ttop += cdy;\r\n\t\t\tright += cdx;\r\n\t\t\tbottom += cdy;\r\n\t\t}\r\n\r\n\t\t// Flips over left side\r\n\t\tif (width < 0)\r\n\t\t{\r\n\t\t\tleft += width;\r\n\t\t\twidth = Math.abs(width);\r\n\t\t}\r\n\t\t\r\n\t\t// Flips over top side\r\n\t\tif (height < 0)\r\n\t\t{\r\n\t\t\ttop += height;\r\n\t\t\theight = Math.abs(height);\r\n\t\t}\r\n\r\n\t\tvar result = new mxRectangle(left + tr.x * scale, top + tr.y * scale, width, height);\r\n\t\t\r\n\t\tif (this.minBounds != null)\r\n\t\t{\r\n\t\t\tresult.width = Math.max(result.width, this.minBounds.x * scale + this.minBounds.width * scale +\r\n\t\t\t\tMath.max(0, this.x0 * scale - result.x));\r\n\t\t\tresult.height = Math.max(result.height, this.minBounds.y * scale + this.minBounds.height * scale +\r\n\t\t\t\tMath.max(0, this.y0 * scale - result.y));\r\n\t\t}\r\n\t\t\r\n\t\treturn result;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: redraw\r\n * \r\n * Redraws the handles and the preview.\r\n */\r\nmxVertexHandler.prototype.redraw = function()\r\n{\r\n\tthis.selectionBounds = this.getSelectionBounds(this.state);\r\n\tthis.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y, this.selectionBounds.width, this.selectionBounds.height);\r\n\t\r\n\tthis.redrawHandles();\r\n\tthis.drawPreview();\r\n};\r\n\r\n/**\r\n * Returns the padding to be used for drawing handles for the current <bounds>.\r\n */\r\nmxVertexHandler.prototype.getHandlePadding = function()\r\n{\r\n\t// KNOWN: Tolerance depends on event type (eg. 0 for mouse events)\r\n\tvar result = new mxPoint(0, 0);\r\n\tvar tol = this.tolerance;\r\n\r\n\tif (this.sizers != null && this.sizers.length > 0 && this.sizers[0] != null &&\r\n\t\t(this.bounds.width < 2 * this.sizers[0].bounds.width + 2 * tol ||\r\n\t\tthis.bounds.height < 2 * this.sizers[0].bounds.height + 2 * tol))\r\n\t{\r\n\t\ttol /= 2;\r\n\t\t\r\n\t\tresult.x = this.sizers[0].bounds.width + tol;\r\n\t\tresult.y = this.sizers[0].bounds.height + tol;\r\n\t}\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: redrawHandles\r\n * \r\n * Redraws the handles. To hide certain handles the following code can be used.\r\n * \r\n * (code)\r\n * mxVertexHandler.prototype.redrawHandles = function()\r\n * {\r\n *   mxVertexHandlerRedrawHandles.apply(this, arguments);\r\n *   \r\n *   if (this.sizers != null && this.sizers.length > 7)\r\n *   {\r\n *     this.sizers[1].node.style.display = 'none';\r\n *     this.sizers[6].node.style.display = 'none';\r\n *   }\r\n * };\r\n * (end)\r\n */\r\nmxVertexHandler.prototype.redrawHandles = function()\r\n{\r\n\tvar tol = this.tolerance;\r\n\tthis.horizontalOffset = 0;\r\n\tthis.verticalOffset = 0;\r\n\tvar s = this.bounds;\r\n\r\n\tif (this.sizers != null && this.sizers.length > 0 && this.sizers[0] != null)\r\n\t{\r\n\t\tif (this.index == null && this.manageSizers && this.sizers.length >= 8)\r\n\t\t{\r\n\t\t\t// KNOWN: Tolerance depends on event type (eg. 0 for mouse events)\r\n\t\t\tvar padding = this.getHandlePadding();\r\n\t\t\tthis.horizontalOffset = padding.x;\r\n\t\t\tthis.verticalOffset = padding.y;\r\n\t\t\t\r\n\t\t\tif (this.horizontalOffset != 0 || this.verticalOffset != 0)\r\n\t\t\t{\r\n\t\t\t\ts = new mxRectangle(s.x, s.y, s.width, s.height);\r\n\r\n\t\t\t\ts.x -= this.horizontalOffset / 2;\r\n\t\t\t\ts.width += this.horizontalOffset;\r\n\t\t\t\ts.y -= this.verticalOffset / 2;\r\n\t\t\t\ts.height += this.verticalOffset;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (this.sizers.length >= 8)\r\n\t\t\t{\r\n\t\t\t\tif ((s.width < 2 * this.sizers[0].bounds.width + 2 * tol) ||\r\n\t\t\t\t\t(s.height < 2 * this.sizers[0].bounds.height + 2 * tol))\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.sizers[0].node.style.display = 'none';\r\n\t\t\t\t\tthis.sizers[2].node.style.display = 'none';\r\n\t\t\t\t\tthis.sizers[5].node.style.display = 'none';\r\n\t\t\t\t\tthis.sizers[7].node.style.display = 'none';\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.sizers[0].node.style.display = '';\r\n\t\t\t\t\tthis.sizers[2].node.style.display = '';\r\n\t\t\t\t\tthis.sizers[5].node.style.display = '';\r\n\t\t\t\t\tthis.sizers[7].node.style.display = '';\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tvar r = s.x + s.width;\r\n\t\tvar b = s.y + s.height;\r\n\t\t\r\n\t\tif (this.singleSizer)\r\n\t\t{\r\n\t\t\tthis.moveSizerTo(this.sizers[0], r, b);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar cx = s.x + s.width / 2;\r\n\t\t\tvar cy = s.y + s.height / 2;\r\n\t\t\t\r\n\t\t\tif (this.sizers.length >= 8)\r\n\t\t\t{\r\n\t\t\t\tvar crs = ['nw-resize', 'n-resize', 'ne-resize', 'e-resize', 'se-resize', 's-resize', 'sw-resize', 'w-resize'];\r\n\t\t\t\t\r\n\t\t\t\tvar alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0');\r\n\t\t\t\tvar cos = Math.cos(alpha);\r\n\t\t\t\tvar sin = Math.sin(alpha);\r\n\t\t\t\t\r\n\t\t\t\tvar da = Math.round(alpha * 4 / Math.PI);\r\n\t\t\t\t\r\n\t\t\t\tvar ct = new mxPoint(s.getCenterX(), s.getCenterY());\r\n\t\t\t\tvar pt = mxUtils.getRotatedPoint(new mxPoint(s.x, s.y), cos, sin, ct);\r\n\t\t\t\t\r\n\t\t\t\tthis.moveSizerTo(this.sizers[0], pt.x, pt.y);\r\n\t\t\t\tthis.sizers[0].setCursor(crs[mxUtils.mod(0 + da, crs.length)]);\r\n\t\t\t\t\r\n\t\t\t\tpt.x = cx;\r\n\t\t\t\tpt.y = s.y;\r\n\t\t\t\tpt = mxUtils.getRotatedPoint(pt, cos, sin, ct);\r\n\t\t\t\t\r\n\t\t\t\tthis.moveSizerTo(this.sizers[1], pt.x, pt.y);\r\n\t\t\t\tthis.sizers[1].setCursor(crs[mxUtils.mod(1 + da, crs.length)]);\r\n\t\t\t\t\r\n\t\t\t\tpt.x = r;\r\n\t\t\t\tpt.y = s.y;\r\n\t\t\t\tpt = mxUtils.getRotatedPoint(pt, cos, sin, ct);\r\n\t\t\t\t\r\n\t\t\t\tthis.moveSizerTo(this.sizers[2], pt.x, pt.y);\r\n\t\t\t\tthis.sizers[2].setCursor(crs[mxUtils.mod(2 + da, crs.length)]);\r\n\t\t\t\t\r\n\t\t\t\tpt.x = s.x;\r\n\t\t\t\tpt.y = cy;\r\n\t\t\t\tpt = mxUtils.getRotatedPoint(pt, cos, sin, ct);\r\n\t\t\t\t\r\n\t\t\t\tthis.moveSizerTo(this.sizers[3], pt.x, pt.y);\r\n\t\t\t\tthis.sizers[3].setCursor(crs[mxUtils.mod(7 + da, crs.length)]);\r\n\r\n\t\t\t\tpt.x = r;\r\n\t\t\t\tpt.y = cy;\r\n\t\t\t\tpt = mxUtils.getRotatedPoint(pt, cos, sin, ct);\r\n\t\t\t\t\r\n\t\t\t\tthis.moveSizerTo(this.sizers[4], pt.x, pt.y);\r\n\t\t\t\tthis.sizers[4].setCursor(crs[mxUtils.mod(3 + da, crs.length)]);\r\n\r\n\t\t\t\tpt.x = s.x;\r\n\t\t\t\tpt.y = b;\r\n\t\t\t\tpt = mxUtils.getRotatedPoint(pt, cos, sin, ct);\r\n\t\t\t\t\r\n\t\t\t\tthis.moveSizerTo(this.sizers[5], pt.x, pt.y);\r\n\t\t\t\tthis.sizers[5].setCursor(crs[mxUtils.mod(6 + da, crs.length)]);\r\n\r\n\t\t\t\tpt.x = cx;\r\n\t\t\t\tpt.y = b;\r\n\t\t\t\tpt = mxUtils.getRotatedPoint(pt, cos, sin, ct);\r\n\t\t\t\t\r\n\t\t\t\tthis.moveSizerTo(this.sizers[6], pt.x, pt.y);\r\n\t\t\t\tthis.sizers[6].setCursor(crs[mxUtils.mod(5 + da, crs.length)]);\r\n\r\n\t\t\t\tpt.x = r;\r\n\t\t\t\tpt.y = b;\r\n\t\t\t\tpt = mxUtils.getRotatedPoint(pt, cos, sin, ct);\r\n\t\t\t\t\r\n\t\t\t\tthis.moveSizerTo(this.sizers[7], pt.x, pt.y);\r\n\t\t\t\tthis.sizers[7].setCursor(crs[mxUtils.mod(4 + da, crs.length)]);\r\n\t\t\t\t\r\n\t\t\t\tthis.moveSizerTo(this.sizers[8], cx + this.state.absoluteOffset.x, cy + this.state.absoluteOffset.y);\r\n\t\t\t}\r\n\t\t\telse if (this.state.width >= 2 && this.state.height >= 2)\r\n\t\t\t{\r\n\t\t\t\tthis.moveSizerTo(this.sizers[0], cx + this.state.absoluteOffset.x, cy + this.state.absoluteOffset.y);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.moveSizerTo(this.sizers[0], this.state.x, this.state.y);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tif (this.rotationShape != null)\r\n\t{\r\n\t\tvar alpha = mxUtils.toRadians((this.currentAlpha != null) ? this.currentAlpha : this.state.style[mxConstants.STYLE_ROTATION] || '0');\r\n\t\tvar cos = Math.cos(alpha);\r\n\t\tvar sin = Math.sin(alpha);\r\n\t\t\r\n\t\tvar ct = new mxPoint(this.state.getCenterX(), this.state.getCenterY());\r\n\t\tvar pt = mxUtils.getRotatedPoint(this.getRotationHandlePosition(), cos, sin, ct);\r\n\r\n\t\tif (this.rotationShape.node != null)\r\n\t\t{\r\n\t\t\tthis.moveSizerTo(this.rotationShape, pt.x, pt.y);\r\n\r\n\t\t\t// Hides rotation handle during text editing\r\n\t\t\tthis.rotationShape.node.style.visibility = (this.state.view.graph.isEditing()) ? 'hidden' : '';\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (this.selectionBorder != null)\r\n\t{\r\n\t\tthis.selectionBorder.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0');\r\n\t}\r\n\t\r\n\tif (this.edgeHandlers != null)\r\n\t{\t\t\r\n\t\tfor (var i = 0; i < this.edgeHandlers.length; i++)\r\n\t\t{\r\n\t\t\tthis.edgeHandlers[i].redraw();\r\n\t\t}\r\n\t}\r\n\r\n\tif (this.customHandles != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.customHandles.length; i++)\r\n\t\t{\r\n\t\t\tvar temp = this.customHandles[i].shape.node.style.display;\r\n\t\t\tthis.customHandles[i].redraw();\r\n\t\t\tthis.customHandles[i].shape.node.style.display = temp;\r\n\r\n\t\t\t// Hides custom handles during text editing\r\n\t\t\tthis.customHandles[i].shape.node.style.visibility = (this.graph.isEditing()) ? 'hidden' : '';\r\n\t\t}\r\n\t}\r\n\r\n\tthis.updateParentHighlight();\r\n};\r\n\r\n/**\r\n * Function: getRotationHandlePosition\r\n * \r\n * Returns an <mxPoint> that defines the rotation handle position.\r\n */\r\nmxVertexHandler.prototype.getRotationHandlePosition = function()\r\n{\r\n\treturn new mxPoint(this.bounds.x + this.bounds.width / 2, this.bounds.y + this.rotationHandleVSpacing)\r\n};\r\n\r\n/**\r\n * Function: updateParentHighlight\r\n * \r\n * Updates the highlight of the parent if <parentHighlightEnabled> is true.\r\n */\r\nmxVertexHandler.prototype.updateParentHighlight = function()\r\n{\r\n\t// If not destroyed\r\n\tif (this.selectionBorder != null)\r\n\t{\r\n\t\tif (this.parentHighlight != null)\r\n\t\t{\r\n\t\t\tvar parent = this.graph.model.getParent(this.state.cell);\r\n\t\r\n\t\t\tif (this.graph.model.isVertex(parent))\r\n\t\t\t{\r\n\t\t\t\tvar pstate = this.graph.view.getState(parent);\r\n\t\t\t\tvar b = this.parentHighlight.bounds;\r\n\t\t\t\t\r\n\t\t\t\tif (pstate != null && (b.x != pstate.x || b.y != pstate.y ||\r\n\t\t\t\t\tb.width != pstate.width || b.height != pstate.height))\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.parentHighlight.bounds = pstate;\r\n\t\t\t\t\tthis.parentHighlight.redraw();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.parentHighlight.destroy();\r\n\t\t\t\tthis.parentHighlight = null;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (this.parentHighlightEnabled)\r\n\t\t{\r\n\t\t\tvar parent = this.graph.model.getParent(this.state.cell);\r\n\t\t\t\r\n\t\t\tif (this.graph.model.isVertex(parent))\r\n\t\t\t{\r\n\t\t\t\tvar pstate = this.graph.view.getState(parent);\r\n\t\t\t\t\r\n\t\t\t\tif (pstate != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.parentHighlight = this.createParentHighlightShape(pstate);\r\n\t\t\t\t\t// VML dialect required here for event transparency in IE\r\n\t\t\t\t\tthis.parentHighlight.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;\r\n\t\t\t\t\tthis.parentHighlight.pointerEvents = false;\r\n\t\t\t\t\tthis.parentHighlight.rotation = Number(pstate.style[mxConstants.STYLE_ROTATION] || '0');\r\n\t\t\t\t\tthis.parentHighlight.init(this.graph.getView().getOverlayPane());\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: drawPreview\r\n * \r\n * Redraws the preview.\r\n */\r\nmxVertexHandler.prototype.drawPreview = function()\r\n{\r\n\tif (this.preview != null)\r\n\t{\r\n\t\tthis.preview.bounds = this.bounds;\r\n\t\t\r\n\t\tif (this.preview.node.parentNode == this.graph.container)\r\n\t\t{\r\n\t\t\tthis.preview.bounds.width = Math.max(0, this.preview.bounds.width - 1);\r\n\t\t\tthis.preview.bounds.height = Math.max(0, this.preview.bounds.height - 1);\r\n\t\t}\r\n\t\r\n\t\tthis.preview.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0');\r\n\t\tthis.preview.redraw();\r\n\t}\r\n\t\r\n\tthis.selectionBorder.bounds = this.bounds;\r\n\tthis.selectionBorder.redraw();\r\n\t\r\n\tif (this.parentHighlight != null)\r\n\t{\r\n\t\tthis.parentHighlight.redraw();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Destroys the handler and all its resources and DOM nodes.\r\n */\r\nmxVertexHandler.prototype.destroy = function()\r\n{\r\n\tif (this.escapeHandler != null)\r\n\t{\r\n\t\tthis.state.view.graph.removeListener(this.escapeHandler);\r\n\t\tthis.escapeHandler = null;\r\n\t}\r\n\t\r\n\tif (this.preview != null)\r\n\t{\r\n\t\tthis.preview.destroy();\r\n\t\tthis.preview = null;\r\n\t}\r\n\t\r\n\tif (this.parentHighlight != null)\r\n\t{\r\n\t\tthis.parentHighlight.destroy();\r\n\t\tthis.parentHighlight = null;\r\n\t}\r\n\t\r\n\tif (this.selectionBorder != null)\r\n\t{\r\n\t\tthis.selectionBorder.destroy();\r\n\t\tthis.selectionBorder = null;\r\n\t}\r\n\t\r\n\tthis.labelShape = null;\r\n\tthis.removeHint();\r\n\r\n\tif (this.sizers != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.sizers.length; i++)\r\n\t\t{\r\n\t\t\tthis.sizers[i].destroy();\r\n\t\t}\r\n\t\t\r\n\t\tthis.sizers = null;\r\n\t}\r\n\r\n\tif (this.customHandles != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.customHandles.length; i++)\r\n\t\t{\r\n\t\t\tthis.customHandles[i].destroy();\r\n\t\t}\r\n\t\t\r\n\t\tthis.customHandles = null;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxEdgeHandler\r\n *\r\n * Graph event handler that reconnects edges and modifies control points and\r\n * the edge label location. Uses <mxTerminalMarker> for finding and\r\n * highlighting new source and target vertices. This handler is automatically\r\n * created in <mxGraph.createHandler> for each selected edge.\r\n * \r\n * To enable adding/removing control points, the following code can be used:\r\n * \r\n * (code)\r\n * mxEdgeHandler.prototype.addEnabled = true;\r\n * mxEdgeHandler.prototype.removeEnabled = true;\r\n * (end)\r\n * \r\n * Note: This experimental feature is not recommended for production use.\r\n * \r\n * Constructor: mxEdgeHandler\r\n *\r\n * Constructs an edge handler for the specified <mxCellState>.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> of the cell to be handled.\r\n */\r\nfunction mxEdgeHandler(state)\r\n{\r\n\tif (state != null)\r\n\t{\r\n\t\tthis.state = state;\r\n\t\tthis.init();\r\n\t\t\r\n\t\t// Handles escape keystrokes\r\n\t\tthis.escapeHandler = mxUtils.bind(this, function(sender, evt)\r\n\t\t{\r\n\t\t\tvar dirty = this.index != null;\r\n\t\t\tthis.reset();\r\n\t\t\t\r\n\t\t\tif (dirty)\r\n\t\t\t{\r\n\t\t\t\tthis.graph.cellRenderer.redraw(this.state, false, state.view.isRendering());\r\n\t\t\t}\r\n\t\t});\r\n\t\t\r\n\t\tthis.state.view.graph.addListener(mxEvent.ESCAPE, this.escapeHandler);\r\n\t}\r\n};\r\n\r\n/**\r\n * Variable: graph\r\n * \r\n * Reference to the enclosing <mxGraph>.\r\n */\r\nmxEdgeHandler.prototype.graph = null;\r\n\r\n/**\r\n * Variable: state\r\n * \r\n * Reference to the <mxCellState> being modified.\r\n */\r\nmxEdgeHandler.prototype.state = null;\r\n\r\n/**\r\n * Variable: marker\r\n * \r\n * Holds the <mxTerminalMarker> which is used for highlighting terminals.\r\n */\r\nmxEdgeHandler.prototype.marker = null;\r\n\r\n/**\r\n * Variable: constraintHandler\r\n * \r\n * Holds the <mxConstraintHandler> used for drawing and highlighting\r\n * constraints.\r\n */\r\nmxEdgeHandler.prototype.constraintHandler = null;\r\n\r\n/**\r\n * Variable: error\r\n * \r\n * Holds the current validation error while a connection is being changed.\r\n */\r\nmxEdgeHandler.prototype.error = null;\r\n\r\n/**\r\n * Variable: shape\r\n * \r\n * Holds the <mxShape> that represents the preview edge.\r\n */\r\nmxEdgeHandler.prototype.shape = null;\r\n\r\n/**\r\n * Variable: bends\r\n * \r\n * Holds the <mxShapes> that represent the points.\r\n */\r\nmxEdgeHandler.prototype.bends = null;\r\n\r\n/**\r\n * Variable: labelShape\r\n * \r\n * Holds the <mxShape> that represents the label position.\r\n */\r\nmxEdgeHandler.prototype.labelShape = null;\r\n\r\n/**\r\n * Variable: cloneEnabled\r\n * \r\n * Specifies if cloning by control-drag is enabled. Default is true.\r\n */\r\nmxEdgeHandler.prototype.cloneEnabled = true;\r\n\r\n/**\r\n * Variable: addEnabled\r\n * \r\n * Specifies if adding bends by shift-click is enabled. Default is false.\r\n * Note: This experimental feature is not recommended for production use.\r\n */\r\nmxEdgeHandler.prototype.addEnabled = false;\r\n\r\n/**\r\n * Variable: removeEnabled\r\n * \r\n * Specifies if removing bends by shift-click is enabled. Default is false.\r\n * Note: This experimental feature is not recommended for production use.\r\n */\r\nmxEdgeHandler.prototype.removeEnabled = false;\r\n\r\n/**\r\n * Variable: dblClickRemoveEnabled\r\n * \r\n * Specifies if removing bends by double click is enabled. Default is false.\r\n */\r\nmxEdgeHandler.prototype.dblClickRemoveEnabled = false;\r\n\r\n/**\r\n * Variable: mergeRemoveEnabled\r\n * \r\n * Specifies if removing bends by dropping them on other bends is enabled.\r\n * Default is false.\r\n */\r\nmxEdgeHandler.prototype.mergeRemoveEnabled = false;\r\n\r\n/**\r\n * Variable: straightRemoveEnabled\r\n * \r\n * Specifies if removing bends by creating straight segments should be enabled.\r\n * If enabled, this can be overridden by holding down the alt key while moving.\r\n * Default is false.\r\n */\r\nmxEdgeHandler.prototype.straightRemoveEnabled = false;\r\n\r\n/**\r\n * Variable: virtualBendsEnabled\r\n * \r\n * Specifies if virtual bends should be added in the center of each\r\n * segments. These bends can then be used to add new waypoints.\r\n * Default is false.\r\n */\r\nmxEdgeHandler.prototype.virtualBendsEnabled = false;\r\n\r\n/**\r\n * Variable: virtualBendOpacity\r\n * \r\n * Opacity to be used for virtual bends (see <virtualBendsEnabled>).\r\n * Default is 20.\r\n */\r\nmxEdgeHandler.prototype.virtualBendOpacity = 20;\r\n\r\n/**\r\n * Variable: parentHighlightEnabled\r\n * \r\n * Specifies if the parent should be highlighted if a child cell is selected.\r\n * Default is false.\r\n */\r\nmxEdgeHandler.prototype.parentHighlightEnabled = false;\r\n\r\n/**\r\n * Variable: preferHtml\r\n * \r\n * Specifies if bends should be added to the graph container. This is updated\r\n * in <init> based on whether the edge or one of its terminals has an HTML\r\n * label in the container.\r\n */\r\nmxEdgeHandler.prototype.preferHtml = false;\r\n\r\n/**\r\n * Variable: allowHandleBoundsCheck\r\n * \r\n * Specifies if the bounds of handles should be used for hit-detection in IE\r\n * Default is true.\r\n */\r\nmxEdgeHandler.prototype.allowHandleBoundsCheck = true;\r\n\r\n/**\r\n * Variable: snapToTerminals\r\n * \r\n * Specifies if waypoints should snap to the routing centers of terminals.\r\n * Default is false.\r\n */\r\nmxEdgeHandler.prototype.snapToTerminals = false;\r\n\r\n/**\r\n * Variable: handleImage\r\n * \r\n * Optional <mxImage> to be used as handles. Default is null.\r\n */\r\nmxEdgeHandler.prototype.handleImage = null;\r\n\r\n/**\r\n * Variable: tolerance\r\n * \r\n * Optional tolerance for hit-detection in <getHandleForEvent>. Default is 0.\r\n */\r\nmxEdgeHandler.prototype.tolerance = 0;\r\n\r\n/**\r\n * Variable: outlineConnect\r\n * \r\n * Specifies if connections to the outline of a highlighted target should be\r\n * enabled. This will allow to place the connection point along the outline of\r\n * the highlighted target. Default is false.\r\n */\r\nmxEdgeHandler.prototype.outlineConnect = false;\r\n\r\n/**\r\n * Variable: manageLabelHandle\r\n * \r\n * Specifies if the label handle should be moved if it intersects with another\r\n * handle. Uses <checkLabelHandle> for checking and moving. Default is false.\r\n */\r\nmxEdgeHandler.prototype.manageLabelHandle = false;\r\n\r\n/**\r\n * Function: init\r\n * \r\n * Initializes the shapes required for this edge handler.\r\n */\r\nmxEdgeHandler.prototype.init = function()\r\n{\r\n\tthis.graph = this.state.view.graph;\r\n\tthis.marker = this.createMarker();\r\n\tthis.constraintHandler = new mxConstraintHandler(this.graph);\r\n\t\r\n\t// Clones the original points from the cell\r\n\t// and makes sure at least one point exists\r\n\tthis.points = [];\r\n\t\r\n\t// Uses the absolute points of the state\r\n\t// for the initial configuration and preview\r\n\tthis.abspoints = this.getSelectionPoints(this.state);\r\n\tthis.shape = this.createSelectionShape(this.abspoints);\r\n\tthis.shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?\r\n\t\tmxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;\r\n\tthis.shape.init(this.graph.getView().getOverlayPane());\r\n\tthis.shape.pointerEvents = false;\r\n\tthis.shape.setCursor(mxConstants.CURSOR_MOVABLE_EDGE);\r\n\tmxEvent.redirectMouseEvents(this.shape.node, this.graph, this.state);\r\n\r\n\t// Updates preferHtml\r\n\tthis.preferHtml = this.state.text != null &&\r\n\t\tthis.state.text.node.parentNode == this.graph.container;\r\n\t\r\n\tif (!this.preferHtml)\r\n\t{\r\n\t\t// Checks source terminal\r\n\t\tvar sourceState = this.state.getVisibleTerminalState(true);\r\n\t\t\r\n\t\tif (sourceState != null)\r\n\t\t{\r\n\t\t\tthis.preferHtml = sourceState.text != null &&\r\n\t\t\t\tsourceState.text.node.parentNode == this.graph.container;\r\n\t\t}\r\n\t\t\r\n\t\tif (!this.preferHtml)\r\n\t\t{\r\n\t\t\t// Checks target terminal\r\n\t\t\tvar targetState = this.state.getVisibleTerminalState(false);\r\n\t\t\t\r\n\t\t\tif (targetState != null)\r\n\t\t\t{\r\n\t\t\t\tthis.preferHtml = targetState.text != null &&\r\n\t\t\t\ttargetState.text.node.parentNode == this.graph.container;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\t// Adds highlight for parent group\r\n\tif (this.parentHighlightEnabled)\r\n\t{\r\n\t\tvar parent = this.graph.model.getParent(this.state.cell);\r\n\t\t\r\n\t\tif (this.graph.model.isVertex(parent))\r\n\t\t{\r\n\t\t\tvar pstate = this.graph.view.getState(parent);\r\n\t\t\t\r\n\t\t\tif (pstate != null)\r\n\t\t\t{\r\n\t\t\t\tthis.parentHighlight = this.createParentHighlightShape(pstate);\r\n\t\t\t\t// VML dialect required here for event transparency in IE\r\n\t\t\t\tthis.parentHighlight.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;\r\n\t\t\t\tthis.parentHighlight.pointerEvents = false;\r\n\t\t\t\tthis.parentHighlight.rotation = Number(pstate.style[mxConstants.STYLE_ROTATION] || '0');\r\n\t\t\t\tthis.parentHighlight.init(this.graph.getView().getOverlayPane());\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\t// Creates bends for the non-routed absolute points\r\n\t// or bends that don't correspond to points\r\n\tif (this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells ||\r\n\t\tmxGraphHandler.prototype.maxCells <= 0)\r\n\t{\r\n\t\tthis.bends = this.createBends();\r\n\r\n\t\tif (this.isVirtualBendsEnabled())\r\n\t\t{\r\n\t\t\tthis.virtualBends = this.createVirtualBends();\r\n\t\t}\r\n\t}\r\n\r\n\t// Adds a rectangular handle for the label position\r\n\tthis.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y);\r\n\tthis.labelShape = this.createLabelHandleShape();\r\n\tthis.initBend(this.labelShape);\r\n\tthis.labelShape.setCursor(mxConstants.CURSOR_LABEL_HANDLE);\r\n\t\r\n\tthis.customHandles = this.createCustomHandles();\r\n\t\r\n\tthis.redraw();\r\n};\r\n\r\n/**\r\n * Function: createCustomHandles\r\n * \r\n * Returns an array of custom handles. This implementation returns null.\r\n */\r\nmxEdgeHandler.prototype.createCustomHandles = function()\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: isVirtualBendsEnabled\r\n * \r\n * Returns true if virtual bends should be added. This returns true if\r\n * <virtualBendsEnabled> is true and the current style allows and\r\n * renders custom waypoints.\r\n */\r\nmxEdgeHandler.prototype.isVirtualBendsEnabled = function(evt)\r\n{\r\n\treturn this.virtualBendsEnabled && (this.state.style[mxConstants.STYLE_EDGE] == null ||\r\n\t\t\tthis.state.style[mxConstants.STYLE_EDGE] == mxConstants.NONE ||\r\n\t\t\tthis.state.style[mxConstants.STYLE_NOEDGESTYLE] == 1)  &&\r\n\t\t\tmxUtils.getValue(this.state.style, mxConstants.STYLE_SHAPE, null) != 'arrow';\r\n};\r\n\r\n/**\r\n * Function: isAddPointEvent\r\n * \r\n * Returns true if the given event is a trigger to add a new point. This\r\n * implementation returns true if shift is pressed.\r\n */\r\nmxEdgeHandler.prototype.isAddPointEvent = function(evt)\r\n{\r\n\treturn mxEvent.isShiftDown(evt);\r\n};\r\n\r\n/**\r\n * Function: isRemovePointEvent\r\n * \r\n * Returns true if the given event is a trigger to remove a point. This\r\n * implementation returns true if shift is pressed.\r\n */\r\nmxEdgeHandler.prototype.isRemovePointEvent = function(evt)\r\n{\r\n\treturn mxEvent.isShiftDown(evt);\r\n};\r\n\r\n/**\r\n * Function: getSelectionPoints\r\n * \r\n * Returns the list of points that defines the selection stroke.\r\n */\r\nmxEdgeHandler.prototype.getSelectionPoints = function(state)\r\n{\r\n\treturn state.absolutePoints;\r\n};\r\n\r\n/**\r\n * Function: createSelectionShape\r\n * \r\n * Creates the shape used to draw the selection border.\r\n */\r\nmxEdgeHandler.prototype.createParentHighlightShape = function(bounds)\r\n{\r\n\tvar shape = new mxRectangleShape(bounds, null, this.getSelectionColor());\r\n\tshape.strokewidth = this.getSelectionStrokeWidth();\r\n\tshape.isDashed = this.isSelectionDashed();\r\n\t\r\n\treturn shape;\r\n};\r\n\r\n/**\r\n * Function: createSelectionShape\r\n * \r\n * Creates the shape used to draw the selection border.\r\n */\r\nmxEdgeHandler.prototype.createSelectionShape = function(points)\r\n{\r\n\tvar shape = new this.state.shape.constructor();\r\n\tshape.outline = true;\r\n\tshape.apply(this.state);\r\n\t\r\n\tshape.isDashed = this.isSelectionDashed();\r\n\tshape.stroke = this.getSelectionColor();\r\n\tshape.isShadow = false;\r\n\t\r\n\treturn shape;\r\n};\r\n\r\n/**\r\n * Function: getSelectionColor\r\n * \r\n * Returns <mxConstants.EDGE_SELECTION_COLOR>.\r\n */\r\nmxEdgeHandler.prototype.getSelectionColor = function()\r\n{\r\n\treturn mxConstants.EDGE_SELECTION_COLOR;\r\n};\r\n\r\n/**\r\n * Function: getSelectionStrokeWidth\r\n * \r\n * Returns <mxConstants.EDGE_SELECTION_STROKEWIDTH>.\r\n */\r\nmxEdgeHandler.prototype.getSelectionStrokeWidth = function()\r\n{\r\n\treturn mxConstants.EDGE_SELECTION_STROKEWIDTH;\r\n};\r\n\r\n/**\r\n * Function: isSelectionDashed\r\n * \r\n * Returns <mxConstants.EDGE_SELECTION_DASHED>.\r\n */\r\nmxEdgeHandler.prototype.isSelectionDashed = function()\r\n{\r\n\treturn mxConstants.EDGE_SELECTION_DASHED;\r\n};\r\n\r\n/**\r\n * Function: isConnectableCell\r\n * \r\n * Returns true if the given cell is connectable. This is a hook to\r\n * disable floating connections. This implementation returns true.\r\n */\r\nmxEdgeHandler.prototype.isConnectableCell = function(cell)\r\n{\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: getCellAt\r\n * \r\n * Creates and returns the <mxCellMarker> used in <marker>.\r\n */\r\nmxEdgeHandler.prototype.getCellAt = function(x, y)\r\n{\r\n\treturn (!this.outlineConnect) ? this.graph.getCellAt(x, y) : null;\r\n};\r\n\r\n/**\r\n * Function: createMarker\r\n * \r\n * Creates and returns the <mxCellMarker> used in <marker>.\r\n */\r\nmxEdgeHandler.prototype.createMarker = function()\r\n{\r\n\tvar marker = new mxCellMarker(this.graph);\r\n\tvar self = this; // closure\r\n\r\n\t// Only returns edges if they are connectable and never returns\r\n\t// the edge that is currently being modified\r\n\tmarker.getCell = function(me)\r\n\t{\r\n\t\tvar cell = mxCellMarker.prototype.getCell.apply(this, arguments);\r\n\r\n\t\t// Checks for cell at preview point (with grid)\r\n\t\tif ((cell == self.state.cell || cell == null) && self.currentPoint != null)\r\n\t\t{\r\n\t\t\tcell = self.graph.getCellAt(self.currentPoint.x, self.currentPoint.y);\r\n\t\t}\r\n\t\t\r\n\t\t// Uses connectable parent vertex if one exists\r\n\t\tif (cell != null && !this.graph.isCellConnectable(cell))\r\n\t\t{\r\n\t\t\tvar parent = this.graph.getModel().getParent(cell);\r\n\t\t\t\r\n\t\t\tif (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent))\r\n\t\t\t{\r\n\t\t\t\tcell = parent;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tvar model = self.graph.getModel();\r\n\t\t\r\n\t\tif ((this.graph.isSwimlane(cell) && self.currentPoint != null &&\r\n\t\t\tthis.graph.hitsSwimlaneContent(cell, self.currentPoint.x, self.currentPoint.y)) ||\r\n\t\t\t(!self.isConnectableCell(cell)) || (cell == self.state.cell ||\r\n\t\t\t(cell != null && !self.graph.connectableEdges && model.isEdge(cell))) ||\r\n\t\t\tmodel.isAncestor(self.state.cell, cell))\r\n\t\t{\r\n\t\t\tcell = null;\r\n\t\t}\r\n\t\t\r\n\t\tif (!this.graph.isCellConnectable(cell))\r\n\t\t{\r\n\t\t\tcell = null;\r\n\t\t}\r\n\t\t\r\n\t\treturn cell;\r\n\t};\r\n\r\n\t// Sets the highlight color according to validateConnection\r\n\tmarker.isValidState = function(state)\r\n\t{\r\n\t\tvar model = self.graph.getModel();\r\n\t\tvar other = self.graph.view.getTerminalPort(state,\r\n\t\t\tself.graph.view.getState(model.getTerminal(self.state.cell,\r\n\t\t\t!self.isSource)), !self.isSource);\r\n\t\tvar otherCell = (other != null) ? other.cell : null;\r\n\t\tvar source = (self.isSource) ? state.cell : otherCell;\r\n\t\tvar target = (self.isSource) ? otherCell : state.cell;\r\n\t\t\r\n\t\t// Updates the error message of the handler\r\n\t\tself.error = self.validateConnection(source, target);\r\n\r\n\t\treturn self.error == null;\r\n\t};\r\n\t\r\n\treturn marker;\r\n};\r\n\r\n/**\r\n * Function: validateConnection\r\n * \r\n * Returns the error message or an empty string if the connection for the\r\n * given source, target pair is not valid. Otherwise it returns null. This\r\n * implementation uses <mxGraph.getEdgeValidationError>.\r\n * \r\n * Parameters:\r\n * \r\n * source - <mxCell> that represents the source terminal.\r\n * target - <mxCell> that represents the target terminal.\r\n */\r\nmxEdgeHandler.prototype.validateConnection = function(source, target)\r\n{\r\n\treturn this.graph.getEdgeValidationError(this.state.cell, source, target);\r\n};\r\n\r\n/**\r\n * Function: createBends\r\n * \r\n * Creates and returns the bends used for modifying the edge. This is\r\n * typically an array of <mxRectangleShapes>.\r\n */\r\n mxEdgeHandler.prototype.createBends = function()\r\n {\r\n\tvar cell = this.state.cell;\r\n\tvar bends = [];\r\n\r\n\tfor (var i = 0; i < this.abspoints.length; i++)\r\n\t{\r\n\t\tif (this.isHandleVisible(i))\r\n\t\t{\r\n\t\t\tvar source = i == 0;\r\n\t\t\tvar target = i == this.abspoints.length - 1;\r\n\t\t\tvar terminal = source || target;\r\n\r\n\t\t\tif (terminal || this.graph.isCellBendable(cell))\r\n\t\t\t{\r\n\t\t\t\t(mxUtils.bind(this, function(index)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar bend = this.createHandleShape(index);\r\n\t\t\t\t\tthis.initBend(bend, mxUtils.bind(this, mxUtils.bind(this, function()\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (this.dblClickRemoveEnabled)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tthis.removePoint(this.state, index);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t})));\r\n\t\r\n\t\t\t\t\tif (this.isHandleEnabled(i))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tbend.setCursor((terminal) ? mxConstants.CURSOR_TERMINAL_HANDLE : mxConstants.CURSOR_BEND_HANDLE);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tbends.push(bend);\r\n\t\t\t\t\r\n\t\t\t\t\tif (!terminal)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.points.push(new mxPoint(0,0));\r\n\t\t\t\t\t\tbend.node.style.visibility = 'hidden';\r\n\t\t\t\t\t}\r\n\t\t\t\t}))(i);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn bends;\r\n};\r\n\r\n/**\r\n * Function: createVirtualBends\r\n * \r\n * Creates and returns the bends used for modifying the edge. This is\r\n * typically an array of <mxRectangleShapes>.\r\n */\r\n mxEdgeHandler.prototype.createVirtualBends = function()\r\n {\r\n\tvar cell = this.state.cell;\r\n\tvar last = this.abspoints[0];\r\n\tvar bends = [];\r\n\r\n\tif (this.graph.isCellBendable(cell))\r\n\t{\r\n\t\tfor (var i = 1; i < this.abspoints.length; i++)\r\n\t\t{\r\n\t\t\t(mxUtils.bind(this, function(bend)\r\n\t\t\t{\r\n\t\t\t\tthis.initBend(bend);\r\n\t\t\t\tbend.setCursor(mxConstants.CURSOR_VIRTUAL_BEND_HANDLE);\r\n\t\t\t\tbends.push(bend);\r\n\t\t\t}))(this.createHandleShape());\r\n\t\t}\r\n\t}\r\n\r\n\treturn bends;\r\n};\r\n\r\n/**\r\n * Function: isHandleEnabled\r\n * \r\n * Creates the shape used to display the given bend.\r\n */\r\nmxEdgeHandler.prototype.isHandleEnabled = function(index)\r\n{\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: isHandleVisible\r\n * \r\n * Returns true if the handle at the given index is visible.\r\n */\r\nmxEdgeHandler.prototype.isHandleVisible = function(index)\r\n{\r\n\tvar source = this.state.getVisibleTerminalState(true);\r\n\tvar target = this.state.getVisibleTerminalState(false);\r\n\tvar geo = this.graph.getCellGeometry(this.state.cell);\r\n\tvar edgeStyle = (geo != null) ? this.graph.view.getEdgeStyle(this.state, geo.points, source, target) : null;\r\n\r\n\treturn edgeStyle != mxEdgeStyle.EntityRelation || index == 0 || index == this.abspoints.length - 1;\r\n};\r\n\r\n/**\r\n * Function: createHandleShape\r\n * \r\n * Creates the shape used to display the given bend. Note that the index may be\r\n * null for special cases, such as when called from\r\n * <mxElbowEdgeHandler.createVirtualBend>. Only images and rectangles should be\r\n * returned if support for HTML labels with not foreign objects is required.\r\n * Index if null for virtual handles.\r\n */\r\nmxEdgeHandler.prototype.createHandleShape = function(index)\r\n{\r\n\tif (this.handleImage != null)\r\n\t{\r\n\t\tvar shape = new mxImageShape(new mxRectangle(0, 0, this.handleImage.width, this.handleImage.height), this.handleImage.src);\r\n\t\t\r\n\t\t// Allows HTML rendering of the images\r\n\t\tshape.preserveImageAspect = false;\r\n\r\n\t\treturn shape;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar s = mxConstants.HANDLE_SIZE;\r\n\t\t\r\n\t\tif (this.preferHtml)\r\n\t\t{\r\n\t\t\ts -= 1;\r\n\t\t}\r\n\t\t\r\n\t\treturn new mxRectangleShape(new mxRectangle(0, 0, s, s), mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createLabelHandleShape\r\n * \r\n * Creates the shape used to display the the label handle.\r\n */\r\nmxEdgeHandler.prototype.createLabelHandleShape = function()\r\n{\r\n\tif (this.labelHandleImage != null)\r\n\t{\r\n\t\tvar shape = new mxImageShape(new mxRectangle(0, 0, this.labelHandleImage.width, this.labelHandleImage.height), this.labelHandleImage.src);\r\n\t\t\r\n\t\t// Allows HTML rendering of the images\r\n\t\tshape.preserveImageAspect = false;\r\n\r\n\t\treturn shape;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar s = mxConstants.LABEL_HANDLE_SIZE;\r\n\t\treturn new mxRectangleShape(new mxRectangle(0, 0, s, s), mxConstants.LABEL_HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: initBend\r\n * \r\n * Helper method to initialize the given bend.\r\n * \r\n * Parameters:\r\n * \r\n * bend - <mxShape> that represents the bend to be initialized.\r\n */\r\nmxEdgeHandler.prototype.initBend = function(bend, dblClick)\r\n{\r\n\tif (this.preferHtml)\r\n\t{\r\n\t\tbend.dialect = mxConstants.DIALECT_STRICTHTML;\r\n\t\tbend.init(this.graph.container);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tbend.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?\r\n\t\t\tmxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;\r\n\t\tbend.init(this.graph.getView().getOverlayPane());\r\n\t}\r\n\r\n\tmxEvent.redirectMouseEvents(bend.node, this.graph, this.state,\r\n\t\t\tnull, null, null, dblClick);\r\n\t\r\n\t// Fixes lost event tracking for images in quirks / IE8 standards\r\n\tif (mxClient.IS_QUIRKS || document.documentMode == 8)\r\n\t{\r\n\t\tmxEvent.addListener(bend.node, 'dragstart', function(evt)\r\n\t\t{\r\n\t\t\tmxEvent.consume(evt);\r\n\t\t\t\r\n\t\t\treturn false;\r\n\t\t});\r\n\t}\r\n\t\r\n\tif (mxClient.IS_TOUCH)\r\n\t{\r\n\t\tbend.node.setAttribute('pointer-events', 'none');\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getHandleForEvent\r\n * \r\n * Returns the index of the handle for the given event.\r\n */\r\nmxEdgeHandler.prototype.getHandleForEvent = function(me)\r\n{\r\n\t// Connection highlight may consume events before they reach sizer handle\r\n\tvar tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.tolerance : 1;\r\n\tvar hit = (this.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)) ?\r\n\t\tnew mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null;\r\n\tvar minDistSq = null;\r\n\tvar result = null;\r\n\r\n\tfunction checkShape(shape)\r\n\t{\r\n\t\tif (shape != null && shape.node.style.display != 'none' && shape.node.style.visibility != 'hidden' &&\r\n\t\t\t(me.isSource(shape) || (hit != null && mxUtils.intersects(shape.bounds, hit))))\r\n\t\t{\r\n\t\t\tvar dx = me.getGraphX() - shape.bounds.getCenterX();\r\n\t\t\tvar dy = me.getGraphY() - shape.bounds.getCenterY();\r\n\t\t\tvar tmp = dx * dx + dy * dy;\r\n\t\t\t\r\n\t\t\tif (minDistSq == null || tmp <= minDistSq)\r\n\t\t\t{\r\n\t\t\t\tminDistSq = tmp;\r\n\t\t\t\r\n\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn false;\r\n\t}\r\n\t\r\n\tif (this.customHandles != null && this.isCustomHandleEvent(me))\r\n\t{\r\n\t\t// Inverse loop order to match display order\r\n\t\tfor (var i = this.customHandles.length - 1; i >= 0; i--)\r\n\t\t{\r\n\t\t\tif (checkShape(this.customHandles[i].shape))\r\n\t\t\t{\r\n\t\t\t\t// LATER: Return reference to active shape\r\n\t\t\t\treturn mxEvent.CUSTOM_HANDLE - i;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tif (me.isSource(this.state.text) || checkShape(this.labelShape))\r\n\t{\r\n\t\tresult = mxEvent.LABEL_HANDLE;\r\n\t}\r\n\t\r\n\tif (this.bends != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.bends.length; i++)\r\n\t\t{\r\n\t\t\tif (checkShape(this.bends[i]))\r\n\t\t\t{\r\n\t\t\t\tresult = i;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (this.virtualBends != null && this.isAddVirtualBendEvent(me))\r\n\t{\r\n\t\tfor (var i = 0; i < this.virtualBends.length; i++)\r\n\t\t{\r\n\t\t\tif (checkShape(this.virtualBends[i]))\r\n\t\t\t{\r\n\t\t\t\tresult = mxEvent.VIRTUAL_HANDLE - i;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: isAddVirtualBendEvent\r\n * \r\n * Returns true if the given event allows virtual bends to be added. This\r\n * implementation returns true.\r\n */\r\nmxEdgeHandler.prototype.isAddVirtualBendEvent = function(me)\r\n{\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: isCustomHandleEvent\r\n * \r\n * Returns true if the given event allows custom handles to be changed. This\r\n * implementation returns true.\r\n */\r\nmxEdgeHandler.prototype.isCustomHandleEvent = function(me)\r\n{\r\n\treturn true;\r\n};\r\n\r\n/**\r\n * Function: mouseDown\r\n * \r\n * Handles the event by checking if a special element of the handler\r\n * was clicked, in which case the index parameter is non-null. The\r\n * indices may be one of <LABEL_HANDLE> or the number of the respective\r\n * control point. The source and target points are used for reconnecting\r\n * the edge.\r\n */\r\nmxEdgeHandler.prototype.mouseDown = function(sender, me)\r\n{\r\n\tvar handle = this.getHandleForEvent(me);\r\n\t\r\n\tif (this.bends != null && this.bends[handle] != null)\r\n\t{\r\n\t\tvar b = this.bends[handle].bounds;\r\n\t\tthis.snapPoint = new mxPoint(b.getCenterX(), b.getCenterY());\r\n\t}\r\n\t\r\n\tif (this.addEnabled && handle == null && this.isAddPointEvent(me.getEvent()))\r\n\t{\r\n\t\tthis.addPoint(this.state, me.getEvent());\r\n\t\tme.consume();\r\n\t}\r\n\telse if (handle != null && !me.isConsumed() && this.graph.isEnabled())\r\n\t{\r\n\t\tif (this.removeEnabled && this.isRemovePointEvent(me.getEvent()))\r\n\t\t{\r\n\t\t\tthis.removePoint(this.state, handle);\r\n\t\t}\r\n\t\telse if (handle != mxEvent.LABEL_HANDLE || this.graph.isLabelMovable(me.getCell()))\r\n\t\t{\r\n\t\t\tif (handle <= mxEvent.VIRTUAL_HANDLE)\r\n\t\t\t{\r\n\t\t\t\tmxUtils.setOpacity(this.virtualBends[mxEvent.VIRTUAL_HANDLE - handle].node, 100);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.start(me.getX(), me.getY(), handle);\r\n\t\t}\r\n\t\t\r\n\t\tme.consume();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: start\r\n * \r\n * Starts the handling of the mouse gesture.\r\n */\r\nmxEdgeHandler.prototype.start = function(x, y, index)\r\n{\r\n\tthis.startX = x;\r\n\tthis.startY = y;\r\n\r\n\tthis.isSource = (this.bends == null) ? false : index == 0;\r\n\tthis.isTarget = (this.bends == null) ? false : index == this.bends.length - 1;\r\n\tthis.isLabel = index == mxEvent.LABEL_HANDLE;\r\n\r\n\tif (this.isSource || this.isTarget)\r\n\t{\r\n\t\tvar cell = this.state.cell;\r\n\t\tvar terminal = this.graph.model.getTerminal(cell, this.isSource);\r\n\r\n\t\tif ((terminal == null && this.graph.isTerminalPointMovable(cell, this.isSource)) ||\r\n\t\t\t(terminal != null && this.graph.isCellDisconnectable(cell, terminal, this.isSource)))\r\n\t\t{\r\n\t\t\tthis.index = index;\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.index = index;\r\n\t}\r\n\t\r\n\t// Hides other custom handles\r\n\tif (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE)\r\n\t{\r\n\t\tif (this.customHandles != null)\r\n\t\t{\r\n\t\t\tfor (var i = 0; i < this.customHandles.length; i++)\r\n\t\t\t{\r\n\t\t\t\tif (i != mxEvent.CUSTOM_HANDLE - this.index)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.customHandles[i].setVisible(false);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: clonePreviewState\r\n * \r\n * Returns a clone of the current preview state for the given point and terminal.\r\n */\r\nmxEdgeHandler.prototype.clonePreviewState = function(point, terminal)\r\n{\r\n\treturn this.state.clone();\r\n};\r\n\r\n/**\r\n * Function: getSnapToTerminalTolerance\r\n * \r\n * Returns the tolerance for the guides. Default value is\r\n * gridSize * scale / 2.\r\n */\r\nmxEdgeHandler.prototype.getSnapToTerminalTolerance = function()\r\n{\r\n\treturn this.graph.gridSize * this.graph.view.scale / 2;\r\n};\r\n\r\n/**\r\n * Function: updateHint\r\n * \r\n * Hook for subclassers do show details while the handler is active.\r\n */\r\nmxEdgeHandler.prototype.updateHint = function(me, point) { };\r\n\r\n/**\r\n * Function: removeHint\r\n * \r\n * Hooks for subclassers to hide details when the handler gets inactive.\r\n */\r\nmxEdgeHandler.prototype.removeHint = function() { };\r\n\r\n/**\r\n * Function: roundLength\r\n * \r\n * Hook for rounding the unscaled width or height. This uses Math.round.\r\n */\r\nmxEdgeHandler.prototype.roundLength = function(length)\r\n{\r\n\treturn Math.round(length);\r\n};\r\n\r\n/**\r\n * Function: isSnapToTerminalsEvent\r\n * \r\n * Returns true if <snapToTerminals> is true and if alt is not pressed.\r\n */\r\nmxEdgeHandler.prototype.isSnapToTerminalsEvent = function(me)\r\n{\r\n\treturn this.snapToTerminals && !mxEvent.isAltDown(me.getEvent());\r\n};\r\n\r\n/**\r\n * Function: getPointForEvent\r\n * \r\n * Returns the point for the given event.\r\n */\r\nmxEdgeHandler.prototype.getPointForEvent = function(me)\r\n{\r\n\tvar view = this.graph.getView();\r\n\tvar scale = view.scale;\r\n\tvar point = new mxPoint(this.roundLength(me.getGraphX() / scale) * scale,\r\n\t\tthis.roundLength(me.getGraphY() / scale) * scale);\r\n\t\r\n\tvar tt = this.getSnapToTerminalTolerance();\r\n\tvar overrideX = false;\r\n\tvar overrideY = false;\t\t\r\n\t\r\n\tif (tt > 0 && this.isSnapToTerminalsEvent(me))\r\n\t{\r\n\t\tfunction snapToPoint(pt)\r\n\t\t{\r\n\t\t\tif (pt != null)\r\n\t\t\t{\r\n\t\t\t\tvar x = pt.x;\r\n\r\n\t\t\t\tif (Math.abs(point.x - x) < tt)\r\n\t\t\t\t{\r\n\t\t\t\t\tpoint.x = x;\r\n\t\t\t\t\toverrideX = true;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tvar y = pt.y;\r\n\r\n\t\t\t\tif (Math.abs(point.y - y) < tt)\r\n\t\t\t\t{\r\n\t\t\t\t\tpoint.y = y;\r\n\t\t\t\t\toverrideY = true;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Temporary function\r\n\t\tfunction snapToTerminal(terminal)\r\n\t\t{\r\n\t\t\tif (terminal != null)\r\n\t\t\t{\r\n\t\t\t\tsnapToPoint.call(this, new mxPoint(view.getRoutingCenterX(terminal),\r\n\t\t\t\t\t\tview.getRoutingCenterY(terminal)));\r\n\t\t\t}\r\n\t\t};\r\n\r\n\t\tsnapToTerminal.call(this, this.state.getVisibleTerminalState(true));\r\n\t\tsnapToTerminal.call(this, this.state.getVisibleTerminalState(false));\r\n\r\n\t\tif (this.state.absolutePoints != null)\r\n\t\t{\r\n\t\t\tfor (var i = 0; i < this.state.absolutePoints.length; i++)\r\n\t\t\t{\r\n\t\t\t\tsnapToPoint.call(this, this.state.absolutePoints[i]);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tif (this.graph.isGridEnabledEvent(me.getEvent()))\r\n\t{\r\n\t\tvar tr = view.translate;\r\n\t\t\r\n\t\tif (!overrideX)\r\n\t\t{\r\n\t\t\tpoint.x = (this.graph.snap(point.x / scale - tr.x) + tr.x) * scale;\r\n\t\t}\r\n\t\t\r\n\t\tif (!overrideY)\r\n\t\t{\r\n\t\t\tpoint.y = (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale;\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn point;\r\n};\r\n\r\n/**\r\n * Function: getPreviewTerminalState\r\n * \r\n * Updates the given preview state taking into account the state of the constraint handler.\r\n */\r\nmxEdgeHandler.prototype.getPreviewTerminalState = function(me)\r\n{\r\n\tthis.constraintHandler.update(me, this.isSource, true, me.isSource(this.marker.highlight.shape) ? null : this.currentPoint);\r\n\t\r\n\tif (this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null)\r\n\t{\r\n\t\t// Handles special case where grid is large and connection point is at actual point in which\r\n\t\t// case the outline is not followed as long as we're < gridSize / 2 away from that point\r\n\t\tif (this.marker.highlight != null && this.marker.highlight.state != null &&\r\n\t\t\tthis.marker.highlight.state.cell == this.constraintHandler.currentFocus.cell)\r\n\t\t{\r\n\t\t\t// Direct repaint needed if cell already highlighted\r\n\t\t\tif (this.marker.highlight.shape.stroke != 'transparent')\r\n\t\t\t{\r\n\t\t\t\tthis.marker.highlight.shape.stroke = 'transparent';\r\n\t\t\t\tthis.marker.highlight.repaint();\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.marker.markCell(this.constraintHandler.currentFocus.cell, 'transparent');\r\n\t\t}\r\n\t\t\r\n\t\tvar model = this.graph.getModel();\r\n\t\tvar other = this.graph.view.getTerminalPort(this.state,\r\n\t\t\t\tthis.graph.view.getState(model.getTerminal(this.state.cell,\r\n\t\t\t!this.isSource)), !this.isSource);\r\n\t\tvar otherCell = (other != null) ? other.cell : null;\r\n\t\tvar source = (this.isSource) ? this.constraintHandler.currentFocus.cell : otherCell;\r\n\t\tvar target = (this.isSource) ? otherCell : this.constraintHandler.currentFocus.cell;\r\n\t\t\r\n\t\t// Updates the error message of the handler\r\n\t\tthis.error = this.validateConnection(source, target);\r\n\t\tvar result = null;\r\n\t\t\r\n\t\tif (this.error == null)\r\n\t\t{\r\n\t\t\tresult = this.constraintHandler.currentFocus;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.constraintHandler.reset();\r\n\t\t}\r\n\t\t\r\n\t\treturn result;\r\n\t}\r\n\telse if (!this.graph.isIgnoreTerminalEvent(me.getEvent()))\r\n\t{\r\n\t\tthis.marker.process(me);\r\n\t\tvar state = this.marker.getValidState();\r\n\t\t\r\n\t\tif (state != null && this.graph.isCellLocked(state.cell))\r\n\t\t{\r\n\t\t\tthis.marker.reset();\r\n\t\t}\r\n\t\t\r\n\t\treturn this.marker.getValidState();\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.marker.reset();\r\n\t\t\r\n\t\treturn null;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getPreviewPoints\r\n * \r\n * Updates the given preview state taking into account the state of the constraint handler.\r\n * \r\n * Parameters:\r\n * \r\n * pt - <mxPoint> that contains the current pointer position.\r\n * me - Optional <mxMouseEvent> that contains the current event.\r\n */\r\nmxEdgeHandler.prototype.getPreviewPoints = function(pt, me)\r\n{\r\n\tvar geometry = this.graph.getCellGeometry(this.state.cell);\r\n\tvar points = (geometry.points != null) ? geometry.points.slice() : null;\r\n\tvar point = new mxPoint(pt.x, pt.y);\r\n\tvar result = null;\r\n\t\r\n\tif (!this.isSource && !this.isTarget)\r\n\t{\r\n\t\tthis.convertPoint(point, false);\r\n\t\t\r\n\t\tif (points == null)\r\n\t\t{\r\n\t\t\tpoints = [point];\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Adds point from virtual bend\r\n\t\t\tif (this.index <= mxEvent.VIRTUAL_HANDLE)\r\n\t\t\t{\r\n\t\t\t\tpoints.splice(mxEvent.VIRTUAL_HANDLE - this.index, 0, point);\r\n\t\t\t}\r\n\r\n\t\t\t// Removes point if dragged on terminal point\r\n\t\t\tif (!this.isSource && !this.isTarget)\r\n\t\t\t{\r\n\t\t\t\tfor (var i = 0; i < this.bends.length; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (i != this.index)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar bend = this.bends[i];\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (bend != null && mxUtils.contains(bend.bounds, pt.x, pt.y))\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tif (this.index <= mxEvent.VIRTUAL_HANDLE)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tpoints.splice(mxEvent.VIRTUAL_HANDLE - this.index, 1);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tpoints.splice(this.index - 1, 1);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tresult = points;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Removes point if user tries to straighten a segment\r\n\t\t\t\tif (result == null && this.straightRemoveEnabled && (me == null || !mxEvent.isAltDown(me.getEvent())))\r\n\t\t\t\t{\r\n\t\t\t\t\tvar tol = this.graph.tolerance * this.graph.tolerance;\r\n\t\t\t\t\tvar abs = this.state.absolutePoints.slice();\r\n\t\t\t\t\tabs[this.index] = pt;\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Handes special case where removing waypoint affects tolerance (flickering)\r\n\t\t\t\t\tvar src = this.state.getVisibleTerminalState(true);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (src != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar c = this.graph.getConnectionConstraint(this.state, src, true);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t// Checks if point is not fixed\r\n\t\t\t\t\t\tif (c == null || this.graph.getConnectionPoint(src, c) == null)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tabs[0] = new mxPoint(src.view.getRoutingCenterX(src), src.view.getRoutingCenterY(src));\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tvar trg = this.state.getVisibleTerminalState(false);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (trg != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar c = this.graph.getConnectionConstraint(this.state, trg, false);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t// Checks if point is not fixed\r\n\t\t\t\t\t\tif (c == null || this.graph.getConnectionPoint(trg, c) == null)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tabs[abs.length - 1] = new mxPoint(trg.view.getRoutingCenterX(trg), trg.view.getRoutingCenterY(trg));\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tfunction checkRemove(idx, tmp)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (idx > 0 && idx < abs.length - 1 &&\r\n\t\t\t\t\t\t\tmxUtils.ptSegDistSq(abs[idx - 1].x, abs[idx - 1].y,\r\n\t\t\t\t\t\t\t\tabs[idx + 1].x, abs[idx + 1].y, tmp.x, tmp.y) < tol)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tpoints.splice(idx - 1, 1);\r\n\t\t\t\t\t\t\tresult = points;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t};\r\n\t\t\t\t\t\r\n\t\t\t\t\t// LATER: Check if other points can be removed if a segment is made straight\r\n\t\t\t\t\tcheckRemove(this.index, pt);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Updates existing point\r\n\t\t\tif (result == null && this.index > mxEvent.VIRTUAL_HANDLE)\r\n\t\t\t{\r\n\t\t\t\tpoints[this.index - 1] = point;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\telse if (this.graph.resetEdgesOnConnect)\r\n\t{\r\n\t\tpoints = null;\r\n\t}\r\n\t\r\n\treturn (result != null) ? result : points;\r\n};\r\n\r\n/**\r\n * Function: isOutlineConnectEvent\r\n * \r\n * Returns true if <outlineConnect> is true and the source of the event is the outline shape\r\n * or shift is pressed.\r\n */\r\nmxEdgeHandler.prototype.isOutlineConnectEvent = function(me)\r\n{\r\n\tvar offset = mxUtils.getOffset(this.graph.container);\r\n\tvar evt = me.getEvent();\r\n\t\r\n\tvar clientX = mxEvent.getClientX(evt);\r\n\tvar clientY = mxEvent.getClientY(evt);\r\n\t\r\n\tvar doc = document.documentElement;\r\n\tvar left = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);\r\n\tvar top = (window.pageYOffset || doc.scrollTop)  - (doc.clientTop || 0);\r\n\t\r\n\tvar gridX = this.currentPoint.x - this.graph.container.scrollLeft + offset.x - left;\r\n\tvar gridY = this.currentPoint.y - this.graph.container.scrollTop + offset.y - top;\r\n\r\n\treturn this.outlineConnect && !mxEvent.isShiftDown(me.getEvent()) &&\r\n\t\t(me.isSource(this.marker.highlight.shape) ||\r\n\t\t(mxEvent.isAltDown(me.getEvent()) && me.getState() != null) ||\r\n\t\tthis.marker.highlight.isHighlightAt(clientX, clientY) ||\r\n\t\t((gridX != clientX || gridY != clientY) && me.getState() == null &&\r\n\t\tthis.marker.highlight.isHighlightAt(gridX, gridY)));\r\n};\r\n\r\n/**\r\n * Function: updatePreviewState\r\n * \r\n * Updates the given preview state taking into account the state of the constraint handler.\r\n */\r\nmxEdgeHandler.prototype.updatePreviewState = function(edge, point, terminalState, me, outline)\r\n{\r\n\t// Computes the points for the edge style and terminals\r\n\tvar sourceState = (this.isSource) ? terminalState : this.state.getVisibleTerminalState(true);\r\n\tvar targetState = (this.isTarget) ? terminalState : this.state.getVisibleTerminalState(false);\r\n\t\r\n\tvar sourceConstraint = this.graph.getConnectionConstraint(edge, sourceState, true);\r\n\tvar targetConstraint = this.graph.getConnectionConstraint(edge, targetState, false);\r\n\r\n\tvar constraint = this.constraintHandler.currentConstraint;\r\n\r\n\tif (constraint == null && outline)\r\n\t{\r\n\t\tif (terminalState != null)\r\n\t\t{\r\n\t\t\t// Handles special case where mouse is on outline away from actual end point\r\n\t\t\t// in which case the grid is ignored and mouse point is used instead\r\n\t\t\tif (me.isSource(this.marker.highlight.shape))\r\n\t\t\t{\r\n\t\t\t\tpoint = new mxPoint(me.getGraphX(), me.getGraphY());\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tconstraint = this.graph.getOutlineConstraint(point, terminalState, me);\r\n\t\t\tthis.constraintHandler.setFocus(me, terminalState, this.isSource);\r\n\t\t\tthis.constraintHandler.currentConstraint = constraint;\r\n\t\t\tthis.constraintHandler.currentPoint = point;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tconstraint = new mxConnectionConstraint();\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (this.outlineConnect && this.marker.highlight != null && this.marker.highlight.shape != null)\r\n\t{\r\n\t\tvar s = this.graph.view.scale;\r\n\t\t\r\n\t\tif (this.constraintHandler.currentConstraint != null &&\r\n\t\t\tthis.constraintHandler.currentFocus != null)\r\n\t\t{\r\n\t\t\tthis.marker.highlight.shape.stroke = (outline) ? mxConstants.OUTLINE_HIGHLIGHT_COLOR : 'transparent';\r\n\t\t\tthis.marker.highlight.shape.strokewidth = mxConstants.OUTLINE_HIGHLIGHT_STROKEWIDTH / s / s;\r\n\t\t\tthis.marker.highlight.repaint();\r\n\t\t}\r\n\t\telse if (this.marker.hasValidState())\r\n\t\t{\r\n\t\t\tthis.marker.highlight.shape.stroke = (this.marker.getValidState() == me.getState()) ?\r\n\t\t\t\tmxConstants.DEFAULT_VALID_COLOR : 'transparent';\r\n\t\t\tthis.marker.highlight.shape.strokewidth = mxConstants.HIGHLIGHT_STROKEWIDTH / s / s;\r\n\t\t\tthis.marker.highlight.repaint();\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (this.isSource)\r\n\t{\r\n\t\tsourceConstraint = constraint;\r\n\t}\r\n\telse if (this.isTarget)\r\n\t{\r\n\t\ttargetConstraint = constraint;\r\n\t}\r\n\t\r\n\tif (this.isSource || this.isTarget)\r\n\t{\r\n\t\tif (constraint != null && constraint.point != null)\r\n\t\t{\r\n\t\t\tedge.style[(this.isSource) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X] = constraint.point.x;\r\n\t\t\tedge.style[(this.isSource) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y] = constraint.point.y;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tdelete edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X];\r\n\t\t\tdelete edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y];\r\n\t\t}\r\n\t}\r\n\t\r\n\tedge.setVisibleTerminalState(sourceState, true);\r\n\tedge.setVisibleTerminalState(targetState, false);\r\n\t\r\n\tif (!this.isSource || sourceState != null)\r\n\t{\r\n\t\tedge.view.updateFixedTerminalPoint(edge, sourceState, true, sourceConstraint);\r\n\t}\r\n\t\r\n\tif (!this.isTarget || targetState != null)\r\n\t{\r\n\t\tedge.view.updateFixedTerminalPoint(edge, targetState, false, targetConstraint);\r\n\t}\r\n\t\r\n\tif ((this.isSource || this.isTarget) && terminalState == null)\r\n\t{\r\n\t\tedge.setAbsoluteTerminalPoint(point, this.isSource);\r\n\r\n\t\tif (this.marker.getMarkedState() == null)\r\n\t\t{\r\n\t\t\tthis.error = (this.graph.allowDanglingEdges) ? null : '';\r\n\t\t}\r\n\t}\r\n\t\r\n\tedge.view.updatePoints(edge, this.points, sourceState, targetState);\r\n\tedge.view.updateFloatingTerminalPoints(edge, sourceState, targetState);\r\n};\r\n\r\n/**\r\n * Function: mouseMove\r\n * \r\n * Handles the event by updating the preview.\r\n */\r\nmxEdgeHandler.prototype.mouseMove = function(sender, me)\r\n{\r\n\tif (this.index != null && this.marker != null)\r\n\t{\r\n\t\tthis.currentPoint = this.getPointForEvent(me);\r\n\t\tthis.error = null;\r\n\t\t\r\n\t\t// Uses the current point from the constraint handler if available\r\n\t\tif (!this.graph.isIgnoreTerminalEvent(me.getEvent()) && mxEvent.isShiftDown(me.getEvent()) && this.snapPoint != null)\r\n\t\t{\r\n\t\t\tif (Math.abs(this.snapPoint.x - this.currentPoint.x) < Math.abs(this.snapPoint.y - this.currentPoint.y))\r\n\t\t\t{\r\n\t\t\t\tthis.currentPoint.x = this.snapPoint.x;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.currentPoint.y = this.snapPoint.y;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE)\r\n\t\t{\r\n\t\t\tif (this.customHandles != null)\r\n\t\t\t{\r\n\t\t\t\tthis.customHandles[mxEvent.CUSTOM_HANDLE - this.index].processEvent(me);\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (this.isLabel)\r\n\t\t{\r\n\t\t\tthis.label.x = this.currentPoint.x;\r\n\t\t\tthis.label.y = this.currentPoint.y;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.points = this.getPreviewPoints(this.currentPoint, me);\r\n\t\t\tvar terminalState = (this.isSource || this.isTarget) ? this.getPreviewTerminalState(me) : null;\r\n\r\n\t\t\tif (this.constraintHandler.currentConstraint != null &&\r\n\t\t\t\tthis.constraintHandler.currentFocus != null &&\r\n\t\t\t\tthis.constraintHandler.currentPoint != null)\r\n\t\t\t{\r\n\t\t\t\tthis.currentPoint = this.constraintHandler.currentPoint.clone();\r\n\t\t\t}\r\n\t\t\telse if (this.outlineConnect)\r\n\t\t\t{\r\n\t\t\t\t// Need to check outline before cloning terminal state\r\n\t\t\t\tvar outline = (this.isSource || this.isTarget) ? this.isOutlineConnectEvent(me) : false\r\n\t\t\t\t\t\t\r\n\t\t\t\tif (outline)\r\n\t\t\t\t{\r\n\t\t\t\t\tterminalState = this.marker.highlight.state;\r\n\t\t\t\t}\r\n\t\t\t\telse if (terminalState != null && terminalState != me.getState() && this.marker.highlight.shape != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.marker.highlight.shape.stroke = 'transparent';\r\n\t\t\t\t\tthis.marker.highlight.repaint();\r\n\t\t\t\t\tterminalState = null;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (terminalState != null && this.graph.isCellLocked(terminalState.cell))\r\n\t\t\t{\r\n\t\t\t\tterminalState = null;\r\n\t\t\t\tthis.marker.reset();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar clone = this.clonePreviewState(this.currentPoint, (terminalState != null) ? terminalState.cell : null);\r\n\t\t\tthis.updatePreviewState(clone, this.currentPoint, terminalState, me, outline);\r\n\r\n\t\t\t// Sets the color of the preview to valid or invalid, updates the\r\n\t\t\t// points of the preview and redraws\r\n\t\t\tvar color = (this.error == null) ? this.marker.validColor : this.marker.invalidColor;\r\n\t\t\tthis.setPreviewColor(color);\r\n\t\t\tthis.abspoints = clone.absolutePoints;\r\n\t\t\tthis.active = true;\r\n\t\t}\r\n\r\n\t\t// This should go before calling isOutlineConnectEvent above. As a workaround\r\n\t\t// we add an offset of gridSize to the hint to avoid problem with hit detection\r\n\t\t// in highlight.isHighlightAt (which uses comonentFromPoint)\r\n\t\tthis.updateHint(me, this.currentPoint);\r\n\t\tthis.drawPreview();\r\n\t\tmxEvent.consume(me.getEvent());\r\n\t\tme.consume();\r\n\t}\r\n\t// Workaround for disabling the connect highlight when over handle\r\n\telse if (mxClient.IS_IE && this.getHandleForEvent(me) != null)\r\n\t{\r\n\t\tme.consume(false);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: mouseUp\r\n * \r\n * Handles the event to applying the previewed changes on the edge by\r\n * using <moveLabel>, <connect> or <changePoints>.\r\n */\r\nmxEdgeHandler.prototype.mouseUp = function(sender, me)\r\n{\r\n\t// Workaround for wrong event source in Webkit\r\n\tif (this.index != null && this.marker != null)\r\n\t{\r\n\t\tvar edge = this.state.cell;\r\n\t\t\r\n\t\t// Ignores event if mouse has not been moved\r\n\t\tif (me.getX() != this.startX || me.getY() != this.startY)\r\n\t\t{\r\n\t\t\tvar clone = !this.graph.isIgnoreTerminalEvent(me.getEvent()) && this.graph.isCloneEvent(me.getEvent()) &&\r\n\t\t\t\tthis.cloneEnabled && this.graph.isCellsCloneable();\r\n\t\t\t\r\n\t\t\t// Displays the reason for not carriying out the change\r\n\t\t\t// if there is an error message with non-zero length\r\n\t\t\tif (this.error != null)\r\n\t\t\t{\r\n\t\t\t\tif (this.error.length > 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.graph.validationAlert(this.error);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE)\r\n\t\t\t{\r\n\t\t\t\tif (this.customHandles != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar model = this.graph.getModel();\r\n\t\t\t\t\t\r\n\t\t\t\t\tmodel.beginUpdate();\r\n\t\t\t\t\ttry\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.customHandles[mxEvent.CUSTOM_HANDLE - this.index].execute();\r\n\t\t\t\t\t}\r\n\t\t\t\t\tfinally\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tmodel.endUpdate();\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if (this.isLabel)\r\n\t\t\t{\r\n\t\t\t\tthis.moveLabel(this.state, this.label.x, this.label.y);\r\n\t\t\t}\r\n\t\t\telse if (this.isSource || this.isTarget)\r\n\t\t\t{\r\n\t\t\t\tvar terminal = null;\r\n\t\t\t\t\r\n\t\t\t\tif (this.constraintHandler.currentConstraint != null &&\r\n\t\t\t\t\tthis.constraintHandler.currentFocus != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tterminal = this.constraintHandler.currentFocus.cell;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (terminal == null && this.marker.hasValidState() && this.marker.highlight != null &&\r\n\t\t\t\t\tthis.marker.highlight.shape != null &&\r\n\t\t\t\t\tthis.marker.highlight.shape.stroke != 'transparent' &&\r\n\t\t\t\t\tthis.marker.highlight.shape.stroke != 'white')\r\n\t\t\t\t{\r\n\t\t\t\t\tterminal = this.marker.validState.cell;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (terminal != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar model = this.graph.getModel();\r\n\t\t\t\t\tvar parent = model.getParent(edge);\r\n\t\t\t\t\t\r\n\t\t\t\t\tmodel.beginUpdate();\r\n\t\t\t\t\ttry\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t// Clones and adds the cell\r\n\t\t\t\t\t\tif (clone)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar geo = model.getGeometry(edge);\r\n\t\t\t\t\t\t\tvar clone = this.graph.cloneCell(edge);\r\n\t\t\t\t\t\t\tmodel.add(parent, clone, model.getChildCount(parent));\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tif (geo != null)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tgeo = geo.clone();\r\n\t\t\t\t\t\t\t\tmodel.setGeometry(clone, geo);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tvar other = model.getTerminal(edge, !this.isSource);\r\n\t\t\t\t\t\t\tthis.graph.connectCell(clone, other, !this.isSource);\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tedge = clone;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tedge = this.connect(edge, terminal, this.isSource, clone, me);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tfinally\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tmodel.endUpdate();\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if (this.graph.isAllowDanglingEdges())\r\n\t\t\t\t{\r\n\t\t\t\t\tvar pt = this.abspoints[(this.isSource) ? 0 : this.abspoints.length - 1];\r\n\t\t\t\t\tpt.x = this.roundLength(pt.x / this.graph.view.scale - this.graph.view.translate.x);\r\n\t\t\t\t\tpt.y = this.roundLength(pt.y / this.graph.view.scale - this.graph.view.translate.y);\r\n\r\n\t\t\t\t\tvar pstate = this.graph.getView().getState(\r\n\t\t\t\t\t\t\tthis.graph.getModel().getParent(edge));\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\tif (pstate != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tpt.x -= pstate.origin.x;\r\n\t\t\t\t\t\tpt.y -= pstate.origin.y;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tpt.x -= this.graph.panDx / this.graph.view.scale;\r\n\t\t\t\t\tpt.y -= this.graph.panDy / this.graph.view.scale;\r\n\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t// Destroys and recreates this handler\r\n\t\t\t\t\tedge = this.changeTerminalPoint(edge, pt, this.isSource, clone);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if (this.active)\r\n\t\t\t{\r\n\t\t\t\tedge = this.changePoints(edge, this.points, clone);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.graph.getView().invalidate(this.state.cell);\r\n\t\t\t\tthis.graph.getView().validate(this.state.cell);\t\t\t\t\t\t\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Resets the preview color the state of the handler if this\r\n\t\t// handler has not been recreated\r\n\t\tif (this.marker != null)\r\n\t\t{\r\n\t\t\tthis.reset();\r\n\r\n\t\t\t// Updates the selection if the edge has been cloned\r\n\t\t\tif (edge != this.state.cell)\r\n\t\t\t{\r\n\t\t\t\tthis.graph.setSelectionCell(edge);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tme.consume();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: reset\r\n * \r\n * Resets the state of this handler.\r\n */\r\nmxEdgeHandler.prototype.reset = function()\r\n{\r\n\tif (this.active)\r\n\t{\r\n\t\tthis.refresh();\r\n\t}\r\n\t\r\n\tthis.error = null;\r\n\tthis.index = null;\r\n\tthis.label = null;\r\n\tthis.points = null;\r\n\tthis.snapPoint = null;\r\n\tthis.isLabel = false;\r\n\tthis.isSource = false;\r\n\tthis.isTarget = false;\r\n\tthis.active = false;\r\n\t\r\n\tif (this.livePreview && this.sizers != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.sizers.length; i++)\r\n\t\t{\r\n\t\t\tif (this.sizers[i] != null)\r\n\t\t\t{\r\n\t\t\t\tthis.sizers[i].node.style.display = '';\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tif (this.marker != null)\r\n\t{\r\n\t\tthis.marker.reset();\r\n\t}\r\n\t\r\n\tif (this.constraintHandler != null)\r\n\t{\r\n\t\tthis.constraintHandler.reset();\r\n\t}\r\n\t\r\n\tif (this.customHandles != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.customHandles.length; i++)\r\n\t\t{\r\n\t\t\tthis.customHandles[i].reset();\r\n\t\t}\r\n\t}\r\n\r\n\tthis.setPreviewColor(mxConstants.EDGE_SELECTION_COLOR);\r\n\tthis.removeHint();\r\n\tthis.redraw();\r\n};\r\n\r\n/**\r\n * Function: setPreviewColor\r\n * \r\n * Sets the color of the preview to the given value.\r\n */\r\nmxEdgeHandler.prototype.setPreviewColor = function(color)\r\n{\r\n\tif (this.shape != null)\r\n\t{\r\n\t\tthis.shape.stroke = color;\r\n\t}\r\n};\r\n\r\n\r\n/**\r\n * Function: convertPoint\r\n * \r\n * Converts the given point in-place from screen to unscaled, untranslated\r\n * graph coordinates and applies the grid. Returns the given, modified\r\n * point instance.\r\n * \r\n * Parameters:\r\n * \r\n * point - <mxPoint> to be converted.\r\n * gridEnabled - Boolean that specifies if the grid should be applied.\r\n */\r\nmxEdgeHandler.prototype.convertPoint = function(point, gridEnabled)\r\n{\r\n\tvar scale = this.graph.getView().getScale();\r\n\tvar tr = this.graph.getView().getTranslate();\r\n\t\t\r\n\tif (gridEnabled)\r\n\t{\r\n\t\tpoint.x = this.graph.snap(point.x);\r\n\t\tpoint.y = this.graph.snap(point.y);\r\n\t}\r\n\t\r\n\tpoint.x = Math.round(point.x / scale - tr.x);\r\n\tpoint.y = Math.round(point.y / scale - tr.y);\r\n\r\n\tvar pstate = this.graph.getView().getState(\r\n\t\tthis.graph.getModel().getParent(this.state.cell));\r\n\r\n\tif (pstate != null)\r\n\t{\r\n\t\tpoint.x -= pstate.origin.x;\r\n\t\tpoint.y -= pstate.origin.y;\r\n\t}\r\n\r\n\treturn point;\r\n};\r\n\r\n/**\r\n * Function: moveLabel\r\n * \r\n * Changes the coordinates for the label of the given edge.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCell> that represents the edge.\r\n * x - Integer that specifies the x-coordinate of the new location.\r\n * y - Integer that specifies the y-coordinate of the new location.\r\n */\r\nmxEdgeHandler.prototype.moveLabel = function(edgeState, x, y)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\tvar geometry = model.getGeometry(edgeState.cell);\r\n\t\r\n\tif (geometry != null)\r\n\t{\r\n\t\tvar scale = this.graph.getView().scale;\r\n\t\tgeometry = geometry.clone();\r\n\t\t\r\n\t\tif (geometry.relative)\r\n\t\t{\r\n\t\t\t// Resets the relative location stored inside the geometry\r\n\t\t\tvar pt = this.graph.getView().getRelativePoint(edgeState, x, y);\r\n\t\t\tgeometry.x = Math.round(pt.x * 10000) / 10000;\r\n\t\t\tgeometry.y = Math.round(pt.y);\r\n\t\t\t\r\n\t\t\t// Resets the offset inside the geometry to find the offset\r\n\t\t\t// from the resulting point\r\n\t\t\tgeometry.offset = new mxPoint(0, 0);\r\n\t\t\tvar pt = this.graph.view.getPoint(edgeState, geometry);\r\n\t\t\tgeometry.offset = new mxPoint(Math.round((x - pt.x) / scale), Math.round((y - pt.y) / scale));\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar points = edgeState.absolutePoints;\r\n\t\t\tvar p0 = points[0];\r\n\t\t\tvar pe = points[points.length - 1];\r\n\t\t\t\r\n\t\t\tif (p0 != null && pe != null)\r\n\t\t\t{\r\n\t\t\t\tvar cx = p0.x + (pe.x - p0.x) / 2;\r\n\t\t\t\tvar cy = p0.y + (pe.y - p0.y) / 2;\r\n\t\t\t\t\r\n\t\t\t\tgeometry.offset = new mxPoint(Math.round((x - cx) / scale), Math.round((y - cy) / scale));\r\n\t\t\t\tgeometry.x = 0;\r\n\t\t\t\tgeometry.y = 0;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tmodel.setGeometry(edgeState.cell, geometry);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: connect\r\n * \r\n * Changes the terminal or terminal point of the given edge in the graph\r\n * model.\r\n * \r\n * Parameters:\r\n * \r\n * edge - <mxCell> that represents the edge to be reconnected.\r\n * terminal - <mxCell> that represents the new terminal.\r\n * isSource - Boolean indicating if the new terminal is the source or\r\n * target terminal.\r\n * isClone - Boolean indicating if the new connection should be a clone of\r\n * the old edge.\r\n * me - <mxMouseEvent> that contains the mouse up event.\r\n */\r\nmxEdgeHandler.prototype.connect = function(edge, terminal, isSource, isClone, me)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\tvar parent = model.getParent(edge);\r\n\t\r\n\tmodel.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tvar constraint = this.constraintHandler.currentConstraint;\r\n\t\t\r\n\t\tif (constraint == null)\r\n\t\t{\r\n\t\t\tconstraint = new mxConnectionConstraint();\r\n\t\t}\r\n\r\n\t\tthis.graph.connectCell(edge, terminal, isSource, constraint);\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tmodel.endUpdate();\r\n\t}\r\n\t\r\n\treturn edge;\r\n};\r\n\r\n/**\r\n * Function: changeTerminalPoint\r\n * \r\n * Changes the terminal point of the given edge.\r\n */\r\nmxEdgeHandler.prototype.changeTerminalPoint = function(edge, point, isSource, clone)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\r\n\tmodel.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tif (clone)\r\n\t\t{\r\n\t\t\tvar parent = model.getParent(edge);\r\n\t\t\tvar terminal = model.getTerminal(edge, !isSource);\r\n\t\t\tedge = this.graph.cloneCell(edge);\r\n\t\t\tmodel.add(parent, edge, model.getChildCount(parent));\r\n\t\t\tmodel.setTerminal(edge, terminal, !isSource);\r\n\t\t}\r\n\r\n\t\tvar geo = model.getGeometry(edge);\r\n\t\t\r\n\t\tif (geo != null)\r\n\t\t{\r\n\t\t\tgeo = geo.clone();\r\n\t\t\tgeo.setTerminalPoint(point, isSource);\r\n\t\t\tmodel.setGeometry(edge, geo);\r\n\t\t\tthis.graph.connectCell(edge, null, isSource, new mxConnectionConstraint());\r\n\t\t}\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tmodel.endUpdate();\r\n\t}\r\n\t\r\n\treturn edge;\r\n};\r\n\r\n/**\r\n * Function: changePoints\r\n * \r\n * Changes the control points of the given edge in the graph model.\r\n */\r\nmxEdgeHandler.prototype.changePoints = function(edge, points, clone)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\tmodel.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tif (clone)\r\n\t\t{\r\n\t\t\tvar parent = model.getParent(edge);\r\n\t\t\tvar source = model.getTerminal(edge, true);\r\n\t\t\tvar target = model.getTerminal(edge, false);\r\n\t\t\tedge = this.graph.cloneCell(edge);\r\n\t\t\tmodel.add(parent, edge, model.getChildCount(parent));\r\n\t\t\tmodel.setTerminal(edge, source, true);\r\n\t\t\tmodel.setTerminal(edge, target, false);\r\n\t\t}\r\n\t\t\r\n\t\tvar geo = model.getGeometry(edge);\r\n\t\t\r\n\t\tif (geo != null)\r\n\t\t{\r\n\t\t\tgeo = geo.clone();\r\n\t\t\tgeo.points = points;\r\n\t\t\t\r\n\t\t\tmodel.setGeometry(edge, geo);\r\n\t\t}\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tmodel.endUpdate();\r\n\t}\r\n\t\r\n\treturn edge;\r\n};\r\n\r\n/**\r\n * Function: addPoint\r\n * \r\n * Adds a control point for the given state and event.\r\n */\r\nmxEdgeHandler.prototype.addPoint = function(state, evt)\r\n{\r\n\tvar pt = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(evt),\r\n\t\t\tmxEvent.getClientY(evt));\r\n\tvar gridEnabled = this.graph.isGridEnabledEvent(evt);\r\n\tthis.convertPoint(pt, gridEnabled);\r\n\tthis.addPointAt(state, pt.x, pt.y);\r\n\tmxEvent.consume(evt);\r\n};\r\n\r\n/**\r\n * Function: addPointAt\r\n * \r\n * Adds a control point at the given point.\r\n */\r\nmxEdgeHandler.prototype.addPointAt = function(state, x, y)\r\n{\r\n\tvar geo = this.graph.getCellGeometry(state.cell);\r\n\tvar pt = new mxPoint(x, y);\r\n\t\r\n\tif (geo != null)\r\n\t{\r\n\t\tgeo = geo.clone();\r\n\t\tvar t = this.graph.view.translate;\r\n\t\tvar s = this.graph.view.scale;\r\n\t\tvar offset = new mxPoint(t.x * s, t.y * s);\r\n\t\t\r\n\t\tvar parent = this.graph.model.getParent(this.state.cell);\r\n\t\t\r\n\t\tif (this.graph.model.isVertex(parent))\r\n\t\t{\r\n\t\t\tvar pState = this.graph.view.getState(parent);\r\n\t\t\toffset = new mxPoint(pState.x, pState.y);\r\n\t\t}\r\n\t\t\r\n\t\tvar index = mxUtils.findNearestSegment(state, pt.x * s + offset.x, pt.y * s + offset.y);\r\n\r\n\t\tif (geo.points == null)\r\n\t\t{\r\n\t\t\tgeo.points = [pt];\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tgeo.points.splice(index, 0, pt);\r\n\t\t}\r\n\t\t\r\n\t\tthis.graph.getModel().setGeometry(state.cell, geo);\r\n\t\tthis.refresh();\t\r\n\t\tthis.redraw();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: removePoint\r\n * \r\n * Removes the control point at the given index from the given state.\r\n */\r\nmxEdgeHandler.prototype.removePoint = function(state, index)\r\n{\r\n\tif (index > 0 && index < this.abspoints.length - 1)\r\n\t{\r\n\t\tvar geo = this.graph.getCellGeometry(this.state.cell);\r\n\t\t\r\n\t\tif (geo != null && geo.points != null)\r\n\t\t{\r\n\t\t\tgeo = geo.clone();\r\n\t\t\tgeo.points.splice(index - 1, 1);\r\n\t\t\tthis.graph.getModel().setGeometry(state.cell, geo);\r\n\t\t\tthis.refresh();\r\n\t\t\tthis.redraw();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getHandleFillColor\r\n * \r\n * Returns the fillcolor for the handle at the given index.\r\n */\r\nmxEdgeHandler.prototype.getHandleFillColor = function(index)\r\n{\r\n\tvar isSource = index == 0;\r\n\tvar cell = this.state.cell;\r\n\tvar terminal = this.graph.getModel().getTerminal(cell, isSource);\r\n\tvar color = mxConstants.HANDLE_FILLCOLOR;\r\n\t\r\n\tif ((terminal != null && !this.graph.isCellDisconnectable(cell, terminal, isSource)) ||\r\n\t\t(terminal == null && !this.graph.isTerminalPointMovable(cell, isSource)))\r\n\t{\r\n\t\tcolor = mxConstants.LOCKED_HANDLE_FILLCOLOR;\r\n\t}\r\n\telse if (terminal != null && this.graph.isCellDisconnectable(cell, terminal, isSource))\r\n\t{\r\n\t\tcolor = mxConstants.CONNECT_HANDLE_FILLCOLOR;\r\n\t}\r\n\t\r\n\treturn color;\r\n};\r\n\r\n/**\r\n * Function: redraw\r\n * \r\n * Redraws the preview, and the bends- and label control points.\r\n */\r\nmxEdgeHandler.prototype.redraw = function()\r\n{\r\n\tthis.abspoints = this.state.absolutePoints.slice();\r\n\tthis.redrawHandles();\r\n\t\r\n\tvar g = this.graph.getModel().getGeometry(this.state.cell);\r\n\tvar pts = g.points;\r\n\r\n\tif (this.bends != null && this.bends.length > 0)\r\n\t{\r\n\t\tif (pts != null)\r\n\t\t{\r\n\t\t\tif (this.points == null)\r\n\t\t\t{\r\n\t\t\t\tthis.points = [];\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tfor (var i = 1; i < this.bends.length - 1; i++)\r\n\t\t\t{\r\n\t\t\t\tif (this.bends[i] != null && this.abspoints[i] != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.points[i - 1] = pts[i - 1];\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tthis.drawPreview();\r\n};\r\n\r\n/**\r\n * Function: redrawHandles\r\n * \r\n * Redraws the handles.\r\n */\r\nmxEdgeHandler.prototype.redrawHandles = function()\r\n{\r\n\tvar cell = this.state.cell;\r\n\r\n\t// Updates the handle for the label position\r\n\tvar b = this.labelShape.bounds;\r\n\tthis.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y);\r\n\tthis.labelShape.bounds = new mxRectangle(Math.round(this.label.x - b.width / 2),\r\n\t\tMath.round(this.label.y - b.height / 2), b.width, b.height);\r\n\r\n\t// Shows or hides the label handle depending on the label\r\n\tvar lab = this.graph.getLabel(cell);\r\n\tthis.labelShape.visible = (lab != null && lab.length > 0 && this.graph.isLabelMovable(cell));\r\n\t\r\n\tif (this.bends != null && this.bends.length > 0)\r\n\t{\r\n\t\tvar n = this.abspoints.length - 1;\r\n\t\t\r\n\t\tvar p0 = this.abspoints[0];\r\n\t\tvar x0 = p0.x;\r\n\t\tvar y0 = p0.y;\r\n\t\t\r\n\t\tb = this.bends[0].bounds;\r\n\t\tthis.bends[0].bounds = new mxRectangle(Math.floor(x0 - b.width / 2),\r\n\t\t\t\tMath.floor(y0 - b.height / 2), b.width, b.height);\r\n\t\tthis.bends[0].fill = this.getHandleFillColor(0);\r\n\t\tthis.bends[0].redraw();\r\n\t\t\r\n\t\tif (this.manageLabelHandle)\r\n\t\t{\r\n\t\t\tthis.checkLabelHandle(this.bends[0].bounds);\r\n\t\t}\r\n\t\t\t\t\r\n\t\tvar pe = this.abspoints[n];\r\n\t\tvar xn = pe.x;\r\n\t\tvar yn = pe.y;\r\n\t\t\r\n\t\tvar bn = this.bends.length - 1;\r\n\t\tb = this.bends[bn].bounds;\r\n\t\tthis.bends[bn].bounds = new mxRectangle(Math.floor(xn - b.width / 2),\r\n\t\t\t\tMath.floor(yn - b.height / 2), b.width, b.height);\r\n\t\tthis.bends[bn].fill = this.getHandleFillColor(bn);\r\n\t\tthis.bends[bn].redraw();\r\n\t\t\t\t\r\n\t\tif (this.manageLabelHandle)\r\n\t\t{\r\n\t\t\tthis.checkLabelHandle(this.bends[bn].bounds);\r\n\t\t}\r\n\t\t\r\n\t\tthis.redrawInnerBends(p0, pe);\r\n\t}\r\n\r\n\tif (this.abspoints != null && this.virtualBends != null && this.virtualBends.length > 0)\r\n\t{\r\n\t\tvar last = this.abspoints[0];\r\n\t\t\r\n\t\tfor (var i = 0; i < this.virtualBends.length; i++)\r\n\t\t{\r\n\t\t\tif (this.virtualBends[i] != null && this.abspoints[i + 1] != null)\r\n\t\t\t{\r\n\t\t\t\tvar pt = this.abspoints[i + 1];\r\n\t\t\t\tvar b = this.virtualBends[i];\r\n\t\t\t\tvar x = last.x + (pt.x - last.x) / 2;\r\n\t\t\t\tvar y = last.y + (pt.y - last.y) / 2;\r\n\t\t\t\tb.bounds = new mxRectangle(Math.floor(x - b.bounds.width / 2),\r\n\t\t\t\t\t\tMath.floor(y - b.bounds.height / 2), b.bounds.width, b.bounds.height);\r\n\t\t\t\tb.redraw();\r\n\t\t\t\tmxUtils.setOpacity(b.node, this.virtualBendOpacity);\r\n\t\t\t\tlast = pt;\r\n\t\t\t\t\r\n\t\t\t\tif (this.manageLabelHandle)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.checkLabelHandle(b.bounds);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (this.labelShape != null)\r\n\t{\r\n\t\tthis.labelShape.redraw();\r\n\t}\r\n\t\r\n\tif (this.customHandles != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.customHandles.length; i++)\r\n\t\t{\r\n\t\t\tthis.customHandles[i].redraw();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: hideHandles\r\n * \r\n * Shortcut to <hideSizers>.\r\n */\r\nmxEdgeHandler.prototype.setHandlesVisible = function(visible)\r\n{\r\n\tif (this.bends != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.bends.length; i++)\r\n\t\t{\r\n\t\t\tthis.bends[i].node.style.display = (visible) ? '' : 'none';\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (this.virtualBends != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.virtualBends.length; i++)\r\n\t\t{\r\n\t\t\tthis.virtualBends[i].node.style.display = (visible) ? '' : 'none';\r\n\t\t}\r\n\t}\r\n\r\n\tif (this.labelShape != null)\r\n\t{\r\n\t\tthis.labelShape.node.style.display = (visible) ? '' : 'none';\r\n\t}\r\n\t\r\n\tif (this.customHandles != null)\r\n\t{\r\n\t\tfor (var i = 0; i < this.customHandles.length; i++)\r\n\t\t{\r\n\t\t\tthis.customHandles[i].setVisible(visible);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: redrawInnerBends\r\n * \r\n * Updates and redraws the inner bends.\r\n * \r\n * Parameters:\r\n * \r\n * p0 - <mxPoint> that represents the location of the first point.\r\n * pe - <mxPoint> that represents the location of the last point.\r\n */\r\nmxEdgeHandler.prototype.redrawInnerBends = function(p0, pe)\r\n{\r\n\tfor (var i = 1; i < this.bends.length - 1; i++)\r\n\t{\r\n\t\tif (this.bends[i] != null)\r\n\t\t{\r\n\t\t\tif (this.abspoints[i] != null)\r\n\t\t\t{\r\n\t\t\t\tvar x = this.abspoints[i].x;\r\n\t\t\t\tvar y = this.abspoints[i].y;\r\n\t\t\t\t\r\n\t\t\t\tvar b = this.bends[i].bounds;\r\n\t\t\t\tthis.bends[i].node.style.visibility = 'visible';\r\n\t\t\t\tthis.bends[i].bounds = new mxRectangle(Math.round(x - b.width / 2),\r\n\t\t\t\t\t\tMath.round(y - b.height / 2), b.width, b.height);\r\n\t\t\t\t\r\n\t\t\t\tif (this.manageLabelHandle)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.checkLabelHandle(this.bends[i].bounds);\r\n\t\t\t\t}\r\n\t\t\t\telse if (this.handleImage == null && this.labelShape.visible && mxUtils.intersects(this.bends[i].bounds, this.labelShape.bounds))\r\n\t\t\t\t{\r\n\t\t\t\t\tw = mxConstants.HANDLE_SIZE + 3;\r\n\t\t\t\t\th = mxConstants.HANDLE_SIZE + 3;\r\n\t\t\t\t\tthis.bends[i].bounds = new mxRectangle(Math.round(x - w / 2), Math.round(y - h / 2), w, h);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.bends[i].redraw();\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.bends[i].destroy();\r\n\t\t\t\tthis.bends[i] = null;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: checkLabelHandle\r\n * \r\n * Checks if the label handle intersects the given bounds and moves it if it\r\n * intersects.\r\n */\r\nmxEdgeHandler.prototype.checkLabelHandle = function(b)\r\n{\r\n\tif (this.labelShape != null)\r\n\t{\r\n\t\tvar b2 = this.labelShape.bounds;\r\n\t\t\r\n\t\tif (mxUtils.intersects(b, b2))\r\n\t\t{\r\n\t\t\tif (b.getCenterY() < b2.getCenterY())\r\n\t\t\t{\r\n\t\t\t\tb2.y = b.y + b.height;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tb2.y = b.y - b2.height;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: drawPreview\r\n * \r\n * Redraws the preview.\r\n */\r\nmxEdgeHandler.prototype.drawPreview = function()\r\n{\r\n\tif (this.isLabel)\r\n\t{\r\n\t\tvar b = this.labelShape.bounds;\r\n\t\tvar bounds = new mxRectangle(Math.round(this.label.x - b.width / 2),\r\n\t\t\t\tMath.round(this.label.y - b.height / 2), b.width, b.height);\r\n\t\tthis.labelShape.bounds = bounds;\r\n\t\tthis.labelShape.redraw();\r\n\t}\r\n\telse if (this.shape != null)\r\n\t{\r\n\t\tthis.shape.apply(this.state);\r\n\t\tthis.shape.points = this.abspoints;\r\n\t\tthis.shape.scale = this.state.view.scale;\r\n\t\tthis.shape.isDashed = this.isSelectionDashed();\r\n\t\tthis.shape.stroke = this.getSelectionColor();\r\n\t\tthis.shape.strokewidth = this.getSelectionStrokeWidth() / this.shape.scale / this.shape.scale;\r\n\t\tthis.shape.isShadow = false;\r\n\t\tthis.shape.redraw();\r\n\t}\r\n\t\r\n\tif (this.parentHighlight != null)\r\n\t{\r\n\t\tthis.parentHighlight.redraw();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: refresh\r\n * \r\n * Refreshes the bends of this handler.\r\n */\r\nmxEdgeHandler.prototype.refresh = function()\r\n{\r\n\tthis.abspoints = this.getSelectionPoints(this.state);\r\n\tthis.points = [];\r\n\r\n\tif (this.shape != null)\r\n\t{\r\n\t\tthis.shape.points = this.abspoints;\r\n\t}\r\n\t\r\n\tif (this.bends != null)\r\n\t{\r\n\t\tthis.destroyBends(this.bends);\r\n\t\tthis.bends = this.createBends();\r\n\t}\r\n\t\r\n\tif (this.virtualBends != null)\r\n\t{\r\n\t\tthis.destroyBends(this.virtualBends);\r\n\t\tthis.virtualBends = this.createVirtualBends();\r\n\t}\r\n\t\r\n\tif (this.customHandles != null)\r\n\t{\r\n\t\tthis.destroyBends(this.customHandles);\r\n\t\tthis.customHandles = this.createCustomHandles();\r\n\t}\r\n\t\r\n\t// Puts label node on top of bends\r\n\tif (this.labelShape != null && this.labelShape.node != null && this.labelShape.node.parentNode != null)\r\n\t{\r\n\t\tthis.labelShape.node.parentNode.appendChild(this.labelShape.node);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: destroyBends\r\n * \r\n * Destroys all elements in <bends>.\r\n */\r\nmxEdgeHandler.prototype.destroyBends = function(bends)\r\n{\r\n\tif (bends != null)\r\n\t{\r\n\t\tfor (var i = 0; i < bends.length; i++)\r\n\t\t{\r\n\t\t\tif (bends[i] != null)\r\n\t\t\t{\r\n\t\t\t\tbends[i].destroy();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Destroys the handler and all its resources and DOM nodes. This does\r\n * normally not need to be called as handlers are destroyed automatically\r\n * when the corresponding cell is deselected.\r\n */\r\nmxEdgeHandler.prototype.destroy = function()\r\n{\r\n\tif (this.escapeHandler != null)\r\n\t{\r\n\t\tthis.state.view.graph.removeListener(this.escapeHandler);\r\n\t\tthis.escapeHandler = null;\r\n\t}\r\n\t\r\n\tif (this.marker != null)\r\n\t{\r\n\t\tthis.marker.destroy();\r\n\t\tthis.marker = null;\r\n\t}\r\n\t\r\n\tif (this.shape != null)\r\n\t{\r\n\t\tthis.shape.destroy();\r\n\t\tthis.shape = null;\r\n\t}\r\n\t\r\n\tif (this.parentHighlight != null)\r\n\t{\r\n\t\tthis.parentHighlight.destroy();\r\n\t\tthis.parentHighlight = null;\r\n\t}\r\n\t\r\n\tif (this.labelShape != null)\r\n\t{\r\n\t\tthis.labelShape.destroy();\r\n\t\tthis.labelShape = null;\r\n\t}\r\n\r\n\tif (this.constraintHandler != null)\r\n\t{\r\n\t\tthis.constraintHandler.destroy();\r\n\t\tthis.constraintHandler = null;\r\n\t}\r\n\t\r\n\tthis.destroyBends(this.virtualBends);\r\n\tthis.virtualBends = null;\r\n\t\r\n\tthis.destroyBends(this.customHandles);\r\n\tthis.customHandles = null;\r\n\r\n\tthis.destroyBends(this.bends);\r\n\tthis.bends = null;\r\n\t\r\n\tthis.removeHint();\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxElbowEdgeHandler\r\n *\r\n * Graph event handler that reconnects edges and modifies control points and\r\n * the edge label location. Uses <mxTerminalMarker> for finding and\r\n * highlighting new source and target vertices. This handler is automatically\r\n * created in <mxGraph.createHandler>. It extends <mxEdgeHandler>.\r\n * \r\n * Constructor: mxEdgeHandler\r\n *\r\n * Constructs an edge handler for the specified <mxCellState>.\r\n * \r\n * Parameters:\r\n * \r\n * state - <mxCellState> of the cell to be modified.\r\n */\r\nfunction mxElbowEdgeHandler(state)\r\n{\r\n\tmxEdgeHandler.call(this, state);\r\n};\r\n\r\n/**\r\n * Extends mxEdgeHandler.\r\n */\r\nmxUtils.extend(mxElbowEdgeHandler, mxEdgeHandler);\r\n\r\n/**\r\n * Specifies if a double click on the middle handle should call\r\n * <mxGraph.flipEdge>. Default is true.\r\n */\r\nmxElbowEdgeHandler.prototype.flipEnabled = true;\r\n\r\n/**\r\n * Variable: doubleClickOrientationResource\r\n * \r\n * Specifies the resource key for the tooltip to be displayed on the single\r\n * control point for routed edges. If the resource for this key does not\r\n * exist then the value is used as the error message. Default is\r\n * 'doubleClickOrientation'.\r\n */\r\nmxElbowEdgeHandler.prototype.doubleClickOrientationResource =\r\n\t(mxClient.language != 'none') ? 'doubleClickOrientation' : '';\r\n\r\n/**\r\n * Function: createBends\r\n * \r\n * Overrides <mxEdgeHandler.createBends> to create custom bends.\r\n */\r\n mxElbowEdgeHandler.prototype.createBends = function()\r\n {\r\n\tvar bends = [];\r\n\t\r\n\t// Source\r\n\tvar bend = this.createHandleShape(0);\r\n\tthis.initBend(bend);\r\n\tbend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);\r\n\tbends.push(bend);\r\n\r\n\t// Virtual\r\n\tbends.push(this.createVirtualBend(mxUtils.bind(this, function(evt)\r\n\t{\r\n\t\tif (!mxEvent.isConsumed(evt) && this.flipEnabled)\r\n\t\t{\r\n\t\t\tthis.graph.flipEdge(this.state.cell, evt);\r\n\t\t\tmxEvent.consume(evt);\r\n\t\t}\r\n\t})));\r\n\tthis.points.push(new mxPoint(0,0));\r\n\r\n\t// Target\r\n\tbend = this.createHandleShape(2);\r\n\tthis.initBend(bend);\r\n\tbend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);\r\n\tbends.push(bend);\r\n\t\r\n\treturn bends;\r\n };\r\n\r\n/**\r\n * Function: createVirtualBend\r\n * \r\n * Creates a virtual bend that supports double clicking and calls\r\n * <mxGraph.flipEdge>.\r\n */\r\nmxElbowEdgeHandler.prototype.createVirtualBend = function(dblClickHandler)\r\n{\r\n\tvar bend = this.createHandleShape();\r\n\tthis.initBend(bend, dblClickHandler);\r\n\r\n\tbend.setCursor(this.getCursorForBend());\r\n\r\n\tif (!this.graph.isCellBendable(this.state.cell))\r\n\t{\r\n\t\tbend.node.style.display = 'none';\r\n\t}\r\n\r\n\treturn bend;\r\n};\r\n\r\n/**\r\n * Function: getCursorForBend\r\n * \r\n * Returns the cursor to be used for the bend.\r\n */\r\nmxElbowEdgeHandler.prototype.getCursorForBend = function()\r\n{\r\n\treturn (this.state.style[mxConstants.STYLE_EDGE] == mxEdgeStyle.TopToBottom ||\r\n\t\tthis.state.style[mxConstants.STYLE_EDGE] == mxConstants.EDGESTYLE_TOPTOBOTTOM ||\r\n\t\t((this.state.style[mxConstants.STYLE_EDGE] == mxEdgeStyle.ElbowConnector ||\r\n\t\tthis.state.style[mxConstants.STYLE_EDGE] == mxConstants.EDGESTYLE_ELBOW)&&\r\n\t\tthis.state.style[mxConstants.STYLE_ELBOW] == mxConstants.ELBOW_VERTICAL)) ? \r\n\t\t'row-resize' : 'col-resize';\r\n};\r\n\r\n/**\r\n * Function: getTooltipForNode\r\n * \r\n * Returns the tooltip for the given node.\r\n */\r\nmxElbowEdgeHandler.prototype.getTooltipForNode = function(node)\r\n{\r\n\tvar tip = null;\r\n\t\r\n\tif (this.bends != null && this.bends[1] != null && (node == this.bends[1].node ||\r\n\t\tnode.parentNode == this.bends[1].node))\r\n\t{\r\n\t\ttip = this.doubleClickOrientationResource;\r\n\t\ttip = mxResources.get(tip) || tip; // translate\r\n\t}\r\n\r\n\treturn tip;\r\n};\r\n\r\n/**\r\n * Function: convertPoint\r\n * \r\n * Converts the given point in-place from screen to unscaled, untranslated\r\n * graph coordinates and applies the grid.\r\n * \r\n * Parameters:\r\n * \r\n * point - <mxPoint> to be converted.\r\n * gridEnabled - Boolean that specifies if the grid should be applied.\r\n */\r\nmxElbowEdgeHandler.prototype.convertPoint = function(point, gridEnabled)\r\n{\r\n\tvar scale = this.graph.getView().getScale();\r\n\tvar tr = this.graph.getView().getTranslate();\r\n\tvar origin = this.state.origin;\r\n\t\r\n\tif (gridEnabled)\r\n\t{\r\n\t\tpoint.x = this.graph.snap(point.x);\r\n\t\tpoint.y = this.graph.snap(point.y);\r\n\t}\r\n\t\r\n\tpoint.x = Math.round(point.x / scale - tr.x - origin.x);\r\n\tpoint.y = Math.round(point.y / scale - tr.y - origin.y);\r\n\t\r\n\treturn point;\r\n};\r\n\r\n/**\r\n * Function: redrawInnerBends\r\n * \r\n * Updates and redraws the inner bends.\r\n * \r\n * Parameters:\r\n * \r\n * p0 - <mxPoint> that represents the location of the first point.\r\n * pe - <mxPoint> that represents the location of the last point.\r\n */\r\nmxElbowEdgeHandler.prototype.redrawInnerBends = function(p0, pe)\r\n{\r\n\tvar g = this.graph.getModel().getGeometry(this.state.cell);\r\n\tvar pts = this.state.absolutePoints;\r\n\tvar pt = null;\r\n\r\n\t// Keeps the virtual bend on the edge shape\r\n\tif (pts.length > 1)\r\n\t{\r\n\t\tp0 = pts[1];\r\n\t\tpe = pts[pts.length - 2];\r\n\t}\r\n\telse if (g.points != null && g.points.length > 0)\r\n\t{\r\n\t\tpt = pts[0];\r\n\t}\r\n\t\r\n\tif (pt == null)\r\n\t{\r\n\t\tpt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tpt = new mxPoint(this.graph.getView().scale * (pt.x + this.graph.getView().translate.x + this.state.origin.x),\r\n\t\t\t\tthis.graph.getView().scale * (pt.y + this.graph.getView().translate.y + this.state.origin.y));\r\n\t}\r\n\r\n\t// Makes handle slightly bigger if the yellow  label handle\r\n\t// exists and intersects this green handle\r\n\tvar b = this.bends[1].bounds;\r\n\tvar w = b.width;\r\n\tvar h = b.height;\r\n\tvar bounds = new mxRectangle(Math.round(pt.x - w / 2), Math.round(pt.y - h / 2), w, h);\r\n\r\n\tif (this.manageLabelHandle)\r\n\t{\r\n\t\tthis.checkLabelHandle(bounds);\r\n\t}\r\n\telse if (this.handleImage == null && this.labelShape.visible && mxUtils.intersects(bounds, this.labelShape.bounds))\r\n\t{\r\n\t\tw = mxConstants.HANDLE_SIZE + 3;\r\n\t\th = mxConstants.HANDLE_SIZE + 3;\r\n\t\tbounds = new mxRectangle(Math.floor(pt.x - w / 2), Math.floor(pt.y - h / 2), w, h);\r\n\t}\r\n\r\n\tthis.bends[1].bounds = bounds;\r\n\tthis.bends[1].redraw();\r\n\t\r\n\tif (this.manageLabelHandle)\r\n\t{\r\n\t\tthis.checkLabelHandle(this.bends[1].bounds);\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nfunction mxEdgeSegmentHandler(state)\r\n{\r\n\tmxEdgeHandler.call(this, state);\r\n};\r\n\r\n/**\r\n * Extends mxEdgeHandler.\r\n */\r\nmxUtils.extend(mxEdgeSegmentHandler, mxElbowEdgeHandler);\r\n\r\n/**\r\n * Function: getCurrentPoints\r\n * \r\n * Returns the current absolute points.\r\n */\r\nmxEdgeSegmentHandler.prototype.getCurrentPoints = function()\r\n{\r\n\tvar pts = this.state.absolutePoints;\r\n\t\r\n\tif (pts != null)\r\n\t{\r\n\t\t// Special case for straight edges where we add a virtual middle handle for moving the edge\r\n\t\tvar tol = Math.max(1, this.graph.view.scale);\r\n\t\t\r\n\t\tif (pts.length == 2 || (pts.length == 3 &&\r\n\t\t\t(Math.abs(pts[0].x - pts[1].x) < tol && Math.abs(pts[1].x - pts[2].x) < tol ||\r\n\t\t\tMath.abs(pts[0].y - pts[1].y) < tol && Math.abs(pts[1].y - pts[2].y) < tol)))\r\n\t\t{\r\n\t\t\tvar cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2;\r\n\t\t\tvar cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2;\r\n\t\t\t\r\n\t\t\tpts = [pts[0], new mxPoint(cx, cy), new mxPoint(cx, cy), pts[pts.length - 1]];\t\r\n\t\t}\r\n\t}\r\n\r\n\treturn pts;\r\n};\r\n\r\n/**\r\n * Function: getPreviewPoints\r\n * \r\n * Updates the given preview state taking into account the state of the constraint handler.\r\n */\r\nmxEdgeSegmentHandler.prototype.getPreviewPoints = function(point)\r\n{\r\n\tif (this.isSource || this.isTarget)\r\n\t{\r\n\t\treturn mxElbowEdgeHandler.prototype.getPreviewPoints.apply(this, arguments);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar pts = this.getCurrentPoints();\r\n\t\tvar last = this.convertPoint(pts[0].clone(), false);\r\n\t\tpoint = this.convertPoint(point.clone(), false);\r\n\t\tvar result = [];\r\n\r\n\t\tfor (var i = 1; i < pts.length; i++)\r\n\t\t{\r\n\t\t\tvar pt = this.convertPoint(pts[i].clone(), false);\r\n\t\t\t\r\n\t\t\tif (i == this.index)\r\n\t\t\t{\r\n\t\t\t\tif (Math.round(last.x - pt.x) == 0)\r\n\t\t \t\t{\r\n\t\t\t\t\tlast.x = point.x;\r\n\t\t\t\t\tpt.x = point.x;\r\n\t\t \t\t}\r\n\t\t \t\t\r\n\t\t\t\tif (Math.round(last.y - pt.y) == 0)\r\n\t\t \t\t{\r\n\t\t \t\t\tlast.y = point.y;\r\n\t\t \t\t\tpt.y = point.y;\r\n\t\t \t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (i < pts.length - 1)\r\n\t\t\t{\r\n\t\t\t\tresult.push(pt);\r\n\t\t\t}\r\n\r\n\t\t\tlast = pt;\r\n\t\t}\r\n\t\t\r\n\t\t// Replaces single point that intersects with source or target\r\n\t\tif (result.length == 1)\r\n\t\t{\r\n\t\t\tvar source = this.state.getVisibleTerminalState(true);\r\n\t\t\tvar target = this.state.getVisibleTerminalState(false);\r\n\t\t\tvar scale = this.state.view.getScale();\r\n\t\t\tvar tr = this.state.view.getTranslate();\r\n\t\t\t\r\n\t\t\tvar x = result[0].x * scale + tr.x;\r\n\t\t\tvar y = result[0].y * scale + tr.y;\r\n\t\t\t\r\n\t\t\tif ((source != null && mxUtils.contains(source, x, y)) ||\r\n\t\t\t\t(target != null && mxUtils.contains(target, x, y)))\r\n\t\t\t{\r\n\t\t\t\tresult = [point, point];\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn result;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: updatePreviewState\r\n * \r\n * Overridden to perform optimization of the edge style result.\r\n */\r\nmxEdgeSegmentHandler.prototype.updatePreviewState = function(edge, point, terminalState, me)\r\n{\r\n\tmxEdgeHandler.prototype.updatePreviewState.apply(this, arguments);\r\n\r\n\t// Checks and corrects preview by running edge style again\r\n\tif (!this.isSource && !this.isTarget)\r\n\t{\r\n\t\tpoint = this.convertPoint(point.clone(), false);\r\n\t\tvar pts = edge.absolutePoints;\r\n\t\tvar pt0 = pts[0];\r\n\t\tvar pt1 = pts[1];\r\n\r\n\t\tvar result = [];\r\n\t\t\r\n\t\tfor (var i = 2; i < pts.length; i++)\r\n\t\t{\r\n\t\t\tvar pt2 = pts[i];\r\n\t\t\r\n\t\t\t// Merges adjacent segments only if more than 2 to allow for straight edges\r\n\t\t\tif ((Math.round(pt0.x - pt1.x) != 0 || Math.round(pt1.x - pt2.x) != 0) &&\r\n\t\t\t\t(Math.round(pt0.y - pt1.y) != 0 || Math.round(pt1.y - pt2.y) != 0))\r\n\t\t\t{\r\n\t\t\t\tresult.push(this.convertPoint(pt1.clone(), false));\r\n\t\t\t}\r\n\r\n\t\t\tpt0 = pt1;\r\n\t\t\tpt1 = pt2;\r\n\t\t}\r\n\t\t\r\n\t\tvar source = this.state.getVisibleTerminalState(true);\r\n\t\tvar target = this.state.getVisibleTerminalState(false);\r\n\t\tvar rpts = this.state.absolutePoints;\r\n\t\t\r\n\t\t// A straight line is represented by 3 handles\r\n\t\tif (result.length == 0 && (Math.round(pts[0].x - pts[pts.length - 1].x) == 0 ||\r\n\t\t\tMath.round(pts[0].y - pts[pts.length - 1].y) == 0))\r\n\t\t{\r\n\t\t\tresult = [point, point];\r\n\t\t}\r\n\t\t// Handles special case of transitions from straight vertical to routed\r\n\t\telse if (pts.length == 5 && result.length == 2 && source != null && target != null &&\r\n\t\t\t\trpts != null && Math.round(rpts[0].x - rpts[rpts.length - 1].x) == 0)\r\n\t\t{\r\n\t\t\tvar view = this.graph.getView();\r\n\t\t\tvar scale = view.getScale();\r\n\t\t\tvar tr = view.getTranslate();\r\n\t\t\t\r\n\t\t\tvar y0 = view.getRoutingCenterY(source) / scale - tr.y;\r\n\t\t\t\r\n\t\t\t// Use fixed connection point y-coordinate if one exists\r\n\t\t\tvar sc = this.graph.getConnectionConstraint(edge, source, true);\r\n\t\t\t\r\n\t\t\tif (sc != null)\r\n\t\t\t{\r\n\t\t\t\tvar pt = this.graph.getConnectionPoint(source, sc);\r\n\t\t\t\t\r\n\t\t\t\tif (pt != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.convertPoint(pt, false);\r\n\t\t\t\t\ty0 = pt.y;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar ye = view.getRoutingCenterY(target) / scale - tr.y;\r\n\t\t\t\r\n\t\t\t// Use fixed connection point y-coordinate if one exists\r\n\t\t\tvar tc = this.graph.getConnectionConstraint(edge, target, false);\r\n\t\t\t\r\n\t\t\tif (tc)\r\n\t\t\t{\r\n\t\t\t\tvar pt = this.graph.getConnectionPoint(target, tc);\r\n\t\t\t\t\r\n\t\t\t\tif (pt != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.convertPoint(pt, false);\r\n\t\t\t\t\tye = pt.y;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tresult = [new mxPoint(point.x, y0), new mxPoint(point.x, ye)];\r\n\t\t}\r\n\r\n\t\tthis.points = result;\r\n\r\n\t\t// LATER: Check if points and result are different\r\n\t\tedge.view.updateFixedTerminalPoints(edge, source, target);\r\n\t\tedge.view.updatePoints(edge, this.points, source, target);\r\n\t\tedge.view.updateFloatingTerminalPoints(edge, source, target);\r\n\t}\r\n};\r\n\r\n/**\r\n * Overriden to merge edge segments.\r\n */\r\nmxEdgeSegmentHandler.prototype.connect = function(edge, terminal, isSource, isClone, me)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\tvar geo = model.getGeometry(edge);\r\n\tvar result = null;\r\n\t\r\n\t// Merges adjacent edge segments\r\n\tif (geo != null && geo.points != null && geo.points.length > 0)\r\n\t{\r\n\t\tvar pts = this.abspoints;\r\n\t\tvar pt0 = pts[0];\r\n\t\tvar pt1 = pts[1];\r\n\t\tresult = [];\r\n\t\t\r\n\t\tfor (var i = 2; i < pts.length; i++)\r\n\t\t{\r\n\t\t\tvar pt2 = pts[i];\r\n\t\t\r\n\t\t\t// Merges adjacent segments only if more than 2 to allow for straight edges\r\n\t\t\tif ((Math.round(pt0.x - pt1.x) != 0 || Math.round(pt1.x - pt2.x) != 0) &&\r\n\t\t\t\t(Math.round(pt0.y - pt1.y) != 0 || Math.round(pt1.y - pt2.y) != 0))\r\n\t\t\t{\r\n\t\t\t\tresult.push(this.convertPoint(pt1.clone(), false));\r\n\t\t\t}\r\n\t\r\n\t\t\tpt0 = pt1;\r\n\t\t\tpt1 = pt2;\r\n\t\t}\r\n\t}\r\n\t\r\n\tmodel.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tif (result != null)\r\n\t\t{\r\n\t\t\tvar geo = model.getGeometry(edge);\r\n\t\t\t\r\n\t\t\tif (geo != null)\r\n\t\t\t{\r\n\t\t\t\tgeo = geo.clone();\r\n\t\t\t\tgeo.points = result;\r\n\t\t\t\t\r\n\t\t\t\tmodel.setGeometry(edge, geo);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tedge = mxEdgeHandler.prototype.connect.apply(this, arguments);\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tmodel.endUpdate();\r\n\t}\r\n\t\r\n\treturn edge;\r\n};\r\n\r\n/**\r\n * Function: getTooltipForNode\r\n * \r\n * Returns no tooltips.\r\n */\r\nmxEdgeSegmentHandler.prototype.getTooltipForNode = function(node)\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: createBends\r\n * \r\n * Adds custom bends for the center of each segment.\r\n */\r\nmxEdgeSegmentHandler.prototype.start = function(x, y, index)\r\n{\r\n\tmxEdgeHandler.prototype.start.apply(this, arguments);\r\n\t\r\n\tif (this.bends != null && this.bends[index] != null &&\r\n\t\t!this.isSource && !this.isTarget)\r\n\t{\r\n\t\tmxUtils.setOpacity(this.bends[index].node, 100);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createBends\r\n * \r\n * Adds custom bends for the center of each segment.\r\n */\r\nmxEdgeSegmentHandler.prototype.createBends = function()\r\n{\r\n\tvar bends = [];\r\n\t\r\n\t// Source\r\n\tvar bend = this.createHandleShape(0);\r\n\tthis.initBend(bend);\r\n\tbend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);\r\n\tbends.push(bend);\r\n\r\n\tvar pts = this.getCurrentPoints();\r\n\r\n\t// Waypoints (segment handles)\r\n\tif (this.graph.isCellBendable(this.state.cell))\r\n\t{\r\n\t\tif (this.points == null)\r\n\t\t{\r\n\t\t\tthis.points = [];\r\n\t\t}\r\n\r\n\t\tfor (var i = 0; i < pts.length - 1; i++)\r\n\t\t{\r\n\t\t\tbend = this.createVirtualBend();\r\n\t\t\tbends.push(bend);\r\n\t\t\tvar horizontal = Math.round(pts[i].x - pts[i + 1].x) == 0;\r\n\t\t\t\r\n\t\t\t// Special case where dy is 0 as well\r\n\t\t\tif (Math.round(pts[i].y - pts[i + 1].y) == 0 && i < pts.length - 2)\r\n\t\t\t{\r\n\t\t\t\thorizontal = Math.round(pts[i].x - pts[i + 2].x) == 0;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tbend.setCursor((horizontal) ? 'col-resize' : 'row-resize');\r\n\t\t\tthis.points.push(new mxPoint(0,0));\r\n\t\t}\r\n\t}\r\n\r\n\t// Target\r\n\tvar bend = this.createHandleShape(pts.length);\r\n\tthis.initBend(bend);\r\n\tbend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);\r\n\tbends.push(bend);\r\n\r\n\treturn bends;\r\n};\r\n\r\n/**\r\n * Function: redraw\r\n * \r\n * Overridden to invoke <refresh> before the redraw.\r\n */\r\nmxEdgeSegmentHandler.prototype.redraw = function()\r\n{\r\n\tthis.refresh();\r\n\tmxEdgeHandler.prototype.redraw.apply(this, arguments);\r\n};\r\n\r\n/**\r\n * Function: redrawInnerBends\r\n * \r\n * Updates the position of the custom bends.\r\n */\r\nmxEdgeSegmentHandler.prototype.redrawInnerBends = function(p0, pe)\r\n{\r\n\tif (this.graph.isCellBendable(this.state.cell))\r\n\t{\r\n\t\tvar pts = this.getCurrentPoints();\r\n\t\t\r\n\t\tif (pts != null && pts.length > 1)\r\n\t\t{\r\n\t\t\tvar straight = false;\r\n\t\t\t\r\n\t\t\t// Puts handle in the center of straight edges\r\n\t\t\tif (pts.length == 4 && Math.round(pts[1].x - pts[2].x) == 0 && Math.round(pts[1].y - pts[2].y) == 0)\r\n\t\t\t{\r\n\t\t\t\tstraight = true;\r\n\t\t\t\t\r\n\t\t\t\tif (Math.round(pts[0].y - pts[pts.length - 1].y) == 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2;\r\n\t\t\t\t\tpts[1] = new mxPoint(cx, pts[1].y);\r\n\t\t\t\t\tpts[2] = new mxPoint(cx, pts[2].y);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tvar cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2;\r\n\t\t\t\t\tpts[1] = new mxPoint(pts[1].x, cy);\r\n\t\t\t\t\tpts[2] = new mxPoint(pts[2].x, cy);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tfor (var i = 0; i < pts.length - 1; i++)\r\n\t\t\t{\r\n\t\t\t\tif (this.bends[i + 1] != null)\r\n\t\t\t\t{\r\n\t\t \t\t\tvar p0 = pts[i];\r\n\t \t\t\t\tvar pe = pts[i + 1];\r\n\t\t\t \t\tvar pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2);\r\n\t\t\t \t\tvar b = this.bends[i + 1].bounds;\r\n\t\t\t \t\tthis.bends[i + 1].bounds = new mxRectangle(Math.floor(pt.x - b.width / 2),\r\n\t\t\t \t\t\t\tMath.floor(pt.y - b.height / 2), b.width, b.height);\r\n\t\t\t\t \tthis.bends[i + 1].redraw();\r\n\t\t\t\t \t\r\n\t\t\t\t \tif (this.manageLabelHandle)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tthis.checkLabelHandle(this.bends[i + 1].bounds);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (straight)\r\n\t\t\t{\r\n\t\t\t\tmxUtils.setOpacity(this.bends[1].node, this.virtualBendOpacity);\r\n\t\t\t\tmxUtils.setOpacity(this.bends[3].node, this.virtualBendOpacity);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxKeyHandler\r\n *\r\n * Event handler that listens to keystroke events. This is not a singleton,\r\n * however, it is normally only required once if the target is the document\r\n * element (default).\r\n * \r\n * This handler installs a key event listener in the topmost DOM node and\r\n * processes all events that originate from descandants of <mxGraph.container>\r\n * or from the topmost DOM node. The latter means that all unhandled keystrokes\r\n * are handled by this object regardless of the focused state of the <graph>.\r\n * \r\n * Example:\r\n * \r\n * The following example creates a key handler that listens to the delete key\r\n * (46) and deletes the selection cells if the graph is enabled.\r\n * \r\n * (code)\r\n * var keyHandler = new mxKeyHandler(graph);\r\n * keyHandler.bindKey(46, function(evt)\r\n * {\r\n *   if (graph.isEnabled())\r\n *   {\r\n *     graph.removeCells();\r\n *   }\r\n * });\r\n * (end)\r\n * \r\n * Keycodes:\r\n * \r\n * See http://tinyurl.com/yp8jgl or http://tinyurl.com/229yqw for a list of\r\n * keycodes or install a key event listener into the document element and print\r\n * the key codes of the respective events to the console.\r\n * \r\n * To support the Command key and the Control key on the Mac, the following\r\n * code can be used.\r\n *\r\n * (code)\r\n * keyHandler.getFunction = function(evt)\r\n * {\r\n *   if (evt != null)\r\n *   {\r\n *     return (mxEvent.isControlDown(evt) || (mxClient.IS_MAC && evt.metaKey)) ? this.controlKeys[evt.keyCode] : this.normalKeys[evt.keyCode];\r\n *   }\r\n *   \r\n *   return null;\r\n * };\r\n * (end)\r\n * \r\n * Constructor: mxKeyHandler\r\n *\r\n * Constructs an event handler that executes functions bound to specific\r\n * keystrokes.\r\n * \r\n * Parameters:\r\n * \r\n * graph - Reference to the associated <mxGraph>.\r\n * target - Optional reference to the event target. If null, the document\r\n * element is used as the event target, that is, the object where the key\r\n * event listener is installed.\r\n */\r\nfunction mxKeyHandler(graph, target)\r\n{\r\n\tif (graph != null)\r\n\t{\r\n\t\tthis.graph = graph;\r\n\t\tthis.target = target || document.documentElement;\r\n\t\t\r\n\t\t// Creates the arrays to map from keycodes to functions\r\n\t\tthis.normalKeys = [];\r\n\t\tthis.shiftKeys = [];\r\n\t\tthis.controlKeys = [];\r\n\t\tthis.controlShiftKeys = [];\r\n\t\t\r\n\t\tthis.keydownHandler = mxUtils.bind(this, function(evt)\r\n\t\t{\r\n\t\t\tthis.keyDown(evt);\r\n\t\t});\r\n\r\n\t\t// Installs the keystroke listener in the target\r\n\t\tmxEvent.addListener(this.target, 'keydown', this.keydownHandler);\r\n\t\t\r\n\t\t// Automatically deallocates memory in IE\r\n\t\tif (mxClient.IS_IE)\r\n\t\t{\r\n\t\t\tmxEvent.addListener(window, 'unload',\r\n\t\t\t\tmxUtils.bind(this, function()\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.destroy();\r\n\t\t\t\t})\r\n\t\t\t);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Variable: graph\r\n * \r\n * Reference to the <mxGraph> associated with this handler.\r\n */\r\nmxKeyHandler.prototype.graph = null;\r\n\r\n/**\r\n * Variable: target\r\n * \r\n * Reference to the target DOM, that is, the DOM node where the key event\r\n * listeners are installed.\r\n */\r\nmxKeyHandler.prototype.target = null;\r\n\r\n/**\r\n * Variable: normalKeys\r\n * \r\n * Maps from keycodes to functions for non-pressed control keys.\r\n */\r\nmxKeyHandler.prototype.normalKeys = null;\r\n\r\n/**\r\n * Variable: shiftKeys\r\n * \r\n * Maps from keycodes to functions for pressed shift keys.\r\n */\r\nmxKeyHandler.prototype.shiftKeys = null;\r\n\r\n/**\r\n * Variable: controlKeys\r\n * \r\n * Maps from keycodes to functions for pressed control keys.\r\n */\r\nmxKeyHandler.prototype.controlKeys = null;\r\n\r\n/**\r\n * Variable: controlShiftKeys\r\n * \r\n * Maps from keycodes to functions for pressed control and shift keys.\r\n */\r\nmxKeyHandler.prototype.controlShiftKeys = null;\r\n\r\n/**\r\n * Variable: enabled\r\n * \r\n * Specifies if events are handled. Default is true.\r\n */\r\nmxKeyHandler.prototype.enabled = true;\r\n\r\n/**\r\n * Function: isEnabled\r\n * \r\n * Returns true if events are handled. This implementation returns\r\n * <enabled>.\r\n */\r\nmxKeyHandler.prototype.isEnabled = function()\r\n{\r\n\treturn this.enabled;\r\n};\r\n\r\n/**\r\n * Function: setEnabled\r\n * \r\n * Enables or disables event handling by updating <enabled>.\r\n * \r\n * Parameters:\r\n * \r\n * enabled - Boolean that specifies the new enabled state.\r\n */\r\nmxKeyHandler.prototype.setEnabled = function(enabled)\r\n{\r\n\tthis.enabled = enabled;\r\n};\r\n\r\n/**\r\n * Function: bindKey\r\n * \r\n * Binds the specified keycode to the given function. This binding is used\r\n * if the control key is not pressed.\r\n * \r\n * Parameters:\r\n *\r\n * code - Integer that specifies the keycode.\r\n * funct - JavaScript function that takes the key event as an argument.\r\n */\r\nmxKeyHandler.prototype.bindKey = function(code, funct)\r\n{\r\n\tthis.normalKeys[code] = funct;\r\n};\r\n\r\n/**\r\n * Function: bindShiftKey\r\n * \r\n * Binds the specified keycode to the given function. This binding is used\r\n * if the shift key is pressed.\r\n * \r\n * Parameters:\r\n *\r\n * code - Integer that specifies the keycode.\r\n * funct - JavaScript function that takes the key event as an argument.\r\n */\r\nmxKeyHandler.prototype.bindShiftKey = function(code, funct)\r\n{\r\n\tthis.shiftKeys[code] = funct;\r\n};\r\n\r\n/**\r\n * Function: bindControlKey\r\n * \r\n * Binds the specified keycode to the given function. This binding is used\r\n * if the control key is pressed.\r\n * \r\n * Parameters:\r\n *\r\n * code - Integer that specifies the keycode.\r\n * funct - JavaScript function that takes the key event as an argument.\r\n */\r\nmxKeyHandler.prototype.bindControlKey = function(code, funct)\r\n{\r\n\tthis.controlKeys[code] = funct;\r\n};\r\n\r\n/**\r\n * Function: bindControlShiftKey\r\n * \r\n * Binds the specified keycode to the given function. This binding is used\r\n * if the control and shift key are pressed.\r\n * \r\n * Parameters:\r\n *\r\n * code - Integer that specifies the keycode.\r\n * funct - JavaScript function that takes the key event as an argument.\r\n */\r\nmxKeyHandler.prototype.bindControlShiftKey = function(code, funct)\r\n{\r\n\tthis.controlShiftKeys[code] = funct;\r\n};\r\n\r\n/**\r\n * Function: isControlDown\r\n * \r\n * Returns true if the control key is pressed. This uses <mxEvent.isControlDown>.\r\n * \r\n * Parameters:\r\n * \r\n * evt - Key event whose control key pressed state should be returned.\r\n */\r\nmxKeyHandler.prototype.isControlDown = function(evt)\r\n{\r\n\treturn mxEvent.isControlDown(evt);\r\n};\r\n\r\n/**\r\n * Function: getFunction\r\n * \r\n * Returns the function associated with the given key event or null if no\r\n * function is associated with the given event.\r\n * \r\n * Parameters:\r\n * \r\n * evt - Key event whose associated function should be returned.\r\n */\r\nmxKeyHandler.prototype.getFunction = function(evt)\r\n{\r\n\tif (evt != null && !mxEvent.isAltDown(evt))\r\n\t{\r\n\t\tif (this.isControlDown(evt))\r\n\t\t{\r\n\t\t\tif (mxEvent.isShiftDown(evt))\r\n\t\t\t{\r\n\t\t\t\treturn this.controlShiftKeys[evt.keyCode];\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\treturn this.controlKeys[evt.keyCode];\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tif (mxEvent.isShiftDown(evt))\r\n\t\t\t{\r\n\t\t\t\treturn this.shiftKeys[evt.keyCode];\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\treturn this.normalKeys[evt.keyCode];\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn null;\r\n};\r\n\t\r\n/**\r\n * Function: isGraphEvent\r\n * \r\n * Returns true if the event should be processed by this handler, that is,\r\n * if the event source is either the target, one of its direct children, a\r\n * descendant of the <mxGraph.container>, or the <mxGraph.cellEditor> of the\r\n * <graph>.\r\n * \r\n * Parameters:\r\n * \r\n * evt - Key event that represents the keystroke.\r\n */\r\nmxKeyHandler.prototype.isGraphEvent = function(evt)\r\n{\r\n\tvar source = mxEvent.getSource(evt);\r\n\t\r\n\t// Accepts events from the target object or\r\n\t// in-place editing inside graph\r\n\tif ((source == this.target || source.parentNode == this.target) ||\r\n\t\t(this.graph.cellEditor != null && this.graph.cellEditor.isEventSource(evt)))\r\n\t{\r\n\t\treturn true;\r\n\t}\r\n\t\r\n\t// Accepts events from inside the container\r\n\treturn mxUtils.isAncestorNode(this.graph.container, source);\r\n};\r\n\r\n/**\r\n * Function: keyDown\r\n * \r\n * Handles the event by invoking the function bound to the respective keystroke\r\n * if <isEnabledForEvent> returns true for the given event and if\r\n * <isEventIgnored> returns false, except for escape for which\r\n * <isEventIgnored> is not invoked.\r\n * \r\n * Parameters:\r\n * \r\n * evt - Key event that represents the keystroke.\r\n */\r\nmxKeyHandler.prototype.keyDown = function(evt)\r\n{\r\n\tif (this.isEnabledForEvent(evt))\r\n\t{\r\n\t\t// Cancels the editing if escape is pressed\r\n\t\tif (evt.keyCode == 27 /* Escape */)\r\n\t\t{\r\n\t\t\tthis.escape(evt);\r\n\t\t}\r\n\t\t\r\n\t\t// Invokes the function for the keystroke\r\n\t\telse if (!this.isEventIgnored(evt))\r\n\t\t{\r\n\t\t\tvar boundFunction = this.getFunction(evt);\r\n\t\t\t\r\n\t\t\tif (boundFunction != null)\r\n\t\t\t{\r\n\t\t\t\tboundFunction(evt);\r\n\t\t\t\tmxEvent.consume(evt);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isEnabledForEvent\r\n * \r\n * Returns true if the given event should be handled. <isEventIgnored> is\r\n * called later if the event is not an escape key stroke, in which case\r\n * <escape> is called. This implementation returns true if <isEnabled>\r\n * returns true for both, this handler and <graph>, if the event is not\r\n * consumed and if <isGraphEvent> returns true.\r\n * \r\n * Parameters:\r\n * \r\n * evt - Key event that represents the keystroke.\r\n */\r\nmxKeyHandler.prototype.isEnabledForEvent = function(evt)\r\n{\r\n\treturn (this.graph.isEnabled() && !mxEvent.isConsumed(evt) &&\r\n\t\tthis.isGraphEvent(evt) && this.isEnabled());\r\n};\r\n\r\n/**\r\n * Function: isEventIgnored\r\n * \r\n * Returns true if the given keystroke should be ignored. This returns\r\n * graph.isEditing().\r\n * \r\n * Parameters:\r\n * \r\n * evt - Key event that represents the keystroke.\r\n */\r\nmxKeyHandler.prototype.isEventIgnored = function(evt)\r\n{\r\n\treturn this.graph.isEditing();\r\n};\r\n\r\n/**\r\n * Function: escape\r\n * \r\n * Hook to process ESCAPE keystrokes. This implementation invokes\r\n * <mxGraph.stopEditing> to cancel the current editing, connecting\r\n * and/or other ongoing modifications.\r\n * \r\n * Parameters:\r\n * \r\n * evt - Key event that represents the keystroke. Possible keycode in this\r\n * case is 27 (ESCAPE).\r\n */\r\nmxKeyHandler.prototype.escape = function(evt)\r\n{\r\n\tif (this.graph.isEscapeEnabled())\r\n\t{\r\n\t\tthis.graph.escape(evt);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Destroys the handler and all its references into the DOM. This does\r\n * normally not need to be called, it is called automatically when the\r\n * window unloads (in IE).\r\n */\r\nmxKeyHandler.prototype.destroy = function()\r\n{\r\n\tif (this.target != null && this.keydownHandler != null)\r\n\t{\r\n\t\tmxEvent.removeListener(this.target, 'keydown', this.keydownHandler);\r\n\t\tthis.keydownHandler = null;\r\n\t}\r\n\t\r\n\tthis.target = null;\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxTooltipHandler\r\n * \r\n * Graph event handler that displays tooltips. <mxGraph.getTooltip> is used to\r\n * get the tooltip for a cell or handle. This handler is built-into\r\n * <mxGraph.tooltipHandler> and enabled using <mxGraph.setTooltips>.\r\n *\r\n * Example:\r\n * \r\n * (code>\r\n * new mxTooltipHandler(graph);\r\n * (end)\r\n * \r\n * Constructor: mxTooltipHandler\r\n * \r\n * Constructs an event handler that displays tooltips with the specified\r\n * delay (in milliseconds). If no delay is specified then a default delay\r\n * of 500 ms (0.5 sec) is used.\r\n * \r\n * Parameters:\r\n * \r\n * graph - Reference to the enclosing <mxGraph>.\r\n * delay - Optional delay in milliseconds.\r\n */\r\nfunction mxTooltipHandler(graph, delay)\r\n{\r\n\tif (graph != null)\r\n\t{\r\n\t\tthis.graph = graph;\r\n\t\tthis.delay = delay || 500;\r\n\t\tthis.graph.addMouseListener(this);\r\n\t}\r\n};\r\n\r\n/**\r\n * Variable: zIndex\r\n * \r\n * Specifies the zIndex for the tooltip and its shadow. Default is 10005.\r\n */\r\nmxTooltipHandler.prototype.zIndex = 10005;\r\n\r\n/**\r\n * Variable: graph\r\n * \r\n * Reference to the enclosing <mxGraph>.\r\n */\r\nmxTooltipHandler.prototype.graph = null;\r\n\r\n/**\r\n * Variable: delay\r\n * \r\n * Delay to show the tooltip in milliseconds. Default is 500.\r\n */\r\nmxTooltipHandler.prototype.delay = null;\r\n\r\n/**\r\n * Variable: ignoreTouchEvents\r\n * \r\n * Specifies if touch and pen events should be ignored. Default is true.\r\n */\r\nmxTooltipHandler.prototype.ignoreTouchEvents = true;\r\n\r\n/**\r\n * Variable: hideOnHover\r\n * \r\n * Specifies if the tooltip should be hidden if the mouse is moved over the\r\n * current cell. Default is false.\r\n */\r\nmxTooltipHandler.prototype.hideOnHover = false;\r\n\r\n/**\r\n * Variable: destroyed\r\n * \r\n * True if this handler was destroyed using <destroy>.\r\n */\r\nmxTooltipHandler.prototype.destroyed = false;\r\n\r\n/**\r\n * Variable: enabled\r\n * \r\n * Specifies if events are handled. Default is true.\r\n */\r\nmxTooltipHandler.prototype.enabled = true;\r\n\r\n/**\r\n * Function: isEnabled\r\n * \r\n * Returns true if events are handled. This implementation\r\n * returns <enabled>.\r\n */\r\nmxTooltipHandler.prototype.isEnabled = function()\r\n{\r\n\treturn this.enabled;\r\n};\r\n\r\n/**\r\n * Function: setEnabled\r\n * \r\n * Enables or disables event handling. This implementation\r\n * updates <enabled>.\r\n */\r\nmxTooltipHandler.prototype.setEnabled = function(enabled)\r\n{\r\n\tthis.enabled = enabled;\r\n};\r\n\r\n/**\r\n * Function: isHideOnHover\r\n * \r\n * Returns <hideOnHover>.\r\n */\r\nmxTooltipHandler.prototype.isHideOnHover = function()\r\n{\r\n\treturn this.hideOnHover;\r\n};\r\n\r\n/**\r\n * Function: setHideOnHover\r\n * \r\n * Sets <hideOnHover>.\r\n */\r\nmxTooltipHandler.prototype.setHideOnHover = function(value)\r\n{\r\n\tthis.hideOnHover = value;\r\n};\r\n\r\n/**\r\n * Function: init\r\n * \r\n * Initializes the DOM nodes required for this tooltip handler.\r\n */\r\nmxTooltipHandler.prototype.init = function()\r\n{\r\n\tif (document.body != null)\r\n\t{\r\n\t\tthis.div = document.createElement('div');\r\n\t\tthis.div.className = 'mxTooltip';\r\n\t\tthis.div.style.visibility = 'hidden';\r\n\r\n\t\tdocument.body.appendChild(this.div);\r\n\r\n\t\tmxEvent.addGestureListeners(this.div, mxUtils.bind(this, function(evt)\r\n\t\t{\r\n\t\t\tthis.hideTooltip();\r\n\t\t}));\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getStateForEvent\r\n * \r\n * Returns the <mxCellState> to be used for showing a tooltip for this event.\r\n */\r\nmxTooltipHandler.prototype.getStateForEvent = function(me)\r\n{\r\n\treturn me.getState();\r\n};\r\n\r\n/**\r\n * Function: mouseDown\r\n * \r\n * Handles the event by initiating a rubberband selection. By consuming the\r\n * event all subsequent events of the gesture are redirected to this\r\n * handler.\r\n */\r\nmxTooltipHandler.prototype.mouseDown = function(sender, me)\r\n{\r\n\tthis.reset(me, false);\r\n\tthis.hideTooltip();\r\n};\r\n\r\n/**\r\n * Function: mouseMove\r\n * \r\n * Handles the event by updating the rubberband selection.\r\n */\r\nmxTooltipHandler.prototype.mouseMove = function(sender, me)\r\n{\r\n\tif (me.getX() != this.lastX || me.getY() != this.lastY)\r\n\t{\r\n\t\tthis.reset(me, true);\r\n\t\tvar state = this.getStateForEvent(me);\r\n\t\t\r\n\t\tif (this.isHideOnHover() || state != this.state || (me.getSource() != this.node &&\r\n\t\t\t(!this.stateSource || (state != null && this.stateSource ==\r\n\t\t\t(me.isSource(state.shape) || !me.isSource(state.text))))))\r\n\t\t{\r\n\t\t\tthis.hideTooltip();\r\n\t\t}\r\n\t}\r\n\t\r\n\tthis.lastX = me.getX();\r\n\tthis.lastY = me.getY();\r\n};\r\n\r\n/**\r\n * Function: mouseUp\r\n * \r\n * Handles the event by resetting the tooltip timer or hiding the existing\r\n * tooltip.\r\n */\r\nmxTooltipHandler.prototype.mouseUp = function(sender, me)\r\n{\r\n\tthis.reset(me, true);\r\n\tthis.hideTooltip();\r\n};\r\n\r\n\r\n/**\r\n * Function: resetTimer\r\n * \r\n * Resets the timer.\r\n */\r\nmxTooltipHandler.prototype.resetTimer = function()\r\n{\r\n\tif (this.thread != null)\r\n\t{\r\n\t\twindow.clearTimeout(this.thread);\r\n\t\tthis.thread = null;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: reset\r\n * \r\n * Resets and/or restarts the timer to trigger the display of the tooltip.\r\n */\r\nmxTooltipHandler.prototype.reset = function(me, restart, state)\r\n{\r\n\tif (!this.ignoreTouchEvents || mxEvent.isMouseEvent(me.getEvent()))\r\n\t{\r\n\t\tthis.resetTimer();\r\n\t\tstate = (state != null) ? state : this.getStateForEvent(me);\r\n\t\t\r\n\t\tif (restart && this.isEnabled() && state != null && (this.div == null ||\r\n\t\t\tthis.div.style.visibility == 'hidden'))\r\n\t\t{\r\n\t\t\tvar node = me.getSource();\r\n\t\t\tvar x = me.getX();\r\n\t\t\tvar y = me.getY();\r\n\t\t\tvar stateSource = me.isSource(state.shape) || me.isSource(state.text);\r\n\t\r\n\t\t\tthis.thread = window.setTimeout(mxUtils.bind(this, function()\r\n\t\t\t{\r\n\t\t\t\tif (!this.graph.isEditing() && !this.graph.popupMenuHandler.isMenuShowing() && !this.graph.isMouseDown)\r\n\t\t\t\t{\r\n\t\t\t\t\t// Uses information from inside event cause using the event at\r\n\t\t\t\t\t// this (delayed) point in time is not possible in IE as it no\r\n\t\t\t\t\t// longer contains the required information (member not found)\r\n\t\t\t\t\tvar tip = this.graph.getTooltip(state, node, x, y);\r\n\t\t\t\t\tthis.show(tip, x, y);\r\n\t\t\t\t\tthis.state = state;\r\n\t\t\t\t\tthis.node = node;\r\n\t\t\t\t\tthis.stateSource = stateSource;\r\n\t\t\t\t}\r\n\t\t\t}), this.delay);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: hide\r\n * \r\n * Hides the tooltip and resets the timer.\r\n */\r\nmxTooltipHandler.prototype.hide = function()\r\n{\r\n\tthis.resetTimer();\r\n\tthis.hideTooltip();\r\n};\r\n\r\n/**\r\n * Function: hideTooltip\r\n * \r\n * Hides the tooltip.\r\n */\r\nmxTooltipHandler.prototype.hideTooltip = function()\r\n{\r\n\tif (this.div != null)\r\n\t{\r\n\t\tthis.div.style.visibility = 'hidden';\r\n\t\tthis.div.innerHTML = '';\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: show\r\n * \r\n * Shows the tooltip for the specified cell and optional index at the\r\n * specified location (with a vertical offset of 10 pixels).\r\n */\r\nmxTooltipHandler.prototype.show = function(tip, x, y)\r\n{\r\n\tif (!this.destroyed && tip != null && tip.length > 0)\r\n\t{\r\n\t\t// Initializes the DOM nodes if required\r\n\t\tif (this.div == null)\r\n\t\t{\r\n\t\t\tthis.init();\r\n\t\t}\r\n\t\t\r\n\t\tvar origin = mxUtils.getScrollOrigin();\r\n\r\n\t\tthis.div.style.zIndex = this.zIndex;\r\n\t\tthis.div.style.left = (x + origin.x) + 'px';\r\n\t\tthis.div.style.top = (y + mxConstants.TOOLTIP_VERTICAL_OFFSET +\r\n\t\t\torigin.y) + 'px';\r\n\r\n\t\tif (!mxUtils.isNode(tip))\r\n\t\t{\t\r\n\t\t\tthis.div.innerHTML = tip.replace(/\\n/g, '<br>');\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.div.innerHTML = '';\r\n\t\t\tthis.div.appendChild(tip);\r\n\t\t}\r\n\t\t\r\n\t\tthis.div.style.visibility = '';\r\n\t\tmxUtils.fit(this.div);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Destroys the handler and all its resources and DOM nodes.\r\n */\r\nmxTooltipHandler.prototype.destroy = function()\r\n{\r\n\tif (!this.destroyed)\r\n\t{\r\n\t\tthis.graph.removeMouseListener(this);\r\n\t\tmxEvent.release(this.div);\r\n\t\t\r\n\t\tif (this.div != null && this.div.parentNode != null)\r\n\t\t{\r\n\t\t\tthis.div.parentNode.removeChild(this.div);\r\n\t\t}\r\n\t\t\r\n\t\tthis.destroyed = true;\r\n\t\tthis.div = null;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxCellTracker\r\n * \r\n * Event handler that highlights cells. Inherits from <mxCellMarker>.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * new mxCellTracker(graph, '#00FF00');\r\n * (end)\r\n * \r\n * For detecting dragEnter, dragOver and dragLeave on cells, the following\r\n * code can be used:\r\n * \r\n * (code)\r\n * graph.addMouseListener(\r\n * {\r\n *   cell: null,\r\n *   mouseDown: function(sender, me) { },\r\n *   mouseMove: function(sender, me)\r\n *   {\r\n *     var tmp = me.getCell();\r\n *     \r\n *     if (tmp != this.cell)\r\n *     {\r\n *       if (this.cell != null)\r\n *       {\r\n *         this.dragLeave(me.getEvent(), this.cell);\r\n *       }\r\n *       \r\n *       this.cell = tmp;\r\n *       \r\n *       if (this.cell != null)\r\n *       {\r\n *         this.dragEnter(me.getEvent(), this.cell);\r\n *       }\r\n *     }\r\n *     \r\n *     if (this.cell != null)\r\n *     {\r\n *       this.dragOver(me.getEvent(), this.cell);\r\n *     }\r\n *   },\r\n *   mouseUp: function(sender, me) { },\r\n *   dragEnter: function(evt, cell)\r\n *   {\r\n *     mxLog.debug('dragEnter', cell.value);\r\n *   },\r\n *   dragOver: function(evt, cell)\r\n *   {\r\n *     mxLog.debug('dragOver', cell.value);\r\n *   },\r\n *   dragLeave: function(evt, cell)\r\n *   {\r\n *     mxLog.debug('dragLeave', cell.value);\r\n *   }\r\n * });\r\n * (end)\r\n * \r\n * Constructor: mxCellTracker\r\n * \r\n * Constructs an event handler that highlights cells.\r\n * \r\n * Parameters:\r\n * \r\n * graph - Reference to the enclosing <mxGraph>.\r\n * color - Color of the highlight. Default is blue.\r\n * funct - Optional JavaScript function that is used to override\r\n * <mxCellMarker.getCell>.\r\n */\r\nfunction mxCellTracker(graph, color, funct)\r\n{\r\n\tmxCellMarker.call(this, graph, color);\r\n\r\n\tthis.graph.addMouseListener(this);\r\n\t\r\n\tif (funct != null)\r\n\t{\r\n\t\tthis.getCell = funct;\r\n\t}\r\n\t\r\n\t// Automatic deallocation of memory\r\n\tif (mxClient.IS_IE)\r\n\t{\r\n\t\tmxEvent.addListener(window, 'unload', mxUtils.bind(this, function()\r\n\t\t{\r\n\t\t\tthis.destroy();\r\n\t\t}));\r\n\t}\r\n};\r\n\r\n/**\r\n * Extends mxCellMarker.\r\n */\r\nmxUtils.extend(mxCellTracker, mxCellMarker);\r\n\r\n/**\r\n * Function: mouseDown\r\n * \r\n * Ignores the event. The event is not consumed.\r\n */\r\nmxCellTracker.prototype.mouseDown = function(sender, me) { };\r\n\r\n/**\r\n * Function: mouseMove\r\n * \r\n * Handles the event by highlighting the cell under the mousepointer if it\r\n * is over the hotspot region of the cell.\r\n */\r\nmxCellTracker.prototype.mouseMove = function(sender, me)\r\n{\r\n\tif (this.isEnabled())\r\n\t{\r\n\t\tthis.process(me);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: mouseUp\r\n * \r\n * Handles the event by reseting the highlight.\r\n */\r\nmxCellTracker.prototype.mouseUp = function(sender, me) { };\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Destroys the object and all its resources and DOM nodes. This doesn't\r\n * normally need to be called. It is called automatically when the window\r\n * unloads.\r\n */\r\nmxCellTracker.prototype.destroy = function()\r\n{\r\n\tif (!this.destroyed)\r\n\t{\r\n\t\tthis.destroyed = true;\r\n\r\n\t\tthis.graph.removeMouseListener(this);\r\n\t\tmxCellMarker.prototype.destroy.apply(this);\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxCellHighlight\r\n * \r\n * A helper class to highlight cells. Here is an example for a given cell.\r\n * \r\n * (code)\r\n * var highlight = new mxCellHighlight(graph, '#ff0000', 2);\r\n * highlight.highlight(graph.view.getState(cell)));\r\n * (end)\r\n * \r\n * Constructor: mxCellHighlight\r\n * \r\n * Constructs a cell highlight.\r\n */\r\nfunction mxCellHighlight(graph, highlightColor, strokeWidth, dashed)\r\n{\r\n\tif (graph != null)\r\n\t{\r\n\t\tthis.graph = graph;\r\n\t\tthis.highlightColor = (highlightColor != null) ? highlightColor : mxConstants.DEFAULT_VALID_COLOR;\r\n\t\tthis.strokeWidth = (strokeWidth != null) ? strokeWidth : mxConstants.HIGHLIGHT_STROKEWIDTH;\r\n\t\tthis.dashed = (dashed != null) ? dashed : false;\r\n\t\tthis.opacity = mxConstants.HIGHLIGHT_OPACITY;\r\n\r\n\t\t// Updates the marker if the graph changes\r\n\t\tthis.repaintHandler = mxUtils.bind(this, function()\r\n\t\t{\r\n\t\t\t// Updates reference to state\r\n\t\t\tif (this.state != null)\r\n\t\t\t{\r\n\t\t\t\tvar tmp = this.graph.view.getState(this.state.cell);\r\n\t\t\t\t\r\n\t\t\t\tif (tmp == null)\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.hide();\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.state = tmp;\r\n\t\t\t\t\tthis.repaint();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tthis.graph.getView().addListener(mxEvent.SCALE, this.repaintHandler);\r\n\t\tthis.graph.getView().addListener(mxEvent.TRANSLATE, this.repaintHandler);\r\n\t\tthis.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.repaintHandler);\r\n\t\tthis.graph.getModel().addListener(mxEvent.CHANGE, this.repaintHandler);\r\n\t\t\r\n\t\t// Hides the marker if the current root changes\r\n\t\tthis.resetHandler = mxUtils.bind(this, function()\r\n\t\t{\r\n\t\t\tthis.hide();\r\n\t\t});\r\n\r\n\t\tthis.graph.getView().addListener(mxEvent.DOWN, this.resetHandler);\r\n\t\tthis.graph.getView().addListener(mxEvent.UP, this.resetHandler);\r\n\t}\r\n};\r\n\r\n/**\r\n * Variable: keepOnTop\r\n * \r\n * Specifies if the highlights should appear on top of everything\r\n * else in the overlay pane. Default is false.\r\n */\r\nmxCellHighlight.prototype.keepOnTop = false;\r\n\r\n/**\r\n * Variable: graph\r\n * \r\n * Reference to the enclosing <mxGraph>.\r\n */\r\nmxCellHighlight.prototype.graph = true;\r\n\r\n/**\r\n * Variable: state\r\n * \r\n * Reference to the <mxCellState>.\r\n */\r\nmxCellHighlight.prototype.state = null;\r\n\r\n/**\r\n * Variable: spacing\r\n * \r\n * Specifies the spacing between the highlight for vertices and the vertex.\r\n * Default is 2.\r\n */\r\nmxCellHighlight.prototype.spacing = 2;\r\n\r\n/**\r\n * Variable: resetHandler\r\n * \r\n * Holds the handler that automatically invokes reset if the highlight\r\n * should be hidden.\r\n */\r\nmxCellHighlight.prototype.resetHandler = null;\r\n\r\n/**\r\n * Function: setHighlightColor\r\n * \r\n * Sets the color of the rectangle used to highlight drop targets.\r\n * \r\n * Parameters:\r\n * \r\n * color - String that represents the new highlight color.\r\n */\r\nmxCellHighlight.prototype.setHighlightColor = function(color)\r\n{\r\n\tthis.highlightColor = color;\r\n\t\r\n\tif (this.shape != null)\r\n\t{\r\n\t\tthis.shape.stroke = color;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: drawHighlight\r\n * \r\n * Creates and returns the highlight shape for the given state.\r\n */\r\nmxCellHighlight.prototype.drawHighlight = function()\r\n{\r\n\tthis.shape = this.createShape();\r\n\tthis.repaint();\r\n\r\n\tif (!this.keepOnTop && this.shape.node.parentNode.firstChild != this.shape.node)\r\n\t{\r\n\t\tthis.shape.node.parentNode.insertBefore(this.shape.node, this.shape.node.parentNode.firstChild);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createShape\r\n * \r\n * Creates and returns the highlight shape for the given state.\r\n */\r\nmxCellHighlight.prototype.createShape = function()\r\n{\r\n\tvar shape = this.graph.cellRenderer.createShape(this.state);\r\n\t\r\n\tshape.svgStrokeTolerance = this.graph.tolerance;\r\n\tshape.points = this.state.absolutePoints;\r\n\tshape.apply(this.state);\r\n\tshape.stroke = this.highlightColor;\r\n\tshape.opacity = this.opacity;\r\n\tshape.isDashed = this.dashed;\r\n\tshape.isShadow = false;\r\n\t\r\n\tshape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;\r\n\tshape.init(this.graph.getView().getOverlayPane());\r\n\tmxEvent.redirectMouseEvents(shape.node, this.graph, this.state);\r\n\t\r\n\tif (this.graph.dialect != mxConstants.DIALECT_SVG)\r\n\t{\r\n\t\tshape.pointerEvents = false;\r\n\t}\r\n\telse\r\n\t{\r\n\t\tshape.svgPointerEvents = 'stroke';\r\n\t}\r\n\t\r\n\treturn shape;\r\n};\r\n\r\n/**\r\n * Function: repaint\r\n * \r\n * Updates the highlight after a change of the model or view.\r\n */\r\nmxCellHighlight.prototype.getStrokeWidth = function(state)\r\n{\r\n\treturn this.strokeWidth;\r\n};\r\n\r\n/**\r\n * Function: repaint\r\n * \r\n * Updates the highlight after a change of the model or view.\r\n */\r\nmxCellHighlight.prototype.repaint = function()\r\n{\r\n\tif (this.state != null && this.shape != null)\r\n\t{\r\n\t\tthis.shape.scale = this.state.view.scale;\r\n\t\t\r\n\t\tif (this.graph.model.isEdge(this.state.cell))\r\n\t\t{\r\n\t\t\tthis.shape.strokewidth = this.getStrokeWidth();\r\n\t\t\tthis.shape.points = this.state.absolutePoints;\r\n\t\t\tthis.shape.outline = false;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.shape.bounds = new mxRectangle(this.state.x - this.spacing, this.state.y - this.spacing,\r\n\t\t\t\t\tthis.state.width + 2 * this.spacing, this.state.height + 2 * this.spacing);\r\n\t\t\tthis.shape.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0');\r\n\t\t\tthis.shape.strokewidth = this.getStrokeWidth() / this.state.view.scale;\r\n\t\t\tthis.shape.outline = true;\r\n\t\t}\r\n\r\n\t\t// Uses cursor from shape in highlight\r\n\t\tif (this.state.shape != null)\r\n\t\t{\r\n\t\t\tthis.shape.setCursor(this.state.shape.getCursor());\r\n\t\t}\r\n\t\t\r\n\t\t// Workaround for event transparency in VML with transparent color\r\n\t\t// is to use a non-transparent color with near zero opacity\r\n\t\tif (mxClient.IS_QUIRKS || document.documentMode == 8)\r\n\t\t{\r\n\t\t\tif (this.shape.stroke == 'transparent')\r\n\t\t\t{\r\n\t\t\t\t// KNOWN: Quirks mode does not seem to catch events if\r\n\t\t\t\t// we do not force an update of the DOM via a change such\r\n\t\t\t\t// as mxLog.debug. Since IE6 is EOL we do not add a fix.\r\n\t\t\t\tthis.shape.stroke = 'white';\r\n\t\t\t\tthis.shape.opacity = 1;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.shape.opacity = this.opacity;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tthis.shape.redraw();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: hide\r\n * \r\n * Resets the state of the cell marker.\r\n */\r\nmxCellHighlight.prototype.hide = function()\r\n{\r\n\tthis.highlight(null);\r\n};\r\n\r\n/**\r\n * Function: mark\r\n * \r\n * Marks the <markedState> and fires a <mark> event.\r\n */\r\nmxCellHighlight.prototype.highlight = function(state)\r\n{\r\n\tif (this.state != state)\r\n\t{\r\n\t\tif (this.shape != null)\r\n\t\t{\r\n\t\t\tthis.shape.destroy();\r\n\t\t\tthis.shape = null;\r\n\t\t}\r\n\r\n\t\tthis.state = state;\r\n\t\t\r\n\t\tif (this.state != null)\r\n\t\t{\r\n\t\t\tthis.drawHighlight();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isHighlightAt\r\n * \r\n * Returns true if this highlight is at the given position.\r\n */\r\nmxCellHighlight.prototype.isHighlightAt = function(x, y)\r\n{\r\n\tvar hit = false;\r\n\t\r\n\t// Quirks mode is currently not supported as it used a different coordinate system\r\n\tif (this.shape != null && document.elementFromPoint != null && !mxClient.IS_QUIRKS)\r\n\t{\r\n\t\tvar elt = document.elementFromPoint(x, y);\r\n\r\n\t\twhile (elt != null)\r\n\t\t{\r\n\t\t\tif (elt == this.shape.node)\r\n\t\t\t{\r\n\t\t\t\thit = true;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\telt = elt.parentNode;\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn hit;\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Destroys the handler and all its resources and DOM nodes.\r\n */\r\nmxCellHighlight.prototype.destroy = function()\r\n{\r\n\tthis.graph.getView().removeListener(this.resetHandler);\r\n\tthis.graph.getView().removeListener(this.repaintHandler);\r\n\tthis.graph.getModel().removeListener(this.repaintHandler);\r\n\t\r\n\tif (this.shape != null)\r\n\t{\r\n\t\tthis.shape.destroy();\r\n\t\tthis.shape = null;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxDefaultKeyHandler\r\n *\r\n * Binds keycodes to actionnames in an editor. This aggregates an internal\r\n * <handler> and extends the implementation of <mxKeyHandler.escape> to not\r\n * only cancel the editing, but also hide the properties dialog and fire an\r\n * <mxEditor.escape> event via <editor>. An instance of this class is created\r\n * by <mxEditor> and stored in <mxEditor.keyHandler>.\r\n * \r\n * Example:\r\n * \r\n * Bind the delete key to the delete action in an existing editor.\r\n * \r\n * (code)\r\n * var keyHandler = new mxDefaultKeyHandler(editor);\r\n * keyHandler.bindAction(46, 'delete');\r\n * (end)\r\n *\r\n * Codec:\r\n * \r\n * This class uses the <mxDefaultKeyHandlerCodec> to read configuration\r\n * data into an existing instance. See <mxDefaultKeyHandlerCodec> for a\r\n * description of the configuration format.\r\n * \r\n * Keycodes:\r\n * \r\n * See <mxKeyHandler>.\r\n * \r\n * An <mxEvent.ESCAPE> event is fired via the editor if the escape key is\r\n * pressed.\r\n * \r\n * Constructor: mxDefaultKeyHandler\r\n *\r\n * Constructs a new default key handler for the <mxEditor.graph> in the\r\n * given <mxEditor>. (The editor may be null if a prototypical instance for\r\n * a <mxDefaultKeyHandlerCodec> is created.)\r\n * \r\n * Parameters:\r\n * \r\n * editor - Reference to the enclosing <mxEditor>.\r\n */\r\nfunction mxDefaultKeyHandler(editor)\r\n{\r\n\tif (editor != null)\r\n\t{\r\n\t\tthis.editor = editor;\r\n\t\tthis.handler = new mxKeyHandler(editor.graph);\r\n\t\t\r\n\t\t// Extends the escape function of the internal key\r\n\t\t// handle to hide the properties dialog and fire\r\n\t\t// the escape event via the editor instance\r\n\t\tvar old = this.handler.escape;\r\n\t\t\r\n\t\tthis.handler.escape = function(evt)\r\n\t\t{\r\n\t\t\told.apply(this, arguments);\r\n\t\t\teditor.hideProperties();\r\n\t\t\teditor.fireEvent(new mxEventObject(mxEvent.ESCAPE, 'event', evt));\r\n\t\t};\r\n\t}\r\n};\r\n\t\r\n/**\r\n * Variable: editor\r\n *\r\n * Reference to the enclosing <mxEditor>.\r\n */\r\nmxDefaultKeyHandler.prototype.editor = null;\r\n\r\n/**\r\n * Variable: handler\r\n *\r\n * Holds the <mxKeyHandler> for key event handling.\r\n */\r\nmxDefaultKeyHandler.prototype.handler = null;\r\n\r\n/**\r\n * Function: bindAction\r\n *\r\n * Binds the specified keycode to the given action in <editor>. The\r\n * optional control flag specifies if the control key must be pressed\r\n * to trigger the action.\r\n *\r\n * Parameters:\r\n *\r\n * code - Integer that specifies the keycode.\r\n * action - Name of the action to execute in <editor>.\r\n * control - Optional boolean that specifies if control must be pressed.\r\n * Default is false.\r\n */\r\nmxDefaultKeyHandler.prototype.bindAction = function (code, action, control)\r\n{\r\n\tvar keyHandler = mxUtils.bind(this, function()\r\n\t{\r\n\t\tthis.editor.execute(action);\r\n\t});\r\n\r\n\t// Binds the function to control-down keycode\r\n\tif (control)\r\n\t{\r\n\t\tthis.handler.bindControlKey(code, keyHandler);\r\n\t}\r\n\r\n\t// Binds the function to the normal keycode\r\n\telse\r\n\t{\r\n\t\tthis.handler.bindKey(code, keyHandler);\t\t\t\t\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n *\r\n * Destroys the <handler> associated with this object. This does normally\r\n * not need to be called, the <handler> is destroyed automatically when the\r\n * window unloads (in IE) by <mxEditor>.\r\n */\r\nmxDefaultKeyHandler.prototype.destroy = function ()\r\n{\r\n\tthis.handler.destroy();\r\n\tthis.handler = null;\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxDefaultPopupMenu\r\n *\r\n * Creates popupmenus for mouse events. This object holds an XML node\r\n * which is a description of the popup menu to be created. In\r\n * <createMenu>, the configuration is applied to the context and\r\n * the resulting menu items are added to the menu dynamically. See\r\n * <createMenu> for a description of the configuration format.\r\n * \r\n * This class does not create the DOM nodes required for the popup menu, it\r\n * only parses an XML description to invoke the respective methods on an\r\n * <mxPopupMenu> each time the menu is displayed.\r\n *\r\n * Codec:\r\n * \r\n * This class uses the <mxDefaultPopupMenuCodec> to read configuration\r\n * data into an existing instance, however, the actual parsing is done\r\n * by this class during program execution, so the format is described\r\n * below.\r\n * \r\n * Constructor: mxDefaultPopupMenu\r\n *\r\n * Constructs a new popupmenu-factory based on given configuration.\r\n *\r\n * Paramaters:\r\n *\r\n * config - XML node that contains the configuration data.\r\n */\r\nfunction mxDefaultPopupMenu(config)\r\n{\r\n\tthis.config = config;\r\n};\r\n\r\n/**\r\n * Variable: imageBasePath\r\n *\r\n * Base path for all icon attributes in the config. Default is null.\r\n */\r\nmxDefaultPopupMenu.prototype.imageBasePath = null;\r\n\r\n/**\r\n * Variable: config\r\n *\r\n * XML node used as the description of new menu items. This node is\r\n * used in <createMenu> to dynamically create the menu items if their\r\n * respective conditions evaluate to true for the given arguments.\r\n */\r\nmxDefaultPopupMenu.prototype.config = null;\r\n\r\n/**\r\n * Function: createMenu\r\n *\r\n * This function is called from <mxEditor> to add items to the\r\n * given menu based on <config>. The config is a sequence of\r\n * the following nodes and attributes.\r\n *\r\n * Child Nodes: \r\n *\r\n * add - Adds a new menu item. See below for attributes.\r\n * separator - Adds a separator. No attributes.\r\n * condition - Adds a custom condition. Name attribute.\r\n * \r\n * The add-node may have a child node that defines a function to be invoked\r\n * before the action is executed (or instead of an action to be executed).\r\n *\r\n * Attributes:\r\n *\r\n * as - Resource key for the label (needs entry in property file).\r\n * action - Name of the action to execute in enclosing editor.\r\n * icon - Optional icon (relative/absolute URL).\r\n * iconCls - Optional CSS class for the icon.\r\n * if - Optional name of condition that must be true (see below).\r\n * enabled-if - Optional name of condition that specifies if the menu item\r\n * should be enabled.\r\n * name - Name of custom condition. Only for condition nodes.\r\n *\r\n * Conditions:\r\n *\r\n * nocell - No cell under the mouse.\r\n * ncells - More than one cell selected.\r\n * notRoot - Drilling position is other than home.\r\n * cell - Cell under the mouse.\r\n * notEmpty - Exactly one cell with children under mouse.\r\n * expandable - Exactly one expandable cell under mouse.\r\n * collapsable - Exactly one collapsable cell under mouse.\r\n * validRoot - Exactly one cell which is a possible root under mouse.\r\n * swimlane - Exactly one cell which is a swimlane under mouse.\r\n *\r\n * Example:\r\n *\r\n * To add a new item for a given action to the popupmenu:\r\n * \r\n * (code)\r\n * <mxDefaultPopupMenu as=\"popupHandler\">\r\n *   <add as=\"delete\" action=\"delete\" icon=\"images/delete.gif\" if=\"cell\"/>\r\n * </mxDefaultPopupMenu>\r\n * (end)\r\n * \r\n * To add a new item for a custom function:\r\n * \r\n * (code)\r\n * <mxDefaultPopupMenu as=\"popupHandler\">\r\n *   <add as=\"action1\"><![CDATA[\r\n *\t\tfunction (editor, cell, evt)\r\n *\t\t{\r\n *\t\t\teditor.execute('action1', cell, 'myArg');\r\n *\t\t}\r\n *   ]]></add>\r\n * </mxDefaultPopupMenu>\r\n * (end)\r\n * \r\n * The above example invokes action1 with an additional third argument via\r\n * the editor instance. The third argument is passed to the function that\r\n * defines action1. If the add-node has no action-attribute, then only the\r\n * function defined in the text content is executed, otherwise first the\r\n * function and then the action defined in the action-attribute is\r\n * executed. The function in the text content has 3 arguments, namely the\r\n * <mxEditor> instance, the <mxCell> instance under the mouse, and the\r\n * native mouse event.\r\n *\r\n * Custom Conditions:\r\n *\r\n * To add a new condition for popupmenu items:\r\n *  \r\n * (code)\r\n * <condition name=\"condition1\"><![CDATA[\r\n *   function (editor, cell, evt)\r\n *   {\r\n *     return cell != null;\r\n *   }\r\n * ]]></condition>\r\n * (end)\r\n * \r\n * The new condition can then be used in any item as follows:\r\n * \r\n * (code)\r\n * <add as=\"action1\" action=\"action1\" icon=\"action1.gif\" if=\"condition1\"/>\r\n * (end)\r\n * \r\n * The order in which the items and conditions appear is not significant as\r\n * all connditions are evaluated before any items are created.\r\n * \r\n * Parameters:\r\n *\r\n * editor - Enclosing <mxEditor> instance.\r\n * menu - <mxPopupMenu> that is used for adding items and separators.\r\n * cell - Optional <mxCell> which is under the mousepointer.\r\n * evt - Optional mouse event which triggered the menu. \r\n */\r\nmxDefaultPopupMenu.prototype.createMenu = function(editor, menu, cell, evt)\r\n{\r\n\tif (this.config != null)\r\n\t{\r\n\t\tvar conditions = this.createConditions(editor, cell, evt);\r\n\t\tvar item = this.config.firstChild;\r\n\r\n\t\tthis.addItems(editor, menu, cell, evt, conditions, item, null);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: addItems\r\n * \r\n * Recursively adds the given items and all of its children into the given menu.\r\n * \r\n * Parameters:\r\n *\r\n * editor - Enclosing <mxEditor> instance.\r\n * menu - <mxPopupMenu> that is used for adding items and separators.\r\n * cell - Optional <mxCell> which is under the mousepointer.\r\n * evt - Optional mouse event which triggered the menu.\r\n * conditions - Array of names boolean conditions.\r\n * item - XML node that represents the current menu item.\r\n * parent - DOM node that represents the parent menu item.\r\n */\r\nmxDefaultPopupMenu.prototype.addItems = function(editor, menu, cell, evt, conditions, item, parent)\r\n{\r\n\tvar addSeparator = false;\r\n\t\r\n\twhile (item != null)\r\n\t{\r\n\t\tif (item.nodeName == 'add')\r\n\t\t{\r\n\t\t\tvar condition = item.getAttribute('if');\r\n\t\t\t\r\n\t\t\tif (condition == null || conditions[condition])\r\n\t\t\t{\r\n\t\t\t\tvar as = item.getAttribute('as');\r\n\t\t\t\tas = mxResources.get(as) || as;\r\n\t\t\t\tvar funct = mxUtils.eval(mxUtils.getTextContent(item));\r\n\t\t\t\tvar action = item.getAttribute('action');\r\n\t\t\t\tvar icon = item.getAttribute('icon');\r\n\t\t\t\tvar iconCls = item.getAttribute('iconCls');\r\n\t\t\t\tvar enabledCond = item.getAttribute('enabled-if');\r\n\t\t\t\tvar enabled = enabledCond == null || conditions[enabledCond];\r\n\t\t\t\t\r\n\t\t\t\tif (addSeparator)\r\n\t\t\t\t{\r\n\t\t\t\t\tmenu.addSeparator(parent);\r\n\t\t\t\t\taddSeparator = false;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (icon != null && this.imageBasePath)\r\n\t\t\t\t{\r\n\t\t\t\t\ticon = this.imageBasePath + icon;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tvar row = this.addAction(menu, editor, as, icon, funct, action, cell, parent, iconCls, enabled);\r\n\t\t\t\tthis.addItems(editor, menu, cell, evt, conditions, item.firstChild, row);\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (item.nodeName == 'separator')\r\n\t\t{\r\n\t\t\taddSeparator = true;\r\n\t\t}\r\n\t\t\r\n\t\titem = item.nextSibling;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: addAction\r\n *\r\n * Helper method to bind an action to a new menu item.\r\n * \r\n * Parameters:\r\n *\r\n * menu - <mxPopupMenu> that is used for adding items and separators.\r\n * editor - Enclosing <mxEditor> instance.\r\n * lab - String that represents the label of the menu item.\r\n * icon - Optional URL that represents the icon of the menu item.\r\n * action - Optional name of the action to execute in the given editor.\r\n * funct - Optional function to execute before the optional action. The\r\n * function takes an <mxEditor>, the <mxCell> under the mouse and the\r\n * mouse event that triggered the call.\r\n * cell - Optional <mxCell> to use as an argument for the action.\r\n * parent - DOM node that represents the parent menu item.\r\n * iconCls - Optional CSS class for the menu icon.\r\n * enabled - Optional boolean that specifies if the menu item is enabled.\r\n * Default is true.\r\n */\r\nmxDefaultPopupMenu.prototype.addAction = function(menu, editor, lab, icon, funct, action, cell, parent, iconCls, enabled)\r\n{\r\n\tvar clickHandler = function(evt)\r\n\t{\r\n\t\tif (typeof(funct) == 'function')\r\n\t\t{\r\n\t\t\tfunct.call(editor, editor, cell, evt);\r\n\t\t}\r\n\t\t\r\n\t\tif (action != null)\r\n\t\t{\r\n\t\t\teditor.execute(action, cell, evt);\r\n\t\t}\r\n\t};\r\n\t\r\n\treturn menu.addItem(lab, icon, clickHandler, parent, iconCls, enabled);\r\n};\r\n\r\n/**\r\n * Function: createConditions\r\n * \r\n * Evaluates the default conditions for the given context.\r\n */\r\nmxDefaultPopupMenu.prototype.createConditions = function(editor, cell, evt)\r\n{\r\n\t// Creates array with conditions\r\n\tvar model = editor.graph.getModel();\r\n\tvar childCount = model.getChildCount(cell);\r\n\t\r\n\t// Adds some frequently used conditions\r\n\tvar conditions = [];\r\n\tconditions['nocell'] = cell == null;\r\n\tconditions['ncells'] = editor.graph.getSelectionCount() > 1;\r\n\tconditions['notRoot'] = model.getRoot() !=\r\n\t\tmodel.getParent(editor.graph.getDefaultParent());\r\n\tconditions['cell'] = cell != null;\r\n\t\r\n\tvar isCell = cell != null && editor.graph.getSelectionCount() == 1;\r\n\tconditions['nonEmpty'] = isCell && childCount > 0;\r\n\tconditions['expandable'] = isCell && editor.graph.isCellFoldable(cell, false);\r\n\tconditions['collapsable'] = isCell && editor.graph.isCellFoldable(cell, true);\r\n\tconditions['validRoot'] = isCell && editor.graph.isValidRoot(cell);\r\n\tconditions['emptyValidRoot'] = conditions['validRoot'] && childCount == 0;\r\n\tconditions['swimlane'] = isCell && editor.graph.isSwimlane(cell);\r\n\r\n\t// Evaluates dynamic conditions from config file\r\n\tvar condNodes = this.config.getElementsByTagName('condition');\r\n\t\r\n\tfor (var i=0; i<condNodes.length; i++)\r\n\t{\r\n\t\tvar funct = mxUtils.eval(mxUtils.getTextContent(condNodes[i]));\r\n\t\tvar name = condNodes[i].getAttribute('name');\r\n\t\t\r\n\t\tif (name != null && typeof(funct) == 'function')\r\n\t\t{\r\n\t\t\tconditions[name] = funct(editor, cell, evt);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn conditions;\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxDefaultToolbar\r\n *\r\n * Toolbar for the editor. This modifies the state of the graph\r\n * or inserts new cells upon mouse clicks.\r\n * \r\n * Example:\r\n * \r\n * Create a toolbar with a button to copy the selection into the clipboard,\r\n * and a combo box with one action to paste the selection from the clipboard\r\n * into the graph.\r\n * \r\n * (code)\r\n * var toolbar = new mxDefaultToolbar(container, editor);\r\n * toolbar.addItem('Copy', null, 'copy');\r\n * \r\n * var combo = toolbar.addActionCombo('More actions...');\r\n * toolbar.addActionOption(combo, 'Paste', 'paste');\r\n * (end) \r\n *\r\n * Codec:\r\n * \r\n * This class uses the <mxDefaultToolbarCodec> to read configuration\r\n * data into an existing instance. See <mxDefaultToolbarCodec> for a\r\n * description of the configuration format.\r\n * \r\n * Constructor: mxDefaultToolbar\r\n *\r\n * Constructs a new toolbar for the given container and editor. The\r\n * container and editor may be null if a prototypical instance for a\r\n * <mxDefaultKeyHandlerCodec> is created.\r\n *\r\n * Parameters:\r\n *\r\n * container - DOM node that contains the toolbar.\r\n * editor - Reference to the enclosing <mxEditor>. \r\n */\r\nfunction mxDefaultToolbar(container, editor)\r\n{\r\n\tthis.editor = editor;\r\n\r\n\tif (container != null && editor != null)\r\n\t{\r\n\t\tthis.init(container);\r\n\t}\r\n};\r\n\t\r\n/**\r\n * Variable: editor\r\n *\r\n * Reference to the enclosing <mxEditor>.\r\n */\r\nmxDefaultToolbar.prototype.editor = null;\r\n\r\n/**\r\n * Variable: toolbar\r\n *\r\n * Holds the internal <mxToolbar>.\r\n */\r\nmxDefaultToolbar.prototype.toolbar = null;\r\n\r\n/**\r\n * Variable: resetHandler\r\n *\r\n * Reference to the function used to reset the <toolbar>.\r\n */\r\nmxDefaultToolbar.prototype.resetHandler = null;\r\n\r\n/**\r\n * Variable: spacing\r\n *\r\n * Defines the spacing between existing and new vertices in\r\n * gridSize units when a new vertex is dropped on an existing\r\n * cell. Default is 4 (40 pixels).\r\n */\r\nmxDefaultToolbar.prototype.spacing = 4;\r\n\r\n/**\r\n * Variable: connectOnDrop\r\n * \r\n * Specifies if elements should be connected if new cells are dropped onto\r\n * connectable elements. Default is false.\r\n */\r\nmxDefaultToolbar.prototype.connectOnDrop = false;\r\n\r\n/**\r\n * Variable: init\r\n * \r\n * Constructs the <toolbar> for the given container and installs a listener\r\n * that updates the <mxEditor.insertFunction> on <editor> if an item is\r\n * selected in the toolbar. This assumes that <editor> is not null.\r\n *\r\n * Parameters:\r\n *\r\n * container - DOM node that contains the toolbar.\r\n */\r\nmxDefaultToolbar.prototype.init = function(container)\r\n{\r\n\tif (container != null)\r\n\t{\r\n\t\tthis.toolbar = new mxToolbar(container);\r\n\t\t\r\n\t\t// Installs the insert function in the editor if an item is\r\n\t\t// selected in the toolbar\r\n\t\tthis.toolbar.addListener(mxEvent.SELECT, mxUtils.bind(this, function(sender, evt)\r\n\t\t{\r\n\t\t\tvar funct = evt.getProperty('function');\r\n\t\t\t\r\n\t\t\tif (funct != null)\r\n\t\t\t{\r\n\t\t\t\tthis.editor.insertFunction = mxUtils.bind(this, function()\r\n\t\t\t\t{\r\n\t\t\t\t\tfunct.apply(this, arguments);\r\n\t\t\t\t\tthis.toolbar.resetMode();\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tthis.editor.insertFunction = null;\r\n\t\t\t}\r\n\t\t}));\r\n\t\t\r\n\t\t// Resets the selected tool after a doubleclick or escape keystroke\r\n\t\tthis.resetHandler = mxUtils.bind(this, function()\r\n\t\t{\r\n\t\t\tif (this.toolbar != null)\r\n\t\t\t{\r\n\t\t\t\tthis.toolbar.resetMode(true);\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tthis.editor.graph.addListener(mxEvent.DOUBLE_CLICK, this.resetHandler);\r\n\t\tthis.editor.addListener(mxEvent.ESCAPE, this.resetHandler);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: addItem\r\n *\r\n * Adds a new item that executes the given action in <editor>. The title,\r\n * icon and pressedIcon are used to display the toolbar item.\r\n * \r\n * Parameters:\r\n *\r\n * title - String that represents the title (tooltip) for the item.\r\n * icon - URL of the icon to be used for displaying the item.\r\n * action - Name of the action to execute when the item is clicked.\r\n * pressed - Optional URL of the icon for the pressed state.\r\n */\r\nmxDefaultToolbar.prototype.addItem = function(title, icon, action, pressed)\r\n{\r\n\tvar clickHandler = mxUtils.bind(this, function()\r\n\t{\r\n\t\tif (action != null && action.length > 0)\r\n\t\t{\r\n\t\t\tthis.editor.execute(action);\r\n\t\t}\r\n\t});\r\n\t\r\n\treturn this.toolbar.addItem(title, icon, clickHandler, pressed);\r\n};\r\n\r\n/**\r\n * Function: addSeparator\r\n *\r\n * Adds a vertical separator using the optional icon.\r\n * \r\n * Parameters:\r\n * \r\n * icon - Optional URL of the icon that represents the vertical separator.\r\n * Default is <mxClient.imageBasePath> + '/separator.gif'.\r\n */\r\nmxDefaultToolbar.prototype.addSeparator = function(icon)\r\n{\r\n\ticon = icon || mxClient.imageBasePath + '/separator.gif';\r\n\tthis.toolbar.addSeparator(icon);\r\n};\r\n\t\r\n/**\r\n * Function: addCombo\r\n *\r\n * Helper method to invoke <mxToolbar.addCombo> on <toolbar> and return the\r\n * resulting DOM node.\r\n */\r\nmxDefaultToolbar.prototype.addCombo = function()\r\n{\r\n\treturn this.toolbar.addCombo();\r\n};\r\n\t\t\r\n/**\r\n * Function: addActionCombo\r\n *\r\n * Helper method to invoke <mxToolbar.addActionCombo> on <toolbar> using\r\n * the given title and return the resulting DOM node.\r\n * \r\n * Parameters:\r\n * \r\n * title - String that represents the title of the combo.\r\n */\r\nmxDefaultToolbar.prototype.addActionCombo = function(title)\r\n{\r\n\treturn this.toolbar.addActionCombo(title);\r\n};\r\n\r\n/**\r\n * Function: addActionOption\r\n *\r\n * Binds the given action to a option with the specified label in the\r\n * given combo. Combo is an object returned from an earlier call to\r\n * <addCombo> or <addActionCombo>.\r\n * \r\n * Parameters:\r\n * \r\n * combo - DOM node that represents the combo box.\r\n * title - String that represents the title of the combo.\r\n * action - Name of the action to execute in <editor>.\r\n */\r\nmxDefaultToolbar.prototype.addActionOption = function(combo, title, action)\r\n{\r\n\tvar clickHandler = mxUtils.bind(this, function()\r\n\t{\r\n\t\tthis.editor.execute(action);\r\n\t});\r\n\t\r\n\tthis.addOption(combo, title, clickHandler);\r\n};\r\n\r\n/**\r\n * Function: addOption\r\n *\r\n * Helper method to invoke <mxToolbar.addOption> on <toolbar> and return\r\n * the resulting DOM node that represents the option.\r\n * \r\n * Parameters:\r\n * \r\n * combo - DOM node that represents the combo box.\r\n * title - String that represents the title of the combo.\r\n * value - Object that represents the value of the option.\r\n */\r\nmxDefaultToolbar.prototype.addOption = function(combo, title, value)\r\n{\r\n\treturn this.toolbar.addOption(combo, title, value);\r\n};\r\n\t\r\n/**\r\n * Function: addMode\r\n *\r\n * Creates an item for selecting the given mode in the <editor>'s graph.\r\n * Supported modenames are select, connect and pan.\r\n * \r\n * Parameters:\r\n * \r\n * title - String that represents the title of the item.\r\n * icon - URL of the icon that represents the item.\r\n * mode - String that represents the mode name to be used in\r\n * <mxEditor.setMode>.\r\n * pressed - Optional URL of the icon that represents the pressed state.\r\n * funct - Optional JavaScript function that takes the <mxEditor> as the\r\n * first and only argument that is executed after the mode has been\r\n * selected.\r\n */\r\nmxDefaultToolbar.prototype.addMode = function(title, icon, mode, pressed, funct)\r\n{\r\n\tvar clickHandler = mxUtils.bind(this, function()\r\n\t{\r\n\t\tthis.editor.setMode(mode);\r\n\t\t\r\n\t\tif (funct != null)\r\n\t\t{\r\n\t\t\tfunct(this.editor);\r\n\t\t}\r\n\t});\r\n\t\r\n\treturn this.toolbar.addSwitchMode(title, icon, clickHandler, pressed);\r\n};\r\n\r\n/**\r\n * Function: addPrototype\r\n *\r\n * Creates an item for inserting a clone of the specified prototype cell into\r\n * the <editor>'s graph. The ptype may either be a cell or a function that\r\n * returns a cell.\r\n * \r\n * Parameters:\r\n * \r\n * title - String that represents the title of the item.\r\n * icon - URL of the icon that represents the item.\r\n * ptype - Function or object that represents the prototype cell. If ptype\r\n * is a function then it is invoked with no arguments to create new\r\n * instances.\r\n * pressed - Optional URL of the icon that represents the pressed state.\r\n * insert - Optional JavaScript function that handles an insert of the new\r\n * cell. This function takes the <mxEditor>, new cell to be inserted, mouse\r\n * event and optional <mxCell> under the mouse pointer as arguments.\r\n * toggle - Optional boolean that specifies if the item can be toggled.\r\n * Default is true.\r\n */\r\nmxDefaultToolbar.prototype.addPrototype = function(title, icon, ptype, pressed, insert, toggle)\r\n{\r\n\t// Creates a wrapper function that is in charge of constructing\r\n\t// the new cell instance to be inserted into the graph\r\n\tvar factory = mxUtils.bind(this, function()\r\n\t{\r\n\t\tif (typeof(ptype) == 'function')\r\n\t\t{\r\n\t\t\treturn ptype();\r\n\t\t}\r\n\t\telse if (ptype != null)\r\n\t\t{\r\n\t\t\treturn this.editor.graph.cloneCell(ptype);\r\n\t\t}\r\n\t\t\r\n\t\treturn null;\r\n\t});\r\n\t\r\n\t// Defines the function for a click event on the graph\r\n\t// after this item has been selected in the toolbar\r\n\tvar clickHandler = mxUtils.bind(this, function(evt, cell)\r\n\t{\r\n\t\tif (typeof(insert) == 'function')\r\n\t\t{\r\n\t\t\tinsert(this.editor, factory(), evt, cell);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthis.drop(factory(), evt, cell);\r\n\t\t}\r\n\t\t\r\n\t\tthis.toolbar.resetMode();\r\n\t\tmxEvent.consume(evt);\r\n\t});\r\n\t\r\n\tvar img = this.toolbar.addMode(title, icon, clickHandler, pressed, null, toggle);\r\n\t\t\t\t\r\n\t// Creates a wrapper function that calls the click handler without\r\n\t// the graph argument\r\n\tvar dropHandler = function(graph, evt, cell)\r\n\t{\r\n\t\tclickHandler(evt, cell);\r\n\t};\r\n\t\r\n\tthis.installDropHandler(img, dropHandler);\r\n\t\r\n\treturn img;\r\n};\r\n\r\n/**\r\n * Function: drop\r\n * \r\n * Handles a drop from a toolbar item to the graph. The given vertex\r\n * represents the new cell to be inserted. This invokes <insert> or\r\n * <connect> depending on the given target cell.\r\n * \r\n * Parameters:\r\n * \r\n * vertex - <mxCell> to be inserted.\r\n * evt - Mouse event that represents the drop.\r\n * target - Optional <mxCell> that represents the drop target.\r\n */\r\nmxDefaultToolbar.prototype.drop = function(vertex, evt, target)\r\n{\r\n\tvar graph = this.editor.graph;\r\n\tvar model = graph.getModel();\r\n\t\r\n\tif (target == null ||\r\n\t\tmodel.isEdge(target) ||\r\n\t\t!this.connectOnDrop ||\r\n\t\t!graph.isCellConnectable(target))\r\n\t{\r\n\t\twhile (target != null &&\r\n\t\t\t!graph.isValidDropTarget(target, [vertex], evt))\r\n\t\t{\r\n\t\t\ttarget = model.getParent(target);\r\n\t\t}\r\n\t\t\r\n\t\tthis.insert(vertex, evt, target);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tthis.connect(vertex, evt, target);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: insert\r\n *\r\n * Handles a drop by inserting the given vertex into the given parent cell\r\n * or the default parent if no parent is specified.\r\n * \r\n * Parameters:\r\n * \r\n * vertex - <mxCell> to be inserted.\r\n * evt - Mouse event that represents the drop.\r\n * parent - Optional <mxCell> that represents the parent.\r\n */\r\nmxDefaultToolbar.prototype.insert = function(vertex, evt, target)\r\n{\r\n\tvar graph = this.editor.graph;\r\n\t\r\n\tif (graph.canImportCell(vertex))\r\n\t{\r\n\t\tvar x = mxEvent.getClientX(evt);\r\n\t\tvar y = mxEvent.getClientY(evt);\r\n\t\tvar pt = mxUtils.convertPoint(graph.container, x, y);\r\n\t\t\r\n\t\t// Splits the target edge or inserts into target group\r\n\t\tif (graph.isSplitEnabled() &&\r\n\t\t\tgraph.isSplitTarget(target, [vertex], evt))\r\n\t\t{\r\n\t\t\treturn graph.splitEdge(target, [vertex], null, pt.x, pt.y);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn this.editor.addVertex(target, vertex, pt.x, pt.y);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: connect\r\n * \r\n * Handles a drop by connecting the given vertex to the given source cell.\r\n * \r\n * vertex - <mxCell> to be inserted.\r\n * evt - Mouse event that represents the drop.\r\n * source - Optional <mxCell> that represents the source terminal.\r\n */\r\nmxDefaultToolbar.prototype.connect = function(vertex, evt, source)\r\n{\r\n\tvar graph = this.editor.graph;\r\n\tvar model = graph.getModel();\r\n\t\r\n\tif (source != null &&\r\n\t\tgraph.isCellConnectable(vertex) &&\r\n\t\tgraph.isEdgeValid(null, source, vertex))\r\n\t{\r\n\t\tvar edge = null;\r\n\r\n\t\tmodel.beginUpdate();\r\n\t\ttry\r\n\t\t{\r\n\t\t\tvar geo = model.getGeometry(source);\r\n\t\t\tvar g = model.getGeometry(vertex).clone();\r\n\t\t\t\r\n\t\t\t// Moves the vertex away from the drop target that will\r\n\t\t\t// be used as the source for the new connection\r\n\t\t\tg.x = geo.x + (geo.width - g.width) / 2;\r\n\t\t\tg.y = geo.y + (geo.height - g.height) / 2;\r\n\t\t\t\r\n\t\t\tvar step = this.spacing * graph.gridSize;\r\n\t\t\tvar dist = model.getDirectedEdgeCount(source, true) * 20;\r\n\t\t\t\r\n\t\t\tif (this.editor.horizontalFlow)\r\n\t\t\t{\r\n\t\t\t\tg.x += (g.width + geo.width) / 2 + step + dist;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tg.y += (g.height + geo.height) / 2 + step + dist;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvertex.setGeometry(g);\r\n\t\t\t\r\n\t\t\t// Fires two add-events with the code below - should be fixed\r\n\t\t\t// to only fire one add event for both inserts\r\n\t\t\tvar parent = model.getParent(source);\r\n\t\t\tgraph.addCell(vertex, parent);\r\n\t\t\tgraph.constrainChild(vertex);\r\n\r\n\t\t\t// Creates the edge using the editor instance and calls\r\n\t\t\t// the second function that fires an add event\r\n\t\t\tedge = this.editor.createEdge(source, vertex);\r\n\t\t\t\r\n\t\t\tif (model.getGeometry(edge) == null)\r\n\t\t\t{\r\n\t\t\t\tvar edgeGeometry = new mxGeometry();\r\n\t\t\t\tedgeGeometry.relative = true;\r\n\t\t\t\t\r\n\t\t\t\tmodel.setGeometry(edge, edgeGeometry);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tgraph.addEdge(edge, parent, source, vertex);\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tmodel.endUpdate();\r\n\t\t}\r\n\t\t\r\n\t\tgraph.setSelectionCells([vertex, edge]);\r\n\t\tgraph.scrollCellToVisible(vertex);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: installDropHandler\r\n * \r\n * Makes the given img draggable using the given function for handling a\r\n * drop event.\r\n * \r\n * Parameters:\r\n * \r\n * img - DOM node that represents the image.\r\n * dropHandler - Function that handles a drop of the image.\r\n */\r\nmxDefaultToolbar.prototype.installDropHandler = function (img, dropHandler)\r\n{\r\n\tvar sprite = document.createElement('img');\r\n\tsprite.setAttribute('src', img.getAttribute('src'));\r\n\r\n\t// Handles delayed loading of the images\r\n\tvar loader = mxUtils.bind(this, function(evt)\r\n\t{\r\n\t\t// Preview uses the image node with double size. Later this can be\r\n\t\t// changed to use a separate preview and guides, but for this the\r\n\t\t// dropHandler must use the additional x- and y-arguments and the\r\n\t\t// dragsource which makeDraggable returns much be configured to\r\n\t\t// use guides via mxDragSource.isGuidesEnabled.\r\n\t\tsprite.style.width = (2 * img.offsetWidth) + 'px';\r\n\t\tsprite.style.height = (2 * img.offsetHeight) + 'px';\r\n\r\n\t\tmxUtils.makeDraggable(img, this.editor.graph, dropHandler,\r\n\t\t\tsprite);\r\n\t\tmxEvent.removeListener(sprite, 'load', loader);\r\n\t});\r\n\r\n\tif (mxClient.IS_IE)\r\n\t{\r\n\t\tloader();\r\n\t}\r\n\telse\r\n\t{\r\n\t\tmxEvent.addListener(sprite, 'load', loader);\r\n\t}\t\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Destroys the <toolbar> associated with this object and removes all\r\n * installed listeners. This does normally not need to be called, the\r\n * <toolbar> is destroyed automatically when the window unloads (in IE) by\r\n * <mxEditor>.\r\n */\r\nmxDefaultToolbar.prototype.destroy = function ()\r\n{\r\n\tif (this.resetHandler != null)\r\n\t{\r\n\t\tthis.editor.graph.removeListener('dblclick', this.resetHandler);\r\n\t\tthis.editor.removeListener('escape', this.resetHandler);\r\n\t\tthis.resetHandler = null;\r\n\t}\r\n\t\r\n\tif (this.toolbar != null)\r\n\t{\r\n\t\tthis.toolbar.destroy();\r\n\t\tthis.toolbar = null;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2019, JGraph Ltd\r\n * Copyright (c) 2006-2019, draw.io AG\r\n */\r\n/**\r\n * Class: mxEditor\r\n *\r\n * Extends <mxEventSource> to implement a application wrapper for a graph that\r\n * adds <actions>, I/O using <mxCodec>, auto-layout using <mxLayoutManager>,\r\n * command history using <undoManager>, and standard dialogs and widgets, eg.\r\n * properties, help, outline, toolbar, and popupmenu. It also adds <templates>\r\n * to be used as cells in toolbars, auto-validation using the <validation>\r\n * flag, attribute cycling using <cycleAttributeValues>, higher-level events\r\n * such as <root>, and backend integration using <urlPost> and <urlImage>. \r\n * \r\n * Actions:\r\n * \r\n * Actions are functions stored in the <actions> array under their names. The\r\n * functions take the <mxEditor> as the first, and an optional <mxCell> as the\r\n * second argument and are invoked using <execute>. Any additional arguments\r\n * passed to execute are passed on to the action as-is.\r\n * \r\n * A list of built-in actions is available in the <addActions> description.\r\n * \r\n * Read/write Diagrams:\r\n * \r\n * To read a diagram from an XML string, for example from a textfield within the \r\n * page, the following code is used:\r\n * \r\n * (code)\r\n * var doc = mxUtils.parseXML(xmlString);\r\n * var node = doc.documentElement;\r\n * editor.readGraphModel(node);\r\n * (end)\r\n * \r\n * For reading a diagram from a remote location, use the <open> method.\r\n * \r\n * To save diagrams in XML on a server, you can set the <urlPost> variable. \r\n * This variable will be used in <getUrlPost> to construct a URL for the post \r\n * request that is issued in the <save> method. The post request contains the \r\n * XML representation of the diagram as returned by <writeGraphModel> in the \r\n * xml parameter.\r\n * \r\n * On the server side, the post request is processed using standard\r\n * technologies such as Java Servlets, CGI, .NET or ASP.\r\n * \r\n * Here are some examples of processing a post request in various languages.\r\n * \r\n * - Java: URLDecoder.decode(request.getParameter(\"xml\"), \"UTF-8\").replace(\"\\n\", \"&#xa;\")\r\n * \r\n * Note that the linefeeds should only be replaced if the XML is\r\n * processed in Java, for example when creating an image, but not\r\n * if the XML is passed back to the client-side.\r\n * \r\n * - .NET: HttpUtility.UrlDecode(context.Request.Params[\"xml\"])\r\n * - PHP: urldecode($_POST[\"xml\"])\r\n * \r\n * Creating images:\r\n * \r\n * A backend (Java, PHP or C#) is required for creating images. The\r\n * distribution contains an example for each backend (ImageHandler.java,\r\n * ImageHandler.cs and graph.php). More information about using a backend\r\n * to create images can be found in the readme.html files. Note that the\r\n * preview is implemented using VML/SVG in the browser and does not require\r\n * a backend. The backend is only required to creates images (bitmaps).\r\n * \r\n * Special characters:\r\n * \r\n * Note There are five characters that should always appear in XML content as\r\n * escapes, so that they do not interact with the syntax of the markup. These\r\n * are part of the language for all documents based on XML and for HTML.\r\n * \r\n * - &lt; (<)\r\n * - &gt; (>)\r\n * - &amp; (&)\r\n * - &quot; (\")\r\n * - &apos; (')\r\n * \r\n * Although it is part of the XML language, &apos; is not defined in HTML.\r\n * For this reason the XHTML specification recommends instead the use of\r\n * &#39; if text may be passed to a HTML user agent.\r\n * \r\n * If you are having problems with special characters on the server-side then\r\n * you may want to try the <escapePostData> flag.\r\n * \r\n * For converting decimal escape sequences inside strings, a user has provided\r\n * us with the following function:\r\n * \r\n * (code)\r\n * function html2js(text)\r\n * {\r\n *   var entitySearch = /&#[0-9]+;/;\r\n *   var entity;\r\n *   \r\n *   while (entity = entitySearch.exec(text))\r\n *   {\r\n *     var charCode = entity[0].substring(2, entity[0].length -1);\r\n *     text = text.substring(0, entity.index)\r\n *            + String.fromCharCode(charCode)\r\n *            + text.substring(entity.index + entity[0].length);\r\n *   }\r\n *   \r\n *   return text;\r\n * }\r\n * (end)\r\n * \r\n * Otherwise try using hex escape sequences and the built-in unescape function\r\n * for converting such strings.\r\n * \r\n * Local Files:\r\n * \r\n * For saving and opening local files, no standardized method exists that\r\n * works across all browsers. The recommended way of dealing with local files\r\n * is to create a backend that streams the XML data back to the browser (echo)\r\n * as an attachment so that a Save-dialog is displayed on the client-side and\r\n * the file can be saved to the local disk.\r\n * \r\n * For example, in PHP the code that does this looks as follows.\r\n * \r\n * (code)\r\n * $xml = stripslashes($_POST[\"xml\"]);\r\n * header(\"Content-Disposition: attachment; filename=\\\"diagram.xml\\\"\");\r\n * echo($xml);\r\n * (end)\r\n * \r\n * To open a local file, the file should be uploaded via a form in the browser\r\n * and then opened from the server in the editor.\r\n * \r\n * Cell Properties:\r\n * \r\n * The properties displayed in the properties dialog are the attributes and \r\n * values of the cell's user object, which is an XML node. The XML node is \r\n * defined in the templates section of the config file.\r\n * \r\n * The templates are stored in <mxEditor.templates> and contain cells which\r\n * are cloned at insertion time to create new vertices by use of drag and\r\n * drop from the toolbar. Each entry in the toolbar for adding a new vertex\r\n * must refer to an existing template.\r\n * \r\n * In the following example, the task node is a business object and only the \r\n * mxCell node and its mxGeometry child contain graph information:\r\n * \r\n * (code)\r\n * <Task label=\"Task\" description=\"\">\r\n *   <mxCell vertex=\"true\">\r\n *     <mxGeometry as=\"geometry\" width=\"72\" height=\"32\"/>\r\n *   </mxCell>\r\n * </Task> \r\n * (end)\r\n * \r\n * The idea is that the XML representation is inverse from the in-memory \r\n * representation: The outer XML node is the user object and the inner node is \r\n * the cell. This means the user object of the cell is the Task node with no \r\n * children for the above example:\r\n * \r\n * (code)\r\n * <Task label=\"Task\" description=\"\"/>\r\n * (end)\r\n * \r\n * The Task node can have any tag name, attributes and child nodes. The \r\n * <mxCodec> will use the XML hierarchy as the user object, while removing the \r\n * \"known annotations\", such as the mxCell node. At save-time the cell data \r\n * will be \"merged\" back into the user object. The user object is only modified \r\n * via the properties dialog during the lifecycle of the cell.\r\n * \r\n * In the default implementation of <createProperties>, the user object's\r\n * attributes are put into a form for editing. Attributes are changed using\r\n * the <mxCellAttributeChange> action in the model. The dialog can be replaced \r\n * by overriding the <createProperties> hook or by replacing the showProperties\r\n * action in <actions>. Alternatively, the entry in the config file's popupmenu\r\n * section can be modified to invoke a different action.\r\n * \r\n * If you want to displey the properties dialog on a doubleclick, you can set\r\n * <mxEditor.dblClickAction> to showProperties as follows:\r\n * \r\n * (code)\r\n * editor.dblClickAction = 'showProperties';\r\n * (end)\r\n * \r\n * Popupmenu and Toolbar:\r\n * \r\n * The toolbar and popupmenu are typically configured using the respective\r\n * sections in the config file, that is, the popupmenu is defined as follows:\r\n * \r\n * (code)\r\n * <mxEditor>\r\n *   <mxDefaultPopupMenu as=\"popupHandler\">\r\n * \t\t<add as=\"cut\" action=\"cut\" icon=\"images/cut.gif\"/>\r\n *      ...\r\n * (end)\r\n * \r\n * New entries can be added to the toolbar by inserting an add-node into the\r\n * above configuration. Existing entries may be removed and changed by\r\n * modifying or removing the respective entries in the configuration.\r\n * The configuration is read by the <mxDefaultPopupMenuCodec>, the format of the\r\n * configuration is explained in <mxDefaultPopupMenu.decode>.\r\n * \r\n * The toolbar is defined in the mxDefaultToolbar section. Items can be added\r\n * and removed in this section.\r\n * \r\n * (code)\r\n * <mxEditor>\r\n *   <mxDefaultToolbar>\r\n *     <add as=\"save\" action=\"save\" icon=\"images/save.gif\"/>\r\n *     <add as=\"Swimlane\" template=\"swimlane\" icon=\"images/swimlane.gif\"/>\r\n *     ...\r\n * (end)\r\n * \r\n * The format of the configuration is described in\r\n * <mxDefaultToolbarCodec.decode>.\r\n * \r\n * Ids:\r\n * \r\n * For the IDs, there is an implicit behaviour in <mxCodec>: It moves the Id\r\n * from the cell to the user object at encoding time and vice versa at decoding\r\n * time. For example, if the Task node from above has an id attribute, then\r\n * the <mxCell.id> of the corresponding cell will have this value. If there\r\n * is no Id collision in the model, then the cell may be retrieved using this\r\n * Id with the <mxGraphModel.getCell> function. If there is a collision, a new\r\n * Id will be created for the cell using <mxGraphModel.createId>. At encoding\r\n * time, this new Id will replace the value previously stored under the id\r\n * attribute in the Task node.\r\n * \r\n * See <mxEditorCodec>, <mxDefaultToolbarCodec> and <mxDefaultPopupMenuCodec>\r\n * for information about configuring the editor and user interface.\r\n * \r\n * Programmatically inserting cells:\r\n * \r\n * For inserting a new cell, say, by clicking a button in the document,\r\n * the following code can be used. This requires an reference to the editor.\r\n * \r\n * (code)\r\n * var userObject = new Object();\r\n * var parent = editor.graph.getDefaultParent();\r\n * var model = editor.graph.model;\r\n * model.beginUpdate();\r\n * try\r\n * {\r\n *   editor.graph.insertVertex(parent, null, userObject, 20, 20, 80, 30);\r\n * }\r\n * finally\r\n * {\r\n *   model.endUpdate();\r\n * }\r\n * (end)\r\n * \r\n * If a template cell from the config file should be inserted, then a clone\r\n * of the template can be created as follows. The clone is then inserted using\r\n * the add function instead of addVertex.\r\n * \r\n * (code)\r\n * var template = editor.templates['task'];\r\n * var clone = editor.graph.model.cloneCell(template);\r\n * (end)\r\n * \r\n * Resources:\r\n *\r\n * resources/editor - Language resources for mxEditor\r\n *\r\n * Callback: onInit\r\n *\r\n * Called from within the constructor. In the callback,\r\n * \"this\" refers to the editor instance.\r\n *\r\n * Cookie: mxgraph=seen\r\n *\r\n * Set when the editor is started. Never expires. Use\r\n * <resetFirstTime> to reset this cookie. This cookie\r\n * only exists if <onInit> is implemented.\r\n *\r\n * Event: mxEvent.OPEN\r\n *\r\n * Fires after a file was opened in <open>. The <code>filename</code> property\r\n * contains the filename that was used. The same value is also available in\r\n * <filename>.\r\n *\r\n * Event: mxEvent.SAVE\r\n *\r\n * Fires after the current file was saved in <save>. The <code>url</code>\r\n * property contains the URL that was used for saving.\r\n *\r\n * Event: mxEvent.POST\r\n * \r\n * Fires if a successful response was received in <postDiagram>. The\r\n * <code>request</code> property contains the <mxXmlRequest>, the\r\n * <code>url</code> and <code>data</code> properties contain the URL and the\r\n * data that were used in the post request. \r\n *\r\n * Event: mxEvent.ROOT\r\n *\r\n * Fires when the current root has changed, or when the title of the current\r\n * root has changed. This event has no properties.\r\n *\r\n * Event: mxEvent.BEFORE_ADD_VERTEX\r\n * \r\n * Fires before a vertex is added in <addVertex>. The <code>vertex</code>\r\n * property contains the new vertex and the <code>parent</code> property\r\n * contains its parent.\r\n * \r\n * Event: mxEvent.ADD_VERTEX\r\n * \r\n * Fires between begin- and endUpdate in <addVertex>. The <code>vertex</code>\r\n * property contains the vertex that is being inserted.\r\n * \r\n * Event: mxEvent.AFTER_ADD_VERTEX\r\n * \r\n * Fires after a vertex was inserted and selected in <addVertex>. The\r\n * <code>vertex</code> property contains the new vertex.\r\n * \r\n * Example:\r\n * \r\n * For starting an in-place edit after a new vertex has been added to the\r\n * graph, the following code can be used.\r\n * \r\n * (code)\r\n * editor.addListener(mxEvent.AFTER_ADD_VERTEX, function(sender, evt)\r\n * {\r\n *   var vertex = evt.getProperty('vertex');\r\n * \r\n *   if (editor.graph.isCellEditable(vertex))\r\n *   {\r\n *   \teditor.graph.startEditingAtCell(vertex);\r\n *   }\r\n * });\r\n * (end)\r\n * \r\n * Event: mxEvent.ESCAPE\r\n * \r\n * Fires when the escape key is pressed. The <code>event</code> property\r\n * contains the key event.\r\n * \r\n * Constructor: mxEditor\r\n *\r\n * Constructs a new editor. This function invokes the <onInit> callback\r\n * upon completion.\r\n *\r\n * Example:\r\n *\r\n * (code)\r\n * var config = mxUtils.load('config/diagrameditor.xml').getDocumentElement();\r\n * var editor = new mxEditor(config);\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * config - Optional XML node that contains the configuration.\r\n */\r\nfunction mxEditor(config)\r\n{\r\n\tthis.actions = [];\r\n\tthis.addActions();\r\n\r\n\t// Executes the following only if a document has been instanciated.\r\n\t// That is, don't execute when the editorcodec is setup.\r\n\tif (document.body != null)\r\n\t{\r\n\t\t// Defines instance fields\r\n\t\tthis.cycleAttributeValues = [];\r\n\t\tthis.popupHandler = new mxDefaultPopupMenu();\r\n\t\tthis.undoManager = new mxUndoManager();\r\n\r\n\t\t// Creates the graph and toolbar without the containers\r\n\t\tthis.graph = this.createGraph();\r\n\t\tthis.toolbar = this.createToolbar();\r\n\r\n\t\t// Creates the global keyhandler (requires graph instance)\r\n\t\tthis.keyHandler = new mxDefaultKeyHandler(this);\r\n\r\n\t\t// Configures the editor using the URI\r\n\t\t// which was passed to the ctor\r\n\t\tthis.configure(config);\r\n\t\t\r\n\t\t// Assigns the swimlaneIndicatorColorAttribute on the graph\r\n\t\tthis.graph.swimlaneIndicatorColorAttribute = this.cycleAttributeName;\r\n\r\n\t\t// Checks if the <onInit> hook has been set\r\n\t\tif (this.onInit != null)\r\n\t\t{\r\n\t\t\t// Invokes the <onInit> hook\r\n\t\t\tthis.onInit();\r\n\t\t}\r\n\t\t\r\n\t\t// Automatic deallocation of memory\r\n\t\tif (mxClient.IS_IE)\r\n\t\t{\r\n\t\t\tmxEvent.addListener(window, 'unload', mxUtils.bind(this, function()\r\n\t\t\t{\r\n\t\t\t\tthis.destroy();\r\n\t\t\t}));\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Installs the required language resources at class\r\n * loading time.\r\n */\r\nif (mxLoadResources)\r\n{\r\n\tmxResources.add(mxClient.basePath + '/resources/editor');\r\n}\r\nelse\r\n{\r\n\tmxClient.defaultBundles.push(mxClient.basePath + '/resources/editor');\r\n}\r\n\r\n/**\r\n * Extends mxEventSource.\r\n */\r\nmxEditor.prototype = new mxEventSource();\r\nmxEditor.prototype.constructor = mxEditor;\r\n\r\n/**\r\n * Group: Controls and Handlers\r\n */\r\n\t\r\n/**\r\n * Variable: askZoomResource\r\n * \r\n * Specifies the resource key for the zoom dialog. If the resource for this\r\n * key does not exist then the value is used as the error message. Default\r\n * is 'askZoom'.\r\n */\r\nmxEditor.prototype.askZoomResource = (mxClient.language != 'none') ? 'askZoom' : '';\r\n\t\r\n/**\r\n * Variable: lastSavedResource\r\n * \r\n * Specifies the resource key for the last saved info. If the resource for\r\n * this key does not exist then the value is used as the error message.\r\n * Default is 'lastSaved'.\r\n */\r\nmxEditor.prototype.lastSavedResource = (mxClient.language != 'none') ? 'lastSaved' : '';\r\n\t\r\n/**\r\n * Variable: currentFileResource\r\n * \r\n * Specifies the resource key for the current file info. If the resource for\r\n * this key does not exist then the value is used as the error message.\r\n * Default is 'lastSaved'.\r\n */\r\nmxEditor.prototype.currentFileResource = (mxClient.language != 'none') ? 'currentFile' : '';\r\n\t\r\n/**\r\n * Variable: propertiesResource\r\n * \r\n * Specifies the resource key for the properties window title. If the\r\n * resource for this key does not exist then the value is used as the\r\n * error message. Default is 'properties'.\r\n */\r\nmxEditor.prototype.propertiesResource = (mxClient.language != 'none') ? 'properties' : '';\r\n\t\r\n/**\r\n * Variable: tasksResource\r\n * \r\n * Specifies the resource key for the tasks window title. If the\r\n * resource for this key does not exist then the value is used as the\r\n * error message. Default is 'tasks'.\r\n */\r\nmxEditor.prototype.tasksResource = (mxClient.language != 'none') ? 'tasks' : '';\r\n\t\r\n/**\r\n * Variable: helpResource\r\n * \r\n * Specifies the resource key for the help window title. If the\r\n * resource for this key does not exist then the value is used as the\r\n * error message. Default is 'help'.\r\n */\r\nmxEditor.prototype.helpResource = (mxClient.language != 'none') ? 'help' : '';\r\n\t\r\n/**\r\n * Variable: outlineResource\r\n * \r\n * Specifies the resource key for the outline window title. If the\r\n * resource for this key does not exist then the value is used as the\r\n * error message. Default is 'outline'.\r\n */\r\nmxEditor.prototype.outlineResource = (mxClient.language != 'none') ? 'outline' : '';\r\n\t\r\n/**\r\n * Variable: outline\r\n * \r\n * Reference to the <mxWindow> that contains the outline. The <mxOutline>\r\n * is stored in outline.outline.\r\n */\r\nmxEditor.prototype.outline = null;\r\n\r\n/**\r\n * Variable: graph\r\n *\r\n * Holds a <mxGraph> for displaying the diagram. The graph\r\n * is created in <setGraphContainer>.\r\n */\r\nmxEditor.prototype.graph = null;\r\n\r\n/**\r\n * Variable: graphRenderHint\r\n *\r\n * Holds the render hint used for creating the\r\n * graph in <setGraphContainer>. See <mxGraph>.\r\n * Default is null.\r\n */\r\nmxEditor.prototype.graphRenderHint = null;\r\n\r\n/**\r\n * Variable: toolbar\r\n *\r\n * Holds a <mxDefaultToolbar> for displaying the toolbar. The\r\n * toolbar is created in <setToolbarContainer>.\r\n */\r\nmxEditor.prototype.toolbar = null;\r\n\r\n/**\r\n * Variable: status\r\n *\r\n * DOM container that holds the statusbar. Default is null.\r\n * Use <setStatusContainer> to set this value.\r\n */\r\nmxEditor.prototype.status = null;\r\n\r\n/**\r\n * Variable: popupHandler\r\n *\r\n * Holds a <mxDefaultPopupMenu> for displaying\r\n * popupmenus.\r\n */\r\nmxEditor.prototype.popupHandler = null;\r\n\r\n/**\r\n * Variable: undoManager\r\n *\r\n * Holds an <mxUndoManager> for the command history.\r\n */\r\nmxEditor.prototype.undoManager = null;\r\n\r\n/**\r\n * Variable: keyHandler\r\n *\r\n * Holds a <mxDefaultKeyHandler> for handling keyboard events.\r\n * The handler is created in <setGraphContainer>.\r\n */\r\nmxEditor.prototype.keyHandler = null;\r\n\r\n/**\r\n * Group: Actions and Options\r\n */\r\n\r\n/**\r\n * Variable: actions\r\n *\r\n * Maps from actionnames to actions, which are functions taking\r\n * the editor and the cell as arguments. Use <addAction>\r\n * to add or replace an action and <execute> to execute an action\r\n * by name, passing the cell to be operated upon as the second\r\n * argument.\r\n */\r\nmxEditor.prototype.actions = null;\r\n\r\n/**\r\n * Variable: dblClickAction\r\n *\r\n * Specifies the name of the action to be executed\r\n * when a cell is double clicked. Default is edit.\r\n * \r\n * To handle a singleclick, use the following code.\r\n * \r\n * (code)\r\n * editor.graph.addListener(mxEvent.CLICK, function(sender, evt)\r\n * {\r\n *   var e = evt.getProperty('event');\r\n *   var cell = evt.getProperty('cell');\r\n * \r\n *   if (cell != null && !e.isConsumed())\r\n *   {\r\n *     // Do something useful with cell...\r\n *     e.consume();\r\n *   }\r\n * });\r\n * (end)\r\n */\r\nmxEditor.prototype.dblClickAction = 'edit';\r\n\r\n/**\r\n * Variable: swimlaneRequired\r\n * \r\n * Specifies if new cells must be inserted\r\n * into an existing swimlane. Otherwise, cells\r\n * that are not swimlanes can be inserted as\r\n * top-level cells. Default is false.\r\n */\r\nmxEditor.prototype.swimlaneRequired = false;\r\n\r\n/**\r\n * Variable: disableContextMenu\r\n *\r\n * Specifies if the context menu should be disabled in the graph container.\r\n * Default is true.\r\n */\r\nmxEditor.prototype.disableContextMenu = true;\r\n\r\n/**\r\n * Group: Templates\r\n */\r\n\r\n/**\r\n * Variable: insertFunction\r\n *\r\n * Specifies the function to be used for inserting new\r\n * cells into the graph. This is assigned from the\r\n * <mxDefaultToolbar> if a vertex-tool is clicked.\r\n */\r\nmxEditor.prototype.insertFunction = null;\r\n\r\n/**\r\n * Variable: forcedInserting\r\n *\r\n * Specifies if a new cell should be inserted on a single\r\n * click even using <insertFunction> if there is a cell \r\n * under the mousepointer, otherwise the cell under the \r\n * mousepointer is selected. Default is false.\r\n */\r\nmxEditor.prototype.forcedInserting = false;\r\n\r\n/**\r\n * Variable: templates\r\n * \r\n * Maps from names to protoype cells to be used\r\n * in the toolbar for inserting new cells into\r\n * the diagram.\r\n */\r\nmxEditor.prototype.templates = null;\r\n\r\n/**\r\n * Variable: defaultEdge\r\n * \r\n * Prototype edge cell that is used for creating\r\n * new edges.\r\n */\r\nmxEditor.prototype.defaultEdge = null;\r\n\r\n/**\r\n * Variable: defaultEdgeStyle\r\n * \r\n * Specifies the edge style to be returned in <getEdgeStyle>.\r\n * Default is null.\r\n */\r\nmxEditor.prototype.defaultEdgeStyle = null;\r\n\r\n/**\r\n * Variable: defaultGroup\r\n * \r\n * Prototype group cell that is used for creating\r\n * new groups.\r\n */\r\nmxEditor.prototype.defaultGroup = null;\r\n\r\n/**\r\n * Variable: groupBorderSize\r\n *\r\n * Default size for the border of new groups. If null,\r\n * then then <mxGraph.gridSize> is used. Default is\r\n * null.\r\n */\r\nmxEditor.prototype.groupBorderSize = null;\r\n\r\n/**\r\n * Group: Backend Integration\r\n */\r\n\r\n/**\r\n * Variable: filename\r\n *\r\n * Contains the URL of the last opened file as a string.\r\n * Default is null.\r\n */\r\nmxEditor.prototype.filename = null;\r\n\r\n/**\r\n * Variable: lineFeed\r\n *\r\n * Character to be used for encoding linefeeds in <save>. Default is '&#xa;'.\r\n */\r\nmxEditor.prototype.linefeed = '&#xa;';\r\n\r\n/**\r\n * Variable: postParameterName\r\n *\r\n * Specifies if the name of the post parameter that contains the diagram\r\n * data in a post request to the server. Default is xml.\r\n */\r\nmxEditor.prototype.postParameterName = 'xml';\r\n\r\n/**\r\n * Variable: escapePostData\r\n *\r\n * Specifies if the data in the post request for saving a diagram\r\n * should be converted using encodeURIComponent. Default is true.\r\n */\r\nmxEditor.prototype.escapePostData = true;\r\n\r\n/**\r\n * Variable: urlPost\r\n *\r\n * Specifies the URL to be used for posting the diagram\r\n * to a backend in <save>.\r\n */\r\nmxEditor.prototype.urlPost = null;\r\n\r\n/**\r\n * Variable: urlImage\r\n *\r\n * Specifies the URL to be used for creating a bitmap of\r\n * the graph in the image action.\r\n */\r\nmxEditor.prototype.urlImage = null;\r\n\r\n/**\r\n * Group: Autolayout\r\n */\r\n\r\n/**\r\n * Variable: horizontalFlow\r\n *\r\n * Specifies the direction of the flow\r\n * in the diagram. This is used in the\r\n * layout algorithms. Default is false,\r\n * ie. vertical flow.\r\n */\r\nmxEditor.prototype.horizontalFlow = false;\r\n\r\n/**\r\n * Variable: layoutDiagram\r\n *\r\n * Specifies if the top-level elements in the\r\n * diagram should be layed out using a vertical\r\n * or horizontal stack depending on the setting\r\n * of <horizontalFlow>. The spacing between the\r\n * swimlanes is specified by <swimlaneSpacing>.\r\n * Default is false.\r\n * \r\n * If the top-level elements are swimlanes, then\r\n * the intra-swimlane layout is activated by\r\n * the <layoutSwimlanes> switch.\r\n */\r\nmxEditor.prototype.layoutDiagram = false;\r\n\r\n/**\r\n * Variable: swimlaneSpacing\r\n *\r\n * Specifies the spacing between swimlanes if\r\n * automatic layout is turned on in\r\n * <layoutDiagram>. Default is 0.\r\n */\r\nmxEditor.prototype.swimlaneSpacing = 0;\r\n\r\n/**\r\n * Variable: maintainSwimlanes\r\n * \r\n * Specifies if the swimlanes should be kept at the same\r\n * width or height depending on the setting of\r\n * <horizontalFlow>.  Default is false.\r\n * \r\n * For horizontal flows, all swimlanes\r\n * have the same height and for vertical flows, all swimlanes\r\n * have the same width. Furthermore, the swimlanes are\r\n * automatically \"stacked\" if <layoutDiagram> is true.\r\n */\r\nmxEditor.prototype.maintainSwimlanes = false;\r\n\r\n/**\r\n * Variable: layoutSwimlanes\r\n *\r\n * Specifies if the children of swimlanes should\r\n * be layed out, either vertically or horizontally\r\n * depending on <horizontalFlow>.\r\n * Default is false.\r\n */\r\nmxEditor.prototype.layoutSwimlanes = false;\r\n\r\n/**\r\n * Group: Attribute Cycling\r\n */\r\n \r\n/**\r\n * Variable: cycleAttributeValues\r\n * \r\n * Specifies the attribute values to be cycled when\r\n * inserting new swimlanes. Default is an empty\r\n * array.\r\n */\r\nmxEditor.prototype.cycleAttributeValues = null;\r\n\r\n/**\r\n * Variable: cycleAttributeIndex\r\n * \r\n * Index of the last consumed attribute index. If a new\r\n * swimlane is inserted, then the <cycleAttributeValues>\r\n * at this index will be used as the value for\r\n * <cycleAttributeName>. Default is 0.\r\n */\r\nmxEditor.prototype.cycleAttributeIndex = 0;\r\n\r\n/**\r\n * Variable: cycleAttributeName\r\n * \r\n * Name of the attribute to be assigned a <cycleAttributeValues>\r\n * when inserting new swimlanes. Default is fillColor.\r\n */\r\nmxEditor.prototype.cycleAttributeName = 'fillColor';\r\n\r\n/**\r\n * Group: Windows\r\n */\r\n\r\n/**\r\n * Variable: tasks\r\n * \r\n * Holds the <mxWindow> created in <showTasks>.\r\n */\r\nmxEditor.prototype.tasks = null;\r\n\r\n/**\r\n * Variable: tasksWindowImage\r\n *\r\n * Icon for the tasks window.\r\n */\r\nmxEditor.prototype.tasksWindowImage = null;\r\n\r\n/**\r\n * Variable: tasksTop\r\n * \r\n * Specifies the top coordinate of the tasks window in pixels.\r\n * Default is 20.\r\n */\r\nmxEditor.prototype.tasksTop = 20;\r\n\r\n/**\r\n * Variable: help\r\n * \r\n * Holds the <mxWindow> created in <showHelp>.\r\n */\r\nmxEditor.prototype.help = null;\r\n\r\n/**\r\n * Variable: helpWindowImage\r\n *\r\n * Icon for the help window.\r\n */\r\nmxEditor.prototype.helpWindowImage = null;\r\n\r\n/**\r\n * Variable: urlHelp\r\n *\r\n * Specifies the URL to be used for the contents of the\r\n * Online Help window. This is usually specified in the\r\n * resources file under urlHelp for language-specific\r\n * online help support.\r\n */\r\nmxEditor.prototype.urlHelp = null;\r\n\r\n/**\r\n * Variable: helpWidth\r\n * \r\n * Specifies the width of the help window in pixels.\r\n * Default is 300.\r\n */\r\nmxEditor.prototype.helpWidth = 300;\r\n\t\r\n/**\r\n * Variable: helpHeight\r\n * \r\n * Specifies the height of the help window in pixels.\r\n * Default is 260.\r\n */\r\nmxEditor.prototype.helpHeight = 260;\r\n\r\n/**\r\n * Variable: propertiesWidth\r\n * \r\n * Specifies the width of the properties window in pixels.\r\n * Default is 240.\r\n */\r\nmxEditor.prototype.propertiesWidth = 240;\r\n\t\t\r\n/**\r\n * Variable: propertiesHeight\r\n * \r\n * Specifies the height of the properties window in pixels.\r\n * If no height is specified then the window will be automatically\r\n * sized to fit its contents. Default is null.\r\n */\r\nmxEditor.prototype.propertiesHeight = null;\r\n\t\t\r\n/**\r\n * Variable: movePropertiesDialog\r\n *\r\n * Specifies if the properties dialog should be automatically\r\n * moved near the cell it is displayed for, otherwise the\r\n * dialog is not moved. This value is only taken into \r\n * account if the dialog is already visible. Default is false.\r\n */\r\nmxEditor.prototype.movePropertiesDialog = false;\r\n\r\n/**\r\n * Variable: validating\r\n *\r\n * Specifies if <mxGraph.validateGraph> should automatically be invoked after\r\n * each change. Default is false.\r\n */\r\nmxEditor.prototype.validating = false;\r\n\r\n/**\r\n * Variable: modified\r\n *\r\n * True if the graph has been modified since it was last saved.\r\n */\r\nmxEditor.prototype.modified = false;\r\n\r\n/**\r\n * Function: isModified\r\n * \r\n * Returns <modified>.\r\n */\r\nmxEditor.prototype.isModified = function ()\r\n{\r\n\treturn this.modified;\r\n};\r\n\r\n/**\r\n * Function: setModified\r\n * \r\n * Sets <modified> to the specified boolean value.\r\n */\r\nmxEditor.prototype.setModified = function (value)\r\n{\r\n\tthis.modified = value;\r\n};\r\n\r\n/**\r\n * Function: addActions\r\n *\r\n * Adds the built-in actions to the editor instance.\r\n *\r\n * save - Saves the graph using <urlPost>.\r\n * print - Shows the graph in a new print preview window.\r\n * show - Shows the graph in a new window.\r\n * exportImage - Shows the graph as a bitmap image using <getUrlImage>.\r\n * refresh - Refreshes the graph's display.\r\n * cut - Copies the current selection into the clipboard\r\n * and removes it from the graph.\r\n * copy - Copies the current selection into the clipboard.\r\n * paste - Pastes the clipboard into the graph.\r\n * delete - Removes the current selection from the graph.\r\n * group - Puts the current selection into a new group.\r\n * ungroup - Removes the selected groups and selects the children.\r\n * undo - Undoes the last change on the graph model.\r\n * redo - Redoes the last change on the graph model.\r\n * zoom - Sets the zoom via a dialog.\r\n * zoomIn - Zooms into the graph.\r\n * zoomOut - Zooms out of the graph\r\n * actualSize - Resets the scale and translation on the graph.\r\n * fit - Changes the scale so that the graph fits into the window.\r\n * showProperties - Shows the properties dialog.\r\n * selectAll - Selects all cells.\r\n * selectNone - Clears the selection.\r\n * selectVertices - Selects all vertices.\r\n * selectEdges = Selects all edges.\r\n * edit - Starts editing the current selection cell.\r\n * enterGroup - Drills down into the current selection cell.\r\n * exitGroup - Moves up in the drilling hierachy\r\n * home - Moves to the topmost parent in the drilling hierarchy\r\n * selectPrevious - Selects the previous cell.\r\n * selectNext - Selects the next cell.\r\n * selectParent - Selects the parent of the selection cell.\r\n * selectChild - Selects the first child of the selection cell.\r\n * collapse - Collapses the currently selected cells.\r\n * expand - Expands the currently selected cells.\r\n * bold - Toggle bold text style.\r\n * italic - Toggle italic text style.\r\n * underline - Toggle underline text style.\r\n * alignCellsLeft - Aligns the selection cells at the left.\r\n * alignCellsCenter - Aligns the selection cells in the center.\r\n * alignCellsRight - Aligns the selection cells at the right.\r\n * alignCellsTop - Aligns the selection cells at the top.\r\n * alignCellsMiddle - Aligns the selection cells in the middle.\r\n * alignCellsBottom - Aligns the selection cells at the bottom.\r\n * alignFontLeft - Sets the horizontal text alignment to left.\r\n * alignFontCenter - Sets the horizontal text alignment to center.\r\n * alignFontRight - Sets the horizontal text alignment to right.\r\n * alignFontTop - Sets the vertical text alignment to top.\r\n * alignFontMiddle - Sets the vertical text alignment to middle.\r\n * alignFontBottom - Sets the vertical text alignment to bottom.\r\n * toggleTasks - Shows or hides the tasks window.\r\n * toggleHelp - Shows or hides the help window.\r\n * toggleOutline - Shows or hides the outline window.\r\n * toggleConsole - Shows or hides the console window.\r\n */\r\nmxEditor.prototype.addActions = function ()\r\n{\r\n\tthis.addAction('save', function(editor)\r\n\t{\r\n\t\teditor.save();\r\n\t});\r\n\t\r\n\tthis.addAction('print', function(editor)\r\n\t{\r\n\t\tvar preview = new mxPrintPreview(editor.graph, 1);\r\n\t\tpreview.open();\r\n\t});\r\n\t\r\n\tthis.addAction('show', function(editor)\r\n\t{\r\n\t\tmxUtils.show(editor.graph, null, 10, 10);\r\n\t});\r\n\r\n\tthis.addAction('exportImage', function(editor)\r\n\t{\r\n\t\tvar url = editor.getUrlImage();\r\n\t\t\r\n\t\tif (url == null || mxClient.IS_LOCAL)\r\n\t\t{\r\n\t\t\teditor.execute('show');\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar node = mxUtils.getViewXml(editor.graph, 1);\r\n\t\t\tvar xml = mxUtils.getXml(node, '\\n');\r\n\r\n\t\t\tmxUtils.submit(url, editor.postParameterName + '=' +\r\n\t\t\t\tencodeURIComponent(xml), document, '_blank');\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('refresh', function(editor)\r\n\t{\r\n\t\teditor.graph.refresh();\r\n\t});\r\n\t\r\n\tthis.addAction('cut', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\tmxClipboard.cut(editor.graph);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('copy', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\tmxClipboard.copy(editor.graph);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('paste', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\tmxClipboard.paste(editor.graph);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('delete', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.removeCells();\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('group', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.setSelectionCell(editor.groupCells());\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('ungroup', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.setSelectionCells(editor.graph.ungroupCells());\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('removeFromParent', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.removeCellsFromParent();\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('undo', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.undo();\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('redo', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.redo();\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('zoomIn', function(editor)\r\n\t{\r\n\t\teditor.graph.zoomIn();\r\n\t});\r\n\t\r\n\tthis.addAction('zoomOut', function(editor)\r\n\t{\r\n\t\teditor.graph.zoomOut();\r\n\t});\r\n\t\r\n\tthis.addAction('actualSize', function(editor)\r\n\t{\r\n\t\teditor.graph.zoomActual();\r\n\t});\r\n\t\r\n\tthis.addAction('fit', function(editor)\r\n\t{\r\n\t\teditor.graph.fit();\r\n\t});\r\n\t\r\n\tthis.addAction('showProperties', function(editor, cell)\r\n\t{\r\n\t\teditor.showProperties(cell);\r\n\t});\r\n\t\r\n\tthis.addAction('selectAll', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.selectAll();\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('selectNone', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.clearSelection();\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('selectVertices', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.selectVertices();\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('selectEdges', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.selectEdges();\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('edit', function(editor, cell)\r\n\t{\r\n\t\tif (editor.graph.isEnabled() &&\r\n\t\t\teditor.graph.isCellEditable(cell))\r\n\t\t{\r\n\t\t\teditor.graph.startEditingAtCell(cell);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('toBack', function(editor, cell)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.orderCells(true);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('toFront', function(editor, cell)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.orderCells(false);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('enterGroup', function(editor, cell)\r\n\t{\r\n\t\teditor.graph.enterGroup(cell);\r\n\t});\r\n\t\r\n\tthis.addAction('exitGroup', function(editor)\r\n\t{\r\n\t\teditor.graph.exitGroup();\r\n\t});\r\n\t\r\n\tthis.addAction('home', function(editor)\r\n\t{\r\n\t\teditor.graph.home();\r\n\t});\r\n\t\r\n\tthis.addAction('selectPrevious', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.selectPreviousCell();\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('selectNext', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.selectNextCell();\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('selectParent', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.selectParentCell();\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('selectChild', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.selectChildCell();\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('collapse', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.foldCells(true);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('collapseAll', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\tvar cells = editor.graph.getChildVertices();\r\n\t\t\teditor.graph.foldCells(true, false, cells);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('expand', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.foldCells(false);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('expandAll', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\tvar cells = editor.graph.getChildVertices();\r\n\t\t\teditor.graph.foldCells(false, false, cells);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('bold', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.toggleCellStyleFlags(\r\n\t\t\t\tmxConstants.STYLE_FONTSTYLE,\r\n\t\t\t\tmxConstants.FONT_BOLD);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('italic', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.toggleCellStyleFlags(\r\n\t\t\t\tmxConstants.STYLE_FONTSTYLE,\r\n\t\t\t\tmxConstants.FONT_ITALIC);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('underline', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.toggleCellStyleFlags(\r\n\t\t\t\tmxConstants.STYLE_FONTSTYLE,\r\n\t\t\t\tmxConstants.FONT_UNDERLINE);\r\n\t\t}\r\n\t});\r\n\r\n\tthis.addAction('alignCellsLeft', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.alignCells(mxConstants.ALIGN_LEFT);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('alignCellsCenter', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.alignCells(mxConstants.ALIGN_CENTER);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('alignCellsRight', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.alignCells(mxConstants.ALIGN_RIGHT);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('alignCellsTop', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.alignCells(mxConstants.ALIGN_TOP);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('alignCellsMiddle', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.alignCells(mxConstants.ALIGN_MIDDLE);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('alignCellsBottom', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.alignCells(mxConstants.ALIGN_BOTTOM);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('alignFontLeft', function(editor)\r\n\t{\r\n\t\t\r\n\t\teditor.graph.setCellStyles(\r\n\t\t\tmxConstants.STYLE_ALIGN,\r\n\t\t\tmxConstants.ALIGN_LEFT);\r\n\t});\r\n\t\r\n\tthis.addAction('alignFontCenter', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.setCellStyles(\r\n\t\t\t\tmxConstants.STYLE_ALIGN,\r\n\t\t\t\tmxConstants.ALIGN_CENTER);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('alignFontRight', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.setCellStyles(\r\n\t\t\t\tmxConstants.STYLE_ALIGN,\r\n\t\t\t\tmxConstants.ALIGN_RIGHT);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('alignFontTop', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.setCellStyles(\r\n\t\t\t\tmxConstants.STYLE_VERTICAL_ALIGN,\r\n\t\t\t\tmxConstants.ALIGN_TOP);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('alignFontMiddle', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.setCellStyles(\r\n\t\t\t\tmxConstants.STYLE_VERTICAL_ALIGN,\r\n\t\t\t\tmxConstants.ALIGN_MIDDLE);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('alignFontBottom', function(editor)\r\n\t{\r\n\t\tif (editor.graph.isEnabled())\r\n\t\t{\r\n\t\t\teditor.graph.setCellStyles(\r\n\t\t\t\tmxConstants.STYLE_VERTICAL_ALIGN,\r\n\t\t\t\tmxConstants.ALIGN_BOTTOM);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('zoom', function(editor)\r\n\t{\r\n\t\tvar current = editor.graph.getView().scale*100;\r\n\t\tvar scale = parseFloat(mxUtils.prompt(\r\n\t\t\tmxResources.get(editor.askZoomResource) ||\r\n\t\t\teditor.askZoomResource,\r\n\t\t\tcurrent))/100;\r\n\r\n\t\tif (!isNaN(scale))\r\n\t\t{\r\n\t\t\teditor.graph.getView().setScale(scale);\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('toggleTasks', function(editor)\r\n\t{\r\n\t\tif (editor.tasks != null)\r\n\t\t{\r\n\t\t\teditor.tasks.setVisible(!editor.tasks.isVisible());\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\teditor.showTasks();\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('toggleHelp', function(editor)\r\n\t{\r\n\t\tif (editor.help != null)\r\n\t\t{\r\n\t\t\teditor.help.setVisible(!editor.help.isVisible());\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\teditor.showHelp();\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('toggleOutline', function(editor)\r\n\t{\r\n\t\tif (editor.outline == null)\r\n\t\t{\r\n\t\t\teditor.showOutline();\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\teditor.outline.setVisible(!editor.outline.isVisible());\r\n\t\t}\r\n\t});\r\n\t\r\n\tthis.addAction('toggleConsole', function(editor)\r\n\t{\r\n\t\tmxLog.setVisible(!mxLog.isVisible());\r\n\t});\r\n};\r\n\r\n/**\r\n * Function: configure\r\n *\r\n * Configures the editor using the specified node. To load the\r\n * configuration from a given URL the following code can be used to obtain\r\n * the XML node.\r\n * \r\n * (code)\r\n * var node = mxUtils.load(url).getDocumentElement();\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * node - XML node that contains the configuration.\r\n */\r\nmxEditor.prototype.configure = function (node)\r\n{\r\n\tif (node != null)\r\n\t{\r\n\t\t// Creates a decoder for the XML data\r\n\t\t// and uses it to configure the editor\r\n\t\tvar dec = new mxCodec(node.ownerDocument);\r\n\t\tdec.decode(node, this);\r\n\t\t\r\n\t\t// Resets the counters, modified state and\r\n\t\t// command history\r\n\t\tthis.resetHistory();\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: resetFirstTime\r\n * \r\n * Resets the cookie that is used to remember if the editor has already\r\n * been used.\r\n */\r\nmxEditor.prototype.resetFirstTime = function ()\r\n{\r\n\tdocument.cookie =\r\n\t\t'mxgraph=seen; expires=Fri, 27 Jul 2001 02:47:11 UTC; path=/';\r\n};\r\n\r\n/**\r\n * Function: resetHistory\r\n * \r\n * Resets the command history, modified state and counters.\r\n */\r\nmxEditor.prototype.resetHistory = function ()\r\n{\r\n\tthis.lastSnapshot = new Date().getTime();\r\n\tthis.undoManager.clear();\r\n\tthis.ignoredChanges = 0;\r\n\tthis.setModified(false);\r\n};\r\n\r\n/**\r\n * Function: addAction\r\n * \r\n * Binds the specified actionname to the specified function.\r\n * \r\n * Parameters:\r\n * \r\n * actionname - String that specifies the name of the action\r\n * to be added.\r\n * funct - Function that implements the new action. The first\r\n * argument of the function is the editor it is used\r\n * with, the second argument is the cell it operates\r\n * upon.\r\n * \r\n * Example:\r\n * (code)\r\n * editor.addAction('test', function(editor, cell)\r\n * {\r\n * \t\tmxUtils.alert(\"test \"+cell);\r\n * });\r\n * (end)\r\n */\r\nmxEditor.prototype.addAction = function (actionname, funct)\r\n{\r\n\tthis.actions[actionname] = funct;\r\n};\r\n\r\n/**\r\n * Function: execute\r\n * \r\n * Executes the function with the given name in <actions> passing the\r\n * editor instance and given cell as the first and second argument. All\r\n * additional arguments are passed to the action as well. This method\r\n * contains a try-catch block and displays an error message if an action\r\n * causes an exception. The exception is re-thrown after the error\r\n * message was displayed.\r\n * \r\n * Example:\r\n * \r\n * (code)\r\n * editor.execute(\"showProperties\", cell);\r\n * (end)\r\n */\r\nmxEditor.prototype.execute = function (actionname, cell, evt)\r\n{\r\n\tvar action = this.actions[actionname];\r\n\t\r\n\tif (action != null)\r\n\t{\r\n\t\ttry\r\n\t\t{\r\n\t\t\t// Creates the array of arguments by replacing the actionname\r\n\t\t\t// with the editor instance in the args of this function\r\n\t\t\tvar args = arguments;\r\n\t\t\targs[0] = this;\r\n\t\t\t\r\n\t\t\t// Invokes the function on the editor using the args\r\n\t\t\taction.apply(this, args);\r\n\t\t}\r\n\t\tcatch (e)\r\n\t\t{\r\n\t\t\tmxUtils.error('Cannot execute ' + actionname +\r\n\t\t\t\t': ' + e.message, 280, true);\r\n\t\t\t\r\n\t\t\tthrow e;\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tmxUtils.error('Cannot find action '+actionname, 280, true);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: addTemplate\r\n * \r\n * Adds the specified template under the given name in <templates>.\r\n */\r\nmxEditor.prototype.addTemplate = function (name, template)\r\n{\r\n\tthis.templates[name] = template;\r\n};\r\n\r\n/**\r\n * Function: getTemplate\r\n * \r\n * Returns the template for the given name.\r\n */\r\nmxEditor.prototype.getTemplate = function (name)\r\n{\r\n\treturn this.templates[name];\r\n};\r\n\r\n/**\r\n * Function: createGraph\r\n * \r\n * Creates the <graph> for the editor. The graph is created with no\r\n * container and is initialized from <setGraphContainer>.\r\n */\r\nmxEditor.prototype.createGraph = function ()\r\n{\r\n\tvar graph = new mxGraph(null, null, this.graphRenderHint);\r\n\t\r\n\t// Enables rubberband, tooltips, panning\r\n\tgraph.setTooltips(true);\r\n\tgraph.setPanning(true);\r\n\r\n\t// Overrides the dblclick method on the graph to\r\n\t// invoke the dblClickAction for a cell and reset\r\n\t// the selection tool in the toolbar\r\n\tthis.installDblClickHandler(graph);\r\n\t\r\n\t// Installs the command history\r\n\tthis.installUndoHandler(graph);\r\n\r\n\t// Installs the handlers for the root event\r\n\tthis.installDrillHandler(graph);\r\n\t\r\n\t// Installs the handler for validation\r\n\tthis.installChangeHandler(graph);\r\n\r\n\t// Installs the handler for calling the\r\n\t// insert function and consume the\r\n\t// event if an insert function is defined\r\n\tthis.installInsertHandler(graph);\r\n\r\n\t// Redirects the function for creating the\r\n\t// popupmenu items\r\n\tgraph.popupMenuHandler.factoryMethod =\r\n\t\tmxUtils.bind(this, function(menu, cell, evt)\r\n\t\t{\r\n\t\t\treturn this.createPopupMenu(menu, cell, evt);\r\n\t\t});\r\n\r\n\t// Redirects the function for creating\r\n\t// new connections in the diagram\r\n\tgraph.connectionHandler.factoryMethod =\r\n\t\tmxUtils.bind(this, function(source, target)\r\n\t\t{\r\n\t\t\treturn this.createEdge(source, target);\r\n\t\t});\r\n\t\r\n\t// Maintains swimlanes and installs autolayout\r\n\tthis.createSwimlaneManager(graph);\r\n\tthis.createLayoutManager(graph);\r\n\t\r\n\treturn graph;\r\n};\r\n\r\n/**\r\n * Function: createSwimlaneManager\r\n * \r\n * Sets the graph's container using <mxGraph.init>.\r\n */\r\nmxEditor.prototype.createSwimlaneManager = function (graph)\r\n{\r\n\tvar swimlaneMgr = new mxSwimlaneManager(graph, false);\r\n\r\n\tswimlaneMgr.isHorizontal = mxUtils.bind(this, function()\r\n\t{\r\n\t\treturn this.horizontalFlow;\r\n\t});\r\n\t\r\n\tswimlaneMgr.isEnabled = mxUtils.bind(this, function()\r\n\t{\r\n\t\treturn this.maintainSwimlanes;\r\n\t});\r\n\t\r\n\treturn swimlaneMgr;\r\n};\r\n\r\n/**\r\n * Function: createLayoutManager\r\n * \r\n * Creates a layout manager for the swimlane and diagram layouts, that\r\n * is, the locally defined inter- and intraswimlane layouts.\r\n */\r\nmxEditor.prototype.createLayoutManager = function (graph)\r\n{\r\n\tvar layoutMgr = new mxLayoutManager(graph);\r\n\t\r\n\tvar self = this; // closure\r\n\tlayoutMgr.getLayout = function(cell)\r\n\t{\r\n\t\tvar layout = null;\r\n\t\tvar model = self.graph.getModel();\r\n\t\t\r\n\t\tif (model.getParent(cell) != null)\r\n\t\t{\r\n\t\t\t// Executes the swimlane layout if a child of\r\n\t\t\t// a swimlane has been changed. The layout is\r\n\t\t\t// lazy created in createSwimlaneLayout.\r\n\t\t\tif (self.layoutSwimlanes &&\r\n\t\t\t\tgraph.isSwimlane(cell))\r\n\t\t\t{\r\n\t\t\t\tif (self.swimlaneLayout == null)\r\n\t\t\t\t{\r\n\t\t\t\t\tself.swimlaneLayout = self.createSwimlaneLayout();\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tlayout = self.swimlaneLayout;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Executes the diagram layout if the modified\r\n\t\t\t// cell is a top-level cell. The layout is\r\n\t\t\t// lazy created in createDiagramLayout.\r\n\t\t\telse if (self.layoutDiagram &&\r\n\t\t\t\t(graph.isValidRoot(cell) ||\r\n\t\t\t\tmodel.getParent(model.getParent(cell)) == null))\r\n\t\t\t{\r\n\t\t\t\tif (self.diagramLayout == null)\r\n\t\t\t\t{\r\n\t\t\t\t\tself.diagramLayout = self.createDiagramLayout();\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tlayout = self.diagramLayout;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\t\r\n\t\treturn layout;\r\n\t};\r\n\t\r\n\treturn layoutMgr;\r\n};\r\n\r\n/**\r\n * Function: setGraphContainer\r\n * \r\n * Sets the graph's container using <mxGraph.init>.\r\n */\r\nmxEditor.prototype.setGraphContainer = function (container)\r\n{\r\n\tif (this.graph.container == null)\r\n\t{\r\n\t\t// Creates the graph instance inside the given container and render hint\r\n\t\t//this.graph = new mxGraph(container, null, this.graphRenderHint);\r\n\t\tthis.graph.init(container);\r\n\r\n\t\t// Install rubberband selection as the last\r\n\t\t// action handler in the chain\r\n\t\tthis.rubberband = new mxRubberband(this.graph);\r\n\r\n\t\t// Disables the context menu\r\n\t\tif (this.disableContextMenu)\r\n\t\t{\r\n\t\t\tmxEvent.disableContextMenu(container);\r\n\t\t}\r\n\r\n\t\t// Workaround for stylesheet directives in IE\r\n\t\tif (mxClient.IS_QUIRKS)\r\n\t\t{\r\n\t\t\tnew mxDivResizer(container);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: installDblClickHandler\r\n * \r\n * Overrides <mxGraph.dblClick> to invoke <dblClickAction>\r\n * on a cell and reset the selection tool in the toolbar.\r\n */\r\nmxEditor.prototype.installDblClickHandler = function (graph)\r\n{\r\n\t// Installs a listener for double click events\r\n\tgraph.addListener(mxEvent.DOUBLE_CLICK,\r\n\t\tmxUtils.bind(this, function(sender, evt)\r\n\t\t{\r\n\t\t\tvar cell = evt.getProperty('cell');\r\n\t\t\t\r\n\t\t\tif (cell != null &&\r\n\t\t\t\tgraph.isEnabled() &&\r\n\t\t\t\tthis.dblClickAction != null)\r\n\t\t\t{\r\n\t\t\t\tthis.execute(this.dblClickAction, cell);\r\n\t\t\t\tevt.consume();\r\n\t\t\t}\r\n\t\t})\r\n\t);\r\n};\r\n\t\t\r\n/**\r\n * Function: installUndoHandler\r\n * \r\n * Adds the <undoManager> to the graph model and the view.\r\n */\r\nmxEditor.prototype.installUndoHandler = function (graph)\r\n{\t\t\t\t\r\n\tvar listener = mxUtils.bind(this, function(sender, evt)\r\n\t{\r\n\t\tvar edit = evt.getProperty('edit');\r\n\t\tthis.undoManager.undoableEditHappened(edit);\r\n\t});\r\n\t\r\n\tgraph.getModel().addListener(mxEvent.UNDO, listener);\r\n\tgraph.getView().addListener(mxEvent.UNDO, listener);\r\n\r\n\t// Keeps the selection state in sync\r\n\tvar undoHandler = function(sender, evt)\r\n\t{\r\n\t\tvar changes = evt.getProperty('edit').changes;\r\n\t\tgraph.setSelectionCells(graph.getSelectionCellsForChanges(changes));\r\n\t};\r\n\t\r\n\tthis.undoManager.addListener(mxEvent.UNDO, undoHandler);\r\n\tthis.undoManager.addListener(mxEvent.REDO, undoHandler);\r\n};\r\n\t\t\r\n/**\r\n * Function: installDrillHandler\r\n * \r\n * Installs listeners for dispatching the <root> event.\r\n */\r\nmxEditor.prototype.installDrillHandler = function (graph)\r\n{\t\t\t\t\r\n\tvar listener = mxUtils.bind(this, function(sender)\r\n\t{\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.ROOT));\r\n\t});\r\n\t\r\n\tgraph.getView().addListener(mxEvent.DOWN, listener);\r\n\tgraph.getView().addListener(mxEvent.UP, listener);\r\n};\r\n\r\n/**\r\n * Function: installChangeHandler\r\n * \r\n * Installs the listeners required to automatically validate\r\n * the graph. On each change of the root, this implementation\r\n * fires a <root> event.\r\n */\r\nmxEditor.prototype.installChangeHandler = function (graph)\r\n{\r\n\tvar listener = mxUtils.bind(this, function(sender, evt)\r\n\t{\r\n\t\t// Updates the modified state\r\n\t\tthis.setModified(true);\r\n\r\n\t\t// Automatically validates the graph\r\n\t\t// after each change\r\n\t\tif (this.validating == true)\r\n\t\t{\r\n\t\t\tgraph.validateGraph();\r\n\t\t}\r\n\r\n\t\t// Checks if the root has been changed\r\n\t\tvar changes = evt.getProperty('edit').changes;\r\n\t\t\r\n\t\tfor (var i = 0; i < changes.length; i++)\r\n\t\t{\r\n\t\t\tvar change = changes[i];\r\n\t\t\t\r\n\t\t\tif (change instanceof mxRootChange ||\r\n\t\t\t\t(change instanceof mxValueChange &&\r\n\t\t\t\tchange.cell == this.graph.model.root) ||\r\n\t\t\t\t(change instanceof mxCellAttributeChange &&\r\n\t\t\t\tchange.cell == this.graph.model.root))\r\n\t\t\t{\r\n\t\t\t\tthis.fireEvent(new mxEventObject(mxEvent.ROOT));\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t});\r\n\t\r\n\tgraph.getModel().addListener(mxEvent.CHANGE, listener);\r\n};\r\n\r\n/**\r\n * Function: installInsertHandler\r\n * \r\n * Installs the handler for invoking <insertFunction> if\r\n * one is defined.\r\n */\r\nmxEditor.prototype.installInsertHandler = function (graph)\r\n{\r\n\tvar self = this; // closure\r\n\tvar insertHandler =\r\n\t{\r\n\t\tmouseDown: function(sender, me)\r\n\t\t{\r\n\t\t\tif (self.insertFunction != null &&\r\n\t\t\t\t!me.isPopupTrigger() &&\r\n\t\t\t\t(self.forcedInserting ||\r\n\t\t\t\tme.getState() == null))\r\n\t\t\t{\r\n\t\t\t\tself.graph.clearSelection();\r\n\t\t\t\tself.insertFunction(me.getEvent(), me.getCell());\r\n\r\n\t\t\t\t// Consumes the rest of the events\r\n\t\t\t\t// for this gesture (down, move, up)\r\n\t\t\t\tthis.isActive = true;\r\n\t\t\t\tme.consume();\r\n\t\t\t}\r\n\t\t},\r\n\t\t\r\n\t\tmouseMove: function(sender, me)\r\n\t\t{\r\n\t\t\tif (this.isActive)\r\n\t\t\t{\r\n\t\t\t\tme.consume();\r\n\t\t\t}\r\n\t\t},\r\n\t\t\r\n\t\tmouseUp: function(sender, me)\r\n\t\t{\r\n\t\t\tif (this.isActive)\r\n\t\t\t{\r\n\t\t\t\tthis.isActive = false;\r\n\t\t\t\tme.consume();\r\n\t\t\t}\r\n\t\t}\r\n\t};\r\n\t\r\n\tgraph.addMouseListener(insertHandler);\r\n};\r\n\r\n/**\r\n * Function: createDiagramLayout\r\n * \r\n * Creates the layout instance used to layout the\r\n * swimlanes in the diagram.\r\n */\r\nmxEditor.prototype.createDiagramLayout = function ()\r\n{\r\n\tvar gs = this.graph.gridSize;\r\n\tvar layout = new mxStackLayout(this.graph, !this.horizontalFlow,\r\n\t\t this.swimlaneSpacing, 2*gs, 2*gs);\r\n\t\r\n\t// Overrides isIgnored to only take into account swimlanes\r\n\tlayout.isVertexIgnored = function(cell)\r\n\t{\r\n\t\treturn !layout.graph.isSwimlane(cell);\r\n\t};\r\n\t\r\n\treturn layout;\r\n};\r\n\r\n/**\r\n * Function: createSwimlaneLayout\r\n * \r\n * Creates the layout instance used to layout the\r\n * children of each swimlane.\r\n */\r\nmxEditor.prototype.createSwimlaneLayout = function ()\r\n{\r\n\treturn new mxCompactTreeLayout(this.graph, this.horizontalFlow);\r\n};\r\n\r\n/**\r\n * Function: createToolbar\r\n * \r\n * Creates the <toolbar> with no container.\r\n */\r\nmxEditor.prototype.createToolbar = function ()\r\n{\r\n\treturn new mxDefaultToolbar(null, this);\r\n};\r\n\r\n/**\r\n * Function: setToolbarContainer\r\n * \r\n * Initializes the toolbar for the given container.\r\n */\r\nmxEditor.prototype.setToolbarContainer = function (container)\r\n{\r\n\tthis.toolbar.init(container);\r\n\t\r\n\t// Workaround for stylesheet directives in IE\r\n\tif (mxClient.IS_QUIRKS)\r\n\t{\r\n\t\tnew mxDivResizer(container);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setStatusContainer\r\n * \r\n * Creates the <status> using the specified container.\r\n * \r\n * This implementation adds listeners in the editor to \r\n * display the last saved time and the current filename \r\n * in the status bar.\r\n * \r\n * Parameters:\r\n * \r\n * container - DOM node that will contain the statusbar.\r\n */\r\nmxEditor.prototype.setStatusContainer = function (container)\r\n{\r\n\tif (this.status == null)\r\n\t{\r\n\t\tthis.status = container;\r\n\t\t\r\n\t\t// Prints the last saved time in the status bar\r\n\t\t// when files are saved\r\n\t\tthis.addListener(mxEvent.SAVE, mxUtils.bind(this, function()\r\n\t\t{\r\n\t\t\tvar tstamp = new Date().toLocaleString();\r\n\t\t\tthis.setStatus((mxResources.get(this.lastSavedResource) ||\r\n\t\t\t\tthis.lastSavedResource)+': '+tstamp);\r\n\t\t}));\r\n\t\t\r\n\t\t// Updates the statusbar to display the filename\r\n\t\t// when new files are opened\r\n\t\tthis.addListener(mxEvent.OPEN, mxUtils.bind(this, function()\r\n\t\t{\r\n\t\t\tthis.setStatus((mxResources.get(this.currentFileResource) ||\r\n\t\t\t\tthis.currentFileResource)+': '+this.filename);\r\n\t\t}));\r\n\t\t\r\n\t\t// Workaround for stylesheet directives in IE\r\n\t\tif (mxClient.IS_QUIRKS)\r\n\t\t{\r\n\t\t\tnew mxDivResizer(container);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setStatus\r\n * \r\n * Display the specified message in the status bar.\r\n * \r\n * Parameters:\r\n * \r\n * message - String the specified the message to\r\n * be displayed.\r\n */\r\nmxEditor.prototype.setStatus = function (message)\r\n{\r\n\tif (this.status != null && message != null)\r\n\t{\r\n\t\tthis.status.innerHTML = message;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setTitleContainer\r\n * \r\n * Creates a listener to update the inner HTML of the\r\n * specified DOM node with the value of <getTitle>.\r\n * \r\n * Parameters:\r\n * \r\n * container - DOM node that will contain the title.\r\n */\r\nmxEditor.prototype.setTitleContainer = function (container)\r\n{\r\n\tthis.addListener(mxEvent.ROOT, mxUtils.bind(this, function(sender)\r\n\t{\r\n\t\tcontainer.innerHTML = this.getTitle();\r\n\t}));\r\n\r\n\t// Workaround for stylesheet directives in IE\r\n\tif (mxClient.IS_QUIRKS)\r\n\t{\r\n\t\tnew mxDivResizer(container);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: treeLayout\r\n * \r\n * Executes a vertical or horizontal compact tree layout\r\n * using the specified cell as an argument. The cell may\r\n * either be a group or the root of a tree.\r\n * \r\n * Parameters:\r\n * \r\n * cell - <mxCell> to use in the compact tree layout.\r\n * horizontal - Optional boolean to specify the tree's\r\n * orientation. Default is true.\r\n */\r\nmxEditor.prototype.treeLayout = function (cell, horizontal)\r\n{\r\n\tif (cell != null)\r\n\t{\r\n\t\tvar layout = new mxCompactTreeLayout(this.graph, horizontal);\r\n\t\tlayout.execute(cell);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getTitle\r\n * \r\n * Returns the string value for the current root of the\r\n * diagram.\r\n */\r\nmxEditor.prototype.getTitle = function ()\r\n{\r\n\tvar title = '';\r\n\tvar graph = this.graph;\r\n\tvar cell = graph.getCurrentRoot();\r\n\t\r\n\twhile (cell != null &&\r\n\t\t   graph.getModel().getParent(\r\n\t\t\t\tgraph.getModel().getParent(cell)) != null)\r\n\t{\r\n\t\t// Append each label of a valid root\r\n\t\tif (graph.isValidRoot(cell))\r\n\t\t{\r\n\t\t\ttitle = ' > ' +\r\n\t\t\tgraph.convertValueToString(cell) + title;\r\n\t\t}\r\n\t\t\r\n\t\tcell = graph.getModel().getParent(cell);\r\n\t}\r\n\t\r\n\tvar prefix = this.getRootTitle();\r\n\t\r\n\treturn prefix + title;\r\n};\r\n\r\n/**\r\n * Function: getRootTitle\r\n * \r\n * Returns the string value of the root cell in\r\n * <mxGraph.model>.\r\n */\r\nmxEditor.prototype.getRootTitle = function ()\r\n{\r\n\tvar root = this.graph.getModel().getRoot();\r\n\treturn this.graph.convertValueToString(root);\r\n};\r\n\r\n/**\r\n * Function: undo\r\n * \r\n * Undo the last change in <graph>.\r\n */\r\nmxEditor.prototype.undo = function ()\r\n{\r\n\tthis.undoManager.undo();\r\n};\r\n\r\n/**\r\n * Function: redo\r\n * \r\n * Redo the last change in <graph>.\r\n */\r\nmxEditor.prototype.redo = function ()\r\n{\r\n\tthis.undoManager.redo();\r\n};\r\n\r\n/**\r\n * Function: groupCells\r\n * \r\n * Invokes <createGroup> to create a new group cell and the invokes\r\n * <mxGraph.groupCells>, using the grid size of the graph as the spacing\r\n * in the group's content area.\r\n */\r\nmxEditor.prototype.groupCells = function ()\r\n{\r\n\tvar border = (this.groupBorderSize != null) ?\r\n\t\tthis.groupBorderSize :\r\n\t\tthis.graph.gridSize;\r\n\treturn this.graph.groupCells(this.createGroup(), border);\r\n};\r\n\r\n/**\r\n * Function: createGroup\r\n * \r\n * Creates and returns a clone of <defaultGroup> to be used\r\n * as a new group cell in <group>.\r\n */\r\nmxEditor.prototype.createGroup = function ()\r\n{\r\n\tvar model = this.graph.getModel();\r\n\t\r\n\treturn model.cloneCell(this.defaultGroup);\r\n};\r\n\r\n/**\r\n * Function: open\r\n * \r\n * Opens the specified file synchronously and parses it using\r\n * <readGraphModel>. It updates <filename> and fires an <open>-event after\r\n * the file has been opened. Exceptions should be handled as follows:\r\n * \r\n * (code)\r\n * try\r\n * {\r\n *   editor.open(filename);\r\n * }\r\n * catch (e)\r\n * {\r\n *   mxUtils.error('Cannot open ' + filename +\r\n *     ': ' + e.message, 280, true);\r\n * }\r\n * (end)\r\n *\r\n * Parameters:\r\n * \r\n * filename - URL of the file to be opened.\r\n */\r\nmxEditor.prototype.open = function (filename)\r\n{\r\n\tif (filename != null)\r\n\t{\r\n\t\tvar xml = mxUtils.load(filename).getXml();\r\n\t\tthis.readGraphModel(xml.documentElement);\r\n\t\tthis.filename = filename;\r\n\t\t\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.OPEN, 'filename', filename));\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: readGraphModel\r\n * \r\n * Reads the specified XML node into the existing graph model and resets\r\n * the command history and modified state.\r\n */\r\nmxEditor.prototype.readGraphModel = function (node)\r\n{\r\n\tvar dec = new mxCodec(node.ownerDocument);\r\n\tdec.decode(node, this.graph.getModel());\r\n\tthis.resetHistory();\r\n};\r\n\r\n/**\r\n * Function: save\r\n * \r\n * Posts the string returned by <writeGraphModel> to the given URL or the\r\n * URL returned by <getUrlPost>. The actual posting is carried out by\r\n * <postDiagram>. If the URL is null then the resulting XML will be\r\n * displayed using <mxUtils.popup>. Exceptions should be handled as\r\n * follows:\r\n * \r\n * (code)\r\n * try\r\n * {\r\n *   editor.save();\r\n * }\r\n * catch (e)\r\n * {\r\n *   mxUtils.error('Cannot save : ' + e.message, 280, true);\r\n * }\r\n * (end)\r\n */\r\nmxEditor.prototype.save = function (url, linefeed)\r\n{\r\n\t// Gets the URL to post the data to\r\n\turl = url || this.getUrlPost();\r\n\r\n\t// Posts the data if the URL is not empty\r\n\tif (url != null && url.length > 0)\r\n\t{\r\n\t\tvar data = this.writeGraphModel(linefeed);\r\n\t\tthis.postDiagram(url, data);\r\n\t\t\r\n\t\t// Resets the modified flag\r\n\t\tthis.setModified(false);\r\n\t}\r\n\t\r\n\t// Dispatches a save event\r\n\tthis.fireEvent(new mxEventObject(mxEvent.SAVE, 'url', url));\r\n};\r\n\r\n/**\r\n * Function: postDiagram\r\n * \r\n * Hook for subclassers to override the posting of a diagram\r\n * represented by the given node to the given URL. This fires\r\n * an asynchronous <post> event if the diagram has been posted.\r\n * \r\n * Example:\r\n * \r\n * To replace the diagram with the diagram in the response, use the\r\n * following code.\r\n * \r\n * (code)\r\n * editor.addListener(mxEvent.POST, function(sender, evt)\r\n * {\r\n *   // Process response (replace diagram)\r\n *   var req = evt.getProperty('request');\r\n *   var root = req.getDocumentElement();\r\n *   editor.graph.readGraphModel(root)\r\n * });\r\n * (end)\r\n */\r\nmxEditor.prototype.postDiagram = function (url, data)\r\n{\r\n\tif (this.escapePostData)\r\n\t{\r\n\t\tdata = encodeURIComponent(data);\r\n\t}\r\n\r\n\tmxUtils.post(url, this.postParameterName+'='+data,\r\n\t\tmxUtils.bind(this, function(req)\r\n\t\t{\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.POST,\r\n\t\t\t\t'request', req, 'url', url, 'data', data));\r\n\t\t})\r\n\t);\r\n};\r\n\r\n/**\r\n * Function: writeGraphModel\r\n * \r\n * Hook to create the string representation of the diagram. The default\r\n * implementation uses an <mxCodec> to encode the graph model as\r\n * follows:\r\n * \r\n * (code)\r\n * var enc = new mxCodec();\r\n * var node = enc.encode(this.graph.getModel());\r\n * return mxUtils.getXml(node, this.linefeed);\r\n * (end)\r\n * \r\n * Parameters:\r\n * \r\n * linefeed - Optional character to be used as the linefeed. Default is\r\n * <linefeed>.\r\n */\r\nmxEditor.prototype.writeGraphModel = function (linefeed)\r\n{\r\n\tlinefeed = (linefeed != null) ? linefeed : this.linefeed;\r\n\tvar enc = new mxCodec();\r\n\tvar node = enc.encode(this.graph.getModel());\r\n\r\n\treturn mxUtils.getXml(node, linefeed);\r\n};\r\n\r\n/**\r\n * Function: getUrlPost\r\n * \r\n * Returns the URL to post the diagram to. This is used\r\n * in <save>. The default implementation returns <urlPost>,\r\n * adding <code>?draft=true</code>.\r\n */\r\nmxEditor.prototype.getUrlPost = function ()\r\n{\r\n\treturn this.urlPost;\r\n};\r\n\r\n/**\r\n * Function: getUrlImage\r\n * \r\n * Returns the URL to create the image with. This is typically\r\n * the URL of a backend which accepts an XML representation\r\n * of a graph view to create an image. The function is used\r\n * in the image action to create an image. This implementation\r\n * returns <urlImage>.\r\n */\r\nmxEditor.prototype.getUrlImage = function ()\r\n{\r\n\treturn this.urlImage;\r\n};\r\n\r\n/**\r\n * Function: swapStyles\r\n * \r\n * Swaps the styles for the given names in the graph's\r\n * stylesheet and refreshes the graph.\r\n */\r\nmxEditor.prototype.swapStyles = function (first, second)\r\n{\r\n\tvar style = this.graph.getStylesheet().styles[second];\r\n\tthis.graph.getView().getStylesheet().putCellStyle(\r\n\t\tsecond, this.graph.getStylesheet().styles[first]);\r\n\tthis.graph.getStylesheet().putCellStyle(first, style);\r\n\tthis.graph.refresh();\r\n};\r\n\r\n/**\r\n * Function: showProperties\r\n * \r\n * Creates and shows the properties dialog for the given\r\n * cell. The content area of the dialog is created using\r\n * <createProperties>.\r\n */\r\nmxEditor.prototype.showProperties = function (cell)\r\n{\r\n\tcell = cell || this.graph.getSelectionCell();\r\n\t\r\n\t// Uses the root node for the properties dialog\r\n\t// if not cell was passed in and no cell is\r\n\t// selected\r\n\tif (cell == null)\r\n\t{\r\n\t\tcell = this.graph.getCurrentRoot();\r\n\t\t\r\n\t\tif (cell == null)\r\n\t\t{\r\n\t\t\tcell = this.graph.getModel().getRoot();\r\n\t\t}\r\n\t}\r\n\t\r\n\tif (cell != null)\r\n\t{\r\n\t\t// Makes sure there is no in-place editor in the\r\n\t\t// graph and computes the location of the dialog\r\n\t\tthis.graph.stopEditing(true);\r\n\r\n\t\tvar offset = mxUtils.getOffset(this.graph.container);\r\n\t\tvar x = offset.x+10;\r\n\t\tvar y = offset.y;\r\n\t\t\r\n\t\t// Avoids moving the dialog if it is alredy open\r\n\t\tif (this.properties != null && !this.movePropertiesDialog)\r\n\t\t{\r\n\t\t\tx = this.properties.getX();\r\n\t\t\ty = this.properties.getY();\r\n\t\t}\r\n\t\t\r\n\t\t// Places the dialog near the cell for which it\r\n\t\t// displays the properties\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar bounds = this.graph.getCellBounds(cell);\r\n\t\t\t\r\n\t\t\tif (bounds != null)\r\n\t\t\t{\r\n\t\t\t\tx += bounds.x+Math.min(200, bounds.width);\r\n\t\t\t\ty += bounds.y;\t\t\t\t\r\n\t\t\t}\t\t\t\r\n\t\t}\r\n\t\t\r\n\t\t// Hides the existing properties dialog and creates a new one with the\r\n\t\t// contents created in the hook method\r\n\t\tthis.hideProperties();\r\n\t\tvar node = this.createProperties(cell);\r\n\t\t\r\n\t\tif (node != null)\r\n\t\t{\r\n\t\t\t// Displays the contents in a window and stores a reference to the\r\n\t\t\t// window for later hiding of the window\r\n\t\t\tthis.properties = new mxWindow(mxResources.get(this.propertiesResource) ||\r\n\t\t\t\tthis.propertiesResource, node, x, y, this.propertiesWidth, this.propertiesHeight, false);\r\n\t\t\tthis.properties.setVisible(true);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isPropertiesVisible\r\n * \r\n * Returns true if the properties dialog is currently visible.\r\n */\r\nmxEditor.prototype.isPropertiesVisible = function ()\r\n{\r\n\treturn this.properties != null;\r\n};\r\n\r\n/**\r\n * Function: createProperties\r\n * \r\n * Creates and returns the DOM node that represents the contents\r\n * of the properties dialog for the given cell. This implementation\r\n * works for user objects that are XML nodes and display all the\r\n * node attributes in a form.\r\n */\r\nmxEditor.prototype.createProperties = function (cell)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\tvar value = model.getValue(cell);\r\n\t\r\n\tif (mxUtils.isNode(value))\r\n\t{\r\n\t\t// Creates a form for the user object inside\r\n\t\t// the cell\r\n\t\tvar form = new mxForm('properties');\r\n\t\t\r\n\t\t// Adds a readonly field for the cell id\r\n\t\tvar id = form.addText('ID', cell.getId());\r\n\t\tid.setAttribute('readonly', 'true');\r\n\r\n\t\tvar geo = null;\r\n\t\tvar yField = null;\r\n\t\tvar xField = null;\r\n\t\tvar widthField = null;\r\n\t\tvar heightField = null;\r\n\r\n\t\t// Adds fields for the location and size\r\n\t\tif (model.isVertex(cell))\r\n\t\t{\r\n\t\t\tgeo = model.getGeometry(cell);\r\n\t\t\t\r\n\t\t\tif (geo != null)\r\n\t\t\t{\r\n\t\t\t\tyField = form.addText('top', geo.y);\r\n\t\t\t\txField = form.addText('left', geo.x);\r\n\t\t\t\twidthField = form.addText('width', geo.width);\r\n\t\t\t\theightField = form.addText('height', geo.height);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Adds a field for the cell style\t\t\t\r\n\t\tvar tmp = model.getStyle(cell);\r\n\t\tvar style = form.addText('Style', tmp || '');\r\n\t\t\r\n\t\t// Creates textareas for each attribute of the\r\n\t\t// user object within the cell\r\n\t\tvar attrs = value.attributes;\r\n\t\tvar texts = [];\r\n\t\t\r\n\t\tfor (var i = 0; i < attrs.length; i++)\r\n\t\t{\r\n\t\t\t// Creates a textarea with more lines for\r\n\t\t\t// the cell label\r\n\t\t\tvar val = attrs[i].value;\r\n\t\t\ttexts[i] = form.addTextarea(attrs[i].nodeName, val,\r\n\t\t\t\t(attrs[i].nodeName == 'label') ? 4 : 2);\r\n\t\t}\r\n\t\t\r\n\t\t// Adds an OK and Cancel button to the dialog\r\n\t\t// contents and implements the respective\r\n\t\t// actions below\r\n\t\t\r\n\t\t// Defines the function to be executed when the\r\n\t\t// OK button is pressed in the dialog\r\n\t\tvar okFunction = mxUtils.bind(this, function()\r\n\t\t{\r\n\t\t\t// Hides the dialog\r\n\t\t\tthis.hideProperties();\r\n\t\t\t\r\n\t\t\t// Supports undo for the changes on the underlying\r\n\t\t\t// XML structure / XML node attribute changes.\r\n\t\t\tmodel.beginUpdate();\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tif (geo != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tgeo = geo.clone();\r\n\t\t\t\t\t\r\n\t\t\t\t\tgeo.x = parseFloat(xField.value);\r\n\t\t\t\t\tgeo.y = parseFloat(yField.value);\r\n\t\t\t\t\tgeo.width = parseFloat(widthField.value);\r\n\t\t\t\t\tgeo.height = parseFloat(heightField.value);\r\n\t\t\t\t\t\r\n\t\t\t\t\tmodel.setGeometry(cell, geo);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Applies the style\r\n\t\t\t\tif (style.value.length > 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tmodel.setStyle(cell, style.value);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tmodel.setStyle(cell, null);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Creates an undoable change for each\r\n\t\t\t\t// attribute and executes it using the\r\n\t\t\t\t// model, which will also make the change\r\n\t\t\t\t// part of the current transaction\r\n\t\t\t\tfor (var i=0; i<attrs.length; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar edit = new mxCellAttributeChange(\r\n\t\t\t\t\t\tcell, attrs[i].nodeName,\r\n\t\t\t\t\t\ttexts[i].value);\r\n\t\t\t\t\tmodel.execute(edit);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Checks if the graph wants cells to \r\n\t\t\t\t// be automatically sized and updates\r\n\t\t\t\t// the size as an undoable step if\r\n\t\t\t\t// the feature is enabled\r\n\t\t\t\tif (this.graph.isAutoSizeCell(cell))\r\n\t\t\t\t{\r\n\t\t\t\t\tthis.graph.updateCellSize(cell);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tfinally\r\n\t\t\t{\r\n\t\t\t\tmodel.endUpdate();\r\n\t\t\t}\r\n\t\t});\r\n\t\t\r\n\t\t// Defines the function to be executed when the\r\n\t\t// Cancel button is pressed in the dialog\r\n\t\tvar cancelFunction = mxUtils.bind(this, function()\r\n\t\t{\r\n\t\t\t// Hides the dialog\r\n\t\t\tthis.hideProperties();\r\n\t\t});\r\n\t\t\r\n\t\tform.addButtons(okFunction, cancelFunction);\r\n\t\t\r\n\t\treturn form.table;\r\n\t}\r\n\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: hideProperties\r\n * \r\n * Hides the properties dialog.\r\n */\r\nmxEditor.prototype.hideProperties = function ()\r\n{\r\n\tif (this.properties != null)\r\n\t{\r\n\t\tthis.properties.destroy();\r\n\t\tthis.properties = null;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: showTasks\r\n * \r\n * Shows the tasks window. The tasks window is created using <createTasks>. The\r\n * default width of the window is 200 pixels, the y-coordinate of the location\r\n * can be specifies in <tasksTop> and the x-coordinate is right aligned with a\r\n * 20 pixel offset from the right border. To change the location of the tasks\r\n * window, the following code can be used:\r\n * \r\n * (code)\r\n * var oldShowTasks = mxEditor.prototype.showTasks;\r\n * mxEditor.prototype.showTasks = function()\r\n * {\r\n *   oldShowTasks.apply(this, arguments); // \"supercall\"\r\n *   \r\n *   if (this.tasks != null)\r\n *   {\r\n *     this.tasks.setLocation(10, 10);\r\n *   }\r\n * };\r\n * (end)\r\n */\r\nmxEditor.prototype.showTasks = function ()\r\n{\r\n\tif (this.tasks == null)\r\n\t{\r\n\t\tvar div = document.createElement('div');\r\n\t\tdiv.style.padding = '4px';\r\n\t\tdiv.style.paddingLeft = '20px';\r\n\t\tvar w = document.body.clientWidth;\r\n\t\tvar wnd = new mxWindow(\r\n\t\t\tmxResources.get(this.tasksResource) ||\r\n\t\t\tthis.tasksResource,\r\n\t\t\tdiv, w - 220, this.tasksTop, 200);\r\n\t\twnd.setClosable(true);\r\n\t\twnd.destroyOnClose = false;\r\n\t\t\r\n\t\t// Installs a function to update the contents\r\n\t\t// of the tasks window on every change of the\r\n\t\t// model, selection or root.\r\n\t\tvar funct = mxUtils.bind(this, function(sender)\r\n\t\t{\r\n\t\t\tmxEvent.release(div);\r\n\t\t\tdiv.innerHTML = '';\r\n\t\t\tthis.createTasks(div);\r\n\t\t});\r\n\t\t\r\n\t\tthis.graph.getModel().addListener(mxEvent.CHANGE, funct);\r\n\t\tthis.graph.getSelectionModel().addListener(mxEvent.CHANGE, funct);\r\n\t\tthis.graph.addListener(mxEvent.ROOT, funct);\r\n\t\t\r\n\t\t// Assigns the icon to the tasks window\r\n\t\tif (this.tasksWindowImage != null)\r\n\t\t{\r\n\t\t\twnd.setImage(this.tasksWindowImage);\r\n\t\t}\r\n\t\t\r\n\t\tthis.tasks = wnd;\r\n\t\tthis.createTasks(div);\r\n\t}\r\n\t\r\n\tthis.tasks.setVisible(true);\r\n};\r\n\t\t\r\n/**\r\n * Function: refreshTasks\r\n * \r\n * Updates the contents of the tasks window using <createTasks>.\r\n */\r\nmxEditor.prototype.refreshTasks = function (div)\r\n{\r\n\tif (this.tasks != null)\r\n\t{\r\n\t\tvar div = this.tasks.content;\r\n\t\tmxEvent.release(div);\r\n\t\tdiv.innerHTML = '';\r\n\t\tthis.createTasks(div);\r\n\t}\r\n};\r\n\t\t\r\n/**\r\n * Function: createTasks\r\n * \r\n * Updates the contents of the given DOM node to\r\n * display the tasks associated with the current\r\n * editor state. This is invoked whenever there\r\n * is a possible change of state in the editor.\r\n * Default implementation is empty.\r\n */\r\nmxEditor.prototype.createTasks = function (div)\r\n{\r\n\t// override\r\n};\r\n\t\r\n/**\r\n * Function: showHelp\r\n * \r\n * Shows the help window. If the help window does not exist\r\n * then it is created using an iframe pointing to the resource\r\n * for the <code>urlHelp</code> key or <urlHelp> if the resource\r\n * is undefined.\r\n */\r\nmxEditor.prototype.showHelp = function (tasks)\r\n{\r\n\tif (this.help == null)\r\n\t{\r\n\t\tvar frame = document.createElement('iframe');\r\n\t\tframe.setAttribute('src', mxResources.get('urlHelp') || this.urlHelp);\r\n\t\tframe.setAttribute('height', '100%');\r\n\t\tframe.setAttribute('width', '100%');\r\n\t\tframe.setAttribute('frameBorder', '0');\r\n\t\tframe.style.backgroundColor = 'white';\r\n\t\r\n\t\tvar w = document.body.clientWidth;\r\n\t\tvar h = (document.body.clientHeight || document.documentElement.clientHeight);\r\n\t\t\r\n\t\tvar wnd = new mxWindow(mxResources.get(this.helpResource) || this.helpResource,\r\n\t\t\tframe, (w-this.helpWidth)/2, (h-this.helpHeight)/3, this.helpWidth, this.helpHeight);\r\n\t\twnd.setMaximizable(true);\r\n\t\twnd.setClosable(true);\r\n\t\twnd.destroyOnClose = false;\r\n\t\twnd.setResizable(true);\r\n\r\n\t\t// Assigns the icon to the help window\r\n\t\tif (this.helpWindowImage != null)\r\n\t\t{\r\n\t\t\twnd.setImage(this.helpWindowImage);\r\n\t\t}\r\n\t\t\r\n\t\t// Workaround for ignored iframe height 100% in FF\r\n\t\tif (mxClient.IS_NS)\r\n\t\t{\r\n\t\t\tvar handler = function(sender)\r\n\t\t\t{\r\n\t\t\t\tvar h = wnd.div.offsetHeight;\r\n\t\t\t\tframe.setAttribute('height', (h-26)+'px');\r\n\t\t\t};\r\n\t\t\t\r\n\t\t\twnd.addListener(mxEvent.RESIZE_END, handler);\r\n\t\t\twnd.addListener(mxEvent.MAXIMIZE, handler);\r\n\t\t\twnd.addListener(mxEvent.NORMALIZE, handler);\r\n\t\t\twnd.addListener(mxEvent.SHOW, handler);\r\n\t\t}\r\n\t\t\r\n\t\tthis.help = wnd;\r\n\t}\r\n\t\r\n\tthis.help.setVisible(true);\r\n};\r\n\r\n/**\r\n * Function: showOutline\r\n * \r\n * Shows the outline window. If the window does not exist, then it is\r\n * created using an <mxOutline>.\r\n */\r\nmxEditor.prototype.showOutline = function ()\r\n{\r\n\tvar create = this.outline == null;\r\n\t\r\n\tif (create)\r\n\t{\r\n\t\tvar div = document.createElement('div');\r\n\t\t\r\n\t\tdiv.style.overflow = 'hidden';\r\n\t\tdiv.style.position = 'relative';\r\n\t\tdiv.style.width = '100%';\r\n\t\tdiv.style.height = '100%';\r\n\t\tdiv.style.background = 'white';\r\n\t\tdiv.style.cursor = 'move';\r\n\t\t\r\n\t\tif (document.documentMode == 8)\r\n\t\t{\r\n\t\t\tdiv.style.filter = 'progid:DXImageTransform.Microsoft.alpha(opacity=100)';\r\n\t\t}\r\n\t\t\r\n\t\tvar wnd = new mxWindow(\r\n\t\t\tmxResources.get(this.outlineResource) ||\r\n\t\t\tthis.outlineResource,\r\n\t\t\tdiv, 600, 480, 200, 200, false);\r\n\t\t\t\t\r\n\t\t// Creates the outline in the specified div\r\n\t\t// and links it to the existing graph\r\n\t\tvar outline = new mxOutline(this.graph, div);\t\t\t\r\n\t\twnd.setClosable(true);\r\n\t\twnd.setResizable(true);\r\n\t\twnd.destroyOnClose = false;\r\n\t\t\r\n\t\twnd.addListener(mxEvent.RESIZE_END, function()\r\n\t\t{\r\n\t\t\toutline.update();\r\n\t\t});\r\n\t\t\r\n\t\tthis.outline = wnd;\r\n\t\tthis.outline.outline = outline;\r\n\t}\r\n\t\r\n\t// Finally shows the outline\r\n\tthis.outline.setVisible(true);\r\n\tthis.outline.outline.update(true);\r\n};\r\n\t\t\r\n/**\r\n * Function: setMode\r\n *\r\n * Puts the graph into the specified mode. The following modenames are\r\n * supported:\r\n * \r\n * select - Selects using the left mouse button, new connections\r\n * are disabled.\r\n * connect - Selects using the left mouse button or creates new\r\n * connections if mouse over cell hotspot. See <mxConnectionHandler>.\r\n * pan - Pans using the left mouse button, new connections are disabled.\r\n */\r\nmxEditor.prototype.setMode = function(modename)\r\n{\r\n\tif (modename == 'select')\r\n\t{\r\n\t\tthis.graph.panningHandler.useLeftButtonForPanning = false;\r\n\t\tthis.graph.setConnectable(false);\r\n\t}\r\n\telse if (modename == 'connect')\r\n\t{\r\n\t\tthis.graph.panningHandler.useLeftButtonForPanning = false;\r\n\t\tthis.graph.setConnectable(true);\r\n\t}\r\n\telse if (modename == 'pan')\r\n\t{\r\n\t\tthis.graph.panningHandler.useLeftButtonForPanning = true;\r\n\t\tthis.graph.setConnectable(false);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: createPopupMenu\r\n * \r\n * Uses <popupHandler> to create the menu in the graph's\r\n * panning handler. The redirection is setup in\r\n * <setToolbarContainer>.\r\n */\r\nmxEditor.prototype.createPopupMenu = function (menu, cell, evt)\r\n{\r\n\tthis.popupHandler.createMenu(this, menu, cell, evt);\r\n};\r\n\r\n/**\r\n * Function: createEdge\r\n * \r\n * Uses <defaultEdge> as the prototype for creating new edges\r\n * in the connection handler of the graph. The style of the\r\n * edge will be overridden with the value returned by\r\n * <getEdgeStyle>.\r\n */\r\nmxEditor.prototype.createEdge = function (source, target)\r\n{\r\n\t// Clones the defaultedge prototype\r\n\tvar e = null;\r\n\t\r\n\tif (this.defaultEdge != null)\r\n\t{\r\n\t\tvar model = this.graph.getModel();\r\n\t\te = model.cloneCell(this.defaultEdge);\r\n\t}\r\n\telse\r\n\t{\r\n\t\te = new mxCell('');\r\n\t\te.setEdge(true);\r\n\t\t\r\n\t\tvar geo = new mxGeometry();\r\n\t\tgeo.relative = true;\r\n\t\te.setGeometry(geo);\r\n\t}\r\n\t\r\n\t// Overrides the edge style\r\n\tvar style = this.getEdgeStyle();\r\n\t\r\n\tif (style != null)\r\n\t{\r\n\t\te.setStyle(style);\r\n\t}\r\n\t\r\n\treturn e;\r\n};\r\n\r\n/**\r\n * Function: getEdgeStyle\r\n * \r\n * Returns a string identifying the style of new edges.\r\n * The function is used in <createEdge> when new edges\r\n * are created in the graph.\r\n */\r\nmxEditor.prototype.getEdgeStyle = function ()\r\n{\r\n\treturn this.defaultEdgeStyle;\r\n};\r\n\r\n/**\r\n * Function: consumeCycleAttribute\r\n * \r\n * Returns the next attribute in <cycleAttributeValues>\r\n * or null, if not attribute should be used in the\r\n * specified cell.\r\n */\r\nmxEditor.prototype.consumeCycleAttribute = function (cell)\r\n{\r\n\treturn (this.cycleAttributeValues != null &&\r\n\t\tthis.cycleAttributeValues.length > 0 &&\r\n\t\tthis.graph.isSwimlane(cell)) ?\r\n\t\tthis.cycleAttributeValues[this.cycleAttributeIndex++ %\r\n\t\t\tthis.cycleAttributeValues.length] : null;\r\n};\r\n\r\n/**\r\n * Function: cycleAttribute\r\n * \r\n * Uses the returned value from <consumeCycleAttribute>\r\n * as the value for the <cycleAttributeName> key in\r\n * the given cell's style.\r\n */\r\nmxEditor.prototype.cycleAttribute = function (cell)\r\n{\r\n\tif (this.cycleAttributeName != null)\r\n\t{\r\n\t\tvar value = this.consumeCycleAttribute(cell);\r\n\t\t\r\n\t\tif (value != null)\r\n\t\t{\r\n\t\t\tcell.setStyle(cell.getStyle()+';'+\r\n\t\t\t\tthis.cycleAttributeName+'='+value);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: addVertex\r\n * \r\n * Adds the given vertex as a child of parent at the specified\r\n * x and y coordinate and fires an <addVertex> event.\r\n */\r\nmxEditor.prototype.addVertex = function (parent, vertex, x, y)\r\n{\r\n\tvar model = this.graph.getModel();\r\n\t\r\n\twhile (parent != null && !this.graph.isValidDropTarget(parent))\r\n\t{\r\n\t\tparent = model.getParent(parent);\r\n\t}\r\n\t\r\n\tparent = (parent != null) ? parent : this.graph.getSwimlaneAt(x, y);\r\n\tvar scale = this.graph.getView().scale;\r\n\t\r\n\tvar geo = model.getGeometry(vertex);\r\n\tvar pgeo = model.getGeometry(parent);\r\n\t\r\n\tif (this.graph.isSwimlane(vertex) &&\r\n\t\t!this.graph.swimlaneNesting)\r\n\t{\r\n\t\tparent = null;\r\n\t}\r\n\telse if (parent == null && this.swimlaneRequired)\r\n\t{\r\n\t\treturn null;\r\n\t}\r\n\telse if (parent != null && pgeo != null)\r\n\t{\r\n\t\t// Keeps vertex inside parent\r\n\t\tvar state = this.graph.getView().getState(parent);\r\n\t\t\r\n\t\tif (state != null)\r\n\t\t{\t\t\t\r\n\t\t\tx -= state.origin.x * scale;\r\n\t\t\ty -= state.origin.y * scale;\r\n\t\t\t\r\n\t\t\tif (this.graph.isConstrainedMoving)\r\n\t\t\t{\r\n\t\t\t\tvar width = geo.width;\r\n\t\t\t\tvar height = geo.height;\t\t\t\t\r\n\t\t\t\tvar tmp = state.x+state.width;\r\n\t\t\t\t\r\n\t\t\t\tif (x+width > tmp)\r\n\t\t\t\t{\r\n\t\t\t\t\tx -= x+width - tmp;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\ttmp = state.y+state.height;\r\n\t\t\t\t\r\n\t\t\t\tif (y+height > tmp)\r\n\t\t\t\t{\r\n\t\t\t\t\ty -= y+height - tmp;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (pgeo != null)\r\n\t\t{\r\n\t\t\tx -= pgeo.x*scale;\r\n\t\t\ty -= pgeo.y*scale;\r\n\t\t}\r\n\t}\r\n\t\r\n\tgeo = geo.clone();\r\n\tgeo.x = this.graph.snap(x / scale -\r\n\t\tthis.graph.getView().translate.x -\r\n\t\tthis.graph.gridSize/2);\r\n\tgeo.y = this.graph.snap(y / scale -\r\n\t\tthis.graph.getView().translate.y -\r\n\t\tthis.graph.gridSize/2);\r\n\tvertex.setGeometry(geo);\r\n\t\r\n\tif (parent == null)\r\n\t{\r\n\t\tparent = this.graph.getDefaultParent();\r\n\t}\r\n\r\n\tthis.cycleAttribute(vertex);\r\n\tthis.fireEvent(new mxEventObject(mxEvent.BEFORE_ADD_VERTEX,\r\n\t\t\t'vertex', vertex, 'parent', parent));\r\n\r\n\tmodel.beginUpdate();\r\n\ttry\r\n\t{\r\n\t\tvertex = this.graph.addCell(vertex, parent);\r\n\t\t\r\n\t\tif (vertex != null)\r\n\t\t{\r\n\t\t\tthis.graph.constrainChild(vertex);\r\n\t\t\t\r\n\t\t\tthis.fireEvent(new mxEventObject(mxEvent.ADD_VERTEX, 'vertex', vertex));\r\n\t\t}\r\n\t}\r\n\tfinally\r\n\t{\r\n\t\tmodel.endUpdate();\r\n\t}\r\n\t\r\n\tif (vertex != null)\r\n\t{\r\n\t\tthis.graph.setSelectionCell(vertex);\r\n\t\tthis.graph.scrollCellToVisible(vertex);\r\n\t\tthis.fireEvent(new mxEventObject(mxEvent.AFTER_ADD_VERTEX, 'vertex', vertex));\r\n\t}\r\n\t\r\n\treturn vertex;\r\n};\r\n\r\n/**\r\n * Function: destroy\r\n * \r\n * Removes the editor and all its associated resources. This does not\r\n * normally need to be called, it is called automatically when the window\r\n * unloads.\r\n */\r\nmxEditor.prototype.destroy = function ()\r\n{\r\n\tif (!this.destroyed)\r\n\t{\r\n\t\tthis.destroyed = true;\r\n\r\n\t\tif (this.tasks != null)\r\n\t\t{\r\n\t\t\tthis.tasks.destroy();\r\n\t\t}\r\n\t\t\r\n\t\tif (this.outline != null)\r\n\t\t{\r\n\t\t\tthis.outline.destroy();\r\n\t\t}\r\n\t\t\r\n\t\tif (this.properties != null)\r\n\t\t{\r\n\t\t\tthis.properties.destroy();\r\n\t\t}\r\n\t\t\r\n\t\tif (this.keyHandler != null)\r\n\t\t{\r\n\t\t\tthis.keyHandler.destroy();\r\n\t\t}\r\n\t\t\r\n\t\tif (this.rubberband != null)\r\n\t\t{\r\n\t\t\tthis.rubberband.destroy();\r\n\t\t}\r\n\t\t\r\n\t\tif (this.toolbar != null)\r\n\t\t{\r\n\t\t\tthis.toolbar.destroy();\r\n\t\t}\r\n\t\t\r\n\t\tif (this.graph != null)\r\n\t\t{\r\n\t\t\tthis.graph.destroy();\r\n\t\t}\r\n\t\r\n\t\tthis.status = null;\r\n\t\tthis.templates = null;\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nvar mxCodecRegistry =\r\n{\r\n\t/**\r\n\t * Class: mxCodecRegistry\r\n\t *\r\n\t * Singleton class that acts as a global registry for codecs.\r\n\t *\r\n\t * Adding an <mxCodec>:\r\n\t *\r\n\t * 1. Define a default codec with a new instance of the \r\n\t * object to be handled.\r\n\t *\r\n\t * (code)\r\n\t * var codec = new mxObjectCodec(new mxGraphModel());\r\n\t * (end)\r\n\t *\r\n\t * 2. Define the functions required for encoding and decoding\r\n\t * objects.\r\n\t *\r\n\t * (code)\r\n\t * codec.encode = function(enc, obj) { ... }\r\n\t * codec.decode = function(dec, node, into) { ... }\r\n\t * (end)\r\n\t *\r\n\t * 3. Register the codec in the <mxCodecRegistry>.\r\n\t *\r\n\t * (code)\r\n\t * mxCodecRegistry.register(codec);\r\n\t * (end)\r\n\t *\r\n\t * <mxObjectCodec.decode> may be used to either create a new \r\n\t * instance of an object or to configure an existing instance, \r\n\t * in which case the into argument points to the existing\r\n\t * object. In this case, we say the codec \"configures\" the\r\n\t * object.\r\n\t * \r\n\t * Variable: codecs\r\n\t *\r\n\t * Maps from constructor names to codecs.\r\n\t */\r\n\tcodecs: [],\r\n\t\r\n\t/**\r\n\t * Variable: aliases\r\n\t *\r\n\t * Maps from classnames to codecnames.\r\n\t */\r\n\taliases: [],\r\n\r\n\t/**\r\n\t * Function: register\r\n\t *\r\n\t * Registers a new codec and associates the name of the template\r\n\t * constructor in the codec with the codec object.\r\n\t *\r\n\t * Parameters:\r\n\t *\r\n\t * codec - <mxObjectCodec> to be registered.\r\n\t */\r\n\tregister: function(codec)\r\n\t{\r\n\t\tif (codec != null)\r\n\t\t{\r\n\t\t\tvar name = codec.getName();\r\n\t\t\tmxCodecRegistry.codecs[name] = codec;\r\n\t\t\t\r\n\t\t\tvar classname = mxUtils.getFunctionName(codec.template.constructor);\r\n\r\n\t\t\tif (classname != name)\r\n\t\t\t{\r\n\t\t\t\tmxCodecRegistry.addAlias(classname, name);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn codec;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: addAlias\r\n\t *\r\n\t * Adds an alias for mapping a classname to a codecname.\r\n\t */\r\n\taddAlias: function(classname, codecname)\r\n\t{\r\n\t\tmxCodecRegistry.aliases[classname] = codecname;\r\n\t},\r\n\r\n\t/**\r\n\t * Function: getCodec\r\n\t *\r\n\t * Returns a codec that handles objects that are constructed\r\n\t * using the given constructor.\r\n\t *\r\n\t * Parameters:\r\n\t *\r\n\t * ctor - JavaScript constructor function. \r\n\t */\r\n\tgetCodec: function(ctor)\r\n\t{\r\n\t\tvar codec = null;\r\n\t\t\r\n\t\tif (ctor != null)\r\n\t\t{\r\n\t\t\tvar name = mxUtils.getFunctionName(ctor);\r\n\t\t\tvar tmp = mxCodecRegistry.aliases[name];\r\n\t\t\t\r\n\t\t\tif (tmp != null)\r\n\t\t\t{\r\n\t\t\t\tname = tmp;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tcodec = mxCodecRegistry.codecs[name];\r\n\t\t\t\r\n\t\t\t// Registers a new default codec for the given constructor\r\n\t\t\t// if no codec has been previously defined.\r\n\t\t\tif (codec == null)\r\n\t\t\t{\r\n\t\t\t\ttry\r\n\t\t\t\t{\r\n\t\t\t\t\tcodec = new mxObjectCodec(new ctor());\r\n\t\t\t\t\tmxCodecRegistry.register(codec);\r\n\t\t\t\t}\r\n\t\t\t\tcatch (e)\r\n\t\t\t\t{\r\n\t\t\t\t\t// ignore\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn codec;\r\n\t}\r\n\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxCodec\r\n *\r\n * XML codec for JavaScript object graphs. See <mxObjectCodec> for a\r\n * description of the general encoding/decoding scheme. This class uses the\r\n * codecs registered in <mxCodecRegistry> for encoding/decoding each object.\r\n * \r\n * References:\r\n * \r\n * In order to resolve references, especially forward references, the mxCodec\r\n * constructor must be given the document that contains the referenced\r\n * elements.\r\n *\r\n * Examples:\r\n *\r\n * The following code is used to encode a graph model.\r\n *\r\n * (code)\r\n * var encoder = new mxCodec();\r\n * var result = encoder.encode(graph.getModel());\r\n * var xml = mxUtils.getXml(result);\r\n * (end)\r\n * \r\n * Example:\r\n * \r\n * Using the code below, an XML document is decoded into an existing model. The\r\n * document may be obtained using one of the functions in mxUtils for loading\r\n * an XML file, eg. <mxUtils.get>, or using <mxUtils.parseXml> for parsing an\r\n * XML string.\r\n * \r\n * (code)\r\n * var doc = mxUtils.parseXml(xmlString);\r\n * var codec = new mxCodec(doc);\r\n * codec.decode(doc.documentElement, graph.getModel());\r\n * (end)\r\n * \r\n * Example:\r\n * \r\n * This example demonstrates parsing a list of isolated cells into an existing\r\n * graph model. Note that the cells do not have a parent reference so they can\r\n * be added anywhere in the cell hierarchy after parsing.\r\n * \r\n * (code)\r\n * var xml = '<root><mxCell id=\"2\" value=\"Hello,\" vertex=\"1\"><mxGeometry x=\"20\" y=\"20\" width=\"80\" height=\"30\" as=\"geometry\"/></mxCell><mxCell id=\"3\" value=\"World!\" vertex=\"1\"><mxGeometry x=\"200\" y=\"150\" width=\"80\" height=\"30\" as=\"geometry\"/></mxCell><mxCell id=\"4\" value=\"\" edge=\"1\" source=\"2\" target=\"3\"><mxGeometry relative=\"1\" as=\"geometry\"/></mxCell></root>';\r\n * var doc = mxUtils.parseXml(xml);\r\n * var codec = new mxCodec(doc);\r\n * var elt = doc.documentElement.firstChild;\r\n * var cells = [];\r\n * \r\n * while (elt != null)\r\n * {\r\n *   cells.push(codec.decode(elt));\r\n *   elt = elt.nextSibling;\r\n * }\r\n * \r\n * graph.addCells(cells);\r\n * (end)\r\n * \r\n * Example:\r\n * \r\n * Using the following code, the selection cells of a graph are encoded and the\r\n * output is displayed in a dialog box.\r\n * \r\n * (code)\r\n * var enc = new mxCodec();\r\n * var cells = graph.getSelectionCells();\r\n * mxUtils.alert(mxUtils.getPrettyXml(enc.encode(cells)));\r\n * (end)\r\n * \r\n * Newlines in the XML can be converted to <br>, in which case a '<br>' argument\r\n * must be passed to <mxUtils.getXml> as the second argument.\r\n * \r\n * Debugging:\r\n * \r\n * For debugging I/O you can use the following code to get the sequence of\r\n * encoded objects:\r\n * \r\n * (code)\r\n * var oldEncode = mxCodec.prototype.encode;\r\n * mxCodec.prototype.encode = function(obj)\r\n * {\r\n *   mxLog.show();\r\n *   mxLog.debug('mxCodec.encode: obj='+mxUtils.getFunctionName(obj.constructor));\r\n *   \r\n *   return oldEncode.apply(this, arguments);\r\n * };\r\n * (end)\r\n * \r\n * Note that the I/O system adds object codecs for new object automatically. For\r\n * decoding those objects, the constructor should be written as follows:\r\n * \r\n * (code)\r\n * var MyObj = function(name)\r\n * {\r\n *   // ...\r\n * };\r\n * (end)\r\n * \r\n * Constructor: mxCodec\r\n *\r\n * Constructs an XML encoder/decoder for the specified\r\n * owner document.\r\n *\r\n * Parameters:\r\n *\r\n * document - Optional XML document that contains the data.\r\n * If no document is specified then a new document is created\r\n * using <mxUtils.createXmlDocument>.\r\n */\r\nfunction mxCodec(document)\r\n{\r\n\tthis.document = document || mxUtils.createXmlDocument();\r\n\tthis.objects = [];\r\n};\r\n\r\n/**\r\n * Variable: document\r\n *\r\n * The owner document of the codec.\r\n */\r\nmxCodec.prototype.document = null;\r\n\r\n/**\r\n * Variable: objects\r\n *\r\n * Maps from IDs to objects.\r\n */\r\nmxCodec.prototype.objects = null;\r\n\r\n/**\r\n * Variable: elements\r\n * \r\n * Lookup table for resolving IDs to elements.\r\n */\r\nmxCodec.prototype.elements = null;\r\n\r\n/**\r\n * Variable: encodeDefaults\r\n *\r\n * Specifies if default values should be encoded. Default is false.\r\n */\r\nmxCodec.prototype.encodeDefaults = false;\r\n\r\n\r\n/**\r\n * Function: putObject\r\n * \r\n * Assoiates the given object with the given ID and returns the given object.\r\n * \r\n * Parameters\r\n * \r\n * id - ID for the object to be associated with.\r\n * obj - Object to be associated with the ID.\r\n */\r\nmxCodec.prototype.putObject = function(id, obj)\r\n{\r\n\tthis.objects[id] = obj;\r\n\t\r\n\treturn obj;\r\n};\r\n\r\n/**\r\n * Function: getObject\r\n *\r\n * Returns the decoded object for the element with the specified ID in\r\n * <document>. If the object is not known then <lookup> is used to find an\r\n * object. If no object is found, then the element with the respective ID\r\n * from the document is parsed using <decode>.\r\n */\r\nmxCodec.prototype.getObject = function(id)\r\n{\r\n\tvar obj = null;\r\n\r\n\tif (id != null)\r\n\t{\r\n\t\tobj = this.objects[id];\r\n\t\t\r\n\t\tif (obj == null)\r\n\t\t{\r\n\t\t\tobj = this.lookup(id);\r\n\t\t\t\r\n\t\t\tif (obj == null)\r\n\t\t\t{\r\n\t\t\t\tvar node = this.getElementById(id);\r\n\t\t\t\t\r\n\t\t\t\tif (node != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tobj = this.decode(node);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn obj;\r\n};\r\n\r\n/**\r\n * Function: lookup\r\n *\r\n * Hook for subclassers to implement a custom lookup mechanism for cell IDs.\r\n * This implementation always returns null.\r\n *\r\n * Example:\r\n *\r\n * (code)\r\n * var codec = new mxCodec();\r\n * codec.lookup = function(id)\r\n * {\r\n *   return model.getCell(id);\r\n * };\r\n * (end)\r\n *\r\n * Parameters:\r\n *\r\n * id - ID of the object to be returned.\r\n */\r\nmxCodec.prototype.lookup = function(id)\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: getElementById\r\n *\r\n * Returns the element with the given ID from <document>.\r\n *\r\n * Parameters:\r\n *\r\n * id - String that contains the ID.\r\n */\r\nmxCodec.prototype.getElementById = function(id)\r\n{\r\n\tthis.updateElements();\r\n\t\r\n\treturn this.elements[id];\r\n};\r\n\r\n/**\r\n * Function: updateElements\r\n *\r\n * Returns the element with the given ID from <document>.\r\n *\r\n * Parameters:\r\n *\r\n * id - String that contains the ID.\r\n */\r\nmxCodec.prototype.updateElements = function()\r\n{\r\n\tif (this.elements == null)\r\n\t{\r\n\t\tthis.elements = new Object();\r\n\t\t\r\n\t\tif (this.document.documentElement != null)\r\n\t\t{\r\n\t\t\tthis.addElement(this.document.documentElement);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: addElement\r\n *\r\n * Adds the given element to <elements> if it has an ID.\r\n */\r\nmxCodec.prototype.addElement = function(node)\r\n{\r\n\tif (node.nodeType == mxConstants.NODETYPE_ELEMENT)\r\n\t{\r\n\t\tvar id = node.getAttribute('id');\r\n\t\t\r\n\t\tif (id != null)\r\n\t\t{\r\n\t\t\tif (this.elements[id] == null)\r\n\t\t\t{\r\n\t\t\t\tthis.elements[id] = node;\r\n\t\t\t}\r\n\t\t\telse if (this.elements[id] != node)\r\n\t\t\t{\r\n\t\t\t\tthrow new Error(id + ': Duplicate ID');\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\tnode = node.firstChild;\r\n\t\r\n\twhile (node != null)\r\n\t{\r\n\t\tthis.addElement(node);\r\n\t\tnode = node.nextSibling;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getId\r\n *\r\n * Returns the ID of the specified object. This implementation\r\n * calls <reference> first and if that returns null handles\r\n * the object as an <mxCell> by returning their IDs using\r\n * <mxCell.getId>. If no ID exists for the given cell, then\r\n * an on-the-fly ID is generated using <mxCellPath.create>.\r\n *\r\n * Parameters:\r\n *\r\n * obj - Object to return the ID for.\r\n */\r\nmxCodec.prototype.getId = function(obj)\r\n{\r\n\tvar id = null;\r\n\t\r\n\tif (obj != null)\r\n\t{\r\n\t\tid = this.reference(obj);\r\n\t\t\r\n\t\tif (id == null && obj instanceof mxCell)\r\n\t\t{\r\n\t\t\tid = obj.getId();\r\n\t\t\t\r\n\t\t\tif (id == null)\r\n\t\t\t{\r\n\t\t\t\t// Uses an on-the-fly Id\r\n\t\t\t\tid = mxCellPath.create(obj);\r\n\t\t\t\t\r\n\t\t\t\tif (id.length == 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tid = 'root';\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn id;\r\n};\r\n\r\n/**\r\n * Function: reference\r\n *\r\n * Hook for subclassers to implement a custom method\r\n * for retrieving IDs from objects. This implementation\r\n * always returns null.\r\n *\r\n * Example:\r\n *\r\n * (code)\r\n * var codec = new mxCodec();\r\n * codec.reference = function(obj)\r\n * {\r\n *   return obj.getCustomId();\r\n * };\r\n * (end)\r\n *\r\n * Parameters:\r\n *\r\n * obj - Object whose ID should be returned.\r\n */\r\nmxCodec.prototype.reference = function(obj)\r\n{\r\n\treturn null;\r\n};\r\n\r\n/**\r\n * Function: encode\r\n *\r\n * Encodes the specified object and returns the resulting\r\n * XML node.\r\n *\r\n * Parameters:\r\n *\r\n * obj - Object to be encoded. \r\n */\r\nmxCodec.prototype.encode = function(obj)\r\n{\r\n\tvar node = null;\r\n\t\r\n\tif (obj != null && obj.constructor != null)\r\n\t{\r\n\t\tvar enc = mxCodecRegistry.getCodec(obj.constructor);\r\n\t\t\r\n\t\tif (enc != null)\r\n\t\t{\r\n\t\t\tnode = enc.encode(this, obj);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tif (mxUtils.isNode(obj))\r\n\t\t\t{\r\n\t\t\t\tnode = mxUtils.importNode(this.document, obj, true);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t    \t\tmxLog.warn('mxCodec.encode: No codec for ' + mxUtils.getFunctionName(obj.constructor));\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn node;\r\n};\r\n\r\n/**\r\n * Function: decode\r\n *\r\n * Decodes the given XML node. The optional \"into\"\r\n * argument specifies an existing object to be\r\n * used. If no object is given, then a new instance\r\n * is created using the constructor from the codec.\r\n *\r\n * The function returns the passed in object or\r\n * the new instance if no object was given.\r\n *\r\n * Parameters:\r\n *\r\n * node - XML node to be decoded.\r\n * into - Optional object to be decodec into.\r\n */\r\nmxCodec.prototype.decode = function(node, into)\r\n{\r\n\tthis.updateElements();\r\n\tvar obj = null;\r\n\t\r\n\tif (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT)\r\n\t{\r\n\t\tvar ctor = null;\r\n\t\t\r\n\t\ttry\r\n\t\t{\r\n\t\t\tctor = window[node.nodeName];\r\n\t\t}\r\n\t\tcatch (err)\r\n\t\t{\r\n\t\t\t// ignore\r\n\t\t}\r\n\t\t\r\n\t\tvar dec = mxCodecRegistry.getCodec(ctor);\r\n\t\t\r\n\t\tif (dec != null)\r\n\t\t{\r\n\t\t\tobj = dec.decode(this, node, into);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tobj = node.cloneNode(true);\r\n\t\t\tobj.removeAttribute('as');\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn obj;\r\n};\r\n\r\n/**\r\n * Function: encodeCell\r\n *\r\n * Encoding of cell hierarchies is built-into the core, but\r\n * is a higher-level function that needs to be explicitely\r\n * used by the respective object encoders (eg. <mxModelCodec>,\r\n * <mxChildChangeCodec> and <mxRootChangeCodec>). This\r\n * implementation writes the given cell and its children as a\r\n * (flat) sequence into the given node. The children are not\r\n * encoded if the optional includeChildren is false. The\r\n * function is in charge of adding the result into the\r\n * given node and has no return value.\r\n *\r\n * Parameters:\r\n *\r\n * cell - <mxCell> to be encoded.\r\n * node - Parent XML node to add the encoded cell into.\r\n * includeChildren - Optional boolean indicating if the\r\n * function should include all descendents. Default is true. \r\n */\r\nmxCodec.prototype.encodeCell = function(cell, node, includeChildren)\r\n{\r\n\tnode.appendChild(this.encode(cell));\r\n\t\r\n\tif (includeChildren == null || includeChildren)\r\n\t{\r\n\t\tvar childCount = cell.getChildCount();\r\n\t\t\r\n\t\tfor (var i = 0; i < childCount; i++)\r\n\t\t{\r\n\t\t\tthis.encodeCell(cell.getChildAt(i), node);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isCellCodec\r\n * \r\n * Returns true if the given codec is a cell codec. This uses\r\n * <mxCellCodec.isCellCodec> to check if the codec is of the\r\n * given type.\r\n */\r\nmxCodec.prototype.isCellCodec = function(codec)\r\n{\r\n\tif (codec != null && typeof(codec.isCellCodec) == 'function')\r\n\t{\r\n\t\treturn codec.isCellCodec();\r\n\t}\r\n\t\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: decodeCell\r\n *\r\n * Decodes cells that have been encoded using inversion, ie.\r\n * where the user object is the enclosing node in the XML,\r\n * and restores the group and graph structure in the cells.\r\n * Returns a new <mxCell> instance that represents the\r\n * given node.\r\n *\r\n * Parameters:\r\n *\r\n * node - XML node that contains the cell data.\r\n * restoreStructures - Optional boolean indicating whether\r\n * the graph structure should be restored by calling insert\r\n * and insertEdge on the parent and terminals, respectively.\r\n * Default is true.\r\n */\r\nmxCodec.prototype.decodeCell = function(node, restoreStructures)\r\n{\r\n\trestoreStructures = (restoreStructures != null) ? restoreStructures : true;\r\n\tvar cell = null;\r\n\t\r\n\tif (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT)\r\n\t{\r\n\t\t// Tries to find a codec for the given node name. If that does\r\n\t\t// not return a codec then the node is the user object (an XML node\r\n\t\t// that contains the mxCell, aka inversion).\r\n\t\tvar decoder = mxCodecRegistry.getCodec(node.nodeName);\r\n\t\t\r\n\t\t// Tries to find the codec for the cell inside the user object.\r\n\t\t// This assumes all node names inside the user object are either\r\n\t\t// not registered or they correspond to a class for cells.\r\n\t\tif (!this.isCellCodec(decoder))\r\n\t\t{\r\n\t\t\tvar child = node.firstChild;\r\n\t\t\t\r\n\t\t\twhile (child != null && !this.isCellCodec(decoder))\r\n\t\t\t{\r\n\t\t\t\tdecoder = mxCodecRegistry.getCodec(child.nodeName);\r\n\t\t\t\tchild = child.nextSibling;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (!this.isCellCodec(decoder))\r\n\t\t{\r\n\t\t\tdecoder = mxCodecRegistry.getCodec(mxCell);\r\n\t\t}\r\n\r\n\t\tcell = decoder.decode(this, node);\r\n\t\t\r\n\t\tif (restoreStructures)\r\n\t\t{\r\n\t\t\tthis.insertIntoGraph(cell);\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn cell;\r\n};\r\n\r\n/**\r\n * Function: insertIntoGraph\r\n *\r\n * Inserts the given cell into its parent and terminal cells.\r\n */\r\nmxCodec.prototype.insertIntoGraph = function(cell)\r\n{\r\n\tvar parent = cell.parent;\r\n\tvar source = cell.getTerminal(true);\r\n\tvar target = cell.getTerminal(false);\r\n\r\n\t// Fixes possible inconsistencies during insert into graph\r\n\tcell.setTerminal(null, false);\r\n\tcell.setTerminal(null, true);\r\n\tcell.parent = null;\r\n\t\r\n\tif (parent != null)\r\n\t{\r\n\t\tif (parent == cell)\r\n\t\t{\r\n\t\t\tthrow new Error(parent.id + ': Self Reference');\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tparent.insert(cell);\r\n\t\t}\r\n\t}\r\n\r\n\tif (source != null)\r\n\t{\r\n\t\tsource.insertEdge(cell, true);\r\n\t}\r\n\r\n\tif (target != null)\r\n\t{\r\n\t\ttarget.insertEdge(cell, false);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: setAttribute\r\n *\r\n * Sets the attribute on the specified node to value. This is a\r\n * helper method that makes sure the attribute and value arguments\r\n * are not null.\r\n *\r\n * Parameters:\r\n *\r\n * node - XML node to set the attribute for.\r\n * attributes - Attributename to be set.\r\n * value - New value of the attribute.\r\n */\r\nmxCodec.prototype.setAttribute = function(node, attribute, value)\r\n{\r\n\tif (attribute != null && value != null)\r\n\t{\r\n\t\tnode.setAttribute(attribute, value);\r\n\t}\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxObjectCodec\r\n *\r\n * Generic codec for JavaScript objects that implements a mapping between\r\n * JavaScript objects and XML nodes that maps each field or element to an\r\n * attribute or child node, and vice versa.\r\n * \r\n * Atomic Values:\r\n * \r\n * Consider the following example.\r\n * \r\n * (code)\r\n * var obj = new Object();\r\n * obj.foo = \"Foo\";\r\n * obj.bar = \"Bar\";\r\n * (end)\r\n * \r\n * This object is encoded into an XML node using the following.\r\n * \r\n * (code)\r\n * var enc = new mxCodec();\r\n * var node = enc.encode(obj);\r\n * (end)\r\n * \r\n * The output of the encoding may be viewed using <mxLog> as follows.\r\n * \r\n * (code)\r\n * mxLog.show();\r\n * mxLog.debug(mxUtils.getPrettyXml(node));\r\n * (end)\r\n * \r\n * Finally, the result of the encoding looks as follows.\r\n * \r\n * (code)\r\n * <Object foo=\"Foo\" bar=\"Bar\"/>\r\n * (end)\r\n * \r\n * In the above output, the foo and bar fields have been mapped to attributes\r\n * with the same names, and the name of the constructor was used for the\r\n * nodename.\r\n * \r\n * Booleans:\r\n *\r\n * Since booleans are numbers in JavaScript, all boolean values are encoded\r\n * into 1 for true and 0 for false. The decoder also accepts the string true\r\n * and false for boolean values.\r\n * \r\n * Objects:\r\n * \r\n * The above scheme is applied to all atomic fields, that is, to all non-object\r\n * fields of an object. For object fields, a child node is created with a\r\n * special attribute that contains the fieldname. This special attribute is\r\n * called \"as\" and hence, as is a reserved word that should not be used for a\r\n * fieldname.\r\n * \r\n * Consider the following example where foo is an object and bar is an atomic\r\n * property of foo.\r\n * \r\n * (code)\r\n * var obj = {foo: {bar: \"Bar\"}};\r\n * (end)\r\n * \r\n * This will be mapped to the following XML structure by mxObjectCodec.\r\n * \r\n * (code)\r\n * <Object>\r\n *   <Object bar=\"Bar\" as=\"foo\"/>\r\n * </Object>\r\n * (end)\r\n * \r\n * In the above output, the inner Object node contains the as-attribute that\r\n * specifies the fieldname in the enclosing object. That is, the field foo was\r\n * mapped to a child node with an as-attribute that has the value foo.\r\n * \r\n * Arrays:\r\n * \r\n * Arrays are special objects that are either associative, in which case each\r\n * key, value pair is treated like a field where the key is the fieldname, or\r\n * they are a sequence of atomic values and objects, which is mapped to a\r\n * sequence of child nodes. For object elements, the above scheme is applied\r\n * without the use of the special as-attribute for creating each child. For\r\n * atomic elements, a special add-node is created with the value stored in the\r\n * value-attribute.\r\n * \r\n * For example, the following array contains one atomic value and one object\r\n * with a field called bar. Furthermore it contains two associative entries\r\n * called bar with an atomic value, and foo with an object value.\r\n * \r\n * (code)\r\n * var obj = [\"Bar\", {bar: \"Bar\"}];\r\n * obj[\"bar\"] = \"Bar\";\r\n * obj[\"foo\"] = {bar: \"Bar\"};\r\n * (end)\r\n * \r\n * This array is represented by the following XML nodes.\r\n * \r\n * (code)\r\n * <Array bar=\"Bar\">\r\n *   <add value=\"Bar\"/>\r\n *   <Object bar=\"Bar\"/>\r\n *   <Object bar=\"Bar\" as=\"foo\"/>\r\n * </Array>\r\n * (end)\r\n * \r\n * The Array node name is the name of the constructor. The additional\r\n * as-attribute in the last child contains the key of the associative entry,\r\n * whereas the second last child is part of the array sequence and does not\r\n * have an as-attribute.\r\n * \r\n * References:\r\n * \r\n * Objects may be represented as child nodes or attributes with ID values,\r\n * which are used to lookup the object in a table within <mxCodec>. The\r\n * <isReference> function is in charge of deciding if a specific field should\r\n * be encoded as a reference or not. Its default implementation returns true if\r\n * the fieldname is in <idrefs>, an array of strings that is used to configure\r\n * the <mxObjectCodec>.\r\n * \r\n * Using this approach, the mapping does not guarantee that the referenced\r\n * object itself exists in the document. The fields that are encoded as\r\n * references must be carefully chosen to make sure all referenced objects\r\n * exist in the document, or may be resolved by some other means if necessary.\r\n * \r\n * For example, in the case of the graph model all cells are stored in a tree\r\n * whose root is referenced by the model's root field. A tree is a structure\r\n * that is well suited for an XML representation, however, the additional edges\r\n * in the graph model have a reference to a source and target cell, which are\r\n * also contained in the tree. To handle this case, the source and target cell\r\n * of an edge are treated as references, whereas the children are treated as\r\n * objects. Since all cells are contained in the tree and no edge references a\r\n * source or target outside the tree, this setup makes sure all referenced\r\n * objects are contained in the document.\r\n * \r\n * In the case of a tree structure we must further avoid infinite recursion by\r\n * ignoring the parent reference of each child. This is done by returning true\r\n * in <isExcluded>, whose default implementation uses the array of excluded\r\n * fieldnames passed to the mxObjectCodec constructor.\r\n * \r\n * References are only used for cells in mxGraph. For defining other\r\n * referencable object types, the codec must be able to work out the ID of an\r\n * object. This is done by implementing <mxCodec.reference>. For decoding a\r\n * reference, the XML node with the respective id-attribute is fetched from the\r\n * document, decoded, and stored in a lookup table for later reference. For\r\n * looking up external objects, <mxCodec.lookup> may be implemented.\r\n * \r\n * Expressions:\r\n * \r\n * For decoding JavaScript expressions, the add-node may be used with a text\r\n * content that contains the JavaScript expression. For example, the following\r\n * creates a field called foo in the enclosing object and assigns it the value\r\n * of <mxConstants.ALIGN_LEFT>.\r\n * \r\n * (code)\r\n * <Object>\r\n *   <add as=\"foo\">mxConstants.ALIGN_LEFT</add>\r\n * </Object>\r\n * (end)\r\n * \r\n * The resulting object has a field called foo with the value \"left\". Its XML\r\n * representation looks as follows.\r\n * \r\n * (code)\r\n * <Object foo=\"left\"/>\r\n * (end)\r\n * \r\n * This means the expression is evaluated at decoding time and the result of\r\n * the evaluation is stored in the respective field. Valid expressions are all\r\n * JavaScript expressions, including function definitions, which are mapped to\r\n * functions on the resulting object.\r\n * \r\n * Expressions are only evaluated if <allowEval> is true.\r\n * \r\n * Constructor: mxObjectCodec\r\n *\r\n * Constructs a new codec for the specified template object.\r\n * The variables in the optional exclude array are ignored by\r\n * the codec. Variables in the optional idrefs array are\r\n * turned into references in the XML. The optional mapping\r\n * may be used to map from variable names to XML attributes.\r\n * The argument is created as follows:\r\n *\r\n * (code)\r\n * var mapping = new Object();\r\n * mapping['variableName'] = 'attribute-name';\r\n * (end)\r\n *\r\n * Parameters:\r\n *\r\n * template - Prototypical instance of the object to be\r\n * encoded/decoded.\r\n * exclude - Optional array of fieldnames to be ignored.\r\n * idrefs - Optional array of fieldnames to be converted to/from\r\n * references.\r\n * mapping - Optional mapping from field- to attributenames.\r\n */\r\nfunction mxObjectCodec(template, exclude, idrefs, mapping)\r\n{\r\n\tthis.template = template;\r\n\t\r\n\tthis.exclude = (exclude != null) ? exclude : [];\r\n\tthis.idrefs = (idrefs != null) ? idrefs : [];\r\n\tthis.mapping = (mapping != null) ? mapping : [];\r\n\t\r\n\tthis.reverse = new Object();\r\n\t\r\n\tfor (var i in this.mapping)\r\n\t{\r\n\t\tthis.reverse[this.mapping[i]] = i;\r\n\t}\r\n};\r\n\r\n/**\r\n * Variable: allowEval\r\n *\r\n * Static global switch that specifies if expressions in arrays are allowed.\r\n * Default is false. NOTE: Enabling this carries a possible security risk.\r\n */\r\nmxObjectCodec.allowEval = false;\r\n\r\n/**\r\n * Variable: template\r\n *\r\n * Holds the template object associated with this codec.\r\n */\r\nmxObjectCodec.prototype.template = null;\r\n\r\n/**\r\n * Variable: exclude\r\n *\r\n * Array containing the variable names that should be\r\n * ignored by the codec.\r\n */\r\nmxObjectCodec.prototype.exclude = null;\r\n\r\n/**\r\n * Variable: idrefs\r\n *\r\n * Array containing the variable names that should be\r\n * turned into or converted from references. See\r\n * <mxCodec.getId> and <mxCodec.getObject>.\r\n */\r\nmxObjectCodec.prototype.idrefs = null;\r\n\r\n/**\r\n * Variable: mapping\r\n *\r\n * Maps from from fieldnames to XML attribute names.\r\n */\r\nmxObjectCodec.prototype.mapping = null;\r\n\r\n/**\r\n * Variable: reverse\r\n *\r\n * Maps from from XML attribute names to fieldnames.\r\n */\r\nmxObjectCodec.prototype.reverse = null;\r\n\r\n/**\r\n * Function: getName\r\n * \r\n * Returns the name used for the nodenames and lookup of the codec when\r\n * classes are encoded and nodes are decoded. For classes to work with\r\n * this the codec registry automatically adds an alias for the classname\r\n * if that is different than what this returns. The default implementation\r\n * returns the classname of the template class.\r\n */\r\nmxObjectCodec.prototype.getName = function()\r\n{\r\n\treturn mxUtils.getFunctionName(this.template.constructor);\r\n};\r\n\r\n/**\r\n * Function: cloneTemplate\r\n * \r\n * Returns a new instance of the template for this codec.\r\n */\r\nmxObjectCodec.prototype.cloneTemplate = function()\r\n{\r\n\treturn new this.template.constructor();\r\n};\r\n\r\n/**\r\n * Function: getFieldName\r\n * \r\n * Returns the fieldname for the given attributename.\r\n * Looks up the value in the <reverse> mapping or returns\r\n * the input if there is no reverse mapping for the\r\n * given name.\r\n */\r\nmxObjectCodec.prototype.getFieldName = function(attributename)\r\n{\r\n\tif (attributename != null)\r\n\t{\r\n\t\tvar mapped = this.reverse[attributename];\r\n\t\t\r\n\t\tif (mapped != null)\r\n\t\t{\r\n\t\t\tattributename = mapped;\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn attributename;\r\n};\r\n\r\n/**\r\n * Function: getAttributeName\r\n * \r\n * Returns the attributename for the given fieldname.\r\n * Looks up the value in the <mapping> or returns\r\n * the input if there is no mapping for the\r\n * given name.\r\n */\r\nmxObjectCodec.prototype.getAttributeName = function(fieldname)\r\n{\r\n\tif (fieldname != null)\r\n\t{\r\n\t\tvar mapped = this.mapping[fieldname];\r\n\t\t\r\n\t\tif (mapped != null)\r\n\t\t{\r\n\t\t\tfieldname = mapped;\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn fieldname;\r\n};\r\n\r\n/**\r\n * Function: isExcluded\r\n *\r\n * Returns true if the given attribute is to be ignored by the codec. This\r\n * implementation returns true if the given fieldname is in <exclude> or\r\n * if the fieldname equals <mxObjectIdentity.FIELD_NAME>.\r\n *\r\n * Parameters:\r\n *\r\n * obj - Object instance that contains the field.\r\n * attr - Fieldname of the field.\r\n * value - Value of the field.\r\n * write - Boolean indicating if the field is being encoded or decoded.\r\n * Write is true if the field is being encoded, else it is being decoded.\r\n */\r\nmxObjectCodec.prototype.isExcluded = function(obj, attr, value, write)\r\n{\r\n\treturn attr == mxObjectIdentity.FIELD_NAME ||\r\n\t\tmxUtils.indexOf(this.exclude, attr) >= 0;\r\n};\r\n\r\n/**\r\n * Function: isReference\r\n *\r\n * Returns true if the given fieldname is to be treated\r\n * as a textual reference (ID). This implementation returns\r\n * true if the given fieldname is in <idrefs>.\r\n *\r\n * Parameters:\r\n *\r\n * obj - Object instance that contains the field.\r\n * attr - Fieldname of the field.\r\n * value - Value of the field. \r\n * write - Boolean indicating if the field is being encoded or decoded.\r\n * Write is true if the field is being encoded, else it is being decoded.\r\n */\r\nmxObjectCodec.prototype.isReference = function(obj, attr, value, write)\r\n{\r\n\treturn mxUtils.indexOf(this.idrefs, attr) >= 0;\r\n};\r\n\r\n/**\r\n * Function: encode\r\n *\r\n * Encodes the specified object and returns a node\r\n * representing then given object. Calls <beforeEncode>\r\n * after creating the node and <afterEncode> with the \r\n * resulting node after processing.\r\n *\r\n * Enc is a reference to the calling encoder. It is used\r\n * to encode complex objects and create references.\r\n *\r\n * This implementation encodes all variables of an\r\n * object according to the following rules:\r\n *\r\n * - If the variable name is in <exclude> then it is ignored.\r\n * - If the variable name is in <idrefs> then <mxCodec.getId>\r\n * is used to replace the object with its ID.\r\n * - The variable name is mapped using <mapping>.\r\n * - If obj is an array and the variable name is numeric\r\n * (ie. an index) then it is not encoded.\r\n * - If the value is an object, then the codec is used to\r\n * create a child node with the variable name encoded into\r\n * the \"as\" attribute.\r\n * - Else, if <encodeDefaults> is true or the value differs\r\n * from the template value, then ...\r\n * - ... if obj is not an array, then the value is mapped to\r\n * an attribute.\r\n * - ... else if obj is an array, the value is mapped to an\r\n * add child with a value attribute or a text child node,\r\n * if the value is a function.\r\n *\r\n * If no ID exists for a variable in <idrefs> or if an object\r\n * cannot be encoded, a warning is issued using <mxLog.warn>.\r\n *\r\n * Returns the resulting XML node that represents the given\r\n * object.\r\n *\r\n * Parameters:\r\n *\r\n * enc - <mxCodec> that controls the encoding process.\r\n * obj - Object to be encoded.\r\n */\r\nmxObjectCodec.prototype.encode = function(enc, obj)\r\n{\r\n\tvar node = enc.document.createElement(this.getName());\r\n\t\r\n\tobj = this.beforeEncode(enc, obj, node);\r\n\tthis.encodeObject(enc, obj, node);\r\n\t\r\n\treturn this.afterEncode(enc, obj, node);\r\n};\r\n\t\r\n/**\r\n * Function: encodeObject\r\n *\r\n * Encodes the value of each member in then given obj into the given node using\r\n * <encodeValue>.\r\n * \r\n * Parameters:\r\n *\r\n * enc - <mxCodec> that controls the encoding process.\r\n * obj - Object to be encoded.\r\n * node - XML node that contains the encoded object.\r\n */\r\nmxObjectCodec.prototype.encodeObject = function(enc, obj, node)\r\n{\r\n\tenc.setAttribute(node, 'id', enc.getId(obj));\r\n\t\r\n    for (var i in obj)\r\n    {\r\n\t\tvar name = i;\r\n\t\tvar value = obj[name];\r\n\t\t\r\n    \tif (value != null && !this.isExcluded(obj, name, value, true))\r\n    \t{\r\n    \t\tif (mxUtils.isInteger(name))\r\n    \t\t{\r\n    \t\t\tname = null;\r\n    \t\t}\r\n    \t\t\r\n    \t\tthis.encodeValue(enc, obj, name, value, node);\r\n    \t}\r\n    }\r\n};\r\n\r\n/**\r\n * Function: encodeValue\r\n * \r\n * Converts the given value according to the mappings\r\n * and id-refs in this codec and uses <writeAttribute>\r\n * to write the attribute into the given node.\r\n * \r\n * Parameters:\r\n *\r\n * enc - <mxCodec> that controls the encoding process.\r\n * obj - Object whose property is going to be encoded.\r\n * name - XML node that contains the encoded object.\r\n * value - Value of the property to be encoded.\r\n * node - XML node that contains the encoded object.\r\n */\r\nmxObjectCodec.prototype.encodeValue = function(enc, obj, name, value, node)\r\n{\r\n\tif (value != null)\r\n\t{\r\n\t\tif (this.isReference(obj, name, value, true))\r\n\t\t{\r\n\t\t\tvar tmp = enc.getId(value);\r\n\t\t\t\r\n\t\t\tif (tmp == null)\r\n\t\t\t{\r\n\t\t    \tmxLog.warn('mxObjectCodec.encode: No ID for ' +\r\n\t\t    \t\tthis.getName() + '.' + name + '=' + value);\r\n\t\t    \treturn; // exit\r\n\t\t    }\r\n\t\t    \r\n\t\t    value = tmp;\r\n\t\t}\r\n\r\n\t\tvar defaultValue = this.template[name];\r\n\t\t\r\n\t\t// Checks if the value is a default value and\r\n\t\t// the name is correct\r\n\t\tif (name == null || enc.encodeDefaults || defaultValue != value)\r\n\t\t{\r\n\t\t\tname = this.getAttributeName(name);\r\n\t\t\tthis.writeAttribute(enc, obj, name, value, node);\t\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: writeAttribute\r\n * \r\n * Writes the given value into node using <writePrimitiveAttribute>\r\n * or <writeComplexAttribute> depending on the type of the value.\r\n */\r\nmxObjectCodec.prototype.writeAttribute = function(enc, obj, name, value, node)\r\n{\r\n\tif (typeof(value) != 'object' /* primitive type */)\r\n\t{\r\n\t\tthis.writePrimitiveAttribute(enc, obj, name, value, node);\r\n\t}\r\n\telse /* complex type */\r\n\t{\r\n\t\tthis.writeComplexAttribute(enc, obj, name, value, node);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: writePrimitiveAttribute\r\n * \r\n * Writes the given value as an attribute of the given node.\r\n */\r\nmxObjectCodec.prototype.writePrimitiveAttribute = function(enc, obj, name, value, node)\r\n{\r\n\tvalue = this.convertAttributeToXml(enc, obj, name, value, node);\r\n\t\r\n\tif (name == null)\r\n\t{\r\n\t\tvar child = enc.document.createElement('add');\r\n\t\t\r\n\t\tif (typeof(value) == 'function')\r\n\t\t{\r\n    \t\tchild.appendChild(enc.document.createTextNode(value));\r\n    \t}\r\n    \telse\r\n    \t{\r\n    \t\tenc.setAttribute(child, 'value', value);\r\n    \t}\r\n    \t\r\n\t\tnode.appendChild(child);\r\n\t}\r\n\telse if (typeof(value) != 'function')\r\n\t{\r\n    \tenc.setAttribute(node, name, value);\r\n\t}\t\t\r\n};\r\n\t\r\n/**\r\n * Function: writeComplexAttribute\r\n * \r\n * Writes the given value as a child node of the given node.\r\n */\r\nmxObjectCodec.prototype.writeComplexAttribute = function(enc, obj, name, value, node)\r\n{\r\n\tvar child = enc.encode(value);\r\n\t\r\n\tif (child != null)\r\n\t{\r\n\t\tif (name != null)\r\n\t\t{\r\n    \t\tchild.setAttribute('as', name);\r\n    \t}\r\n    \t\r\n    \tnode.appendChild(child);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tmxLog.warn('mxObjectCodec.encode: No node for ' + this.getName() + '.' + name + ': ' + value);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: convertAttributeToXml\r\n * \r\n * Converts true to \"1\" and false to \"0\" is <isBooleanAttribute> returns true.\r\n * All other values are not converted.\r\n * \r\n * Parameters:\r\n *\r\n * enc - <mxCodec> that controls the encoding process.\r\n * obj - Objec to convert the attribute for.\r\n * name - Name of the attribute to be converted.\r\n * value - Value to be converted.\r\n */\r\nmxObjectCodec.prototype.convertAttributeToXml = function(enc, obj, name, value)\r\n{\r\n\t// Makes sure to encode boolean values as numeric values\r\n\tif (this.isBooleanAttribute(enc, obj, name, value))\r\n\t{\t\r\n\t\t// Checks if the value is true (do not use the value as is, because\r\n\t\t// this would check if the value is not null, so 0 would be true)\r\n\t\tvalue = (value == true) ? '1' : '0';\r\n\t}\r\n\t\r\n\treturn value;\r\n};\r\n\r\n/**\r\n * Function: isBooleanAttribute\r\n * \r\n * Returns true if the given object attribute is a boolean value.\r\n * \r\n * Parameters:\r\n *\r\n * enc - <mxCodec> that controls the encoding process.\r\n * obj - Objec to convert the attribute for.\r\n * name - Name of the attribute to be converted.\r\n * value - Value of the attribute to be converted.\r\n */\r\nmxObjectCodec.prototype.isBooleanAttribute = function(enc, obj, name, value)\r\n{\r\n\treturn (typeof(value.length) == 'undefined' && (value == true || value == false));\r\n};\r\n\r\n/**\r\n * Function: convertAttributeFromXml\r\n * \r\n * Converts booleans and numeric values to the respective types. Values are\r\n * numeric if <isNumericAttribute> returns true.\r\n * \r\n * Parameters:\r\n *\r\n * dec - <mxCodec> that controls the decoding process.\r\n * attr - XML attribute to be converted.\r\n * obj - Objec to convert the attribute for.\r\n */\r\nmxObjectCodec.prototype.convertAttributeFromXml = function(dec, attr, obj)\r\n{\r\n\tvar value = attr.value;\r\n\t\r\n\tif (this.isNumericAttribute(dec, attr, obj))\r\n\t{\r\n\t\tvalue = parseFloat(value);\r\n\t\t\r\n\t\tif (isNaN(value) || !isFinite(value))\r\n\t\t{\r\n\t\t\tvalue = 0;\r\n\t\t}\r\n\t}\r\n\t\r\n\treturn value;\r\n};\r\n\r\n/**\r\n * Function: isNumericAttribute\r\n * \r\n * Returns true if the given XML attribute is or should be a numeric value.\r\n * \r\n * Parameters:\r\n *\r\n * dec - <mxCodec> that controls the decoding process.\r\n * attr - XML attribute to be converted.\r\n * obj - Objec to convert the attribute for.\r\n */\r\nmxObjectCodec.prototype.isNumericAttribute = function(dec, attr, obj)\r\n{\r\n\t// Handles known numeric attributes for generic objects\r\n\tvar result = (obj.constructor == mxGeometry &&\r\n\t\t(attr.name == 'x' || attr.name == 'y' ||\r\n\t\tattr.name == 'width' || attr.name == 'height')) ||\r\n\t\t(obj.constructor == mxPoint &&\r\n\t\t(attr.name == 'x' || attr.name == 'y')) ||\r\n\t\tmxUtils.isNumeric(attr.value);\r\n\t\r\n\treturn result;\r\n};\r\n\r\n/**\r\n * Function: beforeEncode\r\n *\r\n * Hook for subclassers to pre-process the object before\r\n * encoding. This returns the input object. The return\r\n * value of this function is used in <encode> to perform\r\n * the default encoding into the given node.\r\n *\r\n * Parameters:\r\n *\r\n * enc - <mxCodec> that controls the encoding process.\r\n * obj - Object to be encoded.\r\n * node - XML node to encode the object into.\r\n */\r\nmxObjectCodec.prototype.beforeEncode = function(enc, obj, node)\r\n{\r\n\treturn obj;\r\n};\r\n\r\n/**\r\n * Function: afterEncode\r\n *\r\n * Hook for subclassers to post-process the node\r\n * for the given object after encoding and return the\r\n * post-processed node. This implementation returns \r\n * the input node. The return value of this method\r\n * is returned to the encoder from <encode>.\r\n *\r\n * Parameters:\r\n *\r\n * enc - <mxCodec> that controls the encoding process.\r\n * obj - Object to be encoded.\r\n * node - XML node that represents the default encoding.\r\n */\r\nmxObjectCodec.prototype.afterEncode = function(enc, obj, node)\r\n{\r\n\treturn node;\r\n};\r\n\r\n/**\r\n * Function: decode\r\n *\r\n * Parses the given node into the object or returns a new object\r\n * representing the given node.\r\n *\r\n * Dec is a reference to the calling decoder. It is used to decode\r\n * complex objects and resolve references.\r\n *\r\n * If a node has an id attribute then the object cache is checked for the\r\n * object. If the object is not yet in the cache then it is constructed\r\n * using the constructor of <template> and cached in <mxCodec.objects>.\r\n *\r\n * This implementation decodes all attributes and childs of a node\r\n * according to the following rules:\r\n *\r\n * - If the variable name is in <exclude> or if the attribute name is \"id\"\r\n * or \"as\" then it is ignored.\r\n * - If the variable name is in <idrefs> then <mxCodec.getObject> is used\r\n * to replace the reference with an object.\r\n * - The variable name is mapped using a reverse <mapping>.\r\n * - If the value has a child node, then the codec is used to create a\r\n * child object with the variable name taken from the \"as\" attribute.\r\n * - If the object is an array and the variable name is empty then the\r\n * value or child object is appended to the array.\r\n * - If an add child has no value or the object is not an array then\r\n * the child text content is evaluated using <mxUtils.eval>.\r\n *\r\n * For add nodes where the object is not an array and the variable name\r\n * is defined, the default mechanism is used, allowing to override/add\r\n * methods as follows:\r\n *\r\n * (code)\r\n * <Object>\r\n *   <add as=\"hello\"><![CDATA[\r\n *     function(arg1) {\r\n *       mxUtils.alert('Hello '+arg1);\r\n *     }\r\n *   ]]></add>\r\n * </Object>\r\n * (end) \r\n *\r\n * If no object exists for an ID in <idrefs> a warning is issued\r\n * using <mxLog.warn>.\r\n *\r\n * Returns the resulting object that represents the given XML node\r\n * or the object given to the method as the into parameter.\r\n *\r\n * Parameters:\r\n *\r\n * dec - <mxCodec> that controls the decoding process.\r\n * node - XML node to be decoded.\r\n * into - Optional objec to encode the node into.\r\n */\r\nmxObjectCodec.prototype.decode = function(dec, node, into)\r\n{\r\n\tvar id = node.getAttribute('id');\r\n\tvar obj = dec.objects[id];\r\n\t\r\n\tif (obj == null)\r\n\t{\r\n\t\tobj = into || this.cloneTemplate();\r\n\t\t\r\n\t\tif (id != null)\r\n\t\t{\r\n\t\t\tdec.putObject(id, obj);\r\n\t\t}\r\n\t}\r\n\t\r\n\tnode = this.beforeDecode(dec, node, obj);\r\n\tthis.decodeNode(dec, node, obj);\r\n\t\r\n    return this.afterDecode(dec, node, obj);\r\n};\t\r\n\r\n/**\r\n * Function: decodeNode\r\n * \r\n * Calls <decodeAttributes> and <decodeChildren> for the given node.\r\n * \r\n * Parameters:\r\n *\r\n * dec - <mxCodec> that controls the decoding process.\r\n * node - XML node to be decoded.\r\n * obj - Objec to encode the node into.\r\n */\t\r\nmxObjectCodec.prototype.decodeNode = function(dec, node, obj)\r\n{\r\n\tif (node != null)\r\n\t{\r\n\t\tthis.decodeAttributes(dec, node, obj);\r\n\t\tthis.decodeChildren(dec, node, obj);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: decodeAttributes\r\n * \r\n * Decodes all attributes of the given node using <decodeAttribute>.\r\n * \r\n * Parameters:\r\n *\r\n * dec - <mxCodec> that controls the decoding process.\r\n * node - XML node to be decoded.\r\n * obj - Objec to encode the node into.\r\n */\t\r\nmxObjectCodec.prototype.decodeAttributes = function(dec, node, obj)\r\n{\r\n\tvar attrs = node.attributes;\r\n\t\r\n\tif (attrs != null)\r\n\t{\r\n\t\tfor (var i = 0; i < attrs.length; i++)\r\n\t\t{\r\n\t\t\tthis.decodeAttribute(dec, attrs[i], obj);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: isIgnoredAttribute\r\n * \r\n * Returns true if the given attribute should be ignored. This implementation\r\n * returns true if the attribute name is \"as\" or \"id\".\r\n * \r\n * Parameters:\r\n *\r\n * dec - <mxCodec> that controls the decoding process.\r\n * attr - XML attribute to be decoded.\r\n * obj - Objec to encode the attribute into.\r\n */\t\r\nmxObjectCodec.prototype.isIgnoredAttribute = function(dec, attr, obj)\r\n{\r\n\treturn attr.nodeName == 'as' || attr.nodeName == 'id';\r\n};\r\n\r\n/**\r\n * Function: decodeAttribute\r\n * \r\n * Reads the given attribute into the specified object.\r\n * \r\n * Parameters:\r\n *\r\n * dec - <mxCodec> that controls the decoding process.\r\n * attr - XML attribute to be decoded.\r\n * obj - Objec to encode the attribute into.\r\n */\t\r\nmxObjectCodec.prototype.decodeAttribute = function(dec, attr, obj)\r\n{\r\n\tif (!this.isIgnoredAttribute(dec, attr, obj))\r\n\t{\r\n\t\tvar name = attr.nodeName;\r\n\t\t\r\n\t\t// Converts the string true and false to their boolean values.\r\n\t\t// This may require an additional check on the obj to see if\r\n\t\t// the existing field is a boolean value or uninitialized, in\r\n\t\t// which case we may want to convert true and false to a string.\r\n\t\tvar value = this.convertAttributeFromXml(dec, attr, obj);\r\n\t\tvar fieldname = this.getFieldName(name);\r\n\t\t\r\n\t\tif (this.isReference(obj, fieldname, value, false))\r\n\t\t{\r\n\t\t\tvar tmp = dec.getObject(value);\r\n\t\t\t\r\n\t\t\tif (tmp == null)\r\n\t\t\t{\r\n\t\t    \tmxLog.warn('mxObjectCodec.decode: No object for ' +\r\n\t\t    \t\tthis.getName() + '.' + name + '=' + value);\r\n\t\t    \treturn; // exit\r\n\t\t    }\r\n\t\t    \r\n\t\t    value = tmp;\r\n\t\t}\r\n\r\n\t\tif (!this.isExcluded(obj, name, value, false))\r\n\t\t{\r\n\t\t\t//mxLog.debug(mxUtils.getFunctionName(obj.constructor)+'.'+name+'='+value);\r\n\t\t\tobj[name] = value;\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: decodeChildren\r\n * \r\n * Decodes all children of the given node using <decodeChild>.\r\n * \r\n * Parameters:\r\n *\r\n * dec - <mxCodec> that controls the decoding process.\r\n * node - XML node to be decoded.\r\n * obj - Objec to encode the node into.\r\n */\t\r\nmxObjectCodec.prototype.decodeChildren = function(dec, node, obj)\r\n{\r\n\tvar child = node.firstChild;\r\n\t\r\n\twhile (child != null)\r\n\t{\r\n\t\tvar tmp = child.nextSibling;\r\n\t\t\r\n\t\tif (child.nodeType == mxConstants.NODETYPE_ELEMENT &&\r\n\t\t\t!this.processInclude(dec, child, obj))\r\n\t\t{\r\n\t\t\tthis.decodeChild(dec, child, obj);\r\n\t\t}\r\n\t\t\r\n\t\tchild = tmp;\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: decodeChild\r\n * \r\n * Reads the specified child into the given object.\r\n * \r\n * Parameters:\r\n *\r\n * dec - <mxCodec> that controls the decoding process.\r\n * child - XML child element to be decoded.\r\n * obj - Objec to encode the node into.\r\n */\t\r\nmxObjectCodec.prototype.decodeChild = function(dec, child, obj)\r\n{\r\n\tvar fieldname = this.getFieldName(child.getAttribute('as'));\r\n\t\r\n\tif (fieldname == null || !this.isExcluded(obj, fieldname, child, false))\r\n\t{\r\n\t\tvar template = this.getFieldTemplate(obj, fieldname, child);\r\n\t\tvar value = null;\r\n\t\t\r\n\t\tif (child.nodeName == 'add')\r\n\t\t{\r\n\t\t\tvalue = child.getAttribute('value');\r\n\t\t\t\r\n\t\t\tif (value == null && mxObjectCodec.allowEval)\r\n\t\t\t{\r\n\t\t\t\tvalue = mxUtils.eval(mxUtils.getTextContent(child));\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvalue = dec.decode(child, template);\r\n\t\t}\r\n\r\n\t\ttry\r\n\t\t{\r\n\t\t\tthis.addObjectValue(obj, fieldname, value, template);\r\n\t\t}\r\n\t\tcatch (e)\r\n\t\t{\r\n\t\t\tthrow new Error(e.message + ' for ' + child.nodeName);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: getFieldTemplate\r\n * \r\n * Returns the template instance for the given field. This returns the\r\n * value of the field, null if the value is an array or an empty collection\r\n * if the value is a collection. The value is then used to populate the\r\n * field for a new instance. For strongly typed languages it may be\r\n * required to override this to return the correct collection instance\r\n * based on the encoded child.\r\n */\t\r\nmxObjectCodec.prototype.getFieldTemplate = function(obj, fieldname, child)\r\n{\r\n\tvar template = obj[fieldname];\r\n\t\r\n\t// Non-empty arrays are replaced completely\r\n    if (template instanceof Array && template.length > 0)\r\n    {\r\n        template = null;\r\n    }\r\n    \r\n    return template;\r\n};\r\n\r\n/**\r\n * Function: addObjectValue\r\n * \r\n * Sets the decoded child node as a value of the given object. If the\r\n * object is a map, then the value is added with the given fieldname as a\r\n * key. If the fieldname is not empty, then setFieldValue is called or\r\n * else, if the object is a collection, the value is added to the\r\n * collection. For strongly typed languages it may be required to\r\n * override this with the correct code to add an entry to an object.\r\n */\t\r\nmxObjectCodec.prototype.addObjectValue = function(obj, fieldname, value, template)\r\n{\r\n\tif (value != null && value != template)\r\n\t{\r\n\t\tif (fieldname != null && fieldname.length > 0)\r\n\t\t{\r\n\t\t\tobj[fieldname] = value;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tobj.push(value);\r\n\t\t}\r\n\t\t//mxLog.debug('Decoded '+mxUtils.getFunctionName(obj.constructor)+'.'+fieldname+': '+value);\r\n\t}\r\n};\r\n\r\n/**\r\n * Function: processInclude\r\n *\r\n * Returns true if the given node is an include directive and\r\n * executes the include by decoding the XML document. Returns\r\n * false if the given node is not an include directive.\r\n *\r\n * Parameters:\r\n *\r\n * dec - <mxCodec> that controls the encoding/decoding process.\r\n * node - XML node to be checked.\r\n * into - Optional object to pass-thru to the codec.\r\n */\r\nmxObjectCodec.prototype.processInclude = function(dec, node, into)\r\n{\r\n\tif (node.nodeName == 'include')\r\n\t{\r\n\t\tvar name = node.getAttribute('name');\r\n\t\t\r\n\t\tif (name != null)\r\n\t\t{\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tvar xml = mxUtils.load(name).getDocumentElement();\r\n\t\t\t\t\r\n\t\t\t\tif (xml != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tdec.decode(xml, into);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tcatch (e)\r\n\t\t\t{\r\n\t\t\t\t// ignore\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn true;\r\n\t}\r\n\t\r\n\treturn false;\r\n};\r\n\r\n/**\r\n * Function: beforeDecode\r\n *\r\n * Hook for subclassers to pre-process the node for\r\n * the specified object and return the node to be\r\n * used for further processing by <decode>.\r\n * The object is created based on the template in the \r\n * calling method and is never null. This implementation\r\n * returns the input node. The return value of this\r\n * function is used in <decode> to perform\r\n * the default decoding into the given object.\r\n *\r\n * Parameters:\r\n *\r\n * dec - <mxCodec> that controls the decoding process.\r\n * node - XML node to be decoded.\r\n * obj - Object to encode the node into.\r\n */\r\nmxObjectCodec.prototype.beforeDecode = function(dec, node, obj)\r\n{\r\n\treturn node;\r\n};\r\n\r\n/**\r\n * Function: afterDecode\r\n *\r\n * Hook for subclassers to post-process the object after\r\n * decoding. This implementation returns the given object\r\n * without any changes. The return value of this method\r\n * is returned to the decoder from <decode>.\r\n *\r\n * Parameters:\r\n *\r\n * enc - <mxCodec> that controls the encoding process.\r\n * node - XML node to be decoded.\r\n * obj - Object that represents the default decoding.\r\n */\r\nmxObjectCodec.prototype.afterDecode = function(dec, node, obj)\r\n{\r\n\treturn obj;\r\n};\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nmxCodecRegistry.register(function()\r\n{\r\n\t/**\r\n\t * Class: mxCellCodec\r\n\t *\r\n\t * Codec for <mxCell>s. This class is created and registered\r\n\t * dynamically at load time and used implicitely via <mxCodec>\r\n\t * and the <mxCodecRegistry>.\r\n\t *\r\n\t * Transient Fields:\r\n\t *\r\n\t * - children\r\n\t * - edges\r\n\t * - overlays\r\n\t * - mxTransient\r\n\t *\r\n\t * Reference Fields:\r\n\t *\r\n\t * - parent\r\n\t * - source\r\n\t * - target\r\n\t * \r\n\t * Transient fields can be added using the following code:\r\n\t * \r\n\t * mxCodecRegistry.getCodec(mxCell).exclude.push('name_of_field');\r\n\t * \r\n\t * To subclass <mxCell>, replace the template and add an alias as\r\n\t * follows.\r\n\t * \r\n\t * (code)\r\n\t * function CustomCell(value, geometry, style)\r\n\t * {\r\n\t *   mxCell.apply(this, arguments);\r\n\t * }\r\n\t * \r\n\t * mxUtils.extend(CustomCell, mxCell);\r\n\t * \r\n\t * mxCodecRegistry.getCodec(mxCell).template = new CustomCell();\r\n\t * mxCodecRegistry.addAlias('CustomCell', 'mxCell');\r\n\t * (end)\r\n\t */\r\n\tvar codec = new mxObjectCodec(new mxCell(),\r\n\t\t['children', 'edges', 'overlays', 'mxTransient'],\r\n\t\t['parent', 'source', 'target']);\r\n\r\n\t/**\r\n\t * Function: isCellCodec\r\n\t *\r\n\t * Returns true since this is a cell codec.\r\n\t */\r\n\tcodec.isCellCodec = function()\r\n\t{\r\n\t\treturn true;\r\n\t};\r\n\r\n\t/**\r\n\t * Overidden to disable conversion of value to number.\r\n\t */\r\n\tcodec.isNumericAttribute = function(dec, attr, obj)\r\n\t{\r\n\t\treturn attr.nodeName !== 'value' && mxObjectCodec.prototype.isNumericAttribute.apply(this, arguments);\r\n\t};\r\n\t\r\n\t/**\r\n\t * Function: isExcluded\r\n\t *\r\n\t * Excludes user objects that are XML nodes.\r\n\t */ \r\n\tcodec.isExcluded = function(obj, attr, value, isWrite)\r\n\t{\r\n\t\treturn mxObjectCodec.prototype.isExcluded.apply(this, arguments) ||\r\n\t\t\t(isWrite && attr == 'value' &&\r\n\t\t\tvalue.nodeType == mxConstants.NODETYPE_ELEMENT);\r\n\t};\r\n\t\r\n\t/**\r\n\t * Function: afterEncode\r\n\t *\r\n\t * Encodes an <mxCell> and wraps the XML up inside the\r\n\t * XML of the user object (inversion).\r\n\t */\r\n\tcodec.afterEncode = function(enc, obj, node)\r\n\t{\r\n\t\tif (obj.value != null && obj.value.nodeType == mxConstants.NODETYPE_ELEMENT)\r\n\t\t{\r\n\t\t\t// Wraps the graphical annotation up in the user object (inversion)\r\n\t\t\t// by putting the result of the default encoding into a clone of the\r\n\t\t\t// user object (node type 1) and returning this cloned user object.\r\n\t\t\tvar tmp = node;\r\n\t\t\tnode = mxUtils.importNode(enc.document, obj.value, true);\r\n\t\t\tnode.appendChild(tmp);\r\n\t\t\t\r\n\t\t\t// Moves the id attribute to the outermost XML node, namely the\r\n\t\t\t// node which denotes the object boundaries in the file.\r\n\t\t\tvar id = tmp.getAttribute('id');\r\n\t\t\tnode.setAttribute('id', id);\r\n\t\t\ttmp.removeAttribute('id');\r\n\t\t}\r\n\r\n\t\treturn node;\r\n\t};\r\n\r\n\t/**\r\n\t * Function: beforeDecode\r\n\t *\r\n\t * Decodes an <mxCell> and uses the enclosing XML node as\r\n\t * the user object for the cell (inversion).\r\n\t */\r\n\tcodec.beforeDecode = function(dec, node, obj)\r\n\t{\r\n\t\tvar inner = node.cloneNode(true);\r\n\t\tvar classname = this.getName();\r\n\t\t\r\n\t\tif (node.nodeName != classname)\r\n\t\t{\r\n\t\t\t// Passes the inner graphical annotation node to the\r\n\t\t\t// object codec for further processing of the cell.\r\n\t\t\tvar tmp = node.getElementsByTagName(classname)[0];\r\n\t\t\t\r\n\t\t\tif (tmp != null && tmp.parentNode == node)\r\n\t\t\t{\r\n\t\t\t\tmxUtils.removeWhitespace(tmp, true);\r\n\t\t\t\tmxUtils.removeWhitespace(tmp, false);\r\n\t\t\t\ttmp.parentNode.removeChild(tmp);\r\n\t\t\t\tinner = tmp;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tinner = null;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Creates the user object out of the XML node\r\n\t\t\tobj.value = node.cloneNode(true);\r\n\t\t\tvar id = obj.value.getAttribute('id');\r\n\t\t\t\r\n\t\t\tif (id != null)\r\n\t\t\t{\r\n\t\t\t\tobj.setId(id);\r\n\t\t\t\tobj.value.removeAttribute('id');\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Uses ID from XML file as ID for cell in model\r\n\t\t\tobj.setId(node.getAttribute('id'));\r\n\t\t}\r\n\t\t\t\r\n\t\t// Preprocesses and removes all Id-references in order to use the\r\n\t\t// correct encoder (this) for the known references to cells (all).\r\n\t\tif (inner != null)\r\n\t\t{\r\n\t\t\tfor (var i = 0; i < this.idrefs.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar attr = this.idrefs[i];\r\n\t\t\t\tvar ref = inner.getAttribute(attr);\r\n\t\t\t\t\r\n\t\t\t\tif (ref != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tinner.removeAttribute(attr);\r\n\t\t\t\t\tvar object = dec.objects[ref] || dec.lookup(ref);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (object == null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t// Needs to decode forward reference\r\n\t\t\t\t\t\tvar element = dec.getElementById(ref);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (element != null)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar decoder = mxCodecRegistry.codecs[element.nodeName] || this;\r\n\t\t\t\t\t\t\tobject = decoder.decode(dec, element);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tobj[attr] = object;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn inner;\r\n\t};\r\n\r\n\t// Returns the codec into the registry\r\n\treturn codec;\r\n\r\n}());\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nmxCodecRegistry.register(function()\r\n{\r\n\t/**\r\n\t * Class: mxModelCodec\r\n\t *\r\n\t * Codec for <mxGraphModel>s. This class is created and registered\r\n\t * dynamically at load time and used implicitely via <mxCodec>\r\n\t * and the <mxCodecRegistry>.\r\n\t */\r\n\tvar codec = new mxObjectCodec(new mxGraphModel());\r\n\r\n\t/**\r\n\t * Function: encodeObject\r\n\t *\r\n\t * Encodes the given <mxGraphModel> by writing a (flat) XML sequence of\r\n\t * cell nodes as produced by the <mxCellCodec>. The sequence is\r\n\t * wrapped-up in a node with the name root.\r\n\t */\r\n\tcodec.encodeObject = function(enc, obj, node)\r\n\t{\r\n\t\tvar rootNode = enc.document.createElement('root');\r\n\t\tenc.encodeCell(obj.getRoot(), rootNode);\r\n\t\tnode.appendChild(rootNode);\r\n\t};\r\n\r\n\t/**\r\n\t * Function: decodeChild\r\n\t * \r\n\t * Overrides decode child to handle special child nodes.\r\n\t */\t\r\n\tcodec.decodeChild = function(dec, child, obj)\r\n\t{\r\n\t\tif (child.nodeName == 'root')\r\n\t\t{\r\n\t\t\tthis.decodeRoot(dec, child, obj);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tmxObjectCodec.prototype.decodeChild.apply(this, arguments);\r\n\t\t}\r\n\t};\r\n\r\n\t/**\r\n\t * Function: decodeRoot\r\n\t *\r\n\t * Reads the cells into the graph model. All cells\r\n\t * are children of the root element in the node.\r\n\t */\r\n\tcodec.decodeRoot = function(dec, root, model)\r\n\t{\r\n\t\tvar rootCell = null;\r\n\t\tvar tmp = root.firstChild;\r\n\t\t\r\n\t\twhile (tmp != null)\r\n\t\t{\r\n\t\t\tvar cell = dec.decodeCell(tmp);\r\n\t\t\t\r\n\t\t\tif (cell != null && cell.getParent() == null)\r\n\t\t\t{\r\n\t\t\t\trootCell = cell;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\ttmp = tmp.nextSibling;\r\n\t\t}\r\n\r\n\t\t// Sets the root on the model if one has been decoded\r\n\t\tif (rootCell != null)\r\n\t\t{\r\n\t\t\tmodel.setRoot(rootCell);\r\n\t\t}\r\n\t};\r\n\r\n\t// Returns the codec into the registry\r\n\treturn codec;\r\n\r\n}());\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nmxCodecRegistry.register(function()\r\n{\r\n\t/**\r\n\t * Class: mxRootChangeCodec\r\n\t *\r\n\t * Codec for <mxRootChange>s. This class is created and registered\r\n\t * dynamically at load time and used implicitely via <mxCodec> and\r\n\t * the <mxCodecRegistry>.\r\n\t *\r\n\t * Transient Fields:\r\n\t *\r\n\t * - model\r\n\t * - previous\r\n\t * - root\r\n\t */\r\n\tvar codec = new mxObjectCodec(new mxRootChange(),\r\n\t\t['model', 'previous', 'root']);\r\n\r\n\t/**\r\n\t * Function: onEncode\r\n\t *\r\n\t * Encodes the child recursively.\r\n\t */\r\n\tcodec.afterEncode = function(enc, obj, node)\r\n\t{\r\n\t\tenc.encodeCell(obj.root, node);\r\n\t\t\r\n\t\treturn node;\r\n\t};\r\n\r\n\t/**\r\n\t * Function: beforeDecode\r\n\t *\r\n\t * Decodes the optional children as cells\r\n\t * using the respective decoder.\r\n\t */\r\n\tcodec.beforeDecode = function(dec, node, obj)\r\n\t{\r\n\t\tif (node.firstChild != null &&\r\n\t\t\tnode.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT)\r\n\t\t{\r\n\t\t\t// Makes sure the original node isn't modified\r\n\t\t\tnode = node.cloneNode(true);\r\n\t\t\t\r\n\t\t\tvar tmp = node.firstChild;\r\n\t\t\tobj.root = dec.decodeCell(tmp, false);\r\n\r\n\t\t\tvar tmp2 = tmp.nextSibling;\r\n\t\t\ttmp.parentNode.removeChild(tmp);\r\n\t\t\ttmp = tmp2;\r\n\t\t\r\n\t\t\twhile (tmp != null)\r\n\t\t\t{\r\n\t\t\t\ttmp2 = tmp.nextSibling;\r\n\t\t\t\tdec.decodeCell(tmp);\r\n\t\t\t\ttmp.parentNode.removeChild(tmp);\r\n\t\t\t\ttmp = tmp2;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn node;\r\n\t};\r\n\t\r\n\t/**\r\n\t * Function: afterDecode\r\n\t *\r\n\t * Restores the state by assigning the previous value.\r\n\t */\r\n\tcodec.afterDecode = function(dec, node, obj)\r\n\t{\r\n\t\tobj.previous = obj.root;\r\n\t\t\r\n\t\treturn obj;\r\n\t};\r\n\r\n\t// Returns the codec into the registry\r\n\treturn codec;\r\n\r\n}());\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nmxCodecRegistry.register(function()\r\n{\r\n\t/**\r\n\t * Class: mxChildChangeCodec\r\n\t *\r\n\t * Codec for <mxChildChange>s. This class is created and registered\r\n\t * dynamically at load time and used implicitely via <mxCodec> and\r\n\t * the <mxCodecRegistry>.\r\n\t *\r\n\t * Transient Fields:\r\n\t *\r\n\t * - model\r\n\t * - previous\r\n\t * - previousIndex\r\n\t * - child\r\n\t *\r\n\t * Reference Fields:\r\n\t *\r\n\t * - parent\r\n\t */\r\n\tvar codec = new mxObjectCodec(new mxChildChange(),\r\n\t\t['model', 'child', 'previousIndex'],\r\n\t\t['parent', 'previous']);\r\n\r\n\t/**\r\n\t * Function: isReference\r\n\t *\r\n\t * Returns true for the child attribute if the child\r\n\t * cell had a previous parent or if we're reading the\r\n\t * child as an attribute rather than a child node, in\r\n\t * which case it's always a reference.\r\n\t */\r\n\tcodec.isReference = function(obj, attr, value, isWrite)\r\n\t{\r\n\t\tif (attr == 'child' && (!isWrite || obj.model.contains(obj.previous)))\r\n\t\t{\r\n\t\t\treturn true;\r\n\t\t}\r\n\t\t\r\n\t\treturn mxUtils.indexOf(this.idrefs, attr) >= 0;\r\n\t};\r\n\r\n\t/**\r\n\t * Function: isExcluded\r\n\t *\r\n\t * Excludes references to parent or previous if not in the model.\r\n\t */\r\n  \tcodec.isExcluded = function(obj, attr, value, write)\r\n  \t{\r\n  \t\treturn mxObjectCodec.prototype.isExcluded.apply(this, arguments) ||\r\n  \t\t\t(write && value != null && (attr == 'previous' ||\r\n  \t\t\tattr == 'parent') && !obj.model.contains(value));\r\n  \t};\r\n  \t\r\n\t/**\r\n\t * Function: afterEncode\r\n\t *\r\n\t * Encodes the child recusively and adds the result\r\n\t * to the given node.\r\n\t */\r\n\tcodec.afterEncode = function(enc, obj, node)\r\n\t{\r\n\t\tif (this.isReference(obj, 'child', obj.child, true))\r\n\t\t{\r\n\t\t\t// Encodes as reference (id)\r\n\t\t\tnode.setAttribute('child', enc.getId(obj.child));\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// At this point, the encoder is no longer able to know which cells\r\n\t\t\t// are new, so we have to encode the complete cell hierarchy and\r\n\t\t\t// ignore the ones that are already there at decoding time. Note:\r\n\t\t\t// This can only be resolved by moving the notify event into the\r\n\t\t\t// execute of the edit.\r\n\t\t\tenc.encodeCell(obj.child, node);\r\n\t\t}\r\n\t\t\r\n\t\treturn node;\r\n\t};\r\n\r\n\t/**\r\n\t * Function: beforeDecode\r\n\t *\r\n\t * Decodes the any child nodes as using the respective\r\n\t * codec from the registry.\r\n\t */\r\n\tcodec.beforeDecode = function(dec, node, obj)\r\n\t{\r\n\t\tif (node.firstChild != null &&\r\n\t\t\tnode.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT)\r\n\t\t{\r\n\t\t\t// Makes sure the original node isn't modified\r\n\t\t\tnode = node.cloneNode(true);\r\n\t\t\t\r\n\t\t\tvar tmp = node.firstChild;\r\n\t\t\tobj.child = dec.decodeCell(tmp, false);\r\n\r\n\t\t\tvar tmp2 = tmp.nextSibling;\r\n\t\t\ttmp.parentNode.removeChild(tmp);\r\n\t\t\ttmp = tmp2;\r\n\t\t\t\r\n\t\t\twhile (tmp != null)\r\n\t\t\t{\r\n\t\t\t\ttmp2 = tmp.nextSibling;\r\n\t\t\t\t\r\n\t\t\t\tif (tmp.nodeType == mxConstants.NODETYPE_ELEMENT)\r\n\t\t\t\t{\r\n\t\t\t\t\t// Ignores all existing cells because those do not need to\r\n\t\t\t\t\t// be re-inserted into the model. Since the encoded version\r\n\t\t\t\t\t// of these cells contains the new parent, this would leave\r\n\t\t\t\t\t// to an inconsistent state on the model (ie. a parent\r\n\t\t\t\t\t// change without a call to parentForCellChanged).\r\n\t\t\t\t\tvar id = tmp.getAttribute('id');\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (dec.lookup(id) == null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tdec.decodeCell(tmp);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\ttmp.parentNode.removeChild(tmp);\r\n\t\t\t\ttmp = tmp2;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tvar childRef = node.getAttribute('child');\r\n\t\t\tobj.child = dec.getObject(childRef);\r\n\t\t}\r\n\t\t\r\n\t\treturn node;\r\n\t};\r\n\t\r\n\t/**\r\n\t * Function: afterDecode\r\n\t *\r\n\t * Restores object state in the child change.\r\n\t */\r\n\tcodec.afterDecode = function(dec, node, obj)\r\n\t{\r\n\t\t// Cells are decoded here after a complete transaction so the previous\r\n\t\t// parent must be restored on the cell for the case where the cell was\r\n\t\t// added. This is needed for the local model to identify the cell as a\r\n\t\t// new cell and register the ID.\r\n        if (obj.child != null)\r\n        {\r\n            if (obj.child.parent != null && obj.previous != null &&\r\n                obj.child.parent != obj.previous)\r\n            {\r\n                obj.previous = obj.child.parent;\r\n            }\r\n\r\n            obj.child.parent = obj.previous;\r\n            obj.previous = obj.parent;\r\n            obj.previousIndex = obj.index;\r\n        }\r\n\r\n\t\treturn obj;\r\n\t};\r\n\r\n\t// Returns the codec into the registry\r\n\treturn codec;\r\n\r\n}());\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nmxCodecRegistry.register(function()\r\n{\r\n\t/**\r\n\t * Class: mxTerminalChangeCodec\r\n\t *\r\n\t * Codec for <mxTerminalChange>s. This class is created and registered\r\n\t * dynamically at load time and used implicitely via <mxCodec> and\r\n\t * the <mxCodecRegistry>.\r\n\t *\r\n\t * Transient Fields:\r\n\t *\r\n\t * - model\r\n\t * - previous\r\n\t *\r\n\t * Reference Fields:\r\n\t *\r\n\t * - cell\r\n\t * - terminal\r\n\t */\r\n\tvar codec = new mxObjectCodec(new mxTerminalChange(),\r\n\t\t['model', 'previous'], ['cell', 'terminal']);\r\n\r\n\t/**\r\n\t * Function: afterDecode\r\n\t *\r\n\t * Restores the state by assigning the previous value.\r\n\t */\r\n\tcodec.afterDecode = function(dec, node, obj)\r\n\t{\r\n\t\tobj.previous = obj.terminal;\r\n\t\t\r\n\t\treturn obj;\r\n\t};\r\n\r\n\t// Returns the codec into the registry\r\n\treturn codec;\r\n\r\n}());\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxGenericChangeCodec\r\n *\r\n * Codec for <mxValueChange>s, <mxStyleChange>s, <mxGeometryChange>s,\r\n * <mxCollapseChange>s and <mxVisibleChange>s. This class is created\r\n * and registered dynamically at load time and used implicitely\r\n * via <mxCodec> and the <mxCodecRegistry>.\r\n *\r\n * Transient Fields:\r\n *\r\n * - model\r\n * - previous\r\n *\r\n * Reference Fields:\r\n *\r\n * - cell\r\n * \r\n * Constructor: mxGenericChangeCodec\r\n *\r\n * Factory function that creates a <mxObjectCodec> for\r\n * the specified change and fieldname.\r\n *\r\n * Parameters:\r\n *\r\n * obj - An instance of the change object.\r\n * variable - The fieldname for the change data.\r\n */\r\nvar mxGenericChangeCodec = function(obj, variable)\r\n{\r\n\tvar codec = new mxObjectCodec(obj,  ['model', 'previous'], ['cell']);\r\n\r\n\t/**\r\n\t * Function: afterDecode\r\n\t *\r\n\t * Restores the state by assigning the previous value.\r\n\t */\r\n\tcodec.afterDecode = function(dec, node, obj)\r\n\t{\r\n\t\t// Allows forward references in sessions. This is a workaround\r\n\t\t// for the sequence of edits in mxGraph.moveCells and cellsAdded.\r\n\t\tif (mxUtils.isNode(obj.cell))\r\n\t\t{\r\n\t\t\tobj.cell = dec.decodeCell(obj.cell, false);\r\n\t\t}\r\n\r\n\t\tobj.previous = obj[variable];\r\n\r\n\t\treturn obj;\r\n\t};\r\n\t\r\n\treturn codec;\r\n};\r\n\r\n// Registers the codecs\r\nmxCodecRegistry.register(mxGenericChangeCodec(new mxValueChange(), 'value'));\r\nmxCodecRegistry.register(mxGenericChangeCodec(new mxStyleChange(), 'style'));\r\nmxCodecRegistry.register(mxGenericChangeCodec(new mxGeometryChange(), 'geometry'));\r\nmxCodecRegistry.register(mxGenericChangeCodec(new mxCollapseChange(), 'collapsed'));\r\nmxCodecRegistry.register(mxGenericChangeCodec(new mxVisibleChange(), 'visible'));\r\nmxCodecRegistry.register(mxGenericChangeCodec(new mxCellAttributeChange(), 'value'));\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nmxCodecRegistry.register(function()\r\n{\r\n\t/**\r\n\t * Class: mxGraphCodec\r\n\t *\r\n\t * Codec for <mxGraph>s. This class is created and registered\r\n\t * dynamically at load time and used implicitely via <mxCodec>\r\n\t * and the <mxCodecRegistry>.\r\n\t *\r\n\t * Transient Fields:\r\n\t *\r\n\t * - graphListeners\r\n\t * - eventListeners\r\n\t * - view\r\n\t * - container\r\n\t * - cellRenderer\r\n\t * - editor\r\n\t * - selection\r\n\t */\r\n\treturn new mxObjectCodec(new mxGraph(),\r\n\t\t['graphListeners', 'eventListeners', 'view', 'container',\r\n\t\t'cellRenderer', 'editor', 'selection']);\r\n\r\n}());\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nmxCodecRegistry.register(function()\r\n{\r\n\t/**\r\n\t * Class: mxGraphViewCodec\r\n\t *\r\n\t * Custom encoder for <mxGraphView>s. This class is created\r\n\t * and registered dynamically at load time and used implicitely via\r\n\t * <mxCodec> and the <mxCodecRegistry>. This codec only writes views\r\n\t * into a XML format that can be used to create an image for\r\n\t * the graph, that is, it contains absolute coordinates with\r\n\t * computed perimeters, edge styles and cell styles.\r\n\t */\r\n\tvar codec = new mxObjectCodec(new mxGraphView());\r\n\r\n\t/**\r\n\t * Function: encode\r\n\t *\r\n\t * Encodes the given <mxGraphView> using <encodeCell>\r\n\t * starting at the model's root. This returns the\r\n\t * top-level graph node of the recursive encoding.\r\n\t */\r\n\tcodec.encode = function(enc, view)\r\n\t{\r\n\t\treturn this.encodeCell(enc, view,\r\n\t\t\tview.graph.getModel().getRoot());\r\n\t};\r\n\r\n\t/**\r\n\t * Function: encodeCell\r\n\t *\r\n\t * Recursively encodes the specifed cell. Uses layer\r\n\t * as the default nodename. If the cell's parent is\r\n\t * null, then graph is used for the nodename. If\r\n\t * <mxGraphModel.isEdge> returns true for the cell,\r\n\t * then edge is used for the nodename, else if\r\n\t * <mxGraphModel.isVertex> returns true for the cell,\r\n\t * then vertex is used for the nodename.\r\n\t *\r\n\t * <mxGraph.getLabel> is used to create the label\r\n\t * attribute for the cell. For graph nodes and vertices\r\n\t * the bounds are encoded into x, y, width and height.\r\n\t * For edges the points are encoded into a points\r\n\t * attribute as a space-separated list of comma-separated\r\n\t * coordinate pairs (eg. x0,y0 x1,y1 ... xn,yn). All\r\n\t * values from the cell style are added as attribute\r\n\t * values to the node. \r\n\t */\r\n\tcodec.encodeCell = function(enc, view, cell)\r\n\t{\r\n\t\tvar model = view.graph.getModel();\r\n\t\tvar state = view.getState(cell);\r\n\t\tvar parent = model.getParent(cell);\r\n\t\t\r\n\t\tif (parent == null || state != null)\r\n\t\t{\r\n\t\t\tvar childCount = model.getChildCount(cell);\r\n\t\t\tvar geo = view.graph.getCellGeometry(cell);\r\n\t\t\tvar name = null;\r\n\t\t\t\r\n\t\t\tif (parent == model.getRoot())\r\n\t\t\t{\r\n\t\t\t\tname = 'layer';\r\n\t\t\t}\r\n\t\t\telse if (parent == null)\r\n\t\t\t{\r\n\t\t\t\tname = 'graph';\r\n\t\t\t}\r\n\t\t\telse if (model.isEdge(cell))\r\n\t\t\t{\r\n\t\t\t\tname = 'edge';\r\n\t\t\t}\r\n\t\t\telse if (childCount > 0 && geo != null)\r\n\t\t\t{\r\n\t\t\t\tname = 'group';\r\n\t\t\t}\r\n\t\t\telse if (model.isVertex(cell))\r\n\t\t\t{\r\n\t\t\t\tname = 'vertex';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (name != null)\r\n\t\t\t{\r\n\t\t\t\tvar node = enc.document.createElement(name);\r\n\t\t\t\tvar lab = view.graph.getLabel(cell);\r\n\t\t\t\t\r\n\t\t\t\tif (lab != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tnode.setAttribute('label', view.graph.getLabel(cell));\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (view.graph.isHtmlLabel(cell))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tnode.setAttribute('html', true);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\r\n\t\t\t\tif (parent == null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar bounds = view.getGraphBounds();\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (bounds != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tnode.setAttribute('x', Math.round(bounds.x));\r\n\t\t\t\t\t\tnode.setAttribute('y', Math.round(bounds.y));\r\n\t\t\t\t\t\tnode.setAttribute('width', Math.round(bounds.width));\r\n\t\t\t\t\t\tnode.setAttribute('height', Math.round(bounds.height));\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tnode.setAttribute('scale', view.scale);\r\n\t\t\t\t}\r\n\t\t\t\telse if (state != null && geo != null)\r\n\t\t\t\t{\r\n\t\t\t\t\t// Writes each key, value in the style pair to an attribute\r\n\t\t\t\t    for (var i in state.style)\r\n\t\t\t\t    {\r\n\t\t\t\t    \tvar value = state.style[i];\r\n\t\t\r\n\t\t\t\t    \t// Tries to turn objects and functions into strings\r\n\t\t\t\t\t    if (typeof(value) == 'function' &&\r\n\t\t\t\t\t\t\ttypeof(value) == 'object')\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t    \tvalue = mxStyleRegistry.getName(value);\r\n\t\t\t\t        }\r\n\t\t\t\t    \t\r\n\t\t\t\t    \tif (value != null &&\r\n\t\t\t\t    \t\ttypeof(value) != 'function' &&\r\n\t\t\t\t\t\t\ttypeof(value) != 'object')\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tnode.setAttribute(i, value);\r\n\t\t\t\t        }\r\n\t\t\t\t    }\r\n\t\t\t\t    \r\n\t\t\t\t\tvar abs = state.absolutePoints;\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Writes the list of points into one attribute\r\n\t\t\t\t\tif (abs != null && abs.length > 0)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar pts = Math.round(abs[0].x) + ',' + Math.round(abs[0].y);\r\n\t\t\r\n\t\t\t\t\t\tfor (var i=1; i<abs.length; i++)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tpts += ' ' + Math.round(abs[i].x) + ',' +\r\n\t\t\t\t\t\t\t\tMath.round(abs[i].y);\r\n\t\t\t\t\t\t}\r\n\t\t\r\n\t\t\t\t\t\tnode.setAttribute('points', pts);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Writes the bounds into 4 attributes\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tnode.setAttribute('x', Math.round(state.x));\r\n\t\t\t\t\t\tnode.setAttribute('y', Math.round(state.y));\r\n\t\t\t\t\t\tnode.setAttribute('width', Math.round(state.width));\r\n\t\t\t\t\t\tnode.setAttribute('height', Math.round(state.height));\t\t\t\t\r\n\t\t\t\t\t}\r\n\t\t\r\n\t\t\t\t\tvar offset = state.absoluteOffset;\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Writes the offset into 2 attributes\r\n\t\t\t\t\tif (offset != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (offset.x != 0)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tnode.setAttribute('dx', Math.round(offset.x));\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (offset.y != 0)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tnode.setAttribute('dy', Math.round(offset.y));\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\r\n\t\t\t\tfor (var i=0; i<childCount; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar childNode = this.encodeCell(enc,\r\n\t\t\t\t\t\t\tview, model.getChildAt(cell, i));\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (childNode != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tnode.appendChild(childNode);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn node;\r\n\t};\r\n\r\n\t// Returns the codec into the registry\r\n\treturn codec;\r\n\r\n}());\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxStylesheetCodec\r\n *\r\n * Codec for <mxStylesheet>s. This class is created and registered\r\n * dynamically at load time and used implicitely via <mxCodec>\r\n * and the <mxCodecRegistry>.\r\n */\r\nvar mxStylesheetCodec = mxCodecRegistry.register(function()\r\n{\r\n\tvar codec = new mxObjectCodec(new mxStylesheet());\r\n\r\n\t/**\r\n\t * Function: encode\r\n\t *\r\n\t * Encodes a stylesheet. See <decode> for a description of the\r\n\t * format.\r\n\t */\r\n\tcodec.encode = function(enc, obj)\r\n\t{\r\n\t\tvar node = enc.document.createElement(this.getName());\r\n\t\t\r\n\t\tfor (var i in obj.styles)\r\n\t\t{\r\n\t\t\tvar style = obj.styles[i];\r\n\t\t\tvar styleNode = enc.document.createElement('add');\r\n\t\t\t\r\n\t\t\tif (i != null)\r\n\t\t\t{\r\n\t\t\t\tstyleNode.setAttribute('as', i);\r\n\t\t\t\t\r\n\t\t\t\tfor (var j in style)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar value = this.getStringValue(j, style[j]);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (value != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar entry = enc.document.createElement('add');\r\n\t\t\t\t\t\tentry.setAttribute('value', value);\r\n\t\t\t\t\t\tentry.setAttribute('as', j);\r\n\t\t\t\t\t\tstyleNode.appendChild(entry);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (styleNode.childNodes.length > 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tnode.appendChild(styleNode);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t    return node;\r\n\t};\r\n\r\n\t/**\r\n\t * Function: getStringValue\r\n\t *\r\n\t * Returns the string for encoding the given value.\r\n\t */\r\n\tcodec.getStringValue = function(key, value)\r\n\t{\r\n\t\tvar type = typeof(value);\r\n\t\t\r\n\t\tif (type == 'function')\r\n\t\t{\r\n\t\t\tvalue = mxStyleRegistry.getName(style[j]);\r\n\t\t}\r\n\t\telse if (type == 'object')\r\n\t\t{\r\n\t\t\tvalue = null;\r\n\t\t}\r\n\t\t\r\n\t\treturn value;\r\n\t};\r\n\t\r\n\t/**\r\n\t * Function: decode\r\n\t *\r\n\t * Reads a sequence of the following child nodes\r\n\t * and attributes:\r\n\t *\r\n\t * Child Nodes:\r\n\t *\r\n\t * add - Adds a new style.\r\n\t *\r\n\t * Attributes:\r\n\t *\r\n\t * as - Name of the style.\r\n\t * extend - Name of the style to inherit from.\r\n\t *\r\n\t * Each node contains another sequence of add and remove nodes with the following\r\n\t * attributes:\r\n\t *\r\n\t * as - Name of the style (see <mxConstants>).\r\n\t * value - Value for the style.\r\n\t *\r\n\t * Instead of the value-attribute, one can put Javascript expressions into\r\n\t * the node as follows if <mxStylesheetCodec.allowEval> is true:\r\n\t * <add as=\"perimeter\">mxPerimeter.RectanglePerimeter</add>\r\n\t *\r\n\t * A remove node will remove the entry with the name given in the as-attribute\r\n\t * from the style.\r\n\t * \r\n\t * Example:\r\n\t *\r\n\t * (code)\r\n\t * <mxStylesheet as=\"stylesheet\">\r\n\t *   <add as=\"text\">\r\n\t *     <add as=\"fontSize\" value=\"12\"/>\r\n\t *   </add>\r\n\t *   <add as=\"defaultVertex\" extend=\"text\">\r\n\t *     <add as=\"shape\" value=\"rectangle\"/>\r\n\t *   </add>\r\n\t * </mxStylesheet>\r\n\t * (end)\r\n\t */\r\n\tcodec.decode = function(dec, node, into)\r\n\t{\r\n\t\tvar obj = into || new this.template.constructor();\r\n\t\tvar id = node.getAttribute('id');\r\n\t\t\r\n\t\tif (id != null)\r\n\t\t{\r\n\t\t\tdec.objects[id] = obj;\r\n\t\t}\r\n\t\t\r\n\t\tnode = node.firstChild;\r\n\t\t\r\n\t\twhile (node != null)\r\n\t\t{\r\n\t\t\tif (!this.processInclude(dec, node, obj) && node.nodeName == 'add')\r\n\t\t\t{\r\n\t\t\t\tvar as = node.getAttribute('as');\r\n\t\t\t\t\r\n\t\t\t\tif (as != null)\r\n\t\t\t\t{\r\n\t\t\t\t\tvar extend = node.getAttribute('extend');\r\n\t\t\t\t\tvar style = (extend != null) ? mxUtils.clone(obj.styles[extend]) : null;\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (style == null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (extend != null)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tmxLog.warn('mxStylesheetCodec.decode: stylesheet ' +\r\n\t\t\t\t\t\t\t\textend + ' not found to extend');\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tstyle = new Object();\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tvar entry = node.firstChild;\r\n\t\t\t\t\t\r\n\t\t\t\t\twhile (entry != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (entry.nodeType == mxConstants.NODETYPE_ELEMENT)\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t \tvar key = entry.getAttribute('as');\r\n\t\t\t\t\t\t \t\r\n\t\t\t\t\t\t \tif (entry.nodeName == 'add')\r\n\t\t\t\t\t\t \t{\r\n\t\t\t\t\t\t\t \tvar text = mxUtils.getTextContent(entry);\r\n\t\t\t\t\t\t\t \tvar value = null;\r\n\t\t\t\t\t\t\t \t\r\n\t\t\t\t\t\t\t \tif (text != null && text.length > 0 && mxStylesheetCodec.allowEval)\r\n\t\t\t\t\t\t\t \t{\r\n\t\t\t\t\t\t\t \t\tvalue = mxUtils.eval(text);\r\n\t\t\t\t\t\t\t \t}\r\n\t\t\t\t\t\t\t \telse\r\n\t\t\t\t\t\t\t \t{\r\n\t\t\t\t\t\t\t \t\tvalue = entry.getAttribute('value');\r\n\t\t\t\t\t\t\t \t\t\r\n\t\t\t\t\t\t\t \t\tif (mxUtils.isNumeric(value))\r\n\t\t\t\t\t\t\t \t\t{\r\n\t\t\t\t\t\t\t\t\t\tvalue = parseFloat(value);\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t \t}\r\n\r\n\t\t\t\t\t\t\t \tif (value != null)\r\n\t\t\t\t\t\t\t \t{\r\n\t\t\t\t\t\t\t \t\tstyle[key] = value;\r\n\t\t\t\t\t\t\t \t}\r\n\t\t\t\t\t\t \t}\r\n\t\t\t\t\t\t \telse if (entry.nodeName == 'remove')\r\n\t\t\t\t\t\t \t{\r\n\t\t\t\t\t\t \t\tdelete style[key];\r\n\t\t\t\t\t\t \t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tentry = entry.nextSibling;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tobj.putCellStyle(as, style);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tnode = node.nextSibling;\r\n\t\t}\r\n\t\t\r\n\t\treturn obj;\r\n\t};\r\n\r\n\t// Returns the codec into the registry\r\n\treturn codec;\r\n\r\n}());\r\n\r\n/**\r\n * Variable: allowEval\r\n * \r\n * Static global switch that specifies if the use of eval is allowed for\r\n * evaluating text content. Default is true. Set this to false if stylesheets\r\n * may contain user input.\r\n */\r\nmxStylesheetCodec.allowEval = true;\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nmxCodecRegistry.register(function()\r\n{\r\n\t/**\r\n\t * Class: mxDefaultKeyHandlerCodec\r\n\t *\r\n\t * Custom codec for configuring <mxDefaultKeyHandler>s. This class is created\r\n\t * and registered dynamically at load time and used implicitely via\r\n\t * <mxCodec> and the <mxCodecRegistry>. This codec only reads configuration\r\n\t * data for existing key handlers, it does not encode or create key handlers.\r\n\t */\r\n\tvar codec = new mxObjectCodec(new mxDefaultKeyHandler());\r\n\r\n\t/**\r\n\t * Function: encode\r\n\t *\r\n\t * Returns null.\r\n\t */\r\n\tcodec.encode = function(enc, obj)\r\n\t{\r\n\t\treturn null;\r\n\t};\r\n\t\r\n\t/**\r\n\t * Function: decode\r\n\t *\r\n\t * Reads a sequence of the following child nodes\r\n\t * and attributes:\r\n\t *\r\n\t * Child Nodes:\r\n\t *\r\n\t * add - Binds a keystroke to an actionname.\r\n\t *\r\n\t * Attributes:\r\n\t *\r\n\t * as - Keycode.\r\n\t * action - Actionname to execute in editor.\r\n\t * control - Optional boolean indicating if\r\n\t * \t\tthe control key must be pressed.\r\n\t *\r\n\t * Example:\r\n\t *\r\n\t * (code)\r\n\t * <mxDefaultKeyHandler as=\"keyHandler\">\r\n\t *   <add as=\"88\" control=\"true\" action=\"cut\"/>\r\n\t *   <add as=\"67\" control=\"true\" action=\"copy\"/>\r\n\t *   <add as=\"86\" control=\"true\" action=\"paste\"/>\r\n\t * </mxDefaultKeyHandler>\r\n\t * (end)\r\n\t *\r\n\t * The keycodes are for the x, c and v keys.\r\n\t *\r\n\t * See also: <mxDefaultKeyHandler.bindAction>,\r\n\t * http://www.js-examples.com/page/tutorials__key_codes.html\r\n\t */\r\n\tcodec.decode = function(dec, node, into)\r\n\t{\r\n\t\tif (into != null)\r\n\t\t{\r\n\t\t\tvar editor = into.editor;\r\n\t\t\tnode = node.firstChild;\r\n\t\t\t\r\n\t\t\twhile (node != null)\r\n\t\t\t{\r\n\t\t\t\tif (!this.processInclude(dec, node, into) &&\r\n\t\t\t\t\tnode.nodeName == 'add')\r\n\t\t\t\t{\r\n\t\t\t\t\tvar as = node.getAttribute('as');\r\n\t\t\t\t\tvar action = node.getAttribute('action');\r\n\t\t\t\t\tvar control = node.getAttribute('control');\r\n\t\t\t\t\t\r\n\t\t\t\t\tinto.bindAction(as, action, control);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tnode = node.nextSibling;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn into;\r\n\t};\r\n\r\n\t// Returns the codec into the registry\r\n\treturn codec;\r\n\r\n}());\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\n/**\r\n * Class: mxDefaultToolbarCodec\r\n *\r\n * Custom codec for configuring <mxDefaultToolbar>s. This class is created\r\n * and registered dynamically at load time and used implicitely via\r\n * <mxCodec> and the <mxCodecRegistry>. This codec only reads configuration\r\n * data for existing toolbars handlers, it does not encode or create toolbars.\r\n */\r\nvar mxDefaultToolbarCodec = mxCodecRegistry.register(function()\r\n{\r\n\tvar codec = new mxObjectCodec(new mxDefaultToolbar());\r\n\r\n\t/**\r\n\t * Function: encode\r\n\t *\r\n\t * Returns null.\r\n\t */\r\n\tcodec.encode = function(enc, obj)\r\n\t{\r\n\t\treturn null;\r\n\t};\r\n\t\r\n\t/**\r\n\t * Function: decode\r\n\t *\r\n\t * Reads a sequence of the following child nodes\r\n\t * and attributes:\r\n\t *\r\n\t * Child Nodes:\r\n\t *\r\n\t * add - Adds a new item to the toolbar. See below for attributes.\r\n\t * separator - Adds a vertical separator. No attributes.\r\n\t * hr - Adds a horizontal separator. No attributes.\r\n\t * br - Adds a linefeed. No attributes. \r\n\t *\r\n\t * Attributes:\r\n\t *\r\n\t * as - Resource key for the label.\r\n\t * action - Name of the action to execute in enclosing editor.\r\n\t * mode - Modename (see below).\r\n\t * template - Template name for cell insertion.\r\n\t * style - Optional style to override the template style.\r\n\t * icon - Icon (relative/absolute URL).\r\n\t * pressedIcon - Optional icon for pressed state (relative/absolute URL).\r\n\t * id - Optional ID to be used for the created DOM element.\r\n\t * toggle - Optional 0 or 1 to disable toggling of the element. Default is\r\n\t * 1 (true).\r\n\t *\r\n\t * The action, mode and template attributes are mutually exclusive. The\r\n\t * style can only be used with the template attribute. The add node may\r\n\t * contain another sequence of add nodes with as and action attributes\r\n\t * to create a combo box in the toolbar. If the icon is specified then\r\n\t * a list of the child node is expected to have its template attribute\r\n\t * set and the action is ignored instead.\r\n\t * \r\n\t * Nodes with a specified template may define a function to be used for\r\n\t * inserting the cloned template into the graph. Here is an example of such\r\n\t * a node:\r\n\t * \r\n\t * (code)\r\n\t * <add as=\"Swimlane\" template=\"swimlane\" icon=\"images/swimlane.gif\"><![CDATA[\r\n\t *   function (editor, cell, evt, targetCell)\r\n\t *   {\r\n\t *     var pt = mxUtils.convertPoint(\r\n\t *       editor.graph.container, mxEvent.getClientX(evt),\r\n\t *         mxEvent.getClientY(evt));\r\n\t *     return editor.addVertex(targetCell, cell, pt.x, pt.y);\r\n\t *   }\r\n\t * ]]></add>\r\n\t * (end)\r\n\t * \r\n\t * In the above function, editor is the enclosing <mxEditor> instance, cell\r\n\t * is the clone of the template, evt is the mouse event that represents the\r\n\t * drop and targetCell is the cell under the mousepointer where the drop\r\n\t * occurred. The targetCell is retrieved using <mxGraph.getCellAt>.\r\n\t *\r\n\t * Futhermore, nodes with the mode attribute may define a function to\r\n\t * be executed upon selection of the respective toolbar icon. In the\r\n\t * example below, the default edge style is set when this specific\r\n\t * connect-mode is activated:\r\n\t *\r\n\t * (code)\r\n\t * <add as=\"connect\" mode=\"connect\"><![CDATA[\r\n\t *   function (editor)\r\n\t *   {\r\n\t *     if (editor.defaultEdge != null)\r\n\t *     {\r\n\t *       editor.defaultEdge.style = 'straightEdge';\r\n\t *     }\r\n\t *   }\r\n\t * ]]></add>\r\n\t * (end)\r\n\t * \r\n\t * Both functions require <mxDefaultToolbarCodec.allowEval> to be set to true.\r\n\t *\r\n\t * Modes:\r\n\t *\r\n\t * select - Left mouse button used for rubberband- & cell-selection.\r\n\t * connect - Allows connecting vertices by inserting new edges.\r\n\t * pan - Disables selection and switches to panning on the left button.\r\n\t *\r\n\t * Example:\r\n\t *\r\n\t * To add items to the toolbar:\r\n\t * \r\n\t * (code)\r\n\t * <mxDefaultToolbar as=\"toolbar\">\r\n\t *   <add as=\"save\" action=\"save\" icon=\"images/save.gif\"/>\r\n\t *   <br/><hr/>\r\n\t *   <add as=\"select\" mode=\"select\" icon=\"images/select.gif\"/>\r\n\t *   <add as=\"connect\" mode=\"connect\" icon=\"images/connect.gif\"/>\r\n\t * </mxDefaultToolbar>\r\n\t * (end)\r\n\t */\r\n\tcodec.decode = function(dec, node, into)\r\n\t{\r\n\t\tif (into != null)\r\n\t\t{\r\n\t\t\tvar editor = into.editor;\r\n\t\t\tnode = node.firstChild;\r\n\t\t\t\r\n\t\t\twhile (node != null)\r\n\t\t\t{\r\n\t\t\t\tif (node.nodeType == mxConstants.NODETYPE_ELEMENT)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (!this.processInclude(dec, node, into))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tif (node.nodeName == 'separator')\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tinto.addSeparator();\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse if (node.nodeName == 'br')\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tinto.toolbar.addBreak();\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse if (node.nodeName == 'hr')\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tinto.toolbar.addLine();\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse if (node.nodeName == 'add')\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar as = node.getAttribute('as');\r\n\t\t\t\t\t\t\tas = mxResources.get(as) || as;\r\n\t\t\t\t\t\t\tvar icon = node.getAttribute('icon');\r\n\t\t\t\t\t\t\tvar pressedIcon = node.getAttribute('pressedIcon');\r\n\t\t\t\t\t\t\tvar action = node.getAttribute('action');\r\n\t\t\t\t\t\t\tvar mode = node.getAttribute('mode');\r\n\t\t\t\t\t\t\tvar template = node.getAttribute('template');\r\n\t\t\t\t\t\t\tvar toggle = node.getAttribute('toggle') != '0';\r\n\t\t\t\t\t\t\tvar text = mxUtils.getTextContent(node);\r\n\t\t\t\t\t\t\tvar elt = null;\r\n\r\n\t\t\t\t\t\t\tif (action != null)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\telt = into.addItem(as, icon, action, pressedIcon);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\telse if (mode != null)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tvar funct = (mxDefaultToolbarCodec.allowEval) ? mxUtils.eval(text) : null;\r\n\t\t\t\t\t\t\t\telt = into.addMode(as, icon, mode, pressedIcon, funct);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\telse if (template != null || (text != null && text.length > 0))\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tvar cell = editor.templates[template];\r\n\t\t\t\t\t\t\t\tvar style = node.getAttribute('style');\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\tif (cell != null && style != null)\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\tcell = editor.graph.cloneCell(cell);\r\n\t\t\t\t\t\t\t\t\tcell.setStyle(style);\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\tvar insertFunction = null;\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\tif (text != null && text.length > 0 && mxDefaultToolbarCodec.allowEval)\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\tinsertFunction = mxUtils.eval(text);\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\telt = into.addPrototype(as, icon, cell, pressedIcon, insertFunction, toggle);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tvar children = mxUtils.getChildNodes(node);\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\tif (children.length > 0)\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\tif (icon == null)\r\n\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\tvar combo = into.addActionCombo(as);\r\n\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\tfor (var i=0; i<children.length; i++)\r\n\t\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\t\tvar child = children[i];\r\n\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\tif (child.nodeName == 'separator')\r\n\t\t\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\t\t\tinto.addOption(combo, '---');\r\n\t\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t\t\telse if (child.nodeName == 'add')\r\n\t\t\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\t\t\tvar lab = child.getAttribute('as');\r\n\t\t\t\t\t\t\t\t\t\t\t\tvar act = child.getAttribute('action');\r\n\t\t\t\t\t\t\t\t\t\t\t\tinto.addActionOption(combo, lab, act);\r\n\t\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\tvar select = null;\r\n\t\t\t\t\t\t\t\t\t\tvar create = function()\r\n\t\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\t\tvar template = editor.templates[select.value];\r\n\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\tif (template != null)\r\n\t\t\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\t\t\tvar clone = template.clone();\r\n\t\t\t\t\t\t\t\t\t\t\t\tvar style = select.options[select.selectedIndex].cellStyle;\r\n\t\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\t\tif (style != null)\r\n\t\t\t\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\t\t\t\tclone.setStyle(style);\r\n\t\t\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\t\treturn clone;\r\n\t\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t\t\telse\r\n\t\t\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\t\t\tmxLog.warn('Template '+template+' not found');\r\n\t\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\treturn null;\r\n\t\t\t\t\t\t\t\t\t\t};\r\n\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\tvar img = into.addPrototype(as, icon, create, null, null, toggle);\r\n\t\t\t\t\t\t\t\t\t\tselect = into.addCombo();\r\n\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t// Selects the toolbar icon if a selection change\r\n\t\t\t\t\t\t\t\t\t\t// is made in the corresponding combobox.\r\n\t\t\t\t\t\t\t\t\t\tmxEvent.addListener(select, 'change', function()\r\n\t\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\t\tinto.toolbar.selectMode(img, function(evt)\r\n\t\t\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\t\t\tvar pt = mxUtils.convertPoint(editor.graph.container,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\tmxEvent.getClientX(evt), mxEvent.getClientY(evt));\r\n\t\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\t\treturn editor.addVertex(null, funct(), pt.x, pt.y);\r\n\t\t\t\t\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\tinto.toolbar.noReset = false;\r\n\t\t\t\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t// Adds the entries to the combobox\r\n\t\t\t\t\t\t\t\t\t\tfor (var i=0; i<children.length; i++)\r\n\t\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\t\tvar child = children[i];\r\n\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t\t\tif (child.nodeName == 'separator')\r\n\t\t\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\t\t\tinto.addOption(select, '---');\r\n\t\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t\t\telse if (child.nodeName == 'add')\r\n\t\t\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\t\t\tvar lab = child.getAttribute('as');\r\n\t\t\t\t\t\t\t\t\t\t\t\tvar tmp = child.getAttribute('template');\r\n\t\t\t\t\t\t\t\t\t\t\t\tvar option = into.addOption(select, lab, tmp || template);\r\n\t\t\t\t\t\t\t\t\t\t\t\toption.cellStyle = child.getAttribute('style');\r\n\t\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t// Assigns an ID to the created element to access it later.\r\n\t\t\t\t\t\t\tif (elt != null)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tvar id = node.getAttribute('id');\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\tif (id != null && id.length > 0)\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\telt.setAttribute('id', id);\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tnode = node.nextSibling;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn into;\r\n\t};\r\n\t\r\n\t// Returns the codec into the registry\r\n\treturn codec;\r\n\r\n}());\r\n\r\n/**\r\n * Variable: allowEval\r\n * \r\n * Static global switch that specifies if the use of eval is allowed for\r\n * evaluating text content. Default is true. Set this to false if stylesheets\r\n * may contain user input\r\n */\r\nmxDefaultToolbarCodec.allowEval = true;\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nmxCodecRegistry.register(function()\r\n{\r\n\t/**\r\n\t * Class: mxDefaultPopupMenuCodec\r\n\t *\r\n\t * Custom codec for configuring <mxDefaultPopupMenu>s. This class is created\r\n\t * and registered dynamically at load time and used implicitely via\r\n\t * <mxCodec> and the <mxCodecRegistry>. This codec only reads configuration\r\n\t * data for existing popup menus, it does not encode or create menus. Note\r\n\t * that this codec only passes the configuration node to the popup menu,\r\n\t * which uses the config to dynamically create menus. See\r\n\t * <mxDefaultPopupMenu.createMenu>.\r\n\t */\r\n\tvar codec = new mxObjectCodec(new mxDefaultPopupMenu());\r\n\r\n\t/**\r\n\t * Function: encode\r\n\t *\r\n\t * Returns null.\r\n\t */\r\n\tcodec.encode = function(enc, obj)\r\n\t{\r\n\t\treturn null;\r\n\t};\r\n\t\r\n\t/**\r\n\t * Function: decode\r\n\t *\r\n\t * Uses the given node as the config for <mxDefaultPopupMenu>.\r\n\t */\r\n\tcodec.decode = function(dec, node, into)\r\n\t{\r\n\t\tvar inc = node.getElementsByTagName('include')[0];\r\n\t\t\r\n\t\tif (inc != null)\r\n\t\t{\r\n\t\t\tthis.processInclude(dec, inc, into);\r\n\t\t}\r\n\t\telse if (into != null)\r\n\t\t{\r\n\t\t\tinto.config = node;\r\n\t\t}\r\n\t\t\r\n\t\treturn into;\r\n\t};\r\n\t\r\n\t// Returns the codec into the registry\r\n\treturn codec;\r\n\r\n}());\r\n/**\r\n * Copyright (c) 2006-2015, JGraph Ltd\r\n * Copyright (c) 2006-2015, Gaudenz Alder\r\n */\r\nmxCodecRegistry.register(function()\r\n{\r\n\t/**\r\n\t * Class: mxEditorCodec\r\n\t *\r\n\t * Codec for <mxEditor>s. This class is created and registered\r\n\t * dynamically at load time and used implicitely via <mxCodec>\r\n\t * and the <mxCodecRegistry>.\r\n\t *\r\n\t * Transient Fields:\r\n\t *\r\n\t * - modified\r\n\t * - lastSnapshot\r\n\t * - ignoredChanges\r\n\t * - undoManager\r\n\t * - graphContainer\r\n\t * - toolbarContainer\r\n\t */\r\n\tvar codec = new mxObjectCodec(new mxEditor(),\r\n\t\t['modified', 'lastSnapshot', 'ignoredChanges',\r\n\t\t'undoManager', 'graphContainer', 'toolbarContainer']);\r\n\r\n\t/**\r\n\t * Function: beforeDecode\r\n\t *\r\n\t * Decodes the ui-part of the configuration node by reading\r\n\t * a sequence of the following child nodes and attributes\r\n\t * and passes the control to the default decoding mechanism:\r\n\t *\r\n\t * Child Nodes:\r\n\t *\r\n\t * stylesheet - Adds a CSS stylesheet to the document.\r\n\t * resource - Adds the basename of a resource bundle.\r\n\t * add - Creates or configures a known UI element.\r\n\t *\r\n\t * These elements may appear in any order given that the\r\n\t * graph UI element is added before the toolbar element\r\n\t * (see Known Keys).\r\n\t *\r\n\t * Attributes:\r\n\t *\r\n\t * as - Key for the UI element (see below).\r\n\t * element - ID for the element in the document.\r\n\t * style - CSS style to be used for the element or window.\r\n\t * x - X coordinate for the new window.\r\n\t * y - Y coordinate for the new window.\r\n\t * width - Width for the new window.\r\n\t * height - Optional height for the new window.\r\n\t * name - Name of the stylesheet (absolute/relative URL).\r\n\t * basename - Basename of the resource bundle (see <mxResources>).\r\n\t *\r\n\t * The x, y, width and height attributes are used to create a new\r\n\t * <mxWindow> if the element attribute is not specified in an add\r\n\t * node. The name and basename are only used in the stylesheet and\r\n\t * resource nodes, respectively.\r\n\t *\r\n\t * Known Keys:\r\n\t *\r\n\t * graph - Main graph element (see <mxEditor.setGraphContainer>).\r\n\t * title - Title element (see <mxEditor.setTitleContainer>).\r\n\t * toolbar - Toolbar element (see <mxEditor.setToolbarContainer>).\r\n\t * status - Status bar element (see <mxEditor.setStatusContainer>).\r\n\t *\r\n\t * Example:\r\n\t *\r\n\t * (code)\r\n\t * <ui>\r\n\t *   <stylesheet name=\"css/process.css\"/>\r\n\t *   <resource basename=\"resources/app\"/>\r\n\t *   <add as=\"graph\" element=\"graph\"\r\n\t *     style=\"left:70px;right:20px;top:20px;bottom:40px\"/>\r\n\t *   <add as=\"status\" element=\"status\"/>\r\n\t *   <add as=\"toolbar\" x=\"10\" y=\"20\" width=\"54\"/>\r\n\t * </ui>\r\n\t * (end)\r\n\t */\r\n\tcodec.afterDecode = function(dec, node, obj)\r\n\t{\r\n\t\t// Assigns the specified templates for edges\r\n\t\tvar defaultEdge = node.getAttribute('defaultEdge');\r\n\t\t\r\n\t\tif (defaultEdge != null)\r\n\t\t{\r\n\t\t\tnode.removeAttribute('defaultEdge');\r\n\t\t\tobj.defaultEdge = obj.templates[defaultEdge];\r\n\t\t}\r\n\r\n\t\t// Assigns the specified templates for groups\r\n\t\tvar defaultGroup = node.getAttribute('defaultGroup');\r\n\t\t\r\n\t\tif (defaultGroup != null)\r\n\t\t{\r\n\t\t\tnode.removeAttribute('defaultGroup');\r\n\t\t\tobj.defaultGroup = obj.templates[defaultGroup];\r\n\t\t}\r\n\r\n\t\treturn obj;\r\n\t};\r\n\t\r\n\t/**\r\n\t * Function: decodeChild\r\n\t * \r\n\t * Overrides decode child to handle special child nodes.\r\n\t */\t\r\n\tcodec.decodeChild = function(dec, child, obj)\r\n\t{\r\n\t\tif (child.nodeName == 'Array')\r\n\t\t{\r\n\t\t\tvar role = child.getAttribute('as');\r\n\t\t\t\r\n\t\t\tif (role == 'templates')\r\n\t\t\t{\r\n\t\t\t\tthis.decodeTemplates(dec, child, obj);\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (child.nodeName == 'ui')\r\n\t\t{\r\n\t\t\tthis.decodeUi(dec, child, obj);\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tmxObjectCodec.prototype.decodeChild.apply(this, arguments);\r\n\t};\r\n\t\t\r\n\t/**\r\n\t * Function: decodeTemplates\r\n\t *\r\n\t * Decodes the cells from the given node as templates.\r\n\t */\r\n\tcodec.decodeUi = function(dec, node, editor)\r\n\t{\r\n\t\tvar tmp = node.firstChild;\r\n\t\twhile (tmp != null)\r\n\t\t{\r\n\t\t\tif (tmp.nodeName == 'add')\r\n\t\t\t{\r\n\t\t\t\tvar as = tmp.getAttribute('as');\r\n\t\t\t\tvar elt = tmp.getAttribute('element');\r\n\t\t\t\tvar style = tmp.getAttribute('style');\r\n\t\t\t\tvar element = null;\r\n\r\n\t\t\t\tif (elt != null)\r\n\t\t\t\t{\r\n\t\t\t\t\telement = document.getElementById(elt);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (element != null && style != null)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\telement.style.cssText += ';' + style;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tvar x = parseInt(tmp.getAttribute('x'));\r\n\t\t\t\t\tvar y = parseInt(tmp.getAttribute('y'));\r\n\t\t\t\t\tvar width = tmp.getAttribute('width');\r\n\t\t\t\t\tvar height = tmp.getAttribute('height');\r\n\r\n\t\t\t\t\t// Creates a new window around the element\r\n\t\t\t\t\telement = document.createElement('div');\r\n\t\t\t\t\telement.style.cssText = style;\r\n\t\t\t\t\t\r\n\t\t\t\t\tvar wnd = new mxWindow(mxResources.get(as) || as,\r\n\t\t\t\t\t\telement, x, y, width, height, false, true);\r\n\t\t\t\t\twnd.setVisible(true);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// TODO: Make more generic\r\n\t\t\t\tif (as == 'graph')\r\n\t\t\t\t{\r\n\t\t\t\t\teditor.setGraphContainer(element);\r\n\t\t\t\t}\r\n\t\t\t\telse if (as == 'toolbar')\r\n\t\t\t\t{\r\n\t\t\t\t\teditor.setToolbarContainer(element);\r\n\t\t\t\t}\r\n\t\t\t\telse if (as == 'title')\r\n\t\t\t\t{\r\n\t\t\t\t\teditor.setTitleContainer(element);\r\n\t\t\t\t}\r\n\t\t\t\telse if (as == 'status')\r\n\t\t\t\t{\r\n\t\t\t\t\teditor.setStatusContainer(element);\r\n\t\t\t\t}\r\n\t\t\t\telse if (as == 'map')\r\n\t\t\t\t{\r\n\t\t\t\t\teditor.setMapContainer(element);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if (tmp.nodeName == 'resource')\r\n\t\t\t{\r\n\t\t\t\tmxResources.add(tmp.getAttribute('basename'));\r\n\t\t\t}\r\n\t\t\telse if (tmp.nodeName == 'stylesheet')\r\n\t\t\t{\r\n\t\t\t\tmxClient.link('stylesheet', tmp.getAttribute('name'));\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\ttmp = tmp.nextSibling;\r\n\t\t}\t\r\n\t};\r\n\t\r\n\t/**\r\n\t * Function: decodeTemplates\r\n\t *\r\n\t * Decodes the cells from the given node as templates.\r\n\t */\r\n\tcodec.decodeTemplates = function(dec, node, editor)\r\n\t{\r\n\t\tif (editor.templates == null)\r\n\t\t{\r\n\t\t\teditor.templates = [];\r\n\t\t}\r\n\t\t\r\n\t\tvar children = mxUtils.getChildNodes(node);\r\n\t\tfor (var j=0; j<children.length; j++)\r\n\t\t{\r\n\t\t\tvar name = children[j].getAttribute('as');\r\n\t\t\tvar child = children[j].firstChild;\r\n\t\t\t\r\n\t\t\twhile (child != null && child.nodeType != 1)\r\n\t\t\t{\r\n\t\t\t\tchild = child.nextSibling;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (child != null)\r\n\t\t\t{\r\n\t\t\t\t// LATER: Only single cells means you need\r\n\t\t\t\t// to group multiple cells within another\r\n\t\t\t\t// cell. This should be changed to support\r\n\t\t\t\t// arrays of cells, or the wrapper must\r\n\t\t\t\t// be automatically handled in this class.\r\n\t\t\t\teditor.templates[name] = dec.decodeCell(child);\r\n\t\t\t}\r\n\t\t}\r\n\t};\r\n\t\r\n\t// Returns the codec into the registry\r\n\treturn codec;\r\n\r\n}());"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/mxgraph/resources/editor.txt",
    "content": "askZoom=Enter zoom (%)\nproperties=Properties\noutline=Outline\ntasks=Tasks\nhelp=Help\n"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/mxgraph/resources/editor_de.txt",
    "content": "askZoom=Zoom eingeben (%)\nproperties=Eigenschaften\noutline=Uebersicht\ntasks=Aufgaben\nhelp=Hilfe\n"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/mxgraph/resources/editor_zh.txt",
    "content": "askZoom=进入缩放(%25)\nproperties=属性\noutline=轮廓\ntasks=任务\nhelp=帮助"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/mxgraph/resources/graph.txt",
    "content": "alreadyConnected=Nodes already connected\ncontainsValidationErrors=Contains validation errors\nupdatingDocument=Updating Document. Please wait...\nupdatingSelection=Updating Selection. Please wait...\ncollapse-expand=Collapse/Expand\ndoubleClickOrientation=Doubleclick to change orientation\nclose=Close\nerror=Error\ndone=Done\ncancel=Cancel\nok=OK\n"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/mxgraph/resources/graph_de.txt",
    "content": "alreadyConnected=Knoten schon verbunden\ncontainsValidationErrors=Enthält Validierungsfehler\nupdatingDocument=Aktualisiere Dokument. Bitte warten...\nupdatingSelection=Aktualisiere Markierung. Bitte warten...\ncollapse-expand=Einklappen/Ausklappen\ndoubleClickOrientation=Doppelklicken um Orientierung zu ändern\nclose=Schliessen\nerror=Fehler\ndone=Fertig\ncancel=Abbrechen\nok=OK\n"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/mxgraph/resources/graph_zh.txt",
    "content": "alreadyConnected=节点已经连接\ncontainsValidationErrors=包含效验错误\nupdatingDocument=更新文档。请等候......\nupdatingSelection=更新所选项。请等候......\ncollapse-expand=折叠/展开\ndoubleClickOrientation=双击以改变方向\nclose=关闭\nerror=错误\ndone=完成\ncancel=取消\nok=确定"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/js/spider-editor.js",
    "content": "function JsonProperty(object){\r\n\tthis.object = object || {};\r\n}\r\nJsonProperty.prototype.reset = function(object){\r\n\treturn this.object = object;\r\n}\r\nJsonProperty.prototype.set = function(key,value){\r\n\treturn this.object[key] = value;\r\n}\r\nJsonProperty.prototype.get = function(key){\r\n\treturn this.object[key]\r\n}\r\nfunction SpiderEditor(options){\r\n\toptions = options || {};\r\n\tvar emptyFunction = function(){}\r\n\tthis.selectedCellListener = options.selectedCellListener || emptyFunction;\r\n\tif(mxClient.isBrowserSupported()){\r\n\t\tthis.editor = new mxEditor();\r\n\t\tthis.editor.setGraphContainer(options.element);\r\n\t\tthis.graph = this.editor.graph;\r\n\t\tthis.graph.setConnectable(true);\r\n\t\tthis.graph.setMultigraph(false);\t//禁止重复连接\r\n\t\tthis.graph.setAllowLoops(true);\t\t//允许自己连自己\r\n\t\tthis.graph.isHtmlLabel = function(cell){\r\n\t\t\treturn !this.isSwimlane(cell);\r\n\t\t}\r\n\t\tmxConstants.MIN_HOTSPOT_SIZE = 16;\r\n\t\tmxGraphHandler.prototype.guidesEnabled = true\r\n\t\t//注册json编码器\r\n\t\tthis.registerJsonCodec();\r\n\t\t//配置样式\r\n\t\tthis.configureStylesheet();\r\n\t\tvar _this = this;\r\n\t\tvar pasteCount = 0;\r\n\t\tmxEvent.addListener(options.element,'paste',function(e){\r\n\t\t\tvar pasteText = e.clipboardData.getData(\"Text\");\r\n\t\t\tvar doc = mxUtils.parseXml(pasteText);\r\n\t\t\tif(doc){\r\n\t\t\t\tvar root = doc.documentElement;\r\n\t\t\t\tvar dec = new mxCodec(root.ownerDocument);\r\n\t\t\t\tvar cells = dec.decode(root);\r\n\t\t\t\tif(cells&&cells.length > 0){\r\n\t\t\t\t\tpasteCount++;\r\n\t\t\t\t\t_this.graph.setSelectionCells(_this.graph.importCells(cells, pasteCount * 10, pasteCount * 10, _this.graph.getDefaultParent()));\r\n\t\t\t\t}else{\r\n\t\t\t\t\t_this.execute('paste');\r\n\t\t\t\t}\r\n\t\t\t}else{\r\n\t\t\t\t_this.execute('paste');\r\n\t\t\t}\r\n\t\t});\r\n\t\tthis.keyHandler = new mxKeyHandler(this.graph);\r\n\t\tthis.keyHandler.getFunction = function(evt) {\r\n\t\t\tif (evt != null)\r\n\t\t\t{\r\n\t\t\t\treturn (mxEvent.isControlDown(evt) || (mxClient.IS_MAC && evt.metaKey)) ? this.controlKeys[evt.keyCode] : this.normalKeys[evt.keyCode];\r\n\t\t\t}\r\n\t\t\treturn null;\r\n\t\t}\r\n\t\tthis.bindKeyAction();\r\n\t\tvar _this = this;\r\n\t\t//选择节点事件\r\n\t\tthis.graph.getSelectionModel().addListener(mxEvent.CHANGE,function(sender,evt){\r\n\t\t\t_this.onSelectedCell(sender,evt);\r\n\t\t});\r\n\t}\r\n}\r\nSpiderEditor.prototype.bindKeyAction = function(){\r\n\tvar _this = this;\r\n\tthis.keyHandler.bindKey(46,function(){\t//按Delete\r\n\t\t_this.deleteSelectCells();\r\n\t});\r\n\tthis.keyHandler.bindControlKey(90,function(){\t//Ctrl+Z\r\n\t\t_this.execute('undo');\r\n\t})\r\n\tthis.keyHandler.bindControlKey(89,function(){\t//Ctrl+Y\r\n\t\t_this.execute('redo');\r\n\t})\r\n\tthis.keyHandler.bindControlKey(88,function(){ // Ctrl+X\r\n\t\t_this.execute('cut');\r\n\t});\r\n\tthis.keyHandler.bindControlKey(67, function(){\t// Ctrl+C\r\n\t\t_this.executeCopy();\r\n\t});\r\n\tthis.keyHandler.bindControlKey(65,function(){\t// Ctrl+A\r\n\t\teditor.execute('selectAll');\r\n\t});\r\n}\r\nSpiderEditor.prototype.configureStylesheet = function(){\r\n\tvar style = new Object();\r\n\tstyle = this.graph.getStylesheet().getDefaultEdgeStyle();\r\n\tstyle[mxConstants.STYLE_EDGE] = mxConstants.EDGESTYLE_ORTHOGONAL;\r\n\tstyle[mxConstants.STYLE_STROKECOLOR] = 'black';\r\n\tstyle[mxConstants.STYLE_STROKEWIDTH] = '2';\r\n\tstyle = this.graph.getStylesheet().getDefaultVertexStyle();\r\n\tstyle[mxConstants.STYLE_STROKECOLOR] = 'black';\r\n\tstyle[mxConstants.STYLE_FONTSIZE] = 12;\r\n\tthis.graph.alternateEdgeStyle = 'elbow=vertical';\r\n}\r\nSpiderEditor.prototype.deleteSelectCells = function(){\r\n\tthis.graph.escape();\r\n\tvar selectCells = this.graph.getDeletableCells(this.graph.getSelectionCells());\r\n\tif (selectCells != null && selectCells.length > 0){\r\n\t\tvar cells = [];\r\n\t\tfor(var i =0,len = selectCells.length;i<len;i++){\r\n\t\t\tvar cell = selectCells[i];\r\n\t\t\tif((!cell.isVertex())||(cell.data&&cell.data.get('shape') != 'start')){\r\n\t\t\t\tcells.push(cell);\r\n\t\t\t}\r\n\t\t}\r\n\t\tif(cells.length == 0){\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tvar parents = (this.graph.selectParentAfterDelete) ? this.graph.model.getParents(cells) : null;\r\n\t\tthis.graph.removeCells(cells, true);\r\n\t\tif (parents != null){\r\n\t\t\tvar select = [];\r\n\t\t\tfor (var i = 0; i < parents.length; i++){\r\n\t\t\t\tif (this.graph.model.contains(parents[i]) &&\r\n\t\t\t\t\t(this.graph.model.isVertex(parents[i]) ||\r\n\t\t\t\t\tthis.graph.model.isEdge(parents[i]))){\r\n\t\t\t\t\tselect.push(parents[i]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tthis.graph.setSelectionCells(select);\r\n\t\t}\r\n\t}\r\n}\r\nSpiderEditor.prototype.addShape = function(shape,label,element,defaultAdd){\r\n\tvar style = new Object();\r\n\tif(shape == 'comment'){\r\n\t\tstyle[mxConstants.STYLE_FILLCOLOR] = 'none';\r\n\t\tstyle[mxConstants.STYLE_STROKECOLOR] = 'none';\r\n\t}else{\r\n\t\tstyle[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_IMAGE;\r\n\t\tstyle[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_TOP;\r\n\t\tstyle[mxConstants.STYLE_IMAGE_WIDTH] = 32;\r\n\t\tstyle[mxConstants.STYLE_IMAGE_HEIGHT] = 32;\r\n\t\tstyle[mxConstants.STYLE_IMAGE] = element.src;\r\n\t\tstyle[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;\r\n\t\tstyle[mxConstants.STYLE_SPACING_TOP] = -26;\r\n\t}\r\n\tstyle[mxConstants.STYLE_FONTCOLOR] = '#000000';\r\n\tthis.graph.getStylesheet().putCellStyle(shape,style);\r\n\tvar _this = this;\r\n\tvar funct = function(graph, evt, cell, x, y){\r\n\t\tvar parent = _this.graph.getDefaultParent();\r\n\t\tvar model = _this.graph.getModel();\r\n\t\tmodel.beginUpdate();\r\n\t\tvar cell;\r\n\t\ttry{\r\n\t\t\tcell = _this.graph.insertVertex(parent, null, label, x, y, 32, 32,shape);\r\n\t\t\tcell.data = new JsonProperty();\r\n\t\t\tcell.data.set('shape',shape);\r\n\t\t}finally{\r\n\t\t\tmodel.endUpdate();\r\n\t\t}\r\n\t\t_this.graph.setSelectionCell(cell);\r\n\t}\r\n\tvar dragElt = document.createElement('div');\r\n\tdragElt.style.border = 'dashed black 1px';\r\n\tdragElt.style.width = '32px';\r\n\tdragElt.style.height = '32px';\r\n\tvar ds = mxUtils.makeDraggable(element, this.graph, funct, dragElt, 0, 0, true, true);\r\n\tds.setGuidesEnabled(true);\r\n\tif(defaultAdd){\r\n\t\tvar parent = this.graph.getDefaultParent();\r\n\t\tvar model = this.graph.getModel();\r\n\t\tmodel.beginUpdate();\r\n\t\ttry{\r\n\t\t\tcell = this.graph.insertVertex(parent, null, label, 80, 80, 32, 32,shape);\r\n\t\t\tcell.data = new JsonProperty();\r\n\t\t\tcell.data.set('shape',shape);\r\n\t\t}finally{\r\n\t\t\tmodel.endUpdate();\r\n\t\t}\r\n\t}\r\n}\r\nSpiderEditor.prototype.registerJsonCodec = function(){\r\n\tvar codec = new mxObjectCodec(new JsonProperty());\r\n\tcodec.encode = function(enc,obj){\r\n\t\tvar node = enc.document.createElement('JsonProperty');\r\n\t\tmxUtils.setTextContent(node, JSON.stringify(obj.object));\r\n\t\treturn node;\r\n\t}\r\n\tcodec.decode = function(dec, node, into){\r\n\t\treturn new JsonProperty(JSON.parse(mxUtils.getTextContent(node)));\r\n\t}\r\n\tmxCodecRegistry.register(codec);\r\n}\r\nSpiderEditor.prototype.onSelectedCell = function(sender,evt){\r\n\tthis.selectedCellListener(this.getSelectedCell());\r\n}\r\nSpiderEditor.prototype.getModel = function(){\r\n\treturn this.graph.getModel();\r\n}\r\nSpiderEditor.prototype.executeCopy = function(){\r\n\tthis.editor.execute('copy');\r\n\tvar cells = this.graph.getSelectionCells();\r\n\tif(cells && cells.length > 0){\r\n\t\tvar copyText = document.createElement('textarea');\r\n\t\tcopyText.style = 'position:absolute;left:-99999999px';\r\n\t\tdocument.body.appendChild(copyText);\r\n\t\tcopyText.innerHTML = mxUtils.getPrettyXml(new mxCodec().encode(cells));\r\n\t\tcopyText.readOnly = false;\r\n\t\tcopyText.select();\r\n\t\tcopyText.setSelectionRange(0, copyText.value.length);\r\n\t\tdocument.execCommand(\"copy\");\r\n\t\tdocument.body.removeChild(copyText);\r\n\t}\r\n}\r\nSpiderEditor.prototype.execute = function(action){\r\n\tif('copy' == action){\r\n\t\tthis.executeCopy();\r\n\t}else{\r\n\t\tthis.editor.execute(action);\r\n\t}\r\n}\r\nSpiderEditor.prototype.getSelectedCell = function(){\r\n\tvar cell = this.graph.getSelectionCell() || this.graph.getModel().getRoot();\r\n\tcell.data = cell.data || new JsonProperty();\r\n\treturn cell;\r\n}\r\nSpiderEditor.prototype.getXML = function(){\r\n\treturn mxUtils.getPrettyXml(new mxCodec(mxUtils.createXmlDocument()).encode(this.graph.getModel()));\r\n}\r\nSpiderEditor.prototype.selectCell = function(cell){\r\n\tthis.graph.setSelectionCell(cell);\r\n}\r\nSpiderEditor.prototype.valid = function(){\r\n\tvar cells = editor.graph.getModel().cells;\r\n\tfor(var key in cells){\r\n\t\tvar cell = cells[key];\r\n\t\tif(cell&&cell.edge){\r\n\t\t\tif(cell.source == null || cell.target == null){\r\n\t\t\t\treturn cell;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\treturn null;\r\n}\r\nSpiderEditor.prototype.setXML = function(xml){\r\n\tvar doc = mxUtils.parseXml(xml);\r\n\tvar root = doc.documentElement;\r\n\tvar dec = new mxCodec(root.ownerDocument);\r\n\tdec.decode(root,this.getModel());\r\n\tthis.selectedCellListener(this.getSelectedCell());\r\n}\r\nSpiderEditor.prototype.importFromUrl = function(url){\r\n\tvar req = mxUtils.load(url);\r\n\tvar root = req.getDocumentElement();\r\n\tvar dec = new mxCodec(root.ownerDocument);\r\n\tdec.decode(root, this.getModel());\r\n}"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/log.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n\t<head>\r\n\t\t<meta charset=\"UTF-8\">\r\n\t\t<title>SpiderFlow</title>\r\n\t\t<script type=\"text/javascript\" src=\"js/layui/layui.all.js\" ></script>\r\n\t\t<script type=\"text/javascript\" src=\"js/common.js\" ></script>\r\n\t\t<script>$ = layui.$;</script>\r\n\t\t<script type=\"text/javascript\" src=\"js/log-viewer.js\" ></script>\r\n\t\t<style type=\"text/css\">\r\n\t\t\t*{\r\n\t\t\t\tmargin:0;\r\n\t\t\t\tpadding:0;\r\n\t\t\t}\r\n\t\t\thtml,body{\r\n\t\t\t\twidth : 100%;\r\n\t\t\t\theight : 100%;\r\n\t\t\t\toverflow: hidden;\r\n\t\t\t}\r\n\t\t\thtml,body{\r\n\t\t\t\toverflow: hidden;\r\n\t\t\t}\r\n\t\t\t.log-container{\r\n\t\t\t\twidth : 100%;\r\n\t\t\t\tposition: absolute;\r\n                top : 0px;\r\n                bottom : 40px;\r\n\t\t\t\toverflow: auto;\r\n\t\t\t\tbackground-color: #000;\r\n\t\t\t}\r\n            .toolbox-container{\r\n                width : 100%;\r\n                position: absolute;\r\n                height : 24px;\r\n                padding:8px 0;\r\n                bottom : 0px;\r\n                background: #3c3f41;\r\n            }\r\n            .toolbox-container .input-text{\r\n                outline: 0;\r\n                height: 24px;\r\n                line-height: 24px;\r\n                margin-left: 7px;\r\n                background: #45494a;\r\n                border: 1px solid #646464;\r\n                width: 300px;\r\n                color: #ddd;\r\n                padding-left: 5px;\r\n                font-size: 14px;\r\n                float : left;\r\n                margin-right: 20px;\r\n            }\r\n            .toolbox-container .input-text.search-finish{\r\n                background: #743a3a;\r\n            }\r\n            .toolbox-container .input-checkbox{\r\n                visibility: hidden;\r\n            }\r\n            .toolbox-container .input-checkbox +label{\r\n                color : #c9c9c9;\r\n                float : left;\r\n                font-size:12px;\r\n                height:24px;\r\n                line-height: 24px;\r\n                margin-left: 25px;\r\n                margin-right:5px;\r\n                user-select: none;\r\n            }\r\n            .toolbox-container .input-checkbox +label::before{\r\n                display: inline-block;\r\n                background: #43494a;\r\n                border:1px solid #6b6b6b;\r\n                content : '';\r\n                width : 16px;\r\n                height : 16px;\r\n                line-height: 16px;\r\n                position : absolute;\r\n                top : 12px;\r\n                margin-left:-22px;\r\n            }\r\n            .toolbox-container .input-checkbox:checked +label::before{\r\n                display : inline-block;\r\n                content : \"\\2714\";\r\n                text-align: center;\r\n                font-size:12px;\r\n                color:#fff;\r\n            }\r\n            .toolbox-container .btn{\r\n                display: inline-block;\r\n                width : 24px;\r\n                height : 24px;\r\n                border-radius: 2px;\r\n                background-repeat: no-repeat;\r\n                background-position: center center;\r\n                background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAoklEQVQ4T63SwQnCQBCF4fdIN9qEZxEMgqnAGqzDDgSPCZgYgwcvVmGKEeLIGFZwHEWW7HX34x+WISIPIx2GhVV1Ggm7HYgb78kqTaetneyjqAjscgHG+pjAFZJkFr9Bi0LFwy9okYBZX5TcKz+hhxbzWaF3+0Oz9DB/oTCqh1nWxwsEE32k44WS/UWLWdbNVqEA62/IlDcgz8MuwD9rGF18AERoXOuD03ayAAAAAElFTkSuQmCC\");\r\n                float : left;\r\n                cursor: pointer;\r\n            }\r\n            .btn.btn-download{\r\n                width : 48px;\r\n                backgrond:none;\r\n                font-size:12px;\r\n                line-height: 24px;\r\n                color : #ddd;\r\n                background-image: none;\r\n                padding : 0 5px;\r\n                margin-left : 5px;\r\n                background-color: #4c5052;\r\n            }\r\n            .btn.btn-prev{\r\n                background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAoklEQVQ4T63SwQnCQBCF4fdIN9qEZxEMgqnAGqzDDgSPCZgYgwcvVmGKEeLIGFZwHEWW7HX34x+WISIPIx2GhVV1Ggm7HYgb78kqTaetneyjqAjscgHG+pjAFZJkFr9Bi0LFwy9okYBZX5TcKz+hhxbzWaF3+0Oz9DB/oTCqh1nWxwsEE32k44WS/UWLWdbNVqEA62/IlDcgz8MuwD9rGF18AERoXOuD03ayAAAAAElFTkSuQmCC\");\r\n            }\r\n            .btn.btn-next{\r\n                background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAqElEQVQ4T73SzQ2CQBAF4PdCN9iEZ2MiMZEKrME67MDEIySASDx4sQotxgTHjLBIcOCwB/e6+2Xf/BCeh54O/jAvqwMEcwF269UynUqQnaoNIXuQV+bl+aZQgYDxGG5Rou8I3FkUlxCsEwFmY3iIIEH8qXEKWyiKFo+uORZuYkkXT39S1Mb9tmOI3Y3W1Ec/0IptIRM6LKyPIJ58BVsXrz8q/wX4+8q9AR2wXOs7tERxAAAAAElFTkSuQmCC\");\r\n            }\r\n            .btn.btn-page-prev{\r\n                background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAABIElEQVQ4T92SP0sDQRDF31vSieA3sLMQrLQVvE7kktsVIljYWAgGsbDQwtZGGwsRNKUgQoTs3h9t4xew1MLSL6EW3kjucppcEu3danfe/N4Ms0OMOM65OajKogAfU5MT157nvZfTWA60w3CZoi5ATHc1QjqQdEdr/dyfOwC2w2STkEsAlZLhqwIbQeDfFfFv0EXJoYgcZYLgBcTM0D2VXWNqZ3knAKyLzkE2cje5QcomFDrZM4UHJVsA1zOAPNU1f482jFoA670WDkxQPbE2XuoHjak+2DDeB3DcM2/SRvEVBBsCrq0G/m3WwQiwG2+HSZ2QlgK2h6b6Gzh2qoUwruJ/Ap27nxV+PuX/yAVj/Mc/d/VnQMk8qd60XhnY0UL/AhYefjeyfy3+AAAAAElFTkSuQmCC\");\r\n            }\r\n            .btn.btn-page-next{\r\n                background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAABGklEQVQ4T+VSsUoDQRSctwGxzCcICnZWfoBiIcjldlc/QQhWgihYGlsVC0VQmxSWajbvPIhoEX/AQgQ7wX+w9Ngn2VM4k80X+Kp9szMLOzOEMcN8PyPiJ4xZeYtRKAYy9+Y8ipdw52ne2uR5mBcVOne3AIV+KcSitY2nfyl0nG8DcqRAWuskG5gwzhzmPPUQBuSaOt3smIi2goHe769Z3YoJbx23lFJ7pbtyGeLoZPkhiewECHDkcVKNQxQ2CbBBQ3Rq02Swl+M4bwJyUW70Dsj06Bm7VjcOAqMa7E03X67Bn4No6m/g9AXy6zZNr37xkeYw92Y9FWcQLP3850MgzVWtH6uPRSvXbvcn6/XPDShVIykejDGvw5X7BuachnZstBghAAAAAElFTkSuQmCC\");\r\n            }\r\n            .btn.btn-page-home{\r\n                background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAA/klEQVQ4T9WRsUoDURBF7107O/0Av8DCP1CwsNnNMinW3kJELAzYpom9WKXTSmxEXzazlmJrY+cX+A2iguSN7BPDZvMghZVTDMPMPXNhhgDgxvoIw1ZdLwxCpZN1+CdwoUtEEBzboarLE2PPzFYkz05imjnQufsNLPlzGDYDQF58fb4fF0Xx1lwwA96OdDdJOITZalNksCf6yZGIPP/2p2Cp1cB76/8MeAjYsFV/ANyXPL0KijrdjcZnJHsAXuGxJ5I9uFKtnkme0TndRoJLAGsGFt08vQlgWeqBJ3YsYb+bpi/hRQ0wLK+qdXo7NeP1FIxdrQ22NdF3xBz/MfgNtRhyDx//lbEAAAAASUVORK5CYII=\");\r\n            }\r\n            .btn.btn-page-end{\r\n                background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAA/0lEQVQ4T91SsU4CQRSct8QEjd/AF9DR0BJL73EsxoZYSUdl7LWhNcHCGGNBzxVsjruGggT/wMIv4AOws/PGcKgceqeJpVtt3pvZ93ZmBAXHhRFXLeur5EFyiyvgfyOOw/hYhB0auWh73lPeH8dxXJWEfUNMfV/vUnFSIhgAWCDBqbU6y4rjXHQAgyGACslBu9U8/1TVhfEJwHsAu4D0AN6ubdjcjZG+r95lWs165Jyr0ZRuBFLf8k5kmSTsHbV09FH/5mMQBPs75b1rkN31QDzg1ZxZe/iYfeynAFyJyHNJOFDVl6/pKSQWRXFrVTeJJiD0N/D76nPb1EY68S/EN0z7aw9vab4+AAAAAElFTkSuQmCC\");\r\n            }\r\n            .toolbox-container .btn:hover{\r\n                background-color: #4c5052;\r\n            }\r\n            ::-webkit-input-placeholder {\r\n                color: #eee;\r\n            }\r\n\t\t\t.log-row{\r\n\t\t\t\tfont-family: Consolas,serif;\r\n\t\t\t\tfont-size: 12px;\r\n\t\t\t\tline-height: 14px;\r\n\t\t\t\theight : 14px;\r\n\t\t\t\twhite-space: pre;\r\n\t\t\t\tcolor: #e5e5e5;\r\n\t\t\t\tbackground: #000;\r\n\t\t\t}\r\n\t\t\t.log-row::selection{\r\n\t\t\t\tbackground: rgba(255,255,255,.998);\r\n\t\t\t\tcolor : #000;\r\n\t\t\t}\r\n            .log-row em.search-finded{\r\n                font-style: inherit;\r\n                background :#ff0 !important;\r\n                color : #000;\r\n            }\r\n            ::-webkit-scrollbar{\r\n                width : 8px;\r\n                height : 8px;\r\n                background: transparent;\r\n            }\r\n            ::-webkit-scrollbar-track{\r\n                border-radius: 2px;\r\n            }\r\n            ::-webkit-scrollbar-thumb{\r\n                border-radius: 2px;\r\n                background: #999;\r\n            }\r\n\t\t</style>\r\n\t</head>\r\n\t<div class=\"log-container\"></div>\r\n    <div class=\"toolbox-container\">\r\n        <span class=\"btn btn-download\" title=\"下载日志\">下载日志</span>\r\n        <input type=\"text\" id=\"keywords\" placeholder=\"请输入关键词搜索定位日志\" class=\"input-text\"/>\r\n        <input type=\"checkbox\" id=\"reversed\" checked class=\"input-checkbox\"/>\r\n        <label for=\"reversed\">反向搜索</label>\r\n        <input type=\"checkbox\" id=\"matchcase\" checked class=\"input-checkbox\"/>\r\n        <label for=\"matchcase\">区分大小写</label>\r\n        <input type=\"checkbox\" id=\"regx\" class=\"input-checkbox\"/>\r\n        <label for=\"regx\">正则搜索</label>\r\n        <span class=\"btn btn-prev\" title=\"上一个/上一行(↑)\"></span>\r\n        <span class=\"btn btn-next\" title=\"下一个/下一行(↓)\"></span>\r\n        <span class=\"btn btn-page-prev\" title=\"上一页(Page Up)\"></span>\r\n        <span class=\"btn btn-page-next\" title=\"下一页(Page Down)\"></span>\r\n        <span class=\"btn btn-page-home\" title=\"第一页/首页(Home)\"></span>\r\n        <span class=\"btn btn-page-end\" title=\"最后一页/尾页(End)\"></span>\r\n    </div>\r\n\t<script type=\"text/javascript\">\r\n\t\t$(function(){\r\n\t\t    var logId = getQueryString('id');\r\n            var taskId = getQueryString('taskId');\r\n\t\t\tvar viewer = new LogViewer({\r\n\t\t\t\turl: 'spider/log',\r\n\t\t\t\tmaxLines : parseInt(($('.log-container').height() - 8) / 14),\r\n\t\t\t\tlogId : logId,\r\n                taskId: taskId,\r\n\t\t\t\telement : $('.log-container'),\r\n                onSearchFinish : function(hasData){\r\n\t\t\t\t    if(hasData){\r\n                        $('.input-text').removeClass('search-finish');\r\n                    }else{\r\n                        $('.input-text').addClass('search-finish').focus();\r\n                    }\r\n                },\r\n                onLoad : function(hasData){\r\n                    if(!hasData){\r\n                        layui.layer.alert('暂无日志数据');\r\n                    }\r\n                }\r\n\t\t\t});\r\n            var setOptions = function(){\r\n                viewer.setOptions('keywords',$('.toolbox-container .input-text').val());\r\n                viewer.setOptions('matchcase',$('#matchcase').is(':checked'));\r\n                viewer.setOptions('regx',$('#regx').is(':checked'));\r\n                viewer.setOptions('reversed',$('#reversed').is(':checked'));\r\n            }\r\n            var time;\r\n            $('.toolbox-container').on('keydown','.input-text',function(e){\r\n                setOptions();\r\n                if(e.keyCode === 13){\r\n                    viewer.search();\r\n                }\r\n                if(this.value === ''){\r\n                    $(this).removeClass('search-finish');\r\n                }\r\n            }).on('change','.input-checkbox',function(){\r\n                setOptions();\r\n            }).on('click','.btn-prev',function(){\r\n                if(viewer.keywords){\r\n                    viewer.search(true);\r\n                }else{\r\n                    viewer.scroll(true,1);\r\n                }\r\n            }).on('click','.btn-next',function(){\r\n                if(viewer.keywords){\r\n                    viewer.search(false);\r\n                }else{\r\n                    viewer.scroll(false,1);\r\n                }\r\n            }).on('click','.btn-page-prev',function(){\r\n                viewer.scroll(true,viewer.maxLines);\r\n            }).on('click','.btn-page-next',function(){\r\n                viewer.scroll(false,viewer.maxLines);\r\n            }).on('click','.btn-page-home',function(){\r\n                viewer.setOptions('reversed',false);\r\n                viewer.init();\r\n            }).on('click','.btn-page-end',function(){\r\n                viewer.setOptions('reversed',true);\r\n                viewer.init();\r\n            }).on('click','.btn-download',function(){\r\n                window.open('spider/log/download?id=' + logId + \"&taskId=\" + (taskId || ''));\r\n            });\r\n\t\t});\r\n\t</script>\r\n</html>"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/resources/templates/comment.html",
    "content": "<div class=\"layui-tab layui-tab-fixed layui-tab-brief\">\n  <ul class=\"layui-tab-title\">\n    <li class=\"layui-this\">配置</li>\n  </ul>\n  <div class=\"layui-tab-content\">\n    <div class=\"layui-tab-item layui-show\">\n    \t<div class=\"layui-form editor-form-node\">\n\t\t\t<form>\n\t\t\t\t<div class=\"layui-input-block\" style=\"margin-left: 0px\">\n\t\t\t\t\t<textarea name=\"value\" placeholder=\"请输入注释\" autocomplete=\"off\" style=\"height: 100px\" class=\"layui-input\">{{=d.value}}</textarea>\n\t\t\t\t</div>\n\t\t\t</form>\n    \t</div>\n    </div>\n  </div>\n</div>"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/resources/templates/edge.html",
    "content": "<div class=\"layui-tab layui-tab-fixed layui-tab-brief\">\r\n  <ul class=\"layui-tab-title\">\r\n    <li class=\"layui-this\">配置</li>\r\n  </ul>\r\n  <div class=\"layui-tab-content\">\r\n    <div class=\"layui-tab-item layui-show\">\r\n    \t<form class=\"layui-form editor-form-node\">\r\n\t\t\t<div class=\"layui-row\">\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">节点名称</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<input type=\"text\" name=\"value\" placeholder=\"请输入节点名称\" value=\"{{=d.value}}\" autocomplete=\"off\" class=\"layui-input\">\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">流转特性</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<select name=\"exception-flow\" lay-filter=\"exceptionFlow\">\r\n\t\t\t\t\t\t\t\t<option value=\"0\">直接流转</option>\r\n\t\t\t\t\t\t\t\t<option value=\"1\" {{d.data.object['exception-flow'] == '1' ? 'selected' : ''}}>当出现异常流转</option>\r\n\t\t\t\t\t\t\t\t<option value=\"2\" {{d.data.object['exception-flow'] == '2' ? 'selected' : ''}}>未出现异常流转</option>\r\n\t\t\t\t\t\t\t</select>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">流转特性</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<input type=\"checkbox\" title=\"传递变量\" value=\"transmit-variable\" lay-skin=\"primary\" {{d.data.object['transmit-variable'] == '0' ? '' : 'checked'}}/>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n    \t\t<div class=\"layui-form-item\">\r\n    \t\t\t<label class=\"layui-form-label\">流转条件</label>\r\n    \t\t\t<div class=\"layui-input-block\" placeholder=\"请输入流转条件\" codemirror=\"condition\" data-value=\"{{=d.data.object.condition}}\"></div>\r\n    \t\t</div>\r\n\t\t\t<div class=\"layui-row\">\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">线粗细</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<input type=\"number\" min=\"1\" max=\"10\" name=\"lineWidth\"  value=\"{{=d.data.object.lineWidth || 2}}\" autocomplete=\"off\" class=\"layui-input\">\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">线样式</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<select name=\"line-style\">\r\n\t\t\t\t\t\t\t\t<option value=\"sharp\">Sharp</option>\r\n\t\t\t\t\t\t\t\t<option value=\"rounded\" {{d.data.object['line-style'] == 'rounded' ? 'selected' : ''}}>Rounded</option>\r\n\t\t\t\t\t\t\t\t<option value=\"curved\" {{d.data.object['line-style'] == 'curved' ? 'selected' : ''}}>Curved</option>\r\n\t\t\t\t\t\t\t</select>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">线颜色</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<div id=\"line-color\"></div>\r\n\t\t\t\t\t\t\t<input type=\"hidden\" name=\"lineColor\" value=\"{{=d.data.object.lineColor || 'black'}}\"/>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n    \t</form>\r\n    </div>\r\n  </div>\r\n</div>\r\n<script>\r\n\tlayui.colorpicker.render({\r\n\t\telem : '#line-color',\r\n\t\tcolor : $('input[name=lineColor]').val(),\r\n\t\tdone : function(color){\r\n\t\t\t$('input[name=lineColor]').val(color);\r\n\t\t\teditor.graph.setCellStyles('strokeColor',color,[editor.getModel().getCell({{d.cell.id}})]);\r\n\t\t}\r\n\t});\r\n\tlayui.form.on('select(exceptionFlow)',function(data){\r\n\t\tvar color = $('input[name=lineColor]').val();\r\n\t\tif(data.value == '1'){\r\n\t\t\tcolor = 'red';\r\n\t\t}else if(data.value == '2'){\r\n\t\t\tcolor = '#00ff00';\r\n\t\t}else if($('[codemirror=condition]').attr('data-value') != ''){\r\n\t\t\tcolor = 'blue';\r\n\t\t}else{\r\n\t\t\tcolor = 'black';\r\n\t\t}\r\n\t\teditor.graph.setCellStyles('strokeColor',color,[editor.getModel().getCell({{d.cell.id}})]);\r\n\t\t$('input[name=lineColor]').val(color);\r\n\t});\r\n\t$(\"input[name=lineWidth]\").on('change',function(){\r\n\t\tserializeForm();\r\n\t})\r\n</script>"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/resources/templates/executeSql.html",
    "content": "<div class=\"layui-tab layui-tab-fixed layui-tab-brief\">\n  <ul class=\"layui-tab-title\">\n    <li class=\"layui-this\">配置</li>\n  </ul>\n  <div class=\"layui-tab-content\">\n    <div class=\"layui-tab-item layui-show\">\n    \t<form class=\"layui-form editor-form-node\" lay-filter=\"executesql-form\">\n\t\t\t<div class=\"layui-row\">\n\t\t\t\t<div class=\"layui-col-md3\">\n\t\t\t\t\t<div class=\"layui-form-item\">\n\t\t\t\t\t\t<label class=\"layui-form-label\">节点名称</label>\n\t\t\t\t\t\t<div class=\"layui-input-block\">\n\t\t\t\t\t\t\t<input type=\"text\" name=\"value\" placeholder=\"请输入节点名称\" value=\"{{=d.value}}\" autocomplete=\"off\" class=\"layui-input\">\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"layui-col-md3\">\n\t\t\t\t\t<div class=\"layui-form-item\">\n\t\t\t\t\t\t<label class=\"layui-form-label\">数据源</label>\n\t\t\t\t\t\t<div class=\"layui-input-block\">\n\t\t\t\t\t\t\t<select name=\"datasourceId\">\n\t\t\t\t\t\t\t\t{{# layui.each(d.datasources,function(index,datasource){ }}\n\t\t\t\t\t\t\t\t<option value=\"{{=datasource.id}}\" {{datasource.id == d.data.object.datasourceId ? 'selected': ''}}>{{datasource.name}}</option>\n\t\t\t\t\t\t\t\t{{# }) }}\n\t\t\t\t\t\t\t</select>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"layui-col-md3\">\n\t\t\t\t\t<div class=\"layui-form-item\">\n\t\t\t\t\t\t<label class=\"layui-form-label\">语句类型</label>\n\t\t\t\t\t\t<div class=\"layui-input-block\">\n\t\t\t\t\t\t\t<select name=\"statementType\" target-div=\"dynamicDiv\" target-value=\"select\" lay-filter=\"targetCheck\">\n\t\t\t\t\t\t\t\t<option value=\"select\" {{d.data.object.statementType == 'select' ? 'selected':''}}>select</option>\n\t\t\t\t\t\t\t\t<option value=\"selectOne\" {{d.data.object.statementType == 'selectOne' ? 'selected':''}}>selectOne</option>\n\t\t\t\t\t\t\t\t<option value=\"selectInt\" {{d.data.object.statementType == 'selectInt' ? 'selected':''}}>selectInt</option>\n\t\t\t\t\t\t\t\t<option value=\"insert\" {{d.data.object.statementType == 'insert' ? 'selected':''}}>insert</option>\n\t\t\t\t\t\t\t\t<option value=\"delete\" {{d.data.object.statementType == 'delete' ? 'selected':''}}>delete</option>\n\t\t\t\t\t\t\t\t<option value=\"update\" {{d.data.object.statementType == 'update' ? 'selected':''}}>update</option>\n\t\t\t\t\t\t\t\t<option value=\"insertofPk\" {{d.data.object.statementType == 'insertofPk' ? 'selected':''}}>insertofPk</option>\n\t\t\t\t\t\t\t</select>\n\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"layui-col-md3 dynamicDiv\"  {{d.data.object.statementType=='select' || !d.data.object.statementType ? '' : 'style=\"display:none;\"'}} >\n\t\t\t\t\t<div class=\"layui-form-item\">\n\t\t\t\t\t\t<div class=\"layui-input-block\">\n\t\t\t\t\t\t\t<input  type=\"checkbox\" value=\"isStream\" lay-skin=\"primary\"  title=\"输出到sqlRowSet流\"tips-text=\"sqlRowSet流|List<Map<String,Object>\"  {{d.data.object.isStream =='1'?'checked':''}}/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n    \t\t<div class=\"layui-form-item\">\n    \t\t\t<label class=\"layui-form-label\">SQL语句</label>\n    \t\t\t<div class=\"layui-input-block\" style=\"height:200px;\" codemirror=\"sql\" data-value=\"{{=d.data.object.sql}}\"></div>\n    \t\t</div>\n    \t</form>\n    </div>\n  </div>\n</div>\n<script>\n\t$.ajax({\n\t\turl : 'datasource/all',\n\t\tsuccess : function(datasources){\n\t\t\tfor(var i =0;i<datasources.length;i++){\n\t\t\t\tvar ds = datasources[i];\n\t\t\t\t$('select[name=datasourceId]').append('<option value=\"'+ds.id+'\">'+ds.name+'</option>');\n\t\t\t}\n\t\t\tlayui.form.render();\n\t\t\tvar selectDataSourceId = '{{=d.data.object.datasourceId}}';\n\t\t\tif(selectDataSourceId != ''){\n\t\t\t\t$('.layui-form-select dd[lay-value='+selectDataSourceId+']').trigger('click');\n\t\t\t}\n\t\t}\n\t})\n</script>"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/resources/templates/forkJoin.html",
    "content": "<div class=\"layui-tab layui-tab-fixed layui-tab-brief\">\n  <ul class=\"layui-tab-title\">\n    <li class=\"layui-this\">配置</li>\n  </ul>\n  <div class=\"layui-tab-content\">\n    <div class=\"layui-tab-item layui-show\">\n    \t<form class=\"layui-form editor-form-node\">\n    \t\t<div class=\"layui-form-item\">\n    \t\t\t<label class=\"layui-form-label\">节点名称</label>\n    \t\t\t<div class=\"layui-input-block\">\n    \t\t\t\t<input type=\"text\" name=\"value\" placeholder=\"请输入节点名称\" value=\"{{=d.value}}\" autocomplete=\"off\" class=\"layui-input\">\n    \t\t\t</div>\n    \t\t</div>\n    \t</form>\n    </div>\n  </div>\n</div>"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/resources/templates/function.html",
    "content": "<div class=\"layui-tab layui-tab-fixed layui-tab-brief\">\r\n  <ul class=\"layui-tab-title\">\r\n    <li class=\"layui-this\">配置</li>\r\n  </ul>\r\n  <div class=\"layui-tab-content\">\r\n    <div class=\"layui-tab-item layui-show\">\r\n    \t<form class=\"layui-form editor-form-node\">\r\n    \t\t<div class=\"layui-form-item\">\r\n    \t\t\t<label class=\"layui-form-label\">节点名称</label>\r\n    \t\t\t<div class=\"layui-input-block\">\r\n    \t\t\t\t<input type=\"text\" name=\"value\" placeholder=\"请输入节点名称\" value=\"{{=d.value}}\" autocomplete=\"off\" class=\"layui-input\">\r\n    \t\t\t</div>\r\n    \t\t</div>\r\n    \t\t<div class=\"layui-form-item\">\r\n    \t\t\t<label class=\"layui-form-label\">循环变量</label>\r\n    \t\t\t<div class=\"layui-input-block\">\r\n    \t\t\t\t<input type=\"text\" name=\"loopVariableName\" placeholder=\"请输入循环变量\" autocomplete=\"off\" class=\"layui-input input-default\" value=\"{{=d.data.object.loopVariableName}}\">\r\n    \t\t\t</div>\r\n    \t\t</div>\r\n    \t\t<div class=\"layui-form-item\">\r\n    \t\t\t<label class=\"layui-form-label\">循环次数</label>\r\n    \t\t\t<div class=\"layui-input-block\" codemirror=\"loopCount\" placeholder=\"请输入循环次数\" data-value=\"{{=d.data.object.loopCount}}\"></div>\r\n    \t\t</div>\r\n\t\t\t<hr>\r\n\t\t\t{{# layui.each(d.data.object['function'],function(index,item){ }}\r\n\t\t\t<div id=\"function{{=index}}\" class=\"draggable\" draggable=\"true\" ondragstart=\"drag(event)\" ondrop=\"drop(event)\" ondragover=\"allowDrop(event)\">\r\n\t\t\t\t<div class=\"layui-form-item layui-form-relative\">\r\n\t\t\t\t\t<i class=\"layui-icon layui-icon-close function-remove\"></i>\r\n\t\t\t\t\t<label class=\"layui-form-label\">执行函数</label>\r\n\t\t\t\t\t<div class=\"layui-input-block array\" codemirror=\"function\" placeholder=\"执行函数\" data-value=\"{{=d.data.object['function'][index]}}\"></div>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\t{{# }) }}\r\n\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t<button class=\"layui-btn function-add\" type=\"button\">添加一个函数</button>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n    \t</form>\r\n    </div>\r\n  </div>\r\n</div>"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/resources/templates/loop.html",
    "content": "<div class=\"layui-tab layui-tab-fixed layui-tab-brief\">\r\n  <ul class=\"layui-tab-title\">\r\n    <li class=\"layui-this\">配置</li>\r\n  </ul>\r\n  <div class=\"layui-tab-content\">\r\n    <div class=\"layui-tab-item layui-show\">\r\n    \t<form class=\"layui-form editor-form-node\">\r\n\t\t\t<div class=\"layui-row\">\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">节点名称</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<input type=\"text\" name=\"value\" placeholder=\"请输入节点名称\" value=\"{{=d.value}}\" autocomplete=\"off\" class=\"layui-input\">\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">次数或集合</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\" codemirror=\"loopCount\" placeholder=\"请输入循环次数或集合\" data-value=\"{{=d.data.object.loopCount}}\"></div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">循环变量</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<input type=\"text\" name=\"loopItem\" placeholder=\"请输入循环变量(默认item)\" autocomplete=\"off\" class=\"layui-input input-default\" value=\"{{=d.data.object.loopItem}}\">\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\t<div class=\"layui-row\">\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">下标变量</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<input type=\"text\" name=\"loopVariableName\" placeholder=\"请输入下标变量\" autocomplete=\"off\" class=\"layui-input input-default\" value=\"{{=d.data.object.loopVariableName}}\">\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">开始位置</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\" codemirror=\"loopStart\" placeholder=\"请输入循环次数\" data-value=\"{{=d.data.object.loopStart || '0'}}\"></div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">结束位置</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\" codemirror=\"loopEnd\" placeholder=\"请输入循环次数\" data-value=\"{{=d.data.object.loopEnd || -1}}\"></div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n    \t</form>\r\n    </div>\r\n  </div>\r\n</div>"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/resources/templates/output.html",
    "content": "<div class=\"layui-tab layui-tab-fixed layui-tab-brief\">\r\n  <ul class=\"layui-tab-title\">\r\n    <li class=\"layui-this\">配置</li>\r\n  </ul>\r\n  <div class=\"layui-tab-content\">\r\n    <div class=\"layui-tab-item layui-show\">\r\n    \t<form class=\"layui-form editor-form-node\">\r\n\t\t\t<div class=\"layui-row\">\r\n\t\t\t\t<div class=\"layui-col-md3\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">节点名称</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<input type=\"text\" name=\"value\" placeholder=\"请输入节点名称\" value=\"{{=d.value}}\" autocomplete=\"off\" class=\"layui-input\">\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md3\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">循环变量</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<input type=\"text\" name=\"loopVariableName\" placeholder=\"请输入循环变量\" autocomplete=\"off\" class=\"layui-input input-default\" value=\"{{=d.data.object.loopVariableName}}\">\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md3\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">循环次数</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\" codemirror=\"loopCount\" placeholder=\"请输入循环次数\" data-value=\"{{=d.data.object.loopCount}}\"></div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md3\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">输出设置</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<input type=\"checkbox\" title=\"输出全部参数\" value=\"output-all\" lay-skin=\"primary\" {{d.data.object['output-all'] == '1' ? 'checked' : ''}}/>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\t<div class=\"layui-row layui-col-space20\">\r\n\t\t\t\t<div class=\"layui-col-md3\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">数据输出</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<input type=\"checkbox\" target-div=\"databaseDiv\" title=\"输出到数据库\" value=\"output-database\" lay-skin=\"primary\" {{d.data.object['output-database'] == '1' ? 'checked' : ''}} lay-filter=\"targetCheck\"/>\r\n\t\t\t\t\t\t\t<input type=\"checkbox\" class=\"oCheckbox\" target-div=\"csvDiv\" title=\"输出到CSV文件\" value=\"output-csv\" lay-skin=\"primary\" {{d.data.object['output-csv'] == '1' ? 'checked' : ''}} lay-filter=\"targetCheck\"/>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md3 databaseDiv\" {{d.data.object['output-database'] == '1' ? '' : 'style=\"display: none;\"'}}>\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">数据源</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<select name=\"datasourceId\">\r\n\t\t\t\t\t\t\t\t{{# layui.each(d.datasources,function(index,datasource){ }}\r\n\t\t\t\t\t\t\t\t<option value=\"{{=datasource.id}}\" {{datasource.id == d.data.object.datasourceId ? 'selected': ''}}>{{datasource.name}}</option>\r\n\t\t\t\t\t\t\t\t{{# }) }}\r\n\t\t\t\t\t\t\t</select>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md2 databaseDiv\" {{d.data.object['output-database'] == '1' ? '' : 'style=\"display: none;\"'}}>\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<input type=\"text\" name=\"tableName\" placeholder=\"请输入表名\" autocomplete=\"off\" class=\"layui-input input-default\" value=\"{{=d.data.object.tableName}}\">\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md2 csvDiv\" {{d.data.object['output-csv'] == '1' ? '' : 'style=\"display: none;\"'}}>\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<input type=\"text\" name=\"csvName\" placeholder=\"请输入文件名\" autocomplete=\"off\" class=\"layui-input input-default\" value=\"{{=d.data.object.csvName}}\">\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md2 csvDiv\" {{d.data.object['output-csv'] == '1' ? '' : 'style=\"display: none;\"'}}>\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<select name=\"csvEncoding\">\r\n\t\t\t\t\t\t\t<option value=\"GBK\" {{d.data.object['csvEncoding'] == 'GBK' ? 'selected': ''}}>GBK</option>\r\n\t\t\t\t\t\t\t<option value=\"UTF-8\" {{d.data.object['csvEncoding'] == 'UTF-8' ? 'selected': ''}}>UTF-8</option>\r\n\t\t\t\t\t\t\t<option value=\"UTF-8BOM\" {{d.data.object['csvEncoding'] == 'UTF-8BOM' ? 'selected': ''}}>UTF-8 BOM</option>\r\n\t\t\t\t\t\t</select>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\t<table class=\"layui-table\" id=\"spider-output\" data-cell=\"{{=d.cell.id}}\" data-keys=\"output-name,output-value\"></table>\r\n    \t\t<hr>\r\n    \t\t<div class=\"layui-form-item\">\r\n    \t\t\t<div class=\"layui-input-inline\">\r\n\t    \t\t\t<button class=\"layui-btn table-row-add\" type=\"button\" for=\"spider-output\">添加一个输出项</button>\r\n\t    \t\t</div>\r\n\t    \t</div>\r\n    \t</form>\r\n    </div>\r\n  </div>\r\n</div>\r\n<script>\r\n\tfunction renderSpiderOutput(data){\r\n\t\tlayui.table.render({\r\n\t\t\telem : '#spider-output',\r\n\t\t\tlimit: 50,\r\n\t\t\tcols : [[{\r\n\t\t\t\ttitle : '输出项',\r\n\t\t\t\twidth : 150,\r\n\t\t\t\ttemplet : '#output-name-tmpl'\r\n\t\t\t},{\r\n\t\t\t\ttitle : '输出值',\r\n\t\t\t\ttemplet : '#output-value-tmpl'\r\n\t\t\t},{\r\n\t\t\t\ttitle : '操作',\r\n\t\t\t\twidth : 120,\r\n\t\t\t\talign : 'center',\r\n\t\t\t\ttemplet : '#common-operation'\r\n\t\t\t}]],\r\n\t\t\tdata : data,\r\n\t\t\ttext : {\r\n\t\t\t\tnone : '暂未设置输出项'\r\n\t\t\t}\r\n\t\t})\r\n\t}\r\n\trenderSpiderOutput(getCellData({{d.cell.id}},$(\"#spider-output\").data('keys').split(\",\")));\r\n\r\n\t$.ajax({\r\n\t\turl : 'datasource/all',\r\n\t\tsuccess : function(datasources){\r\n\t\t\tfor(var i =0;i<datasources.length;i++){\r\n\t\t\t\tvar ds = datasources[i];\r\n\t\t\t\t$('select[name=datasourceId]').append('<option value=\"'+ds.id+'\">'+ds.name+'</option>');\r\n\t\t\t}\r\n\t\t\tlayui.form.render();\r\n\t\t\tvar selectDataSourceId = '{{=d.data.object.datasourceId}}';\r\n\t\t\tif(selectDataSourceId != ''){\r\n\t\t\t\t$('.layui-form-select dd[lay-value='+selectDataSourceId+']').trigger('click');\r\n\t\t\t}\r\n\t\t}\r\n\t});\r\n</script>"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/resources/templates/process.html",
    "content": "<div class=\"layui-tab layui-tab-fixed layui-tab-brief\">\r\n  <ul class=\"layui-tab-title\">\r\n    <li class=\"layui-this\">配置</li>\r\n  </ul>\r\n  <div class=\"layui-tab-content\">\r\n    <div class=\"layui-tab-item layui-show\">\r\n    \t<form class=\"layui-form editor-form-node\">\r\n    \t\t<div class=\"layui-form-item\">\r\n    \t\t\t<label class=\"layui-form-label\">节点名称</label>\r\n    \t\t\t<div class=\"layui-input-block\">\r\n    \t\t\t\t<input type=\"text\" name=\"value\" placeholder=\"请输入节点名称\" value=\"{{=d.value}}\" autocomplete=\"off\" class=\"layui-input\">\r\n    \t\t\t</div>\r\n    \t\t</div>\r\n    \t\t<div class=\"layui-form-item\">\r\n    \t\t\t<label class=\"layui-form-label\">子流程</label>\r\n    \t\t\t<div class=\"layui-input-block\">\r\n    \t\t\t\t<select name=\"flowId\">\r\n    \t\t\t\t\t{{# layui.each(d.flows,function(index,flow){ }}\r\n    \t\t\t\t\t\t<option value=\"{{=flow.id}}\" {{flow.id == d.data.object.flowId ? 'selected': ''}}>{{flow.name}}</option>\r\n    \t\t\t\t\t{{# }) }}\r\n    \t\t\t\t</select>\r\n    \t\t\t</div>\r\n    \t\t</div>\r\n    \t</form>\r\n    </div>\r\n  </div>\r\n</div>"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/resources/templates/request.html",
    "content": "<div class=\"layui-tab layui-tab-fixed layui-tab-brief\">\r\n  <ul class=\"layui-tab-title\">\r\n    <li class=\"layui-this\">基本配置</li>\r\n    <li>参数</li>\r\n\t<li>Cookie</li>\r\n    <li>Header</li>\r\n    <li>Body</li>\r\n  </ul>\r\n  <div class=\"layui-tab-content editor-form-node\">\r\n    <div class=\"layui-tab-item layui-show\">\r\n    \t<form class=\"layui-form\">\r\n\t\t\t<div class=\"layui-row\">\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">节点名称</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<input type=\"text\" name=\"value\" placeholder=\"请输入节点名称\" value=\"{{=d.value}}\" autocomplete=\"off\" class=\"layui-input\">\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">循环变量</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<input type=\"text\" name=\"loopVariableName\" placeholder=\"请输入循环变量\" autocomplete=\"off\" class=\"layui-input input-default\" value=\"{{=d.data.object.loopVariableName}}\">\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">循环次数</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\" codemirror=\"loopCount\" placeholder=\"请输入循环次数\" data-value=\"{{=d.data.object.loopCount}}\"></div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\t<div class=\"layui-row\">\r\n\t\t\t\t<div class=\"layui-col-md2\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">请求方法</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<select name=\"method\">\r\n\t\t\t\t\t\t\t\t<option value=\"GET\" {{d.data.object.method == 'GET' ? 'selected':''}}>GET</option>\r\n\t\t\t\t\t\t\t\t<option value=\"POST\" {{d.data.object.method == 'POST' ? 'selected':''}}>POST</option>\r\n\t\t\t\t\t\t\t\t<option value=\"PUT\" {{d.data.object.method == 'PUT' ? 'selected':''}}>PUT</option>\r\n\t\t\t\t\t\t\t\t<option value=\"DELETE\" {{d.data.object.method == 'DELETE' ? 'selected':''}}>DELETE</option>\r\n\t\t\t\t\t\t\t\t<option value=\"PATCH\" {{d.data.object.method == 'PATCH' ? 'selected':''}}>PATCH</option>\r\n\t\t\t\t\t\t\t\t<option value=\"HEAD\" {{d.data.object.method == 'HEAD' ? 'selected':''}}>HEAD</option>\r\n\t\t\t\t\t\t\t\t<option value=\"OPTIONS\" {{d.data.object.method == 'OPTIONS' ? 'selected':''}}>OPTIONS</option>\r\n\t\t\t\t\t\t\t\t<option value=\"TRACE\" {{d.data.object.method == 'TRACE' ? 'selected':''}}>TRACE</option>\r\n\t\t\t\t\t\t\t</select>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md10\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">URL</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\" codemirror=\"url\" data-value=\"{{=d.data.object.url}}\"></div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\t<div class=\"layui-row\">\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">延迟时间</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<input type=\"text\" name=\"sleep\" placeholder=\"请输入延迟时间\" autocomplete=\"off\" class=\"layui-input input-default\" value=\"{{=d.data.object.sleep}}\">\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">超时时间</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<input type=\"text\" name=\"timeout\" placeholder=\"请输入超时时间\" autocomplete=\"off\" class=\"layui-input input-default\" value=\"{{=d.data.object.timeout}}\">\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">页面编码</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<input type=\"text\" name=\"response-charset\" placeholder=\"请输入页面编码\" autocomplete=\"off\" class=\"layui-input input-default\" value=\"{{=d.data.object['response-charset']}}\">\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\t<div class=\"layui-row\">\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">重试次数</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<input type=\"text\" name=\"retryCount\" placeholder=\"请输入重试次数\" autocomplete=\"off\" class=\"layui-input input-default\" value=\"{{=d.data.object.retryCount}}\">\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">重试间隔</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<input type=\"text\" name=\"retryInterval\" placeholder=\"请输入重试间隔\" autocomplete=\"off\" class=\"layui-input input-default\" value=\"{{=d.data.object.retryInterval}}\">\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\t<div class=\"layui-row\">\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">代理</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\" placeholder=\"host:port\" codemirror=\"proxy\" data-value=\"{{=d.data.object.proxy}}\"></div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md8\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">请求设置</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<input type=\"checkbox\" title=\"跟随重定向\" value=\"follow-redirect\" lay-skin=\"primary\" {{d.data.object['follow-redirect'] == '0' ? '' : 'checked'}}/>\r\n\t\t\t\t\t\t\t<input type=\"checkbox\" title=\"TLS证书验证\" value=\"tls-validate\" lay-skin=\"primary\" {{d.data.object['tls-validate'] == '0' ? '' : 'checked'}}/>\r\n\t\t\t\t\t\t\t<input type=\"checkbox\" title=\"自动管理Cookie\" value=\"cookie-auto-set\" lay-skin=\"primary\" {{d.data.object['cookie-auto-set'] == '0' ? '' : 'checked'}}/>\r\n\t\t\t\t\t\t\t<input type=\"checkbox\" title=\"自动去重\" value=\"repeat-enable\" lay-skin=\"primary\" {{d.data.object['repeat-enable'] == '1' ? 'checked' : ''}}/>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\r\n    \t</form>\r\n    </div>\r\n    <div class=\"layui-tab-item\">\r\n    \t<form class=\"layui-form\">\r\n\t\t\t<table class=\"layui-table\" id=\"request-parameter\" data-cell=\"{{=d.cell.id}}\" data-keys=\"parameter-name,parameter-value,parameter-description\"></table>\r\n\t   \t\t<div id=\"addParamterBtn\" class=\"layui-form-item\">\r\n\t   \t\t\t<div class=\"layui-input-inline\">\r\n\t    \t\t\t<button class=\"layui-btn table-row-add\" type=\"button\" for=\"request-parameter\">添加一个参数</button>\r\n\t    \t\t</div>\r\n\t\t\t\t<div class=\"layui-input-inline\">\r\n\t\t\t\t\t<button class=\"layui-btn parameter-batch\" type=\"button\" for=\"request-parameter\">批量设置参数</button>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n    \t</form>\r\n    </div>\r\n\t  <div class=\"layui-tab-item\">\r\n\t\t  <form class=\"layui-form\">\r\n\t\t\t  <table class=\"layui-table\" id=\"request-cookie\" data-cell=\"{{=d.cell.id}}\" data-keys=\"cookie-name,cookie-value,cookie-description\"></table>\r\n\t\t\t  <div id=\"addCookieBtn\" class=\"layui-form-item\">\r\n\t\t\t\t  <div class=\"layui-input-inline\">\r\n\t\t\t\t\t  <button class=\"layui-btn table-row-add\" type=\"button\" for=\"request-cookie\">添加一个Cookie</button>\r\n\t\t\t\t  </div>\r\n\t\t\t\t  <div class=\"layui-input-inline\">\r\n\t\t\t\t\t  <button class=\"layui-btn cookie-batch\" type=\"button\" for=\"request-cookie\">批量设置Cookie</button>\r\n\t\t\t\t  </div>\r\n\t\t\t  </div>\r\n\t\t  </form>\r\n\t  </div>\r\n    <div class=\"layui-tab-item\">\r\n    \t<form class=\"layui-form\">\r\n\t\t\t<table class=\"layui-table\" id=\"request-header\" data-cell=\"{{=d.cell.id}}\" data-keys=\"header-name,header-value,header-description\"></table>\r\n\t   \t\t<div id=\"addHeaderBtn\" class=\"layui-form-item\">\r\n\t   \t\t\t<div class=\"layui-input-inline\">\r\n\t    \t\t\t<button class=\"layui-btn table-row-add\" type=\"button\" for=\"request-header\">添加一个Header</button>\r\n\t    \t\t</div>\r\n\t\t\t\t<div class=\"layui-input-inline\">\r\n\t\t\t\t\t<button class=\"layui-btn header-batch\" type=\"button\" for=\"request-header\">批量设置Header</button>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t    </form>\r\n    </div>\r\n  \t\r\n  \t<div class=\"layui-tab-item\">\r\n    \t<form class=\"layui-form\">\r\n    \t\t<div class=\"layui-form-item\">\r\n    \t\t\t<label class=\"layui-form-label\">类型</label>\r\n    \t\t\t<div class=\"layui-input-block\">\r\n    \t\t\t\t<select name=\"body-type\" lay-filter=\"bodyType\">\r\n\t    \t\t\t\t<option value=\"none\" {{d.data.object['body-type'] == 'none' ? 'selected':''}}>none</option>\r\n\t   \t\t\t\t\t<option value=\"form-data\" {{d.data.object['body-type'] == 'form-data' ? 'selected':''}}>form-data</option>\r\n\t   \t\t\t\t\t<option value=\"raw\" {{d.data.object['body-type'] == 'raw' ? 'selected':''}}>raw</option>\r\n   \t\t\t\t\t</select>\r\n    \t\t\t</div>\r\n    \t\t</div>\r\n    \t\t<div  class=\"form-body-raw\" {{d.data.object['body-type'] != 'raw' ? 'style=\"display:none;\"':''}}>\r\n\t    \t\t<div class=\"layui-form-item\">\r\n\t    \t\t\t<label class=\"layui-form-label\">Content-Type</label>\r\n\t    \t\t\t<div class=\"layui-input-block\">\r\n\t    \t\t\t\t<select name=\"body-content-type\">\r\n\t\t    \t\t\t\t<option value=\"text/plain\" {{d.data.object['body-content-type'] == 'text/plain' ? 'selected':''}}>text/plain</option>\r\n\t\t   \t\t\t\t\t<option value=\"application/json\" {{d.data.object['body-content-type'] == 'application/json' ? 'selected':''}}>application/json</option>\r\n\t   \t\t\t\t\t</select>\r\n\t    \t\t\t</div>\r\n\t    \t\t</div>\r\n\t    \t\t<div class=\"layui-form-item\">\r\n\t    \t\t\t<label class=\"layui-form-label\">内容</label>\r\n\t    \t\t\t<div class=\"layui-input-block\" style=\"height:200px;\" placeholder=\"请输入内容\" codemirror=\"request-body\" data-value=\"{{=d.data.object['request-body']}}\"></div>\r\n\t    \t\t</div>\r\n    \t\t</div>\r\n    \t\t<div class=\"form-body-form-data\" {{d.data.object['body-type'] != 'form-data' ? 'style=\"display:none;\"':''}}>\r\n\t  \t\t\t<table class=\"layui-table\" id=\"body-parameter\" data-cell=\"{{=d.cell.id}}\" data-keys=\"parameter-form-name,parameter-form-value,parameter-form-type,parameter-form-filename,parameter-form-description\"></table>\r\n\t    \t\t<div class=\"layui-form-item\">\r\n\t\t   \t\t\t<div class=\"layui-input-inline\">\r\n\t\t    \t\t\t<button class=\"layui-btn table-row-add\" type=\"button\" for=\"body-parameter\">添加一个参数</button>\r\n\t\t    \t\t</div>\r\n\t\t    \t</div>\r\n    \t\t</div>\r\n\t    </form>\r\n    </div>\r\n  </div>\r\n</div>\r\n<script>\r\n\t$(function(){\r\n\t\tfunction renderRequestParameter(data){\r\n\t\t\tlayui.table.render({\r\n\t\t\t\telem : '#request-parameter',\r\n\t\t\t\tlimit: 50,\r\n\t\t\t\tcols : [[{\r\n\t\t\t\t\ttitle : '参数名',\r\n\t\t\t\t\twidth : 150,\r\n\t\t\t\t\ttemplet : '#parameter-name-tmpl'\r\n\t\t\t\t},{\r\n\t\t\t\t\ttitle : '参数值',\r\n\t\t\t\t\ttemplet : '#parameter-value-tmpl'\r\n\t\t\t\t},{\r\n\t\t\t\t\ttitle : '参数描述',\r\n\t\t\t\t\twidth : 250,\r\n\t\t\t\t\ttemplet : '#parameter-description-tmpl'\r\n\t\t\t\t},{\r\n\t\t\t\t\ttitle : '操作',\r\n\t\t\t\t\twidth : 120,\r\n\t\t\t\t\talign : 'center',\r\n\t\t\t\t\ttemplet : '#common-operation'\r\n\t\t\t\t}]],\r\n\t\t\t\tdata : data,\r\n\t\t\t\ttext : {\r\n\t\t\t\t\tnone : '暂未设置参数'\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t}\r\n\t\tfunction renderRequestCookie(data){\r\n\t\t\tlayui.table.render({\r\n\t\t\t\telem : '#request-cookie',\r\n\t\t\t\tlimit: 50,\r\n\t\t\t\tcols : [[{\r\n\t\t\t\t\ttitle : 'Cookie名',\r\n\t\t\t\t\twidth : 150,\r\n\t\t\t\t\ttemplet : '#cookie-name-tmpl'\r\n\t\t\t\t},{\r\n\t\t\t\t\ttitle : 'Cookie值',\r\n\t\t\t\t\ttemplet : '#cookie-value-tmpl'\r\n\t\t\t\t},{\r\n\t\t\t\t\ttitle : '描述',\r\n\t\t\t\t\twidth : 250,\r\n\t\t\t\t\ttemplet : '#cookie-description-tmpl'\r\n\t\t\t\t},{\r\n\t\t\t\t\ttitle : '操作',\r\n\t\t\t\t\twidth : 120,\r\n\t\t\t\t\talign : 'center',\r\n\t\t\t\t\ttemplet : '#common-operation'\r\n\t\t\t\t}]],\r\n\t\t\t\tdata : data,\r\n\t\t\t\ttext : {\r\n\t\t\t\t\tnone : '暂未设置Cookie'\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t}\r\n\r\n\t\tfunction renderRequestHeader(data){\r\n\t\t\tlayui.table.render({\r\n\t\t\t\telem : '#request-header',\r\n\t\t\t\tlimit: 50,\r\n\t\t\t\tcols : [[{\r\n\t\t\t\t\ttitle : 'Header名',\r\n\t\t\t\t\twidth : 150,\r\n\t\t\t\t\ttemplet : '#header-name-tmpl'\r\n\t\t\t\t},{\r\n\t\t\t\t\ttitle : 'header值',\r\n\t\t\t\t\tminWidth : 400,\r\n\t\t\t\t\ttemplet : '#header-value-tmpl'\r\n\t\t\t\t},{\r\n\t\t\t\t\ttitle : '描述',\r\n\t\t\t\t\twidth : 250,\r\n\t\t\t\t\ttemplet : '#header-description-tmpl'\r\n\t\t\t\t},{\r\n\t\t\t\t\ttitle : '操作',\r\n\t\t\t\t\twidth : 120,\r\n\t\t\t\t\talign : 'center',\r\n\t\t\t\t\ttemplet : '#common-operation'\r\n\t\t\t\t}]],\r\n\t\t\t\tdata : data,\r\n\t\t\t\ttext : {\r\n\t\t\t\t\tnone : '暂未设置Header'\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t}\r\n\t\tfunction renderBodyParameter(data){\r\n\t\t\tlayui.table.render({\r\n\t\t\t\telem : '#body-parameter',\r\n\t\t\t\tcols : [[{\r\n\t\t\t\t\ttitle : '参数名',\r\n\t\t\t\t\twidth : 150,\r\n\t\t\t\t\ttemplet : '#parameter-from-name-tmpl'\r\n\t\t\t\t},{\r\n\t\t\t\t\ttitle : '参数类型',\r\n\t\t\t\t\twidth : 85,\r\n\t\t\t\t\ttemplet : '#parameter-from-type-tmpl'\r\n\t\t\t\t},{\r\n\t\t\t\t\ttitle : '文件名',\r\n\t\t\t\t\twidth : 195,\r\n\t\t\t\t\ttemplet : '#parameter-from-filename-tmpl'\r\n\t\t\t\t},{\r\n\t\t\t\t\ttitle : '参数值',\r\n\t\t\t\t\ttemplet : '#parameter-from-value-tmpl'\r\n\t\t\t\t},{\r\n\t\t\t\t\ttitle : '参数描述',\r\n\t\t\t\t\twidth : 250,\r\n\t\t\t\t\ttemplet : '#parameter-from-description-tmpl'\r\n\t\t\t\t},{\r\n\t\t\t\t\ttitle : '操作',\r\n\t\t\t\t\twidth : 120,\r\n\t\t\t\t\talign : 'center',\r\n\t\t\t\t\ttemplet : '#common-operation'\r\n\t\t\t\t}]],\r\n\t\t\t\tdata : data,\r\n\t\t\t\ttext : {\r\n\t\t\t\t\tnone : '暂未设置参数'\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t}\r\n\t\trenderRequestParameter(getCellData({{d.cell.id}},$(\"#request-parameter\").data('keys').split(\",\")));\r\n\t\trenderRequestCookie(getCellData({{d.cell.id}},$(\"#request-cookie\").data('keys').split(\",\")));\r\n\t\trenderRequestHeader(getCellData({{d.cell.id}},$(\"#request-header\").data('keys').split(\",\")));\r\n\t\trenderBodyParameter(getCellData({{d.cell.id}},$(\"#body-parameter\").data('keys').split(\",\")));\r\n\t});\r\n</script>"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/resources/templates/root.html",
    "content": "<div class=\"layui-tab layui-tab-fixed layui-tab-brief\">\r\n  <ul class=\"layui-tab-title\">\r\n    <li class=\"layui-this\">全局配置</li>\r\n    <li>全局参数</li>\r\n\t<li>全局Cookie</li>\r\n    <li>全局Header</li>\r\n  </ul>\r\n  <div class=\"layui-tab-content editor-form-node\">\r\n    <div class=\"layui-tab-item layui-show\">\r\n    \t<form class=\"layui-form layui-row\">\r\n\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t<label class=\"layui-form-label\">爬虫名称</label>\r\n\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t<input type=\"text\" name=\"spiderName\" placeholder=\"请输入爬虫名称\" autocomplete=\"off\" class=\"layui-input\" value=\"{{d.data.object.spiderName || '未定义名称'}}\">\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t<label class=\"layui-form-label\">提交策略</label>\r\n\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t<select name=\"submit-strategy\">\r\n\t\t\t\t\t\t<option value=\"random\" {{d.data.object['submit-strategy'] == 'random' ? 'selected':''}}>随机</option>\r\n\t\t\t\t\t\t<option value=\"linked\" {{d.data.object['submit-strategy'] == 'linked' ? 'selected':''}}>顺序</option>\r\n\t\t\t\t\t\t<option value=\"child\"  {{d.data.object['submit-strategy'] == 'child'  ? 'selected':''}}>子优先</option>\r\n\t\t\t\t\t\t<option value=\"parent\" {{d.data.object['submit-strategy'] == 'parent' ? 'selected':''}}>父优先</option>\r\n\t\t\t\t\t</select>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t<label class=\"layui-form-label\">最大线程数</label>\r\n\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t<input type=\"number\" min=\"1\" name=\"threadCount\" placeholder=\"请输入线程数\" autocomplete=\"off\" class=\"layui-input\" value=\"{{=d.data.object.threadCount}}\">\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n    \t</form>\r\n    </div>\r\n    <div class=\"layui-tab-item\">\r\n   \t\t<form class=\"layui-form\">\r\n\t\t\t<table class=\"layui-table\" id=\"global-parameter\" data-cell=\"{{=d.cell.id}}\" data-keys=\"parameter-name,parameter-value,parameter-description\"></table>\r\n\t   \t\t<div class=\"layui-form-item\">\r\n\t   \t\t\t<div class=\"layui-input-inline\">\r\n\t    \t\t\t<button class=\"layui-btn table-row-add\" type=\"button\" for=\"global-parameter\">添加一个参数</button>\r\n\t    \t\t</div>\r\n\t\t\t\t<div class=\"layui-input-inline\">\r\n\t\t\t\t\t<button class=\"layui-btn parameter-batch\" type=\"button\" for=\"global-parameter\">批量设置参数</button>\r\n\t\t\t\t</div>\r\n\t    \t</div>\r\n\t    </form>\r\n    </div>\r\n\t<div class=\"layui-tab-item\">\r\n\t  <form class=\"layui-form\">\r\n\t\t  <table class=\"layui-table\" id=\"global-cookie\" data-cell=\"{{=d.cell.id}}\" data-keys=\"cookie-name,cookie-value,cookie-description\"></table>\r\n\t\t  <div id=\"addCookieBtn\" class=\"layui-form-item\">\r\n\t\t\t  <div class=\"layui-input-inline\">\r\n\t\t\t\t  <button class=\"layui-btn table-row-add\" type=\"button\" for=\"global-cookie\">添加一个Cookie</button>\r\n\t\t\t  </div>\r\n\t\t\t  <div class=\"layui-input-inline\">\r\n\t\t\t\t  <button class=\"layui-btn cookie-batch\" type=\"button\" for=\"global-cookie\">批量设置Cookie</button>\r\n\t\t\t  </div>\r\n\t\t  </div>\r\n\t  </form>\r\n\t</div>\r\n    <div class=\"layui-tab-item\">\r\n   \t\t<form class=\"layui-form\">\r\n\t\t\t<table class=\"layui-table\" id=\"global-header\" data-cell=\"{{=d.cell.id}}\" data-keys=\"header-name,header-value,header-description\"></table>\r\n\t\t\t<div class=\"layui-input-inline\">\r\n\t\t\t\t<button class=\"layui-btn table-row-add\" type=\"button\" for=\"global-header\">添加一个Header</button>\r\n\t\t\t</div>\r\n\t\t\t<div class=\"layui-input-inline\">\r\n\t\t\t\t<button class=\"layui-btn header-batch\" type=\"button\" for=\"global-header\">批量设置Header</button>\r\n\t\t\t</div>\r\n\t    </form>\r\n    </div>\r\n  </div>\r\n</div>\r\n<script>\r\n\tfunction renderGlobalParameter(data){\r\n\t\tlayui.table.render({\r\n\t\t\telem : '#global-parameter',\r\n\t\t\tlimit: 50,\r\n\t\t\tcols : [[{\r\n\t\t\t\ttitle : '参数名',\r\n\t\t\t\twidth : 150,\r\n\t\t\t\ttemplet : '#parameter-name-tmpl'\r\n\t\t\t},{\r\n\t\t\t\ttitle : '参数值',\r\n\t\t\t\ttemplet : '#parameter-value-tmpl'\r\n\t\t\t},{\r\n\t\t\t\ttitle : '参数描述',\r\n\t\t\t\twidth : 250,\r\n\t\t\t\ttemplet : '#parameter-description-tmpl'\r\n\t\t\t},{\r\n\t\t\t\ttitle : '操作',\r\n\t\t\t\twidth : 120,\r\n\t\t\t\talign : 'center',\r\n\t\t\t\ttemplet : '#common-operation'\r\n\t\t\t}]],\r\n\t\t\tdata : data,\r\n\t\t\ttext : {\r\n\t\t\t\tnone : '暂未设置参数'\r\n\t\t\t}\r\n\t\t})\r\n\t}\r\n\tfunction renderGlobalCookie(data){\r\n\t\tlayui.table.render({\r\n\t\t\telem : '#global-cookie',\r\n\t\t\tlimit: 50,\r\n\t\t\tcols : [[{\r\n\t\t\t\ttitle : 'Cookie名',\r\n\t\t\t\twidth : 150,\r\n\t\t\t\ttemplet : '#cookie-name-tmpl'\r\n\t\t\t},{\r\n\t\t\t\ttitle : 'Cookie值',\r\n\t\t\t\ttemplet : '#cookie-value-tmpl'\r\n\t\t\t},{\r\n\t\t\t\ttitle : '描述',\r\n\t\t\t\twidth : 250,\r\n\t\t\t\ttemplet : '#cookie-description-tmpl'\r\n\t\t\t},{\r\n\t\t\t\ttitle : '操作',\r\n\t\t\t\twidth : 120,\r\n\t\t\t\talign : 'center',\r\n\t\t\t\ttemplet : '#common-operation'\r\n\t\t\t}]],\r\n\t\t\tdata : data,\r\n\t\t\ttext : {\r\n\t\t\t\tnone : '暂未设置Cookie'\r\n\t\t\t}\r\n\t\t})\r\n\t}\r\n\r\n\tfunction renderGlobalHeader(data){\r\n\t\tlayui.table.render({\r\n\t\t\telem : '#global-header',\r\n\t\t\tlimit: 50,\r\n\t\t\tcols : [[{\r\n\t\t\t\ttitle : 'Header名',\r\n\t\t\t\twidth : 150,\r\n\t\t\t\ttemplet : '#header-name-tmpl'\r\n\t\t\t},{\r\n\t\t\t\ttitle : 'header值',\r\n\t\t\t\tminWidth : 400,\r\n\t\t\t\ttemplet : '#header-value-tmpl'\r\n\t\t\t},{\r\n\t\t\t\ttitle : '描述',\r\n\t\t\t\twidth : 250,\r\n\t\t\t\ttemplet : '#header-description-tmpl'\r\n\t\t\t},{\r\n\t\t\t\ttitle : '操作',\r\n\t\t\t\twidth : 120,\r\n\t\t\t\talign : 'center',\r\n\t\t\t\ttemplet : '#common-operation'\r\n\t\t\t}]],\r\n\t\t\tdata : data,\r\n\t\t\ttext : {\r\n\t\t\t\tnone : '暂未设置Header'\r\n\t\t\t}\r\n\t\t})\r\n\t}\r\n\trenderGlobalParameter(getCellData({{d.cell.id}},$(\"#global-parameter\").data('keys').split(\",\")));\r\n\trenderGlobalCookie(getCellData({{d.cell.id}},$(\"#global-cookie\").data('keys').split(\",\")));\r\n\trenderGlobalHeader(getCellData({{d.cell.id}},$(\"#global-header\").data('keys').split(\",\")));\r\n</script>"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/resources/templates/start.html",
    "content": "<div class=\"layui-tab layui-tab-fixed layui-tab-brief\">\r\n  <ul class=\"layui-tab-title\">\r\n    <li class=\"layui-this\">配置</li>\r\n  </ul>\r\n  <div class=\"layui-tab-content\">\r\n    <div class=\"layui-tab-item layui-show\">\r\n    \t<div class=\"layui-form editor-form-node\">\r\n    \t\t<div class=\"layui-form-item\">\r\n    \t\t\t<label class=\"layui-form-label\">节点名称</label>\r\n    \t\t\t<div class=\"layui-input-block\">\r\n    \t\t\t\t<input type=\"text\" name=\"value\" placeholder=\"请输入节点名称\" value=\"{{=d.value}}\" autocomplete=\"off\" class=\"layui-input\">\r\n    \t\t\t</div>\r\n    \t\t</div>\r\n    \t</div>\r\n    </div>\r\n  </div>\r\n</div>"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/resources/templates/variable.html",
    "content": "<div class=\"layui-tab layui-tab-fixed layui-tab-brief\">\r\n  <ul class=\"layui-tab-title\">\r\n    <li class=\"layui-this\">配置</li>\r\n  </ul>\r\n  <div class=\"layui-tab-content\">\r\n    <div class=\"layui-tab-item layui-show\">\r\n    \t<form class=\"layui-form editor-form-node\">\r\n\t\t\t<div class=\"layui-row\">\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">节点名称</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<input type=\"text\" name=\"value\" placeholder=\"请输入节点名称\" value=\"{{=d.value}}\" autocomplete=\"off\" class=\"layui-input\">\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">循环变量</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\">\r\n\t\t\t\t\t\t\t<input type=\"text\" name=\"loopVariableName\" placeholder=\"请输入循环变量\" autocomplete=\"off\" class=\"layui-input input-default\" value=\"{{=d.data.object.loopVariableName}}\">\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"layui-col-md4\">\r\n\t\t\t\t\t<div class=\"layui-form-item\">\r\n\t\t\t\t\t\t<label class=\"layui-form-label\">循环次数</label>\r\n\t\t\t\t\t\t<div class=\"layui-input-block\" codemirror=\"loopCount\" placeholder=\"请输入循环次数\" data-value=\"{{=d.data.object.loopCount}}\"></div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\t<table class=\"layui-table\" id=\"spider-variable\" data-cell=\"{{=d.cell.id}}\" data-keys=\"variable-name,variable-value,variable-description\"></table>\r\n    \t\t<div class=\"layui-form-item\">\r\n    \t\t\t<div class=\"layui-input-inline\">\r\n\t    \t\t\t<button class=\"layui-btn table-row-add\" type=\"button\" for=\"spider-variable\">添加一个变量</button>\r\n\t    \t\t</div>\r\n\t    \t</div>\r\n    \t</form>\r\n    </div>\r\n  </div>\r\n</div>\r\n\r\n<script>\r\n\t$(function(){\r\n\t\tfunction renderSpiderVariable(data){\r\n\t\t\tlayui.table.render({\r\n\t\t\t\telem : '#spider-variable',\r\n\t\t\t\tlimit: 50,\r\n\t\t\t\tcols : [[{\r\n\t\t\t\t\ttitle : '变量名',\r\n\t\t\t\t\twidth : 150,\r\n\t\t\t\t\ttemplet : '#variable-name-tmpl'\r\n\t\t\t\t},{\r\n\t\t\t\t\ttitle : '变量值',\r\n\t\t\t\t\tminWidth : 400,\r\n\t\t\t\t\ttemplet : '#variable-value-tmpl'\r\n\t\t\t\t},{\r\n\t\t\t\t\ttitle : '描述',\r\n\t\t\t\t\twidth : 250,\r\n\t\t\t\t\ttemplet : '#variable-description-tmpl'\r\n\t\t\t\t},{\r\n\t\t\t\t\ttitle : '操作',\r\n\t\t\t\t\twidth : 120,\r\n\t\t\t\t\talign : 'center',\r\n\t\t\t\t\ttemplet : '#common-operation'\r\n\t\t\t\t}]],\r\n\t\t\t\tdata : data,\r\n\t\t\t\ttext : {\r\n\t\t\t\t\tnone : '暂未设置变量'\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t}\r\n\t\trenderSpiderVariable(getCellData({{d.cell.id}},$(\"#spider-variable\").data('keys').split(\",\")));\r\n\t});\r\n</script>"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/spiderList-notice.html",
    "content": "<!DOCTYPE html>\n<html>\n\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>流程通知设置</title>\n\t<link rel=\"stylesheet\" href=\"js/layui/css/layui.css\" />\n\t<script type=\"text/javascript\" src=\"js/layui/layui.all.js\"></script>\n</head>\n\n<body style=\"padding: 10px;\">\n\t<div>\n\t\t<form class=\"layui-form\" action=\"\" lay-filter=\"flowNotice\" id=\"formFlowNotice\">\n\t\t\t<input type=\"hidden\" name=\"id\" />\n\t\t\t<div class=\"layui-form-item\">\n\t\t\t\t<label class=\"layui-form-label\">收件人</label>\n\t\t\t\t<div class=\"layui-input-block\">\n\t\t\t\t\t<textarea name=\"recipients\" placeholder=\"请输入收件人\" class=\"layui-textarea\"></textarea>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"layui-form-mid layui-word-aux\">\n\t\t\t\t\t<p>收件人,多个收件人用\",\"隔开，每个收件人可添加单独通知标记,如不添加通知标记则使用默认配置通知方式</p>\n\t\t\t\t\t<p>例：sms:13012345678,email:12345678@qq.com,13012345670</p>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div class=\"layui-form-item\">\n\t\t\t\t<label class=\"layui-form-label\">通知方式</label>\n\t\t\t\t<div class=\"layui-input-block\">\n\t\t\t\t\t<select name=\"noticeWay\" id=\"noticeWay\"></select>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div class=\"layui-form-item\">\n\t\t\t\t<label class=\"layui-form-label\">通知类型</label>\n\t\t\t\t<div class=\"layui-input-block\">\n\t\t\t\t\t<input type=\"checkbox\" lay-skin=\"primary\" name=\"startNotice\" title=\"流程开始\" />\n\t\t\t\t\t<input type=\"checkbox\" lay-skin=\"primary\" name=\"exceptionNotice\" title=\"流程异常\" />\n\t\t\t\t\t<input type=\"checkbox\" lay-skin=\"primary\" name=\"endNotice\" title=\"流程结束\" />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div class=\"layui-form-item\">\n\t\t\t\t<div class=\"layui-input-block\">\n\t\t\t\t\t<button type=\"button\" class=\"layui-btn\" id=\"save\">保存</button>\n\t\t\t\t\t<button type=\"button\" class=\"layui-btn layui-btn-primary\" id=\"cancel\">取消</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</form>\n\t</div>\n\t<script type=\"text/javascript\">\n\t\tvar $ = layui.$;\n\t\tvar index = parent.layer.getFrameIndex(window.name); //获取窗口索引\n\t\tvar id = getURLParameter(\"id\");\n\n\t\tfunction getURLParameter(name) {\n\t\t\tvar query = decodeURI(window.location.search.substring(1));\n\t\t\tvar vars = query.split(\"&\");\n\t\t\tfor (var i = 0; i < vars.length; i++) {\n\t\t\t\tvar pair = vars[i].split(\"=\");\n\t\t\t\tif (pair[0] === name) {\n\t\t\t\t\treturn pair[1];\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn \"\";\n\t\t}\n\n\t\tfunction getNoticeWay(successFun) {\n\t\t\t$.ajax({\n\t\t\t\turl: '/flowNotice/getNoticeWay',\n\t\t\t\tsuccess: successFun,\n\t\t\t\terror: function () {\n\t\t\t\t\tparent.layer.msg('获取通知方式类型失败');\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tfunction getNoticeDetail(successFun) {\n\t\t\t$.ajax({\n\t\t\t\turl: '/flowNotice/find',\n\t\t\t\tdata: {\n\t\t\t\t\tid: id\n\t\t\t\t},\n\t\t\t\tsuccess: successFun,\n\t\t\t\terror: function () {\n\t\t\t\t\tparent.layer.msg('获取通知详情失败');\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tvar form = layui.form;\n\n\t\tgetNoticeWay(function (result) {\n\t\t\tvar data = result.data;\n\t\t\tvar $noticeWay = $(\"#noticeWay\");\n\t\t\tfor (var key in data) {\n\t\t\t\t$noticeWay.append($(\"<option value='\" + key + \"'>\" + data[key] + \"</option>\"))\n\t\t\t}\n\t\t\tgetNoticeDetail(function (resultDetail) {\n\t\t\t\tvar data = resultDetail.data;\n\t\t\t\tdata.startNotice = data.startNotice === \"1\" ? true : false;\n\t\t\t\tdata.exceptionNotice = data.exceptionNotice === \"1\" ? true : false;\n\t\t\t\tdata.endNotice = data.endNotice === \"1\" ? true : false;\n\t\t\t\tform.val('flowNotice', data);\n\t\t\t});\n\t\t});\n\n\t\t//保存按钮\n\t\t$('#save').on('click', function () {\n\t\t\tvar data = {};\n\t\t\t$(\"#formFlowNotice [name]\").each(function () {\n\t\t\t\tvar $this = $(this);\n\t\t\t\tvar value = \"\";\n\t\t\t\tswitch ($this.attr(\"type\")) {\n\t\t\t\t\tcase \"checkbox\":\n\t\t\t\t\t\tif ($this.is(':checked')) {\n\t\t\t\t\t\t\tvalue = 1;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tvalue = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tvalue = $this.val();\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tdata[$this.attr(\"name\")] = value;\n\t\t\t});\n\t\t\t$.ajax({\n\t\t\t\turl: '/flowNotice/save',\n\t\t\t\tdata: data,\n\t\t\t\tsuccess: function (result) {\n\t\t\t\t\tif (result.code === 1) {\n\t\t\t\t\t\tparent.layer.close(index);\n\t\t\t\t\t}\n\t\t\t\t\tparent.layer.msg(result.message);\n\t\t\t\t},\n\t\t\t\terror: function () {\n\t\t\t\t\tparent.layer.msg('保存通知详情失败');\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn false;\n\t\t});\n\n\t\t//取消按钮\n\t\t$('#cancel').on('click', function () {\n\t\t\tparent.layer.close(index);\n\t\t});\n\t</script>\n</body>\n\n</html>"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/spiderList.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>SpiderFlow</title>\n\t<link rel=\"stylesheet\" href=\"js/layui/css/layui.css\" />\n\t<link rel=\"stylesheet\" href=\"css/layui-blue.css\" />\n\t<script type=\"text/javascript\" src=\"js/layui/layui.all.js\" ></script>\n</head>\n<body style=\"padding:5px;\">\n<div class=\"layui-form-item\">\n\t<label class=\"layui-form-label\">爬虫名称</label>\n\t<div class=\"layui-input-inline\">\n\t\t<input type=\"text\" name=\"name\" required  lay-verify=\"required\" placeholder=\"请输入爬虫名称\" autocomplete=\"off\" class=\"layui-input\">\n\t</div>\n\t<div class=\"layui-input-inline\" style=\"margin-top:5px\">\n\t\t<a class=\"layui-btn layui-btn-sm layui-btn-normal btn-search\"><i class=\"layui-icon\">&#xe615;</i> 搜索</a>\n\t\t<a class=\"layui-btn layui-btn-sm layui-btn-normal\" href=\"editor.html\"><i class=\"layui-icon\">&#xe654;</i> 添加爬虫</a>\n\t</div>\n</div>\n\n<hr>\n<table class=\"layui-table\" id=\"table\" lay-filter=\"table\"></table>\n<script>\n\tvar $ = layui.$;\n\tvar $table = layui.table.render({\n\t\tid : 'table',\n\t\telem : '#table',\n\t\turl : 'spider/list',\n\t\tmethod : 'post',\n\t\tpage : true,\n\t\tparseData : function(resp){\n\t\t\treturn {\n\t\t\t\tcode : 0,\n\t\t\t\tdata : resp.records,\n\t\t\t\tcount : resp.total\n\t\t\t}\n\t\t},\n\t\tcols : [[{\n\t\t\ttitle : '序号',\n\t\t\twidth : 35,\n\t\t\ttype : 'numbers',\n\t\t\talign : 'center'\n\t\t},{\n\t\t\ttitle : '爬虫名称',\n\t\t\tfield : 'name',\n\t\t\ttemplet : function(row){\n\t\t\t\treturn '<a class=\"layui-btn layui-btn-sm btn-details\" data-name=\"'+row.name+'\" data-id=\"'+row.id+'\">'+row.name+'</a>';\n\t\t\t}\n\t\t},{\n\t\t\ttitle : 'cron',\n\t\t\tfield : 'cron',\n\t\t\twidth : 80,\n\t\t\ttemplet : function(row){\n\t\t\t\treturn '<a class=\"layui-btn layui-btn-sm btn-edit-cron\" data-id=\"'+row.id+'\" data-cron=\"'+row.cron+'\">'+(row.cron || '编辑cron')+'</a>';\n\t\t\t}\n\t\t},{\n\t\t\ttitle : '定时/长任务',\n\t\t\tfield : 'enabled',\n\t\t\twidth : 105,\n\t\t\ttemplet : function(row){\n\t\t\t\treturn '<input type=\"checkbox\" data-cron=\"'+(row.cron||'')+'\" name=\"switch\" lay-skin=\"switch\" lay-text=\"定时|长任务\" value=\"'+row.id+'\" '+(row.enabled == 1 ? 'checked':'')+'>';\n\t\t\t}\n\t\t},{\n\t\t\ttitle : '创建时间',\n\t\t\twidth : 160,\n\t\t\tfield : 'createDate',\n\t\t\talign : 'center'\n\t\t},{\n\t\t\ttitle : '上次执行时间',\n\t\t\twidth : 160,\n\t\t\tfield : 'lastExecuteTime',\n\t\t\talign : 'center'\n\t\t},{\n\t\t\ttitle : '运行中/已完成',\n\t\t\twidth : 120,\n\t\t\tfield : 'executeCount',\n\t\t\talign : 'center',\n\t\t\ttemplet : '#execute-count'\n\t\t},{\n\t\t\ttitle : '下次执行时间',\n\t\t\twidth : 160,\n\t\t\tfield : 'nextExecuteTime',\n\t\t\talign : 'center'\n\t\t},{\n\t\t\ttitle : '操作',\n\t\t\twidth : 250,\n\t\t\talign : 'center',\n\t\t\ttemplet : '#buttons'\n\t\t}]]\n\t})\n\tlayui.form.on('switch',function(e){\n\t\tif(e.elem.checked && !$(e.elem).data('cron')){\n\t\t\tlayui.layer.msg('cron表达式不能为空！');\n\t\t\te.elem.checked = false;\n\t\t\tlayui.form.render();\n\t\t\treturn;\n\t\t}\n\t\t$.ajax({\n\t\t\turl : 'spider/' + (e.elem.checked ? 'start': 'stop'),\n\t\t\tdata : {\n\t\t\t\tid : e.value\n\t\t\t},\n\t\t\tsuccess : function(){\n\t\t\t\tlayui.layer.msg((e.elem.checked ? '切换为定时任务': '切换为长任务') + '成功');\n\t\t\t},\n\t\t\terror : function(){\n\t\t\t\tlayui.layer.msg((e.elem.checked ? '切换为定时任务': '切换为长任务') + '成功');\n\t\t\t}\n\t\t})\n\t})\n\tfunction reloadTable(){\n\t\t$table.reload({\n\t\t\twhere : {\n\t\t\t\tname : $('input[name=name]').val()\n\t\t\t},\n\t\t\tpage : {\n\t\t\t\tcurr : 1\n\t\t\t}\n\t\t})\n\t}\n\t$(\"body\").on('click','.btn-search',function(){\n\t\treloadTable();\n\t}).on('click','.btn-copy',function(){\n\t\tvar id = $(this).data('id');\n\t\tlayui.layer.confirm('您确定要复制此爬虫吗？',{\n\t\t\ttitle : '复制'\n\t\t},function(index){\n\t\t\t$table.reload();\n\t\t\t$.ajax({\n\t\t\t\turl : 'spider/copy',\n\t\t\t\tdata : {\n\t\t\t\t\tid : id\n\t\t\t\t},\n\t\t\t\tsuccess : function(){\n\t\t\t\t\tlayui.layer.msg('复制成功',{time : 500},function(){\n\t\t\t\t\t\t$table.reload();\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\terror : function(){\n\t\t\t\t\tlayui.layer.msg('复制失败')\n\t\t\t\t}\n\t\t\t})\n\t\t\tlayui.layer.close(index);\n\t\t})\n\t}).on('click','.btn-remove',function(){\n\t\tvar id = $(this).data('id');\n\t\tlayui.layer.confirm('您确定要删除此爬虫吗？',{\n\t\t\ttitle : '删除'\n\t\t},function(index){\n\t\t\t$table.reload();\n\t\t\t$.ajax({\n\t\t\t\turl : 'spider/remove',\n\t\t\t\tdata : {\n\t\t\t\t\tid : id\n\t\t\t\t},\n\t\t\t\tsuccess : function(){\n\t\t\t\t\tlayui.layer.msg('删除成功',{time : 500},function(){\n\t\t\t\t\t\t$table.reload();\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\terror : function(){\n\t\t\t\t\tlayui.layer.msg('删除失败')\n\t\t\t\t}\n\t\t\t})\n\t\t\tlayui.layer.close(index);\n\t\t})\n\t}).on('click','.btn-run',function(){\n\t\tvar id = $(this).data('id');\n\t\tlayui.layer.confirm('您确定要手动运行一次该爬虫吗？',{\n\t\t\ttitle : '运行任务'\n\t\t},function(index){\n\t\t\t$table.reload();\n\t\t\t$.ajax({\n\t\t\t\turl : 'spider/run',\n\t\t\t\tdata : {\n\t\t\t\t\tid : id\n\t\t\t\t},\n\t\t\t\tsuccess : function(){\n\t\t\t\t\tlayui.layer.msg('手动运行成功,后台运行中',{time : 500},function(){\n\t\t\t\t\t\t$table.reload();\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\terror : function(){\n\t\t\t\t\tlayui.layer.msg('手动运行失败')\n\t\t\t\t}\n\t\t\t})\n\t\t\tlayui.layer.close(index);\n\t\t})\n\t}).on('click','.btn-task',function(){\n\t\tparent.openTab($(this).data('name') + '-任务详情',$(this).data('id') + '-task','task.html?id=' + $(this).data('id') + '&name=' + encodeURIComponent(encodeURIComponent($(this).data('name'))));\n\t}).on('click','.btn-log',function(){\n\t\tparent.openTab($(this).data('name') + '-日志',$(this).data('id') + '-log','log.html?id=' + $(this).data('id'));\n\t}).on('click','.btn-edit-cron',function(){\n\t\tvar id = $(this).data('id');\n\t\tvar value = $(this).data('cron') || '';\n\t\tparent.$table = $table;\n\t\tparent.layer.open({\n\t\t\ttype: 2,\n\t\t\tarea: ['850px', '660px'],\n\t\t\tfixed: false, //不固定\n\t\t\tmaxmin: true,\n\t\t\tcontent: 'editCron.html?id=' + id + '&cron=' + value\n\t\t});\n\t}).on('click','.btn-details',function () {\n\t\tparent.openTab($(this).data('name') +'-编辑',$(this).data('id') + '-edit','editor.html?id='+$(this).data('id'));\n\t}).on('click','.btn-notice',function () {\n\t\tvar id = $(this).data('id');\n\t\tparent.$table = $table;\n\t\tparent.layer.open({\n\t\t\ttype: 2,\n\t\t\tarea: ['700px', '400px'],\n\t\t\tfixed: false, //不固定\n\t\t\tmaxmin: true,\n\t\t\tcontent: 'spiderList-notice.html?id=' + id\n\t\t});\n\t})\n</script>\n<script type=\"text/html\" id=\"execute-count\">\n\t<a class=\"layui-btn layui-btn-sm btn-task\" data-name=\"{{d.name}}\" data-id=\"{{d.id}}\">{{d.running || 0}}/{{d.executeCount || 0}}</a>\n</script>\n<script type=\"text/html\" id=\"buttons\">\n\t<a class=\"layui-btn layui-btn-sm btn-details\" data-id=\"{{d.id}}\" data-name=\"{{d.name}}\" title=\"查看\"><i class=\"layui-icon\">&#xe615;</i></a>\n\t<a class=\"layui-btn layui-btn-sm btn-notice\" data-id=\"{{d.id}}\" title=\"通知设置\"><i class=\"layui-icon\">&#xe667;</i></a>\n\t<a class=\"layui-btn layui-btn-sm btn-run\" data-id=\"{{d.id}}\" title=\"手动运行\"><i class=\"layui-icon\">&#xe623;</i></a>\n\t<a class=\"layui-btn layui-btn-sm btn-log\" data-name=\"{{d.name}}\" data-id=\"{{d.id}}\" title=\"日志\"><i class=\"layui-icon\">&#xe60e;</i></a>\n\t<a class=\"layui-btn layui-btn-sm btn-remove\" data-id=\"{{d.id}}\" title=\"删除\"><i class=\"layui-icon\">&#xe640;</i></a>\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/task.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>SpiderFlow</title>\n\t<link rel=\"stylesheet\" href=\"js/layui/css/layui.css\" />\n\t<link rel=\"stylesheet\" href=\"css/layui-blue.css\" />\n\t<script type=\"text/javascript\" src=\"js/layui/layui.all.js\" ></script>\n\t<script type=\"text/javascript\" src=\"js/common.js\" ></script>\n</head>\n<body style=\"padding:5px;\">\n<table class=\"layui-table\" id=\"table\" lay-filter=\"table\"></table>\n<script>\n\tvar $ = layui.$;\n\tvar $table = layui.table.render({\n\t\tid : 'table',\n\t\telem : '#table',\n\t\turl : 'task/list?flowId=' + getQueryString('id'),\n\t\tmethod : 'post',\n\t\tpage : true,\n\t\tparseData : function(resp){\n\t\t\treturn {\n\t\t\t\tcode : 0,\n\t\t\t\tdata : resp.records,\n\t\t\t\tcount : resp.total\n\t\t\t}\n\t\t},\n\t\tcols : [[{\n\t\t\ttitle : '序号',\n\t\t\twidth : 60,\n\t\t\ttype : 'numbers',\n\t\t\talign : 'center'\n\t\t},{\n\t\t\ttitle : '任务开始时间',\n\t\t\tfield : 'beginTime',\n\t\t\talign : 'center'\n\t\t},{\n\t\t\ttitle : '任务结束时间',\n\t\t\tfield : 'endTime',\n\t\t\talign : 'center'\n\t\t},{\n\t\t\ttitle : '操作',\n\t\t\twidth : 200,\n\t\t\talign : 'center',\n\t\t\ttemplet : '#buttons'\n\t\t}]]\n\t})\n\t$(\"body\").on('click','.btn-remove',function(){\n\t\tvar id = $(this).data('id');\n\t\tlayui.layer.confirm('您确定要删除此记录吗？',{\n\t\t\ttitle : '删除'\n\t\t},function(index){\n\t\t\t$table.reload();\n\t\t\t$.ajax({\n\t\t\t\turl : 'task/remove',\n\t\t\t\tdata : {\n\t\t\t\t\tid : id\n\t\t\t\t},\n\t\t\t\tsuccess : function(){\n\t\t\t\t\tlayui.layer.msg('删除成功',{time : 500},function(){\n\t\t\t\t\t\t$table.reload();\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\terror : function(){\n\t\t\t\t\tlayui.layer.msg('删除失败')\n\t\t\t\t}\n\t\t\t})\n\t\t\tlayui.layer.close(index);\n\t\t})\n\t}).on('click','.btn-stop',function(){\n\t\tvar id = $(this).data('id');\n\t\tlayui.layer.confirm('您确定要停止该任务吗？',{\n\t\t\ttitle : '停止任务'\n\t\t},function(index){\n\t\t\t$table.reload();\n\t\t\t$.ajax({\n\t\t\t\turl : 'task/stop',\n\t\t\t\tdata : {\n\t\t\t\t\tid : id\n\t\t\t\t},\n\t\t\t\tsuccess : function(){\n\t\t\t\t\tlayui.layer.msg('后台运行停止中...',{time : 500},function(){\n\t\t\t\t\t\t$table.reload();\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\terror : function(){\n\t\t\t\t\tlayui.layer.msg('停止任务失败')\n\t\t\t\t}\n\t\t\t})\n\t\t\tlayui.layer.close(index);\n\t\t})\n\t}).on('click','.btn-log',function(){\n\t\tparent.openTab(decodeURIComponent(decodeURIComponent(getQueryString('name'))) + '-日志',$(this).data('id') + '-log','log.html?id=' + $(this).data('id') + \"&taskId=\" + $(this).data(\"task\"));\n\t})\n</script>\n<script type=\"text/html\" id=\"buttons\">\n\t{{# if(!d.endTime){ }}\n\t\t<a class=\"layui-btn layui-btn-sm btn-stop\" data-id=\"{{d.id}}\">停止</a>\n\t{{# } }}\n\t<a class=\"layui-btn layui-btn-sm btn-log\" data-id=\"{{d.flowId}}\" data-task=\"{{d.id}}\">查看日志</a>\n\t<a class=\"layui-btn layui-btn-sm btn-remove\" data-id=\"{{d.id}}\">删除记录</a>\n</script>\n</body>\n</html>"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/variable-edit.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>DataSource</title>\n\t<link rel=\"stylesheet\" href=\"js/layui/css/layui.css\" />\n\t<link rel=\"stylesheet\" href=\"css/layui-blue.css\" />\n\t<script type=\"text/javascript\" src=\"js/layui/layui.all.js\" ></script>\n\t<script type=\"text/javascript\" src=\"js/common.js\" ></script>\n\t<style type=\"text/css\">\n\t\thtml,body{\n\t\t\twidth:100%;\n\t\t}\n\t\t.layui-form{\n\t\t\twidth : 700px;\n\t\t\tmargin-top:10px;\n\t\t}\n\t\t.layui-form-label{\n\t\t\twidth : 140px;\n\t\t}\n\t\t.layui-input-block{\n\t\t\tmargin-left : 170px;\n\t\t}\n\t\t.btns-submit{\n\t\t\ttext-align : center;\n\t\t}\n\t</style>\n</head>\n<body>\n<form class=\"layui-form\" autocomplete=\"off\" lay-filter=\"form\">\n\t<div class=\"layui-form-item\">\n\t\t<label class=\"layui-form-label\">变量名</label>\n\t\t<div class=\"layui-input-block\">\n\t\t\t<input type=\"text\" name=\"name\" placeholder=\"请输入变量名\" autocomplete=\"off\" class=\"layui-input\" lay-verify=\"required\"/>\n\t\t</div>\n\t</div>\n\t<div class=\"layui-form-item\">\n\t\t<label class=\"layui-form-label\">变量值</label>\n\t\t<div class=\"layui-input-block\">\n\t\t\t<input type=\"text\" name=\"value\" placeholder=\"请输入变量值\" autocomplete=\"off\" class=\"layui-input\" lay-verify=\"required\"/>\n\t\t</div>\n\t</div>\n\t<div class=\"layui-form-item layui-form-text\">\n\t\t<label class=\"layui-form-label\">变量说明</label>\n\t\t<div class=\"layui-input-block\">\n\t\t\t<textarea name=\"description\" placeholder=\"请输入变量说明\" autocomplete=\"off\" class=\"layui-textarea\"  lay-verify=\"required\"></textarea>\n\t\t</div>\n\t</div>\n\t<div class=\"btns-submit\">\n\t\t<button class=\"layui-btn layui-btn-normal\" lay-submit lay-filter=\"save\">保存</button>\n\t\t<button class=\"layui-btn layui-btn-primary btn-return\" type=\"button\" onclick=\"history.go(-1);\">返回</button>\n\t</div>\n</form>\n<script type=\"text/javascript\">\n\tvar $ = layui.$;\n\tvar varId = getQueryString('id');\n\tif(varId){\n\t\t$.ajax({\n\t\t\turl : 'variable/get',\n\t\t\tdata : {\n\t\t\t\tid : varId\n\t\t\t},\n\t\t\tsuccess : function(json){\n\t\t\t\tlayui.form.val('form',json.data)\n\t\t\t}\n\t\t})\n\t}\n\tlayui.form.on('submit(save)',function(){\n\t\t$.ajax({\n\t\t\turl : 'variable/save',\n\t\t\ttype : 'post',\n\t\t\tdata : {\n\t\t\t\tid : varId,\n\t\t\t\tname : $(\"input[name=name]\").val(),\n\t\t\t\tvalue : $(\"input[name=value]\").val(),\n\t\t\t\tdescription : $(\"textarea[name=description]\").val()\n\t\t\t},\n\t\t\tsuccess : function(json){\n\t\t\t\tlayui.layer.msg('保存成功',{\n\t\t\t\t\ttime : 800\n\t\t\t\t},function(){\n\t\t\t\t\tlocation.href = 'variables.html';\n\t\t\t\t})\n\t\t\t},\n\t\t\terror : function(){\n\t\t\t\tlayui.layer.msg('请求失败');\n\t\t\t}\n\t\t})\n\t\treturn false;\n\t})\n</script>\n</body>\n</html>"
  },
  {
    "path": "spider-flow-web/src/main/resources/static/variables.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>DataSource</title>\n\t<link rel=\"stylesheet\" href=\"js/layui/css/layui.css\" />\n\t<link rel=\"stylesheet\" href=\"css/layui-blue.css\" />\n\t<script type=\"text/javascript\" src=\"js/layui/layui.all.js\" ></script>\n</head>\n<body style=\"padding:5px;\">\n<a class=\"layui-btn layui-btn-sm layui-btn-normal\" href=\"variable-edit.html\"><i class=\"layui-icon\">&#xe654;</i> 添加变量</a>\n<hr>\n<table class=\"layui-table\" id=\"table\" lay-filter=\"table\"></table>\n<script>\n\tvar $ = layui.$;\n\tvar $table = layui.table.render({\n\t\tid : 'table',\n\t\telem : '#table',\n\t\turl : 'variable/list',\n\t\tpage : true,\n\t\tparseData : function(resp){\n\t\t\treturn {\n\t\t\t\tcode : 0,\n\t\t\t\tdata : resp.records,\n\t\t\t\tcount : resp.total\n\t\t\t}\n\t\t},\n\t\tcols : [[{\n\t\t\ttitle : '序号',\n\t\t\twidth : 60,\n\t\t\ttype : 'numbers',\n\t\t\talign : 'center'\n\t\t},{\n\t\t\ttitle : '变量名',\n\t\t\tfield : 'name'\n\t\t},{\n\t\t\ttitle : '变量值',\n\t\t\tfield : 'value'\n\t\t},{\n\t\t\ttitle : '变量描述',\n\t\t\tfield : 'description'\n\t\t},{\n\t\t\ttitle : '创建时间',\n\t\t\twidth : 160,\n\t\t\tfield : 'createDate',\n\t\t\talign : 'center'\n\t\t},{\n\t\t\ttitle : '操作',\n\t\t\twidth : 120,\n\t\t\talign : 'center',\n\t\t\ttemplet : '#buttons'\n\t\t}]]\n\t})\n\t$(\"body\").on('click','.btn-remove',function(){\n\t\tvar id = $(this).data('id');\n\t\tlayui.layer.confirm('您确定要删除此变量吗？',{\n\t\t\ttitle : '删除'\n\t\t},function(index){\n\t\t\t$table.reload();\n\t\t\t$.ajax({\n\t\t\t\turl : 'variable/delete',\n\t\t\t\tdata : {\n\t\t\t\t\tid : id\n\t\t\t\t},\n\t\t\t\tsuccess : function(){\n\t\t\t\t\tlayui.layer.msg('删除成功',{time : 500},function(){\n\t\t\t\t\t\t$table.reload();\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\terror : function(){\n\t\t\t\t\tlayui.layer.msg('删除失败')\n\t\t\t\t}\n\t\t\t})\n\t\t\tlayui.layer.close(index);\n\t\t})\n\t})\n</script>\n<script type=\"text/html\" id=\"buttons\">\n\t<a class=\"layui-btn layui-btn-sm\" href=\"variable-edit.html?id={{d.id}}\">编辑</a>\n\t<a class=\"layui-btn layui-btn-sm btn-remove\" data-id=\"{{d.id}}\">删除</a>\n</script>\n</body>\n</html>"
  }
]